Skip to content

Update --customer-account-push to support multiple dev instances#3746

Open
andguy95 wants to merge 4 commits intomainfrom
an-feat-customer-account-push-multiple-urls
Open

Update --customer-account-push to support multiple dev instances#3746
andguy95 wants to merge 4 commits intomainfrom
an-feat-customer-account-push-multiple-urls

Conversation

@andguy95
Copy link
Copy Markdown
Collaborator

WHY are these changes introduced?

When multiple developers (or the same developer) run dev --customer-account-push concurrently on the same store, the second instance overwrites the Customer Account callback URIs set by the first. This breaks Customer Account sign-in for the first dev server because its tunnel URL is no longer registered as a valid callback URI.

WHAT is this pull request doing?

Removes removeRegex from the push path so that each dev server additively registers its own callback URIs without touching any other session's URLs. The cleanup function (invoked on dev server shutdown) still uses removeRegex to remove only that session's specific URLs.

HOW to test your changes?

  1. Run npx shopify hydrogen dev --customer-account-push in a Hydrogen project — confirm Customer Account sign-in works
  2. In a separate terminal, start a second dev server with --customer-account-push on the same storefront
  3. Verify both servers can sign in via Customer Account (the first should no longer break)
  4. Stop the second server — verify the first still works
  5. Stop the first server — verify URLs are cleaned up

Unit tests: pnpm vitest packages/cli/src/commands/hydrogen/customer-account/push.test.ts

image

Checklist

  • I've read the Contributing Guidelines
  • I've considered possible cross-platform impacts (Mac, Linux, Windows)
  • I've added a changeset if this PR contains user-facing or functional changes. Test changes or internal-only config changes do not require a changeset.
  • I've added tests to cover my changes
  • I've added or updated the documentation

@andguy95 andguy95 requested a review from a team as a code owner April 22, 2026 21:32
@shopify
Copy link
Copy Markdown
Contributor

shopify Bot commented Apr 22, 2026

Oxygen deployed a preview of your an-feat-customer-account-push-multiple-urls branch. Details:

Storefront Status Preview link Deployment details Last update (UTC)
Skeleton (skeleton.hydrogen.shop) ✅ Successful (Logs) Preview deployment Inspect deployment April 22, 2026 9:54 PM

Learn more about Hydrogen's GitHub integration.

devOrigin: DEV_ORIGIN,
storefrontId: STOREFRONT_ID,
}),
).rejects.toThrow(AbortError);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

blocking: this test can pass when the feature it claims to test isn't working.

The test name says "throws AbortError with confidential access hint when applicable" but it only asserts that AbortError is thrown, which the previous test (line 160) already covers for a different error message. The confidentialAccessFound logic in the implementation adds a specific "Enable Public access for Hydrogen" link to nextSteps, but nothing here verifies that.

If we removed the entire confidentialAccessFound block from push.ts, this test would still pass.

Let's assert on the error content too. IIRC AbortError accepts nextSteps as the third constructor arg. Something like:

try {
  await runCustomerAccountPush({
    devOrigin: DEV_ORIGIN,
    storefrontId: STOREFRONT_ID,
  });
  expect.fail('Expected AbortError');
} catch (error) {
  expect(error).toBeInstanceOf(AbortError);
  expect((error as AbortError).nextSteps).toContainEqual(
    expect.objectContaining({
      link: expect.objectContaining({
        label: 'Enable Public access for Hydrogen',
      }),
    }),
  );
}

Or if AbortError exposes the next steps differently, adjust accordingly. The key thing is asserting the hint is actually present.

'@shopify/cli-hydrogen': patch
---

Updates `customer-account push` to support multiple concurrent dev servers. Previously, starting a second dev server with `--customer-account-push` would overwrite the callback URIs set by the first, breaking Customer Account sign-in for the first server. Now each server additively registers its own callbIck URIs and removes only its own on shutdown.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Updates `customer-account push` to support multiple concurrent dev servers. Previously, starting a second dev server with `--customer-account-push` would overwrite the callback URIs set by the first, breaking Customer Account sign-in for the first server. Now each server additively registers its own callbIck URIs and removes only its own on shutdown.
Updates `customer-account push` to support multiple concurrent dev servers. Previously, starting a second dev server with `--customer-account-push` would overwrite the callback URIs set by the first, breaking Customer Account sign-in for the first server. Now each server additively registers its own callback URIs and removes only its own on shutdown.


const {session, config} = await login(root);
const customerAccountConfig = config?.storefront?.customerAccountConfig;
const {session} = await login(root);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

praise: the old code did const {session, config} = await login(root) and read config?.storefront?.customerAccountConfig to build removeRegex values. Now it's just const {session} = await login(root). The push path no longer depends on local state at all, which is the mechanism that makes multi-developer concurrency safe. The unused removeRegex?: string parameter was also removed from the function's type signature. Good cleanup.

removeRegex: customerAccountConfig?.javascriptOrigin,
},
logoutUris: {
add: logoutUri ? [logoutUri] : undefined,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

non-blocking: (threading) Re: lines 189-193 (cleanup path). Pre-existing, not introduced by this PR, but worth noting: the cleanup passes raw URL strings (e.g. https://abc123.tryhydrogen.dev) as removeRegex values without escaping the . characters, which are regex metacharacters matching any character.

Under the new additive model where multiple dev sessions coexist, the blast radius of an unescaped regex match is wider than before. Previously, removeRegex ran in a single-session context. Now multiple sessions register URLs independently and each session's cleanup runs removeRegex against the shared URL list. If two dev origins share a similar pattern (e.g. abc123.tryhydrogen.dev vs abc123-tryhydrogen.dev), an unescaped regex could remove another session's URL during cleanup. Low probability given the subdomain structure, but the regex escaping follow-up is more important now than it was before.

removeRegex: customerAccountConfig?.logoutUri,
},
},
);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

non-blocking: (threading) Re: line 97. const error: any = new Error(...) is pre-existing, not introduced by this PR. This pushes complexity upward since every catch handler that touches this error needs to know about the ad-hoc userErrors property. A proper typed error class (e.g. class MutationError extends Error { userErrors: UserError[] }) would let TypeScript enforce correct handling.

maybe as a follow up PR later?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants