Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/local-https-vite-plugin.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,9 @@
---

Add `localHttps()` under `@shopify/hydrogen/vite` for portable local HTTPS development with Customer Account API flows. Frameworks that terminate HTTPS outside Vite can use `localHttps(...).api.getDevServerConfig()`.

Certificates can be provisioned by the plugin (after confirmation on `vite dev`), the `provisionLocalHttps()` helper, or the `hydrogen certs install` CLI command. Each path downloads a pinned, checksum-verified mkcert release for macOS, Linux, or Windows, installs the local certificate authority, and generates the certificate files. The plugin skips automatic provisioning in CI environments; the explicit paths remain available there. The paired `hydrogen certs uninstall` command removes Hydrogen's files and can remove the shared mkcert CA when passed `--remove-ca`.

When a local HTTPS server starts outside CI, the plugin uses Shopify CLI to link an unlinked project and push the callback, portless JavaScript origin, and logout URLs to the Customer Account API configuration. Shopify CLI must include `@shopify/cli-hydrogen` 13.0.4 or later. CI, missing CLI support, cancelled linking, and push failures fall back to printing the values for manual configuration without stopping the development server.

Framework templates and examples expose local HTTPS through the `dev:https` package script, which the Vite configuration detects through `npm_lifecycle_event`.
2 changes: 1 addition & 1 deletion .docs/dependencies.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,6 @@ When changing `@shopify/hydrogen/vite` local HTTPS behaviour, update these toget
- `templates/react-router/README.md`
- `templates/react-router/package.json`
- `templates/react-router/vite.config.ts`
- framework example `https:dev` scripts and configs under `examples/*`
- framework example `dev:https` scripts and configs under `examples/*`

`scripts/preview-template-dist.ts` copies `packages/hydrogen/skills` into template `.agents/skills` when preparing the dist branch, so template source directories should not duplicate those generated skill copies.
52 changes: 52 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,3 +54,55 @@ jobs:

- name: Test
run: pnpm run test

local-https:
name: Local HTTPS (${{ matrix.os }})
runs-on: ${{ matrix.os }}
timeout-minutes: 10
permissions:
contents: read
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
env:
npm_config_registry: https://registry.npmjs.org/
TURBO_TELEMETRY_DISABLED: "1"
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false

- uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320
with:
version: 10.33.0

- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
with:
node-version-file: package.json
cache: pnpm

- name: Install dependencies
run: pnpm install --frozen-lockfile

- name: Provision local HTTPS certificate
if: runner.os != 'Windows'
run: pnpm https:setup

- name: Provision local HTTPS certificate on Windows
if: runner.os == 'Windows'
shell: pwsh
env:
# GitHub-hosted runners cannot accept Windows' root-store prompt.
# Generate the same CA and provide it directly to Node's TLS verifier.
TRUST_STORES: none
run: |
pnpm https:setup
$rootCertificate = Join-Path $env:LOCALAPPDATA 'mkcert\rootCA.pem'
if (!(Test-Path $rootCertificate)) {
throw "mkcert root CA was not created at $rootCertificate"
}
Add-Content -Path $env:GITHUB_ENV -Value "NODE_EXTRA_CA_CERTS=$rootCertificate"

- name: Verify trusted local HTTPS
run: pnpm run test:local-https
1 change: 1 addition & 0 deletions .oxlintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"scripts/**",
"!scripts/copy-hydrogen-to-preview*.ts",
"!scripts/preview-template-dist*.ts",
"!scripts/test-local-https.ts",
"examples/**",
"!examples/shared/local-cdn-assets-plugin/**/*.ts",
"examples/hydrogen/**",
Expand Down
9 changes: 5 additions & 4 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ When designing or adjusting APIs for the `hydrogen` package, closely follow the
## Local HTTPS for Examples

