@shopify/cli-hydrogen 12.0.2 → 13.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,13 @@
1
1
  # skeleton
2
2
 
3
+ ## 2026.4.5
4
+
5
+ ### Patch Changes
6
+
7
+ - Add an explicit `~` app alias to new Hydrogen projects. ([#3852](https://github.com/Shopify/hydrogen/pull/3852)) by [@andguy95](https://github.com/andguy95)
8
+
9
+ JavaScript projects use `jsconfig.json`, which is not reliably covered by Vite's native `resolve.tsconfigPaths` behavior. New projects now define Hydrogen's `~/` import convention directly in the Vite config so imports like `~/assets/favicon.svg` work in both TypeScript and JavaScript projects.
10
+
3
11
  ## 2026.4.4
4
12
 
5
13
  ### Patch Changes
@@ -1,13 +1,13 @@
1
1
  # Hydrogen template: Skeleton
2
2
 
3
- Hydrogen is Shopify’s stack for headless commerce. Hydrogen is designed to dovetail with [Remix](https://remix.run/), Shopify’s full stack web framework. This template contains a **minimal setup** of components, queries and tooling to get started with Hydrogen.
3
+ Hydrogen is Shopify’s stack for headless commerce. Hydrogen is designed to dovetail with [React Router](https://reactrouter.com/), the modern multi-strategy router for React. This template contains a **minimal setup** of components, queries and tooling to get started with Hydrogen.
4
4
 
5
5
  [Check out Hydrogen docs](https://shopify.dev/custom-storefronts/hydrogen)
6
- [Get familiar with Remix](https://remix.run/docs/en/v1)
6
+ [Get familiar with React Router](https://reactrouter.com/start/framework/routing)
7
7
 
8
8
  ## What's included
9
9
 
10
- - Remix
10
+ - React Router
11
11
  - Hydrogen
12
12
  - Oxygen
13
13
  - Vite
@@ -22,7 +22,7 @@ Hydrogen is Shopify’s stack for headless commerce. Hydrogen is designed to dov
22
22
 
23
23
  **Requirements:**
24
24
 
25
- - Node.js version 18.0.0 or higher
25
+ - Node.js version 22.x or 24.x
26
26
 
27
27
  ```bash
28
28
  npm create @shopify/hydrogen@latest
@@ -2,7 +2,7 @@
2
2
  "name": "skeleton",
3
3
  "private": true,
4
4
  "sideEffects": false,
5
- "version": "2026.4.4",
5
+ "version": "2026.4.5",
6
6
  "type": "module",
7
7
  "scripts": {
8
8
  "build": "shopify hydrogen build --codegen",
@@ -32,7 +32,7 @@
32
32
  "@react-router/fs-routes": "7.16.0",
33
33
  "@shopify/cli": "3.93.2",
34
34
  "@shopify/hydrogen-codegen": "0.3.3",
35
- "@shopify/mini-oxygen": "4.1.0",
35
+ "@shopify/mini-oxygen": "4.2.0",
36
36
  "@shopify/oxygen-workers-types": "^4.1.6",
37
37
  "@shopify/prettier-config": "^1.1.2",
38
38
  "@total-typescript/ts-reset": "^0.6.1",
@@ -1,3 +1,4 @@
1
+ import {fileURLToPath} from 'node:url';
1
2
  import {defineConfig} from 'vite';
2
3
  import {hydrogen} from '@shopify/hydrogen/vite';
3
4
  import {oxygen} from '@shopify/mini-oxygen/vite';
@@ -6,6 +7,11 @@ import {reactRouter} from '@react-router/dev/vite';
6
7
  export default defineConfig({
7
8
  plugins: [hydrogen(), oxygen(), reactRouter()],
8
9
  resolve: {
10
+ alias: {
11
+ // Vite's native tsconfig path resolver does not cover JavaScript
12
+ // projects that use jsconfig.json, so define Hydrogen's app alias here.
13
+ '~': fileURLToPath(new URL('./app', import.meta.url)),
14
+ },
9
15
  tsconfigPaths: true,
10
16
  },
11
17
  build: {
@@ -21,6 +21,7 @@ import { isClassicProject, getProjectPaths } from '../../lib/remix-config.js';
21
21
  import { packageManagers } from '../../lib/package-managers.js';
22
22
 
23
23
  const DEPLOY_OUTPUT_FILE_HANDLE = "h2_deploy_log.json";
24
+ const DEFAULT_BUILD_COMMAND = "node --run build";
24
25
  const deploymentLogger = (message, level = "info") => {
25
26
  if (level === "error" || level === "warn") {
26
27
  outputWarn(message);
@@ -46,7 +47,7 @@ class Deploy extends Command {
46
47
  }),
47
48
  force: Flags.boolean({
48
49
  char: "f",
49
- description: "Forces a deployment to proceed if there are uncommitted changes in its Git repository.",
50
+ description: "Forces a deployment to proceed if there are uncommitted changes in its Git repository, and skips confirmation prompts for non-preview environments.",
50
51
  default: false,
51
52
  env: "SHOPIFY_HYDROGEN_FLAG_FORCE",
52
53
  required: false
@@ -69,7 +70,17 @@ class Deploy extends Command {
69
70
  dependsOn: ["auth-bypass-token"]
70
71
  }),
71
72
  "build-command": Flags.string({
72
- description: "Specify a build command to run before deploying. If not specified, `shopify hydrogen build` will be used.",
73
+ description: "Specify a build command to run before deploying. If not specified, the Hydrogen build pipeline will be used. When custom output directories are configured, defaults to `node --run build`.",
74
+ required: false
75
+ }),
76
+ "assets-dir": Flags.string({
77
+ description: "Directory containing the client assets to deploy, relative to the project root. Defaults to the detected Vite client output directory, then falls back to `dist/client`.",
78
+ env: "SHOPIFY_HYDROGEN_FLAG_ASSETS_DIR",
79
+ required: false
80
+ }),
81
+ "worker-dir": Flags.string({
82
+ description: "Directory containing the Oxygen worker entry point (`index.js` or `index.mjs`), relative to the project root. Defaults to the detected Vite server output directory, then falls back to `dist/server`.",
83
+ env: "SHOPIFY_HYDROGEN_FLAG_WORKER_DIR",
73
84
  required: false
74
85
  }),
75
86
  ...commonFlags.lockfileCheck,
@@ -155,17 +166,19 @@ async function runDeploy(options) {
155
166
  env: envHandle,
156
167
  envBranch,
157
168
  environmentFile,
158
- force: forceOnUncommittedChanges,
169
+ force,
159
170
  forceClientSourcemap = false,
160
171
  noVerify,
161
172
  lockfileCheck,
162
173
  jsonOutput,
163
174
  path: root,
164
175
  shop,
176
+ assetsDir: assetsDirFlag,
165
177
  metadataUrl,
166
178
  metadataUser,
167
179
  metadataVersion,
168
- entry: ssrEntry
180
+ entry: ssrEntry,
181
+ workerDir: workerDirFlag
169
182
  } = options;
170
183
  let { metadataDescription } = options;
171
184
  let isCleanGit = true;
@@ -175,7 +188,7 @@ async function runDeploy(options) {
175
188
  if (error instanceof GitDirectoryNotCleanError) {
176
189
  isCleanGit = false;
177
190
  }
178
- if (!forceOnUncommittedChanges && !isCleanGit) {
191
+ if (!force && !isCleanGit) {
179
192
  let errorMessage = "Uncommitted changes detected";
180
193
  let changedFiles = void 0;
181
194
  const nextSteps = [
@@ -335,16 +348,22 @@ ${changedFiles.trimEnd()}`;
335
348
  let assetsDir = "dist/client";
336
349
  let workerDir = "dist/worker";
337
350
  const isClassicCompiler = await isClassicProject(root);
338
- if (!isClassicCompiler) {
351
+ const metadataHydrogenVersion = getHydrogenVersion({ appPath: root });
352
+ const shouldUseDefaultBuildCommand = !buildCommand && !isClassicCompiler && (assetsDirFlag || workerDirFlag || isHydrogenPreviewVersion(metadataHydrogenVersion));
353
+ if (isClassicCompiler) {
354
+ assetsDir = assetsDirFlag ?? assetsDir;
355
+ workerDir = workerDirFlag ?? workerDir;
356
+ } else {
339
357
  const viteConfig = await getViteConfig(root, ssrEntry).catch(() => null);
340
- if (viteConfig) {
341
- assetsDir = relativePath(root, viteConfig.clientOutDir);
342
- workerDir = relativePath(root, viteConfig.serverOutDir);
343
- } else {
344
- workerDir = "dist/server";
345
- }
358
+ const outputDirs = resolveDeploymentOutputDirs({
359
+ root,
360
+ viteOutputDirs: viteConfig,
361
+ assetsDirFlag,
362
+ workerDirFlag
363
+ });
364
+ assetsDir = outputDirs.assetsDir;
365
+ workerDir = outputDirs.workerDir;
346
366
  }
347
- const metadataHydrogenVersion = await getHydrogenVersion({ appPath: root });
348
367
  const config = {
349
368
  assetsDir,
350
369
  bugsnag: true,
@@ -369,7 +388,7 @@ ${changedFiles.trimEnd()}`;
369
388
  workerDir,
370
389
  overriddenEnvironmentVariables
371
390
  };
372
- if (!isCI && !config.defaultEnvironment && (userProvidedEnvironmentTag || userChosenEnvironmentTag)) {
391
+ if (!isCI && !config.defaultEnvironment && (userProvidedEnvironmentTag || userChosenEnvironmentTag) && !force) {
373
392
  let chosenEnvironment = findEnvironmentByBranchOrThrow(
374
393
  deploymentData.environments,
375
394
  config.environmentTag
@@ -403,6 +422,7 @@ Continue?`.value
403
422
  }
404
423
  );
405
424
  let deployError = null;
425
+ let buildError = null;
406
426
  let resolveDeploy;
407
427
  let rejectDeploy;
408
428
  const deployPromise = new Promise((resolve, reject) => {
@@ -436,7 +456,7 @@ Continue?`.value
436
456
  );
437
457
  }
438
458
  };
439
- if (buildCommand) {
459
+ if (buildCommand || shouldUseDefaultBuildCommand) {
440
460
  if (forceClientSourcemap) {
441
461
  console.log("");
442
462
  renderInfo({
@@ -444,24 +464,29 @@ Continue?`.value
444
464
  body: "Client sourcemaps will not be generated."
445
465
  });
446
466
  }
447
- config.buildCommand = buildCommand;
467
+ config.buildCommand = buildCommand ?? DEFAULT_BUILD_COMMAND;
448
468
  } else {
449
469
  hooks.buildFunction = async (assetPath) => {
450
- outputInfo(
451
- outputContent`${colors.whiteBright("Building project...")}`.value
452
- );
453
- if (isClassicCompiler) {
454
- throw new AbortError(REMIX_COMPILER_ERROR_MESSAGE);
470
+ try {
471
+ outputInfo(
472
+ outputContent`${colors.whiteBright("Building project...")}`.value
473
+ );
474
+ if (isClassicCompiler) {
475
+ throw new AbortError(REMIX_COMPILER_ERROR_MESSAGE);
476
+ }
477
+ await runBuild({
478
+ directory: root,
479
+ assetPath,
480
+ lockfileCheck,
481
+ sourcemap: true,
482
+ forceClientSourcemap,
483
+ useCodegen: false,
484
+ entry: ssrEntry
485
+ });
486
+ } catch (error) {
487
+ buildError = error;
488
+ throw error;
455
489
  }
456
- await runBuild({
457
- directory: root,
458
- assetPath,
459
- lockfileCheck,
460
- sourcemap: true,
461
- forceClientSourcemap,
462
- useCodegen: false,
463
- entry: ssrEntry
464
- });
465
490
  };
466
491
  }
467
492
  const uploadStart = async () => {
@@ -523,19 +548,39 @@ Continue?`.value
523
548
  }
524
549
  resolveDeploy();
525
550
  }).catch((error) => {
526
- rejectDeploy(deployError || error);
551
+ rejectDeploy(deployError || buildError || error);
527
552
  });
528
553
  return deployPromise;
529
554
  }
530
- async function getHydrogenVersion({ appPath }) {
555
+ function resolveDeploymentOutputDirs({
556
+ root,
557
+ viteOutputDirs,
558
+ assetsDirFlag,
559
+ workerDirFlag
560
+ }) {
561
+ const viteAssetsDir = viteOutputDirs && relativePath(root, viteOutputDirs.clientOutDir);
562
+ const viteWorkerDir = viteOutputDirs && relativePath(root, viteOutputDirs.serverOutDir);
563
+ return {
564
+ assetsDir: assetsDirFlag ?? viteAssetsDir ?? "dist/client",
565
+ workerDir: workerDirFlag ?? viteWorkerDir ?? "dist/server"
566
+ };
567
+ }
568
+ function isHydrogenPreviewVersion(version) {
569
+ return version?.startsWith("0.0.0-preview-") ?? false;
570
+ }
571
+ function getHydrogenVersion({ appPath }) {
531
572
  const { root } = getProjectPaths(appPath);
532
- const require2 = createRequire(import.meta.url);
533
- const { version } = require2(
534
- require2.resolve("@shopify/hydrogen/package.json", {
535
- paths: [root]
536
- })
537
- );
538
- return version;
573
+ try {
574
+ const require2 = createRequire(import.meta.url);
575
+ const packageJson = require2(
576
+ require2.resolve("@shopify/hydrogen/package.json", {
577
+ paths: [root]
578
+ })
579
+ );
580
+ return typeof packageJson.version === "string" ? packageJson.version : void 0;
581
+ } catch {
582
+ return void 0;
583
+ }
539
584
  }
540
585
 
541
- export { Deploy as default, deploymentLogger, getHydrogenVersion, runDeploy };
586
+ export { Deploy as default, deploymentLogger, getHydrogenVersion, resolveDeploymentOutputDirs, runDeploy };
@@ -1,4 +1,5 @@
1
1
  import Command from '@shopify/cli-kit/node/base-command';
2
+ import { Flags } from '@oclif/core';
2
3
  import { diffLines } from 'diff';
3
4
  import { commonFlags, flagsToCamelObject } from '../../../lib/flags.js';
4
5
  import { login } from '../../../lib/auth.js';
@@ -19,7 +20,17 @@ class EnvPush extends Command {
19
20
  static flags = {
20
21
  ...commonFlags.env,
21
22
  ...commonFlags.envFile,
22
- ...commonFlags.path
23
+ ...commonFlags.path,
24
+ force: Flags.boolean({
25
+ char: "f",
26
+ description: "Push environment variable changes without confirmation.",
27
+ env: "SHOPIFY_HYDROGEN_FLAG_FORCE"
28
+ }),
29
+ "dry-run": Flags.boolean({
30
+ description: "Preview environment variable changes without pushing them.",
31
+ env: "SHOPIFY_HYDROGEN_FLAG_DRY_RUN",
32
+ exclusive: ["force"]
33
+ })
23
34
  };
24
35
  async run() {
25
36
  const { flags } = await this.parse(EnvPush);
@@ -27,8 +38,10 @@ class EnvPush extends Command {
27
38
  }
28
39
  }
29
40
  async function runEnvPush({
41
+ dryRun = false,
30
42
  env: envHandle,
31
43
  envFile,
44
+ force = false,
32
45
  path = process.cwd()
33
46
  }) {
34
47
  let validatedEnvironment;
@@ -114,8 +127,18 @@ async function runEnvPush({
114
127
  body: "No changes to your environment variables."
115
128
  });
116
129
  return;
117
- } else {
118
- const diff = diffLines(comparableRemoteVars, compareableLocalVars);
130
+ }
131
+ const diff = diffLines(comparableRemoteVars, compareableLocalVars);
132
+ if (dryRun) {
133
+ renderInfo({
134
+ body: outputContent`The following changes would be made to your environment variables for ${validatedEnvironment.name}:
135
+
136
+ ${outputToken.linesDiff(diff)}
137
+ No changes were pushed because --dry-run was used.`.value
138
+ });
139
+ return;
140
+ }
141
+ if (!force) {
119
142
  const confirmPush = await renderConfirmationPrompt({
120
143
  confirmationMessage: "Yes, confirm changes",
121
144
  cancellationMessage: "No, make changes later",
@@ -1,8 +1,8 @@
1
- import { createRequire } from 'node:module';
2
1
  import { spawnSync } from 'node:child_process';
3
2
  import { outputNewline, outputDebug } from '@shopify/cli-kit/node/output';
4
- import { cwd, resolvePath, joinPath } from '@shopify/cli-kit/node/path';
3
+ import { cwd, resolvePath } from '@shopify/cli-kit/node/path';
5
4
  import { renderWarning } from '@shopify/cli-kit/node/ui';
5
+ import { isHydrogenProject, applyHydrogenCommandPolicy } from '../lib/hydrogen-command-policy.js';
6
6
 
7
7
  const hook = async function(options) {
8
8
  if (!options.id?.startsWith("hydrogen:") || options.id === "hydrogen:init") {
@@ -36,6 +36,9 @@ const hook = async function(options) {
36
36
  });
37
37
  process.exit(1);
38
38
  }
39
+ if (applyHydrogenCommandPolicy({ id: options.id, projectPath })) {
40
+ process.exit(1);
41
+ }
39
42
  if (commandNeedsVM(options.id, options.argv) && !process.execArgv.includes(EXPERIMENTAL_VM_MODULES_FLAG) && !(process.env.NODE_OPTIONS ?? "").includes(EXPERIMENTAL_VM_MODULES_FLAG)) {
40
43
  outputDebug(
41
44
  `Restarting CLI process with ${EXPERIMENTAL_VM_MODULES_FLAG} flag.`
@@ -50,18 +53,6 @@ const EXPERIMENTAL_VM_MODULES_FLAG = "--experimental-vm-modules";
50
53
  function commandNeedsVM(id = "", argv = []) {
51
54
  return id === "hydrogen:debug:cpu";
52
55
  }
53
- function isHydrogenProject(projectPath) {
54
- try {
55
- const require2 = createRequire(import.meta.url);
56
- const { dependencies, scripts } = require2(
57
- joinPath(projectPath, "package.json")
58
- );
59
- return !!dependencies?.["@shopify/hydrogen"] || // Diff examples don't have dependencies:
60
- !!scripts?.dev?.includes("--diff");
61
- } catch {
62
- return false;
63
- }
64
- }
65
56
  var init_default = hook;
66
57
 
67
58
  export { init_default as default };
package/dist/index.d.ts CHANGED
@@ -126,6 +126,8 @@ declare class EnvPull extends Command {
126
126
  declare class EnvPush extends Command {
127
127
  static description: string;
128
128
  static flags: {
129
+ force: _oclif_core_lib_interfaces_parser_js.BooleanFlag<boolean>;
130
+ 'dry-run': _oclif_core_lib_interfaces_parser_js.BooleanFlag<boolean>;
129
131
  path: _oclif_core_lib_interfaces_parser_js.OptionFlag<string | undefined, _oclif_core_lib_interfaces_parser_js.CustomOptions>;
130
132
  'env-file': _oclif_core_lib_interfaces_parser_js.OptionFlag<string, _oclif_core_lib_interfaces_parser_js.CustomOptions>;
131
133
  env: _oclif_core_lib_interfaces_parser_js.OptionFlag<string | undefined, _oclif_core_lib_interfaces_parser_js.CustomOptions>;
@@ -0,0 +1,45 @@
1
+ import { createRequire } from 'node:module';
2
+ import { outputNewline } from '@shopify/cli-kit/node/output';
3
+ import { joinPath } from '@shopify/cli-kit/node/path';
4
+ import { renderError } from '@shopify/cli-kit/node/ui';
5
+
6
+ function applyHydrogenCommandPolicy({
7
+ id,
8
+ projectPath
9
+ }) {
10
+ if (!id || !isHydrogenProject(projectPath) || !isHydrogenCommandDisabled(projectPath, id)) {
11
+ return false;
12
+ }
13
+ outputNewline();
14
+ renderError({
15
+ headline: `\`shopify ${id.replace(/:/g, " ")}\` is not supported by this version of Hydrogen`,
16
+ body: "The installed version of @shopify/hydrogen disables this command.",
17
+ nextSteps: ["Use your framework or package tooling instead."]
18
+ });
19
+ return true;
20
+ }
21
+ function isHydrogenCommandDisabled(projectPath, id) {
22
+ try {
23
+ const require2 = createRequire(joinPath(projectPath, "package.json"));
24
+ const hydrogenPackageJson = require2("@shopify/hydrogen/package.json");
25
+ const disabledCommands = hydrogenPackageJson?.shopify?.cli?.disabledCommands;
26
+ return Array.isArray(disabledCommands) && disabledCommands.includes(id);
27
+ } catch {
28
+ return false;
29
+ }
30
+ }
31
+ function isHydrogenProject(projectPath) {
32
+ try {
33
+ const require2 = createRequire(import.meta.url);
34
+ const projectPackageJson = require2(joinPath(projectPath, "package.json"));
35
+ return [
36
+ projectPackageJson?.dependencies,
37
+ projectPackageJson?.devDependencies,
38
+ projectPackageJson?.peerDependencies
39
+ ].some((dependencies) => !!dependencies?.["@shopify/hydrogen"]);
40
+ } catch {
41
+ return false;
42
+ }
43
+ }
44
+
45
+ export { applyHydrogenCommandPolicy, isHydrogenCommandDisabled, isHydrogenProject };
@@ -37,9 +37,7 @@ async function getViteConfig(root, ssrEntryFlag) {
37
37
  mode,
38
38
  mode
39
39
  );
40
- const { appDirectory, serverBuildFile, routes } = getReactRouterOrRemixConfigFromVite(resolvedViteConfig);
41
- const serverOutDir = resolvedViteConfig.build.outDir;
42
- const clientOutDir = serverOutDir.replace(/server$/, "client");
40
+ const { appDirectory, clientOutDir, serverBuildFile, serverOutDir, routes } = getBuildConfigFromVite(resolvedViteConfig);
43
41
  const rollupOutput = resolvedViteConfig.build.rollupOptions.output;
44
42
  const { entryFileNames } = (Array.isArray(rollupOutput) ? rollupOutput[0] : rollupOutput) ?? {};
45
43
  const serverOutFile = joinPath(
@@ -68,33 +66,58 @@ async function getViteConfig(root, ssrEntryFlag) {
68
66
  }
69
67
  };
70
68
  }
71
- function getReactRouterOrRemixConfigFromVite(viteConfig) {
72
- try {
73
- const reactRouterConfig = getReactRouterConfigFromVite(viteConfig);
74
- return reactRouterConfig;
75
- } catch (error) {
76
- const remixConfig = getRemixConfigFromVite(viteConfig);
77
- return remixConfig;
69
+ function resolveViteOutputDirs({
70
+ routingBuildDirectory,
71
+ serverEnvironmentOutDir,
72
+ clientEnvironmentOutDir,
73
+ buildOutDir
74
+ }) {
75
+ if (routingBuildDirectory) {
76
+ return {
77
+ clientOutDir: joinPath(routingBuildDirectory, "client"),
78
+ serverOutDir: joinPath(routingBuildDirectory, "server")
79
+ };
78
80
  }
81
+ const serverOutDir = serverEnvironmentOutDir ?? buildOutDir;
82
+ const clientOutDir = clientEnvironmentOutDir && clientEnvironmentOutDir !== serverOutDir ? clientEnvironmentOutDir : serverOutDir.replace(/server$/, "client");
83
+ return {
84
+ clientOutDir,
85
+ serverOutDir
86
+ };
87
+ }
88
+ function getBuildConfigFromVite(viteConfig) {
89
+ const buildConfig = getReactRouterConfigFromVite(viteConfig) ?? getRemixConfigFromVite(viteConfig);
90
+ const { clientOutDir, serverOutDir } = resolveViteOutputDirs({
91
+ routingBuildDirectory: buildConfig?.buildDirectory,
92
+ serverEnvironmentOutDir: viteConfig.environments?.ssr?.build.outDir,
93
+ clientEnvironmentOutDir: viteConfig.environments?.client?.build.outDir,
94
+ buildOutDir: viteConfig.build.outDir
95
+ });
96
+ return {
97
+ ...buildConfig,
98
+ clientOutDir,
99
+ serverOutDir
100
+ };
79
101
  }
80
102
  function getReactRouterConfigFromVite(viteConfig) {
81
- if (!viteConfig.__reactRouterPluginContext) {
82
- throw new Error("Could not resolve React Router config");
83
- }
84
- const { appDirectory, serverBuildFile, routes } = viteConfig.__reactRouterPluginContext.reactRouterConfig;
103
+ if (!viteConfig.__reactRouterPluginContext) return;
104
+ const { appDirectory, buildDirectory, serverBuildFile, routes } = viteConfig.__reactRouterPluginContext.reactRouterConfig;
85
105
  return {
86
106
  appDirectory,
107
+ buildDirectory,
87
108
  serverBuildFile,
88
109
  routes
89
110
  };
90
111
  }
91
112
  function getRemixConfigFromVite(viteConfig) {
92
113
  const { remixConfig } = findHydrogenPlugin(viteConfig)?.api?.getPluginOptions() ?? {};
93
- return remixConfig ? {
94
- appDirectory: remixConfig.appDirectory,
95
- serverBuildFile: remixConfig.serverBuildFile,
96
- routes: remixConfig.routes
97
- } : {};
114
+ if (remixConfig) {
115
+ return {
116
+ appDirectory: remixConfig.appDirectory,
117
+ serverBuildFile: remixConfig.serverBuildFile,
118
+ routes: remixConfig.routes
119
+ };
120
+ }
98
121
  }
99
122
  function findPlugin(config, name) {
100
123
  return config.plugins.find((plugin) => plugin.name === name);
@@ -353,7 +353,7 @@
353
353
  },
354
354
  "force": {
355
355
  "char": "f",
356
- "description": "Forces a deployment to proceed if there are uncommitted changes in its Git repository.",
356
+ "description": "Forces a deployment to proceed if there are uncommitted changes in its Git repository, and skips confirmation prompts for non-preview environments.",
357
357
  "env": "SHOPIFY_HYDROGEN_FLAG_FORCE",
358
358
  "name": "force",
359
359
  "required": false,
@@ -388,13 +388,31 @@
388
388
  "type": "option"
389
389
  },
390
390
  "build-command": {
391
- "description": "Specify a build command to run before deploying. If not specified, `shopify hydrogen build` will be used.",
391
+ "description": "Specify a build command to run before deploying. If not specified, the Hydrogen build pipeline will be used. When custom output directories are configured, defaults to `node --run build`.",
392
392
  "name": "build-command",
393
393
  "required": false,
394
394
  "hasDynamicHelp": false,
395
395
  "multiple": false,
396
396
  "type": "option"
397
397
  },
398
+ "assets-dir": {
399
+ "description": "Directory containing the client assets to deploy, relative to the project root. Defaults to the detected Vite client output directory, then falls back to `dist/client`.",
400
+ "env": "SHOPIFY_HYDROGEN_FLAG_ASSETS_DIR",
401
+ "name": "assets-dir",
402
+ "required": false,
403
+ "hasDynamicHelp": false,
404
+ "multiple": false,
405
+ "type": "option"
406
+ },
407
+ "worker-dir": {
408
+ "description": "Directory containing the Oxygen worker entry point (`index.js` or `index.mjs`), relative to the project root. Defaults to the detected Vite server output directory, then falls back to `dist/server`.",
409
+ "env": "SHOPIFY_HYDROGEN_FLAG_WORKER_DIR",
410
+ "name": "worker-dir",
411
+ "required": false,
412
+ "hasDynamicHelp": false,
413
+ "multiple": false,
414
+ "type": "option"
415
+ },
398
416
  "lockfile-check": {
399
417
  "description": "Checks that there is exactly one valid lockfile in the project. Defaults to `true`. Deactivate with `--no-lockfile-check`.",
400
418
  "env": "SHOPIFY_HYDROGEN_FLAG_LOCKFILE_CHECK",
@@ -787,6 +805,24 @@
787
805
  "hasDynamicHelp": false,
788
806
  "multiple": false,
789
807
  "type": "option"
808
+ },
809
+ "force": {
810
+ "char": "f",
811
+ "description": "Push environment variable changes without confirmation.",
812
+ "env": "SHOPIFY_HYDROGEN_FLAG_FORCE",
813
+ "name": "force",
814
+ "allowNo": false,
815
+ "type": "boolean"
816
+ },
817
+ "dry-run": {
818
+ "description": "Preview environment variable changes without pushing them.",
819
+ "env": "SHOPIFY_HYDROGEN_FLAG_DRY_RUN",
820
+ "exclusive": [
821
+ "force"
822
+ ],
823
+ "name": "dry-run",
824
+ "allowNo": false,
825
+ "type": "boolean"
790
826
  }
791
827
  },
792
828
  "hasDynamicHelp": false,
@@ -1675,5 +1711,5 @@
1675
1711
  ]
1676
1712
  }
1677
1713
  },
1678
- "version": "12.0.2"
1714
+ "version": "13.0.1"
1679
1715
  }
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "access": "public",
5
5
  "@shopify:registry": "https://registry.npmjs.org"
6
6
  },
7
- "version": "12.0.2",
7
+ "version": "13.0.1",
8
8
  "license": "MIT",
9
9
  "type": "module",
10
10
  "repository": {
@@ -59,7 +59,7 @@
59
59
  "graphql-config": "^5.0.3",
60
60
  "vite": "^5.1.0 || ^6.2.0 || ^7.0.0 || ^8.0.0",
61
61
  "@shopify/hydrogen-codegen": "0.3.3",
62
- "@shopify/mini-oxygen": "4.1.0"
62
+ "@shopify/mini-oxygen": "4.2.0"
63
63
  },
64
64
  "peerDependenciesMeta": {
65
65
  "@graphql-codegen/cli": {
@@ -98,7 +98,7 @@
98
98
  "oclif.manifest.json"
99
99
  ],
100
100
  "engines": {
101
- "node": "^20 || ^22 || ^24"
101
+ "node": "^22 || ^24"
102
102
  },
103
103
  "oclif": {
104
104
  "commands": "dist/commands",