@shopify/cli-hydrogen 13.0.0 → 13.0.2

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",
@@ -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: {
@@ -47,7 +47,7 @@ class Deploy extends Command {
47
47
  }),
48
48
  force: Flags.boolean({
49
49
  char: "f",
50
- 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.",
51
51
  default: false,
52
52
  env: "SHOPIFY_HYDROGEN_FLAG_FORCE",
53
53
  required: false
@@ -166,7 +166,7 @@ async function runDeploy(options) {
166
166
  env: envHandle,
167
167
  envBranch,
168
168
  environmentFile,
169
- force: forceOnUncommittedChanges,
169
+ force,
170
170
  forceClientSourcemap = false,
171
171
  noVerify,
172
172
  lockfileCheck,
@@ -188,7 +188,7 @@ async function runDeploy(options) {
188
188
  if (error instanceof GitDirectoryNotCleanError) {
189
189
  isCleanGit = false;
190
190
  }
191
- if (!forceOnUncommittedChanges && !isCleanGit) {
191
+ if (!force && !isCleanGit) {
192
192
  let errorMessage = "Uncommitted changes detected";
193
193
  let changedFiles = void 0;
194
194
  const nextSteps = [
@@ -355,11 +355,11 @@ ${changedFiles.trimEnd()}`;
355
355
  workerDir = workerDirFlag ?? workerDir;
356
356
  } else {
357
357
  const viteConfig = await getViteConfig(root, ssrEntry).catch(() => null);
358
- const outputDirs = await resolveDeploymentOutputDirs({
358
+ const outputDirs = resolveDeploymentOutputDirs({
359
359
  root,
360
- viteConfig,
361
- assetsDir: assetsDirFlag,
362
- workerDir: workerDirFlag
360
+ viteOutputDirs: viteConfig,
361
+ assetsDirFlag,
362
+ workerDirFlag
363
363
  });
364
364
  assetsDir = outputDirs.assetsDir;
365
365
  workerDir = outputDirs.workerDir;
@@ -388,7 +388,7 @@ ${changedFiles.trimEnd()}`;
388
388
  workerDir,
389
389
  overriddenEnvironmentVariables
390
390
  };
391
- if (!isCI && !config.defaultEnvironment && (userProvidedEnvironmentTag || userChosenEnvironmentTag)) {
391
+ if (!isCI && !config.defaultEnvironment && (userProvidedEnvironmentTag || userChosenEnvironmentTag) && !force) {
392
392
  let chosenEnvironment = findEnvironmentByBranchOrThrow(
393
393
  deploymentData.environments,
394
394
  config.environmentTag
@@ -422,6 +422,7 @@ Continue?`.value
422
422
  }
423
423
  );
424
424
  let deployError = null;
425
+ let buildError = null;
425
426
  let resolveDeploy;
426
427
  let rejectDeploy;
427
428
  const deployPromise = new Promise((resolve, reject) => {
@@ -466,21 +467,26 @@ Continue?`.value
466
467
  config.buildCommand = buildCommand ?? DEFAULT_BUILD_COMMAND;
467
468
  } else {
468
469
  hooks.buildFunction = async (assetPath) => {
469
- outputInfo(
470
- outputContent`${colors.whiteBright("Building project...")}`.value
471
- );
472
- if (isClassicCompiler) {
473
- 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;
474
489
  }
475
- await runBuild({
476
- directory: root,
477
- assetPath,
478
- lockfileCheck,
479
- sourcemap: true,
480
- forceClientSourcemap,
481
- useCodegen: false,
482
- entry: ssrEntry
483
- });
484
490
  };
485
491
  }
486
492
  const uploadStart = async () => {
@@ -542,27 +548,21 @@ Continue?`.value
542
548
  }
543
549
  resolveDeploy();
544
550
  }).catch((error) => {
545
- rejectDeploy(deployError || error);
551
+ rejectDeploy(deployError || buildError || error);
546
552
  });
547
553
  return deployPromise;
548
554
  }
549
- async function resolveDeploymentOutputDirs({
555
+ function resolveDeploymentOutputDirs({
550
556
  root,
551
- viteConfig,
552
- assetsDir,
553
- workerDir
557
+ viteOutputDirs,
558
+ assetsDirFlag,
559
+ workerDirFlag
554
560
  }) {
555
- const fallbackOutputDirs = {
556
- assetsDir: "dist/client",
557
- workerDir: "dist/server"
558
- };
559
- const viteOutputDirs = viteConfig ? {
560
- assetsDir: relativePath(root, viteConfig.clientOutDir),
561
- workerDir: relativePath(root, viteConfig.serverOutDir)
562
- } : void 0;
561
+ const viteAssetsDir = viteOutputDirs && relativePath(root, viteOutputDirs.clientOutDir);
562
+ const viteWorkerDir = viteOutputDirs && relativePath(root, viteOutputDirs.serverOutDir);
563
563
  return {
564
- assetsDir: assetsDir ?? viteOutputDirs?.assetsDir ?? fallbackOutputDirs.assetsDir,
565
- workerDir: workerDir ?? viteOutputDirs?.workerDir ?? fallbackOutputDirs.workerDir
564
+ assetsDir: assetsDirFlag ?? viteAssetsDir ?? "dist/client",
565
+ workerDir: workerDirFlag ?? viteWorkerDir ?? "dist/server"
566
566
  };
567
567
  }
568
568
  function isHydrogenPreviewVersion(version) {
@@ -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",
@@ -3,7 +3,7 @@ import Command from '@shopify/cli-kit/node/base-command';
3
3
  import { basename } from '@shopify/cli-kit/node/path';
4
4
  import { renderSuccess, renderConfirmationPrompt, renderWarning, renderTextPrompt, renderTasks } from '@shopify/cli-kit/node/ui';
5
5
  import { AbortError } from '@shopify/cli-kit/node/error';
6
- import { commonFlags } from '../../lib/flags.js';
6
+ import { commonFlags, flagsToCamelObject } from '../../lib/flags.js';
7
7
  import { getStorefronts } from '../../lib/graphql/admin/link-storefront.js';
8
8
  import { setStorefront } from '../../lib/shopify-config.js';
9
9
  import { createStorefront } from '../../lib/graphql/admin/create-storefront.js';
@@ -23,28 +23,45 @@ class Link extends Command {
23
23
  static flags = {
24
24
  ...commonFlags.force,
25
25
  ...commonFlags.path,
26
+ ...commonFlags.shop,
26
27
  storefront: Flags.string({
27
28
  description: `The name of a Hydrogen Storefront (e.g. "Jane's Apparel")`,
28
- env: "SHOPIFY_HYDROGEN_STOREFRONT"
29
+ env: "SHOPIFY_HYDROGEN_STOREFRONT",
30
+ exclusive: ["create-storefront", "name"]
31
+ }),
32
+ "create-storefront": Flags.boolean({
33
+ description: "Create a new Hydrogen storefront.",
34
+ env: "SHOPIFY_HYDROGEN_FLAG_CREATE_STOREFRONT",
35
+ exclusive: ["storefront"]
36
+ }),
37
+ name: Flags.string({
38
+ description: "The name to use when creating a new Hydrogen storefront.",
39
+ env: "SHOPIFY_HYDROGEN_FLAG_NAME",
40
+ exclusive: ["storefront"]
29
41
  })
30
42
  };
31
43
  async run() {
32
44
  const { flags } = await this.parse(Link);
33
- await runLink(flags);
45
+ await runLink(flagsToCamelObject(flags));
34
46
  }
35
47
  }
36
48
  async function runLink({
49
+ createStorefront: flagCreateStorefront,
37
50
  force,
38
51
  path: root = process.cwd(),
39
- storefront: flagStorefront
52
+ shop,
53
+ storefront: flagStorefront,
54
+ name
40
55
  }) {
41
56
  const [{ session, config }, cliCommand] = await Promise.all([
42
- login(root),
57
+ login(root, shop),
43
58
  getCliCommand()
44
59
  ]);
45
60
  const linkedStore = await linkStorefront(root, session, config, {
46
61
  force,
62
+ flagCreateStorefront,
47
63
  flagStorefront,
64
+ storefrontName: name,
48
65
  cliCommand
49
66
  });
50
67
  if (!linkedStore) return;
@@ -61,10 +78,19 @@ async function runLink({
61
78
  }
62
79
  async function linkStorefront(root, session, config, {
63
80
  force = false,
81
+ flagCreateStorefront,
64
82
  flagStorefront,
83
+ storefrontName,
65
84
  cliCommand,
66
85
  storefronts
67
86
  }) {
87
+ storefrontName = storefrontName?.trim() || void 0;
88
+ if (flagStorefront && (flagCreateStorefront || storefrontName)) {
89
+ throw new AbortError(
90
+ "`--storefront` cannot be used with storefront creation flags.",
91
+ "Use `--storefront` to link an existing storefront or `--name` to create a new one."
92
+ );
93
+ }
68
94
  if (!config.shop) {
69
95
  throw new AbortError("No shop found in local config, login first.");
70
96
  }
@@ -100,6 +126,13 @@ async function linkStorefront(root, session, config, {
100
126
  });
101
127
  return;
102
128
  }
129
+ } else if (flagCreateStorefront || storefrontName) {
130
+ selectedStorefront = await createNewStorefront(
131
+ root,
132
+ session,
133
+ storefronts,
134
+ storefrontName
135
+ );
103
136
  } else {
104
137
  selectedStorefront = await handleStorefrontSelection(storefronts);
105
138
  if (!selectedStorefront) {
@@ -113,7 +146,7 @@ async function linkStorefront(root, session, config, {
113
146
  await setStorefront(root, selectedStorefront);
114
147
  return selectedStorefront;
115
148
  }
116
- async function createNewStorefront(root, session, storefronts) {
149
+ async function createNewStorefront(root, session, storefronts, storefrontName) {
117
150
  const projectDirectory = basename(root);
118
151
  let defaultProjectName = titleize(projectDirectory);
119
152
  const nameAlreadyUsed = storefronts.some(
@@ -122,7 +155,7 @@ async function createNewStorefront(root, session, storefronts) {
122
155
  if (nameAlreadyUsed) {
123
156
  defaultProjectName = generateRandomName();
124
157
  }
125
- const projectName = await renderTextPrompt({
158
+ const projectName = storefrontName ?? await renderTextPrompt({
126
159
  message: "New storefront name",
127
160
  defaultValue: defaultProjectName
128
161
  });
@@ -49,7 +49,7 @@ let CACHED_CHANGELOG = null;
49
49
  async function runUpgrade({
50
50
  appPath,
51
51
  version: targetVersion,
52
- force
52
+ force = false
53
53
  }) {
54
54
  if (targetVersion === "next") {
55
55
  const isInTests = process.env.SHOPIFY_UNIT_TEST === "1";
@@ -115,7 +115,7 @@ async function runUpgrade({
115
115
  currentDependencies,
116
116
  selectedRelease
117
117
  });
118
- confirmed = await displayConfirmation({
118
+ confirmed = force || await displayConfirmation({
119
119
  cumulativeRelease,
120
120
  selectedRelease,
121
121
  targetVersion
@@ -125,7 +125,8 @@ async function runUpgrade({
125
125
  appPath,
126
126
  cumulativeRelease,
127
127
  currentVersion,
128
- selectedRelease
128
+ selectedRelease,
129
+ force
129
130
  });
130
131
  await upgradeNodeModules({
131
132
  appPath,
@@ -880,7 +881,8 @@ async function generateUpgradeInstructionsFile({
880
881
  appPath,
881
882
  cumulativeRelease,
882
883
  currentVersion,
883
- selectedRelease
884
+ selectedRelease,
885
+ force = false
884
886
  }) {
885
887
  let filename = "";
886
888
  const { featuresMd, breakingChangesMd } = cumulativeRelease.features.filter((feature) => feature.steps).reduce(
@@ -955,7 +957,7 @@ The following packages have been removed as part of this upgrade:
955
957
  if (!await fileExists(filePath)) {
956
958
  await touchFile(filePath);
957
959
  } else {
958
- const overwriteMdFile = await renderConfirmationPrompt({
960
+ const overwriteMdFile = force || await renderConfirmationPrompt({
959
961
  message: `A previous upgrade instructions file already exists for this version.
960
962
  Do you want to overwrite it?`,
961
963
  defaultValue: false
@@ -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>;
@@ -175,6 +177,9 @@ declare class Link extends Command {
175
177
  static description: string;
176
178
  static flags: {
177
179
  storefront: _oclif_core_lib_interfaces_parser_js.OptionFlag<string | undefined, _oclif_core_lib_interfaces_parser_js.CustomOptions>;
180
+ 'create-storefront': _oclif_core_lib_interfaces_parser_js.BooleanFlag<boolean>;
181
+ name: _oclif_core_lib_interfaces_parser_js.OptionFlag<string | undefined, _oclif_core_lib_interfaces_parser_js.CustomOptions>;
182
+ shop: _oclif_core_lib_interfaces_parser_js.OptionFlag<string | undefined, _oclif_core_lib_interfaces_parser_js.CustomOptions>;
178
183
  path: _oclif_core_lib_interfaces_parser_js.OptionFlag<string | undefined, _oclif_core_lib_interfaces_parser_js.CustomOptions>;
179
184
  force: _oclif_core_lib_interfaces_parser_js.BooleanFlag<boolean>;
180
185
  };
@@ -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.environments.ssr?.build.outDir ?? resolvedViteConfig.build.outDir;
42
- const clientOutDir = resolvedViteConfig.environments.client?.build.outDir ?? 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,
@@ -805,6 +805,24 @@
805
805
  "hasDynamicHelp": false,
806
806
  "multiple": false,
807
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"
808
826
  }
809
827
  },
810
828
  "hasDynamicHelp": false,
@@ -1132,13 +1150,47 @@
1132
1150
  "multiple": false,
1133
1151
  "type": "option"
1134
1152
  },
1153
+ "shop": {
1154
+ "char": "s",
1155
+ "description": "Shop URL. It can be the shop prefix (janes-apparel) or the full myshopify.com URL (janes-apparel.myshopify.com, https://janes-apparel.myshopify.com).",
1156
+ "env": "SHOPIFY_SHOP",
1157
+ "name": "shop",
1158
+ "hasDynamicHelp": false,
1159
+ "multiple": false,
1160
+ "type": "option"
1161
+ },
1135
1162
  "storefront": {
1136
1163
  "description": "The name of a Hydrogen Storefront (e.g. \"Jane's Apparel\")",
1137
1164
  "env": "SHOPIFY_HYDROGEN_STOREFRONT",
1165
+ "exclusive": [
1166
+ "create-storefront",
1167
+ "name"
1168
+ ],
1138
1169
  "name": "storefront",
1139
1170
  "hasDynamicHelp": false,
1140
1171
  "multiple": false,
1141
1172
  "type": "option"
1173
+ },
1174
+ "create-storefront": {
1175
+ "description": "Create a new Hydrogen storefront.",
1176
+ "env": "SHOPIFY_HYDROGEN_FLAG_CREATE_STOREFRONT",
1177
+ "exclusive": [
1178
+ "storefront"
1179
+ ],
1180
+ "name": "create-storefront",
1181
+ "allowNo": false,
1182
+ "type": "boolean"
1183
+ },
1184
+ "name": {
1185
+ "description": "The name to use when creating a new Hydrogen storefront.",
1186
+ "env": "SHOPIFY_HYDROGEN_FLAG_NAME",
1187
+ "exclusive": [
1188
+ "storefront"
1189
+ ],
1190
+ "name": "name",
1191
+ "hasDynamicHelp": false,
1192
+ "multiple": false,
1193
+ "type": "option"
1142
1194
  }
1143
1195
  },
1144
1196
  "hasDynamicHelp": false,
@@ -1693,5 +1745,5 @@
1693
1745
  ]
1694
1746
  }
1695
1747
  },
1696
- "version": "13.0.0"
1748
+ "version": "13.0.2"
1697
1749
  }
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "access": "public",
5
5
  "@shopify:registry": "https://registry.npmjs.org"
6
6
  },
7
- "version": "13.0.0",
7
+ "version": "13.0.2",
8
8
  "license": "MIT",
9
9
  "type": "module",
10
10
  "repository": {
@@ -58,8 +58,8 @@
58
58
  "@react-router/dev": "~7.16.0",
59
59
  "graphql-config": "^5.0.3",
60
60
  "vite": "^5.1.0 || ^6.2.0 || ^7.0.0 || ^8.0.0",
61
- "@shopify/hydrogen-codegen": "0.3.3",
62
- "@shopify/mini-oxygen": "4.2.0"
61
+ "@shopify/mini-oxygen": "4.2.0",
62
+ "@shopify/hydrogen-codegen": "0.3.3"
63
63
  },
64
64
  "peerDependenciesMeta": {
65
65
  "@graphql-codegen/cli": {