- Account-enabled framework examples use `https://local.tryhydrogen.dev:5173` for Customer Account OAuth callback testing.
- On macOS, install `mkcert` with Homebrew before running those examples: `brew install mkcert`.
- Vite-based examples consume Hydrogen's default certificates. From the repository root, run `pnpm https:setup` once to trust the local certificate authority and create them under `~/.shopify/hydrogen/certs/`.
- The Next.js example provisions its own certificate. The Hydrogen example uses the Shopify CLI tunnel flow.
- After setup, run the relevant example with `pnpm --filter @shopify/hydrogen-example-<name> https:dev` when that example provides the script.
- Vite-based examples consume Hydrogen's default certificates. Certificates are provisioned automatically on `dev:https` startup, or run `pnpm https:setup` once from the repository root for frameworks that read certificate paths before Vite starts. Both download a pinned, checksum-verified mkcert release, trust the local certificate authority, and create the certificates under `~/.shopify/hydrogen/certs/`.
- Outside CI, the local HTTPS plugin uses Shopify CLI to link an unlinked Hydrogen storefront and push the Customer Account callback, JavaScript origin, and logout URLs. Failures fall back to printing the values for manual configuration.
- The Next.js template provisions its own certificate. The Hydrogen example uses the Shopify CLI tunnel flow.
- Run the relevant example with `pnpm --filter @shopify/hydrogen-example-<name> dev:https` when that example provides the script.
- Reproduce the trusted local HTTPS CI check with `pnpm https:setup && pnpm run test:local-https`.
9 changes: 5 additions & 4 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,16 +29,17 @@ From the repository root:
- `pnpm dev` — run all workspace examples and templates in parallel.
- `pnpm dev:hub` — run the examples with automatically allocated ports and open the browser hub with status, previews, and logs.
- `pnpm --filter @shopify/hydrogen-example-<name> dev` — run one example.
- `pnpm https:setup` then `pnpm --filter @shopify/hydrogen-example-<name> https:dev` — run an account-enabled example on `https://local.tryhydrogen.dev:5173` when it provides an `https:dev` script. The Hydrogen example uses `--customer-account-push` instead of local certificates.
- `pnpm --filter @shopify/hydrogen-example-<name> dev:https` — run an account-enabled example on `https://local.tryhydrogen.dev:5173` when it provides a `dev:https` script. Nuxt and SolidStart require the one-time `pnpm https:setup` prerequisite because they read certificate paths before Vite starts. The Hydrogen example uses `--customer-account-push` instead of local certificates.

Local HTTPS requires `mkcert`. On macOS:
Local HTTPS certificates are provisioned automatically the first time a `dev:https` script starts, or explicitly with:

```sh
brew install mkcert
pnpm https:setup
```

This installs the local certificate authority and creates trusted `local.tryhydrogen.dev` certificates under `~/.shopify/hydrogen/certs/` so Customer Account OAuth can redirect to `https://local.tryhydrogen.dev:5173/account/authorize`.
Both download a pinned, checksum-verified [mkcert](https://github.com/FiloSottile/mkcert) release, install the local certificate authority (this may prompt for your password), and create trusted `local.tryhydrogen.dev` certificates under `~/.shopify/hydrogen/certs/` so Customer Account OAuth can redirect to `https://local.tryhydrogen.dev:5173/account/authorize`.

Outside CI, the local HTTPS plugin also uses Shopify CLI to link an unlinked Hydrogen storefront and push the Customer Account callback, JavaScript origin, and logout URLs. It prints the values for manual configuration when automatic setup is unavailable.

The Next.js template provisions its own development certificate and does not use the Hydrogen certificates.

Expand Down
7 changes: 4 additions & 3 deletions examples/astro/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,14 @@ Port of the canonical `examples/core` design to [Astro](https://astro.build/) ru

The account flow uses `createCustomerSession` and `createCustomerAccountServerHandlers` from `@shopify/hydrogen/customer-account`, Customer Account values from `examples/shared/config.ts`, and an encrypted HttpOnly `__Host-` cookie adapter from `examples/shared/customer-session.ts`.

Customer Account OAuth requires a public HTTPS origin. To test locally without a tunnel, register `https://local.tryhydrogen.dev:5173/account/authorize` as the callback URI and run:
Customer Account OAuth requires a public HTTPS origin. To test locally without a tunnel, run:

```sh
pnpm https:setup
pnpm --filter @shopify/hydrogen-example-astro https:dev
pnpm --filter @shopify/hydrogen-example-astro dev:https
```

The local HTTPS plugin provisions the certificate, links an unlinked Hydrogen storefront, and pushes the Customer Account callback, JavaScript origin, and logout URLs. If automatic setup is unavailable, it prints the values for manual configuration.

## Run

```sh
Expand Down
3 changes: 1 addition & 2 deletions examples/astro/astro.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@ import tailwindcss from "@tailwindcss/vite";
// @ts-check
import { defineConfig } from "astro/config";

const enabled =
process.env.VITE_LOCAL_HTTPS === "1" || process.env.npm_lifecycle_event === "https:dev";
const enabled = process.env.npm_lifecycle_event === "dev:https";
const httpsOptions = { enabled };

export default defineConfig({
Expand Down
2 changes: 1 addition & 1 deletion examples/astro/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"type": "module",
"scripts": {
"dev": "astro dev",
"https:dev": "astro dev",
"dev:https": "astro dev",
"build": "astro build",
"start": "node ./dist/server/entry.mjs",
"typecheck": "astro check"
Expand Down
2 changes: 1 addition & 1 deletion examples/hydrogen/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,5 +49,5 @@ Follow step 1 and 2 of <https://shopify.dev/docs/custom-storefronts/building-wit
For local account testing, use the Customer Account push flow:

```bash
pnpm --dir examples/hydrogen https:dev
pnpm --dir examples/hydrogen dev:https
```
2 changes: 1 addition & 1 deletion examples/hydrogen/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"scripts": {
"build": "shopify hydrogen build --no-lockfile-check",
"dev": "react-router dev",
"https:dev": "shopify hydrogen dev --customer-account-push",
"dev:https": "shopify hydrogen dev --customer-account-push",
"preview": "shopify hydrogen preview --build",
"typecheck": "react-router typegen && tsc --noEmit",
"e2e": "playwright test",
Expand Down
3 changes: 1 addition & 2 deletions examples/nuxt/nuxt.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@ import type { NuxtConfig } from "nuxt/schema";

type VitePlugin = NonNullable<NonNullable<NuxtConfig["vite"]>["plugins"]>[number];

const enabled =
process.env.VITE_LOCAL_HTTPS === "1" || process.env.npm_lifecycle_event === "https:dev";
const enabled = process.env.npm_lifecycle_event === "dev:https";
const httpsOptions = { enabled };
const httpsPlugin = localHttps(httpsOptions);

Expand Down
2 changes: 1 addition & 1 deletion examples/nuxt/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"type": "module",
"scripts": {
"dev": "nuxt dev",
"https:dev": "nuxt dev",
"dev:https": "nuxt dev",
"build": "nuxt build",
"start": "node .output/server/index.mjs",
"typecheck": "nuxt typecheck"
Expand Down
6 changes: 4 additions & 2 deletions examples/solid-start/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,15 @@ Port of the canonical `examples/core` design to [SolidStart](https://start.solid

The account flow uses `createCustomerSession` and `createCustomerAccountServerHandlers` from `@shopify/hydrogen/customer-account`, Customer Account values from `examples/shared/config.ts`, and an encrypted HttpOnly `__Host-` cookie adapter from `examples/shared/customer-session.ts`.

Customer Account OAuth requires a public HTTPS origin. To test locally without a tunnel, register `https://local.tryhydrogen.dev:5173/account/authorize` as the callback URI and run:
Customer Account OAuth requires a public HTTPS origin. SolidStart reads certificate paths before Vite starts, so provision them once before running local HTTPS:

```sh
pnpm https:setup
pnpm --filter @shopify/hydrogen-example-solid-start https:dev
pnpm --filter @shopify/hydrogen-example-solid-start dev:https
```

When the server starts, the local HTTPS plugin links an unlinked Hydrogen storefront and pushes the Customer Account callback, JavaScript origin, and logout URLs. If automatic setup is unavailable, it prints the values for manual configuration.

## Run

```sh
Expand Down
3 changes: 1 addition & 2 deletions examples/solid-start/app.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@ import { localHttps } from "@shopify/hydrogen/vite";
import { defineConfig } from "@solidjs/start/config";
import tailwindcss from "@tailwindcss/vite";

const enabled =
process.env.VITE_LOCAL_HTTPS === "1" || process.env.npm_lifecycle_event === "https:dev";
const enabled = process.env.npm_lifecycle_event === "dev:https";
const httpsOptions = { enabled };
const httpsPlugin = localHttps(httpsOptions);
const devServer = httpsPlugin.api.getDevServerConfig();
Expand Down
2 changes: 1 addition & 1 deletion examples/solid-start/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"type": "module",
"scripts": {
"dev": "HOST=localhost vinxi dev",
"https:dev": "vinxi dev --host local.tryhydrogen.dev --port 5173",
"dev:https": "HOST=local.tryhydrogen.dev vinxi dev --port 5173",
"build": "vinxi build",
"start": "HOST=localhost vinxi start",
"typecheck": "tsc"
Expand Down
7 changes: 4 additions & 3 deletions examples/sveltekit/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,14 @@ Port of the canonical `examples/core` design to [SvelteKit 2](https://svelte.dev

The account flow uses `createCustomerSession` and `createCustomerAccountServerHandlers` from `@shopify/hydrogen/customer-account`, Customer Account values from `examples/shared/config.ts`, and an encrypted HttpOnly `__Host-` cookie adapter from `examples/shared/customer-session.ts`.

Customer Account OAuth requires a public HTTPS origin. To test locally without a tunnel, register `https://local.tryhydrogen.dev:5173/account/authorize` as the callback URI and run:
Customer Account OAuth requires a public HTTPS origin. To test locally without a tunnel, run:

```sh
pnpm https:setup
pnpm --filter @shopify/hydrogen-example-sveltekit https:dev
pnpm --filter @shopify/hydrogen-example-sveltekit dev:https
```

The local HTTPS plugin provisions the certificate, links an unlinked Hydrogen storefront, and pushes the Customer Account callback, JavaScript origin, and logout URLs. If automatic setup is unavailable, it prints the values for manual configuration.

## Run

```sh
Expand Down
2 changes: 1 addition & 1 deletion examples/sveltekit/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"type": "module",
"scripts": {
"dev": "vite dev",
"https:dev": "vite dev",
"dev:https": "vite dev",
"build": "vite build",
"start": "node --env-file-if-exists=.env build",
"prepare": "svelte-kit sync || echo ''",
Expand Down
3 changes: 1 addition & 2 deletions examples/sveltekit/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@ import { sveltekit } from "@sveltejs/kit/vite";
import tailwindcss from "@tailwindcss/vite";
import { defineConfig } from "vite";

const enabled =
process.env.VITE_LOCAL_HTTPS === "1" || process.env.npm_lifecycle_event === "https:dev";
const enabled = process.env.npm_lifecycle_event === "dev:https";
const httpsOptions = { enabled };

export default defineConfig({
Expand Down
7 changes: 4 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
"dev:svelte": "turbo run dev --filter=@shopify/hydrogen-example-sveltekit...",
"dev:hydrogen": "pnpm --dir examples/hydrogen dev",
"dev:hub": "node scripts/examples-dev.ts",
"https:setup": "mkcert -install && mkdir -p \"$HOME/.shopify/hydrogen/certs\" && mkcert -cert-file \"$HOME/.shopify/hydrogen/certs/local.tryhydrogen.dev.pem\" -key-file \"$HOME/.shopify/hydrogen/certs/local.tryhydrogen.dev-key.pem\" local.tryhydrogen.dev",
"https:setup": "turbo run build --filter=@shopify/hydrogen && node packages/hydrogen/bin/hydrogen.mjs certs install",
"download:standard-types": "node scripts/download-standard-types.ts",
"prepare:preview-dist": "node scripts/preview-template-dist.ts prepare",
"validate:preview-dist": "node scripts/preview-template-dist.ts validate",
Expand All @@ -26,14 +26,15 @@
"benchmark:harness": "node scripts/storefront-benchmark-harness/run-opencode-docker.ts",
"typecheck": "turbo run typecheck",
"libcheck": "turbo run libcheck --filter='./packages/*'",
"lint": "oxlint --max-warnings=0 packages/ examples/ templates/ scripts/preview-template-dist*.ts",
"lint:ci": "oxlint --format github --max-warnings=0 packages/ examples/ templates/ scripts/preview-template-dist*.ts",
"lint": "oxlint --max-warnings=0 packages/ examples/ templates/ scripts/preview-template-dist*.ts scripts/test-local-https.ts",
"lint:ci": "oxlint --format github --max-warnings=0 packages/ examples/ templates/ scripts/preview-template-dist*.ts scripts/test-local-https.ts",
"format": "oxfmt",
"format:check": "oxfmt --check",
"changeset": "changeset",
"version-packages": "changeset version",
"test": "pnpm run test:scripts && turbo run test",
"test:scripts": "node --test scripts/*.test.ts",
"test:local-https": "node --use-system-ca scripts/test-local-https.ts",
"test:benchmark-harness": "tsc -p scripts/storefront-benchmark-harness/tsconfig.json && node --test scripts/storefront-benchmark-harness/*.test.ts",
"check": "pnpm run format:check && pnpm run lint && pnpm run typecheck && pnpm run test",
"test:e2e:storefront": "turbo run test:e2e --filter @shopify/storefront-e2e"
Expand Down
2 changes: 1 addition & 1 deletion packages/hydrogen/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ const isLoggedIn = await customerSession.isLoggedIn(
);
```

Customer Account OAuth methods require a public HTTPS origin. The writable session manager should expose the request origin; explicit `origin` options are only needed as overrides. For local development, use a tunnel or trusted local HTTPS through `localHttps` from `@shopify/hydrogen/vite`, and pass the framework's canonical request URL rather than an untrusted forwarded host.
Customer Account OAuth methods require a public HTTPS origin. The writable session manager should expose the request origin; explicit `origin` options are only needed as overrides. For local development, use a tunnel or trusted local HTTPS through `localHttps` from `@shopify/hydrogen/vite`, which provisions certificates and uses Shopify CLI to push Customer Account URLs outside CI. Pass the framework's canonical request URL rather than an untrusted forwarded host.

Pass `customerSession` to `createCartServerHandlers({customerSession})` to associate newly created carts with the current customer when the session has a usable access token or successfully refreshed access token, and mark checkout URLs in authenticated cart GET responses with `logged_in=true`.

Expand Down
2 changes: 2 additions & 0 deletions packages/hydrogen/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -93,13 +93,15 @@
"postcodegen": "node scripts/postprocess-tada-env.ts && oxfmt src/graphql/generated/*.d.ts src/graphql/generated/*.json"
},
"dependencies": {
"cross-spawn": "7.0.6",
"gql.tada": "1.9.2"
},
"devDependencies": {
"@graphql-codegen/cli": "^7.0.0",
"@graphql-codegen/introspection": "^6.0.0",
"@graphql-codegen/typescript": "^6.0.0",
"@testing-library/react": "^16.3.2",
"@types/cross-spawn": "6.0.6",
"@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3",
"@vue/test-utils": "^2.4.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,4 +104,4 @@ The same `@shopify/hydrogen/ts-plugin` and `hydrogen gql check` setup from the `

## Local OAuth

Customer Account OAuth needs a public HTTPS callback origin. For local examples, use a trusted local HTTPS hostname and register the exact `/account/authorize` callback URL in the Customer Account app configuration.
Customer Account OAuth needs a public HTTPS origin. For local development, follow the `hydrogen-local-https` skill; its Vite plugin provisions a trusted certificate and pushes the callback, JavaScript origin, and logout URLs through Shopify CLI outside CI.
Loading
Loading