@shopify/cli-hydrogen 7.1.2 → 8.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.
Files changed (145) hide show
  1. package/dist/commands/hydrogen/build-vite.js +19 -10
  2. package/dist/commands/hydrogen/build.js +10 -2
  3. package/dist/commands/hydrogen/check.js +1 -0
  4. package/dist/commands/hydrogen/codegen.js +1 -0
  5. package/dist/commands/hydrogen/customer-account/push.js +170 -0
  6. package/dist/commands/hydrogen/debug/cpu.js +10 -1
  7. package/dist/commands/hydrogen/deploy.js +110 -36
  8. package/dist/commands/hydrogen/dev-vite.js +128 -59
  9. package/dist/commands/hydrogen/dev.js +108 -51
  10. package/dist/commands/hydrogen/env/list.js +20 -28
  11. package/dist/commands/hydrogen/env/pull.js +29 -19
  12. package/dist/commands/hydrogen/env/{push__unstable.js → push.js} +34 -68
  13. package/dist/commands/hydrogen/generate/route.js +1 -0
  14. package/dist/commands/hydrogen/init.js +39 -21
  15. package/dist/commands/hydrogen/link.js +25 -6
  16. package/dist/commands/hydrogen/list.js +1 -0
  17. package/dist/commands/hydrogen/login.js +1 -0
  18. package/dist/commands/hydrogen/logout.js +1 -0
  19. package/dist/commands/hydrogen/preview.js +31 -16
  20. package/dist/commands/hydrogen/setup/css.js +8 -1
  21. package/dist/commands/hydrogen/setup/markets.js +1 -0
  22. package/dist/commands/hydrogen/setup/vite.js +244 -138
  23. package/dist/commands/hydrogen/setup.js +21 -23
  24. package/dist/commands/hydrogen/shortcut.js +10 -0
  25. package/dist/commands/hydrogen/unlink.js +1 -0
  26. package/dist/commands/hydrogen/upgrade.js +2 -1
  27. package/dist/generator-templates/assets/vite/package.json +3 -4
  28. package/dist/generator-templates/assets/vite/vite.config.js +15 -2
  29. package/dist/generator-templates/starter/CHANGELOG.md +129 -0
  30. package/dist/generator-templates/starter/README.md +3 -44
  31. package/dist/generator-templates/starter/app/components/Footer.tsx +1 -1
  32. package/dist/generator-templates/starter/app/components/Header.tsx +1 -1
  33. package/dist/generator-templates/starter/app/graphql/customer-account/CustomerDetailsQuery.ts +1 -0
  34. package/dist/generator-templates/starter/app/lib/fragments.ts +2 -0
  35. package/dist/generator-templates/starter/app/lib/root-data.ts +11 -0
  36. package/dist/generator-templates/starter/app/root.tsx +4 -20
  37. package/dist/generator-templates/starter/app/routes/account.orders._index.tsx +1 -1
  38. package/dist/generator-templates/starter/app/routes/account.tsx +1 -1
  39. package/dist/generator-templates/starter/app/routes/blogs.$blogHandle._index.tsx +3 -3
  40. package/dist/generator-templates/starter/app/routes/cart.tsx +1 -1
  41. package/dist/generator-templates/starter/app/routes/collections.all.tsx +160 -0
  42. package/dist/generator-templates/starter/app/routes/products.$handle.tsx +1 -2
  43. package/dist/generator-templates/starter/customer-accountapi.generated.d.ts +6 -3
  44. package/dist/generator-templates/starter/{remix.env.d.ts → env.d.ts} +8 -2
  45. package/dist/generator-templates/starter/package.json +14 -9
  46. package/dist/generator-templates/starter/server.ts +2 -1
  47. package/dist/generator-templates/starter/storefrontapi.generated.d.ts +59 -3
  48. package/dist/generator-templates/starter/vite.config.ts +26 -0
  49. package/dist/{commands/hydrogen/init.d.ts → init.d.ts} +11 -4
  50. package/dist/lib/check-lockfile.js +12 -18
  51. package/dist/lib/codegen.js +37 -13
  52. package/dist/lib/common.js +50 -0
  53. package/dist/lib/cpu-profiler.js +4 -1
  54. package/dist/lib/dev-shared.js +99 -0
  55. package/dist/lib/environment-variables.js +51 -30
  56. package/dist/lib/file.js +8 -1
  57. package/dist/lib/flags.js +37 -26
  58. package/dist/lib/get-oxygen-deployment-data.js +10 -17
  59. package/dist/lib/graphql/admin/customer-application-update.js +29 -0
  60. package/dist/lib/graphql/admin/get-oxygen-data.js +1 -0
  61. package/dist/lib/graphql/admin/list-environments.js +1 -0
  62. package/dist/lib/graphql/admin/pull-variables.js +4 -4
  63. package/dist/lib/graphql/admin/test-helper.js +37 -0
  64. package/dist/lib/log.js +86 -13
  65. package/dist/lib/mini-oxygen/common.js +19 -33
  66. package/dist/lib/mini-oxygen/index.js +6 -2
  67. package/dist/lib/mini-oxygen/node.js +43 -31
  68. package/dist/lib/mini-oxygen/workerd.js +72 -165
  69. package/dist/lib/missing-routes.js +1 -1
  70. package/dist/lib/onboarding/common.js +85 -70
  71. package/dist/lib/onboarding/local.js +19 -9
  72. package/dist/lib/onboarding/remote.js +35 -30
  73. package/dist/lib/package-managers.js +24 -0
  74. package/dist/lib/remix-config.js +17 -1
  75. package/dist/lib/render-errors.js +17 -10
  76. package/dist/lib/request-events.js +6 -1
  77. package/dist/lib/setups/i18n/replacers.js +9 -6
  78. package/dist/lib/setups/routes/generate.js +1 -0
  79. package/dist/lib/shell.js +2 -1
  80. package/dist/lib/shopify-config.js +19 -1
  81. package/dist/lib/template-diff.js +36 -15
  82. package/dist/lib/template-downloader.js +35 -5
  83. package/dist/lib/transpile/morph/functions.js +26 -8
  84. package/dist/lib/transpile/morph/typedefs.js +6 -4
  85. package/dist/lib/transpile/project.js +8 -4
  86. package/dist/lib/tunneling.js +44 -0
  87. package/dist/lib/verify-linked-storefront.js +24 -0
  88. package/dist/lib/virtual-routes.js +1 -1
  89. package/dist/lib/vite-config.js +39 -9
  90. package/oclif.manifest.json +704 -508
  91. package/package.json +32 -24
  92. package/dist/commands/hydrogen/deploy.test.js +0 -553
  93. package/dist/commands/hydrogen/env/list.test.js +0 -148
  94. package/dist/commands/hydrogen/env/pull.test.js +0 -207
  95. package/dist/commands/hydrogen/env/push__unstable.test.js +0 -383
  96. package/dist/commands/hydrogen/generate/route.test.js +0 -43
  97. package/dist/commands/hydrogen/init.test.js +0 -641
  98. package/dist/commands/hydrogen/link.test.js +0 -187
  99. package/dist/commands/hydrogen/list.test.js +0 -111
  100. package/dist/commands/hydrogen/setup.test.js +0 -61
  101. package/dist/commands/hydrogen/shortcut.test.js +0 -30
  102. package/dist/commands/hydrogen/unlink.test.js +0 -36
  103. package/dist/commands/hydrogen/upgrade.test.js +0 -786
  104. package/dist/generator-templates/starter/remix.config.js +0 -24
  105. package/dist/lib/auth.test.js +0 -157
  106. package/dist/lib/check-lockfile.test.js +0 -81
  107. package/dist/lib/check-version.test.js +0 -86
  108. package/dist/lib/environment-variables.test.js +0 -149
  109. package/dist/lib/file.test.js +0 -68
  110. package/dist/lib/flags.test.js +0 -43
  111. package/dist/lib/get-oxygen-deployment-data.test.js +0 -120
  112. package/dist/lib/gid.test.js +0 -15
  113. package/dist/lib/graphql/admin/client.test.js +0 -76
  114. package/dist/lib/graphql/admin/create-storefront.test.js +0 -64
  115. package/dist/lib/graphql/admin/link-storefront.test.js +0 -38
  116. package/dist/lib/graphql/admin/list-environments.test.js +0 -44
  117. package/dist/lib/graphql/admin/list-storefronts.test.js +0 -44
  118. package/dist/lib/graphql/admin/pull-variables.test.js +0 -43
  119. package/dist/lib/graphql/business-platform/user-account.test.js +0 -80
  120. package/dist/lib/log.test.js +0 -92
  121. package/dist/lib/mini-oxygen/assets.js +0 -134
  122. package/dist/lib/mini-oxygen/mini-oxygen.test.js +0 -214
  123. package/dist/lib/mini-oxygen/workerd-inspector-logs.js +0 -227
  124. package/dist/lib/mini-oxygen/workerd-inspector-proxy.js +0 -200
  125. package/dist/lib/mini-oxygen/workerd-inspector.js +0 -219
  126. package/dist/lib/missing-routes.test.js +0 -45
  127. package/dist/lib/remix-version-check.test.js +0 -39
  128. package/dist/lib/remix-version-interop.test.js +0 -13
  129. package/dist/lib/setups/i18n/domains.test.js +0 -39
  130. package/dist/lib/setups/i18n/replacers.test.js +0 -261
  131. package/dist/lib/setups/i18n/subdomains.test.js +0 -39
  132. package/dist/lib/setups/i18n/subfolders.test.js +0 -39
  133. package/dist/lib/setups/routes/generate.test.js +0 -296
  134. package/dist/lib/shell.test.js +0 -111
  135. package/dist/lib/shopify-config.test.js +0 -199
  136. package/dist/lib/string.test.js +0 -16
  137. package/dist/lib/virtual-routes.test.js +0 -49
  138. package/dist/lib/vite/hydrogen-middleware.js +0 -82
  139. package/dist/lib/vite/mini-oxygen.js +0 -152
  140. package/dist/lib/vite/plugins.d.ts +0 -27
  141. package/dist/lib/vite/plugins.js +0 -139
  142. package/dist/lib/vite/shared.js +0 -10
  143. package/dist/lib/vite/utils.js +0 -55
  144. package/dist/lib/vite/worker-entry.js +0 -1518
  145. /package/dist/generator-templates/starter/{.eslintrc.js → .eslintrc.cjs} +0 -0
@@ -1,7 +1,7 @@
1
1
  import { diffLines } from 'diff';
2
2
  import Command from '@shopify/cli-kit/node/base-command';
3
- import { renderConfirmationPrompt, renderInfo, renderWarning, renderSuccess } from '@shopify/cli-kit/node/ui';
4
- import { outputContent, outputToken, outputInfo } from '@shopify/cli-kit/node/output';
3
+ import { renderInfo, renderConfirmationPrompt, renderWarning, renderSuccess } from '@shopify/cli-kit/node/ui';
4
+ import { outputInfo, outputContent, outputToken } from '@shopify/cli-kit/node/output';
5
5
  import { fileExists, readFile, writeFile } from '@shopify/cli-kit/node/fs';
6
6
  import { resolvePath } from '@shopify/cli-kit/node/path';
7
7
  import { patchEnvFile } from '@shopify/cli-kit/node/dot-env';
@@ -9,13 +9,17 @@ import colors from '@shopify/cli-kit/node/colors';
9
9
  import { commonFlags, flagsToCamelObject } from '../../../lib/flags.js';
10
10
  import { login } from '../../../lib/auth.js';
11
11
  import { getCliCommand } from '../../../lib/shell.js';
12
- import { renderMissingLink, renderMissingStorefront } from '../../../lib/render-errors.js';
13
- import { linkStorefront } from '../link.js';
12
+ import { findEnvironmentOrThrow, findEnvironmentByBranchOrThrow } from '../../../lib/common.js';
13
+ import { renderMissingStorefront } from '../../../lib/render-errors.js';
14
+ import { getStorefrontEnvironments } from '../../../lib/graphql/admin/list-environments.js';
14
15
  import { getStorefrontEnvVariables } from '../../../lib/graphql/admin/pull-variables.js';
16
+ import { verifyLinkedStorefront } from '../../../lib/verify-linked-storefront.js';
15
17
 
16
18
  class EnvPull extends Command {
19
+ static descriptionWithMarkdown = "Pulls environment variables from the linked Hydrogen storefront and writes them to an `.env` file.";
17
20
  static description = "Populate your .env with variables from your Hydrogen storefront.";
18
21
  static flags = {
22
+ ...commonFlags.env,
19
23
  ...commonFlags.envBranch,
20
24
  ...commonFlags.path,
21
25
  ...commonFlags.force
@@ -26,6 +30,7 @@ class EnvPull extends Command {
26
30
  }
27
31
  }
28
32
  async function runEnvPull({
33
+ env: envHandle,
29
34
  envBranch,
30
35
  path: root = process.cwd(),
31
36
  force
@@ -34,25 +39,30 @@ async function runEnvPull({
34
39
  login(root),
35
40
  getCliCommand()
36
41
  ]);
37
- if (!config.storefront?.id) {
38
- renderMissingLink({ session, cliCommand });
39
- const runLink = await renderConfirmationPrompt({
40
- message: outputContent`Run ${outputToken.genericShellCommand(
41
- `${cliCommand} link`
42
- )}?`.value
43
- });
44
- if (!runLink)
45
- return;
46
- config.storefront = await linkStorefront(root, session, config, {
47
- cliCommand
48
- });
49
- }
50
- if (!config.storefront?.id)
42
+ const linkedStorefront = await verifyLinkedStorefront({
43
+ root,
44
+ session,
45
+ config,
46
+ cliCommand
47
+ });
48
+ if (!linkedStorefront)
51
49
  return;
50
+ config.storefront = linkedStorefront;
51
+ if (envHandle || envBranch) {
52
+ const environments = (await getStorefrontEnvironments(session, config.storefront.id))?.environments || [];
53
+ if (envHandle) {
54
+ findEnvironmentOrThrow(environments, envHandle);
55
+ } else if (envBranch) {
56
+ envHandle = findEnvironmentByBranchOrThrow(
57
+ environments,
58
+ envBranch
59
+ ).handle;
60
+ }
61
+ }
52
62
  const storefront = await getStorefrontEnvVariables(
53
63
  session,
54
64
  config.storefront.id,
55
- envBranch
65
+ envHandle
56
66
  );
57
67
  if (!storefront) {
58
68
  renderMissingStorefront({
@@ -5,24 +5,22 @@ import { commonFlags, flagsToCamelObject } from '../../../lib/flags.js';
5
5
  import { login } from '../../../lib/auth.js';
6
6
  import { getCliCommand } from '../../../lib/shell.js';
7
7
  import { resolvePath } from '@shopify/cli-kit/node/path';
8
- import { renderConfirmationPrompt, renderSelectPrompt, renderInfo, renderSuccess } from '@shopify/cli-kit/node/ui';
9
- import { outputContent, outputToken, outputWarn } from '@shopify/cli-kit/node/output';
10
- import { renderMissingLink } from '../../../lib/render-errors.js';
8
+ import { renderSelectPrompt, renderInfo, renderConfirmationPrompt, renderSuccess } from '@shopify/cli-kit/node/ui';
9
+ import { outputWarn, outputContent, outputToken } from '@shopify/cli-kit/node/output';
10
+ import { orderEnvironmentsBySafety, findEnvironmentOrThrow, createEnvironmentCliChoiceLabel } from '../../../lib/common.js';
11
11
  import { getStorefrontEnvironments } from '../../../lib/graphql/admin/list-environments.js';
12
- import { linkStorefront } from '../link.js';
13
12
  import { getStorefrontEnvVariables } from '../../../lib/graphql/admin/pull-variables.js';
14
13
  import { pushStorefrontEnvVariables } from '../../../lib/graphql/admin/push-variables.js';
15
14
  import { AbortError } from '@shopify/cli-kit/node/error';
16
- import { readAndParseDotEnv } from '@shopify/cli-kit/node/dot-env';
15
+ import { readAndParseDotEnv, createDotEnvFileLine } from '@shopify/cli-kit/node/dot-env';
16
+ import { verifyLinkedStorefront } from '../../../lib/verify-linked-storefront.js';
17
17
 
18
18
  class EnvPush extends Command {
19
19
  static description = "Push environment variables from the local .env file to your linked Hydrogen storefront.";
20
- static hidden = true;
21
20
  static flags = {
22
21
  ...commonFlags.env,
23
22
  "env-file": Flags.string({
24
- description: "Specify the environment variable file name. Default value is '.env'.",
25
- env: "SHOPIFY_HYDROGEN_ENVIRONMENT_FILENAME"
23
+ description: "Path to an environment file to override existing environment variables for the selected environment. Defaults to the '.env' located in your project path `--path`."
26
24
  }),
27
25
  ...commonFlags.path
28
26
  };
@@ -32,99 +30,67 @@ class EnvPush extends Command {
32
30
  }
33
31
  }
34
32
  async function runEnvPush({
35
- env: environmentName,
36
- envFile = ".env",
33
+ env: envHandle,
34
+ envFile,
37
35
  path = process.cwd()
38
36
  }) {
39
- let validatedEnvironment = {};
40
- const dotEnvPath = resolvePath(path, envFile);
37
+ let validatedEnvironment;
38
+ const dotEnvPath = envFile || resolvePath(path, ".env");
41
39
  const { variables: localVariables } = await readAndParseDotEnv(dotEnvPath);
42
40
  const [{ session, config }, cliCommand] = await Promise.all([
43
41
  login(path),
44
42
  getCliCommand()
45
43
  ]);
46
- if (!config.storefront?.id) {
47
- renderMissingLink({ session, cliCommand });
48
- const runLink = await renderConfirmationPrompt({
49
- message: outputContent`Run ${outputToken.genericShellCommand(
50
- `${cliCommand} link`
51
- )}?`.value
52
- });
53
- if (!runLink)
54
- return;
55
- config.storefront = await linkStorefront(path, session, config, {
56
- cliCommand
57
- });
58
- }
59
- if (!config.storefront?.id)
44
+ const linkedStorefront = await verifyLinkedStorefront({
45
+ root: path,
46
+ session,
47
+ config,
48
+ cliCommand
49
+ });
50
+ if (!linkedStorefront)
60
51
  return;
52
+ config.storefront = linkedStorefront;
61
53
  const { environments: environmentsData } = await getStorefrontEnvironments(session, config.storefront.id) ?? {};
62
54
  if (!environmentsData) {
63
55
  throw new AbortError("Failed to fetch environments");
64
56
  }
65
- const environments = [
66
- ...environmentsData.filter((environment) => environment.type === "PREVIEW"),
67
- ...environmentsData.filter((environment) => environment.type === "CUSTOM"),
68
- ...environmentsData.filter(
69
- (environment) => environment.type === "PRODUCTION"
70
- )
71
- ];
57
+ const environments = orderEnvironmentsBySafety(environmentsData);
72
58
  if (environments.length === 0) {
73
59
  throw new AbortError("No environments found");
74
60
  }
75
- if (environmentName) {
76
- const matchedEnvironments = environments.filter(
77
- ({ name }) => name === environmentName
78
- );
79
- if (matchedEnvironments.length === 0) {
80
- throw new AbortError(
81
- "Environment not found",
82
- `We could not find an environment matching the name '${environmentName}'.`
83
- );
84
- } else if (matchedEnvironments.length === 1) {
85
- const { id, name, branch, type } = matchedEnvironments[0] ?? {};
86
- validatedEnvironment = { id, name, branch, type };
87
- } else {
88
- const selection = await renderSelectPrompt({
89
- message: `There were multiple environments found with the name ${environmentName}:`,
90
- choices: [
91
- ...matchedEnvironments.map(({ id: id2, name: name2, branch: branch2, type: type2, url }) => ({
92
- label: `${name2} (${branch2}) ${type2} ${url}`,
93
- value: id2
94
- }))
95
- ]
96
- });
97
- const { id, name, branch, type } = matchedEnvironments.find(({ id: id2 }) => id2 === selection) ?? {};
98
- validatedEnvironment = { id, name, branch, type };
99
- }
61
+ if (envHandle) {
62
+ validatedEnvironment = findEnvironmentOrThrow(environments, envHandle);
100
63
  } else {
101
64
  const choices = [
102
- ...environments.map(({ id: id2, name: name2, branch: branch2 }) => ({
103
- label: branch2 ? `${name2} (${branch2})` : name2,
104
- value: id2
105
- }))
65
+ ...environments.map(({ id, name, branch, handle }) => {
66
+ return {
67
+ label: createEnvironmentCliChoiceLabel(name, handle, branch),
68
+ value: id
69
+ };
70
+ })
106
71
  ];
107
72
  const pushToBranchSelection = await renderSelectPrompt({
108
- message: "Select a set of environment variables to overwrite:",
73
+ message: "Select an environment to overwrite its environment variables:",
109
74
  choices
110
75
  });
111
- const { id, name, branch, type } = environments.find(({ id: id2 }) => id2 === pushToBranchSelection) ?? {};
112
- validatedEnvironment = { id, name, branch, type };
76
+ validatedEnvironment = environments.find(
77
+ ({ id }) => id === pushToBranchSelection
78
+ );
113
79
  }
114
80
  const { environmentVariables = [] } = await getStorefrontEnvVariables(
115
81
  session,
116
82
  config.storefront.id,
117
- validatedEnvironment.branch ?? void 0
83
+ validatedEnvironment.handle
118
84
  ) ?? {};
119
85
  const remoteVars = environmentVariables.filter(
120
86
  ({ isSecret, readOnly }) => !isSecret && !readOnly
121
87
  );
122
- const comparableRemoteVars = remoteVars.sort((a, b) => a.key.localeCompare(b.key)).map(({ key, value }) => `${key}=${value}`).join("\n") + "\n";
88
+ const comparableRemoteVars = remoteVars.sort((a, b) => a.key.localeCompare(b.key)).map(({ key, value }) => createDotEnvFileLine(key, value)).join("\n") + "\n";
123
89
  const compareableLocalVars = Object.keys(localVariables).sort((a, b) => a.localeCompare(b)).reduce((acc, key) => {
124
90
  const { isSecret, readOnly } = environmentVariables.find((variable) => variable.key === key) ?? {};
125
91
  if (isSecret || readOnly)
126
92
  return acc;
127
- return [...acc, `${key}=${localVariables[key]}`];
93
+ return [...acc, createDotEnvFileLine(key, localVariables[key])];
128
94
  }, []).join("\n") + "\n";
129
95
  if (!validatedEnvironment.name)
130
96
  throw new AbortError("Missing environment name");
@@ -8,6 +8,7 @@ import { ALL_ROUTE_CHOICES, generateRoutes } from '../../../lib/setups/routes/ge
8
8
  import { isV1RouteConventionInstalled } from '../../../lib/remix-version-interop.js';
9
9
 
10
10
  class GenerateRoute extends Command {
11
+ static descriptionWithMarkdown = `Generates a set of default routes from the starter template.`;
11
12
  static description = "Generates a standard Shopify route.";
12
13
  static flags = {
13
14
  adapter: Flags.string({
@@ -6,7 +6,6 @@ import { AbortError } from '@shopify/cli-kit/node/error';
6
6
  import { AbortController } from '@shopify/cli-kit/node/abort';
7
7
  import { commonFlags, flagsToCamelObject, parseProcessFlags } from '../../lib/flags.js';
8
8
  import { checkHydrogenVersion } from '../../lib/check-version.js';
9
- import { STYLING_CHOICES } from './../../lib/setups/css/index.js';
10
9
  import { I18N_CHOICES } from '../../lib/setups/i18n/index.js';
11
10
  import { supressNodeExperimentalWarnings } from '../../lib/process.js';
12
11
  import { setupRemoteTemplate, setupLocalStarterTemplate } from '../../lib/onboarding/index.js';
@@ -14,6 +13,7 @@ import { LANGUAGES } from '../../lib/onboarding/common.js';
14
13
 
15
14
  const FLAG_MAP = { f: "force" };
16
15
  class Init extends Command {
16
+ static descriptionWithMarkdown = "Creates a new Hydrogen storefront.";
17
17
  static description = "Creates a new Hydrogen storefront.";
18
18
  static flags = {
19
19
  ...commonFlags.force,
@@ -33,16 +33,13 @@ class Init extends Command {
33
33
  ...commonFlags.installDeps,
34
34
  "mock-shop": Flags.boolean({
35
35
  description: "Use mock.shop as the data source for the storefront.",
36
- default: false,
37
36
  env: "SHOPIFY_HYDROGEN_FLAG_MOCK_DATA"
38
37
  }),
39
- ...commonFlags.styling,
40
38
  ...commonFlags.markets,
41
39
  ...commonFlags.shortcut,
42
40
  routes: Flags.boolean({
43
41
  description: "Generate routes for all pages.",
44
42
  env: "SHOPIFY_HYDROGEN_FLAG_ROUTES",
45
- hidden: true,
46
43
  allowNo: true
47
44
  }),
48
45
  git: Flags.boolean({
@@ -50,29 +47,49 @@ class Init extends Command {
50
47
  env: "SHOPIFY_HYDROGEN_FLAG_GIT",
51
48
  default: true,
52
49
  allowNo: true
50
+ }),
51
+ quickstart: Flags.boolean({
52
+ description: "Scaffolds a new Hydrogen project with a set of sensible defaults. Equivalent to `shopify hydrogen init --path hydrogen-quickstart --mock-shop --language js --shortcut --routes --markets none`",
53
+ env: "SHOPIFY_HYDROGEN_FLAG_QUICKSTART",
54
+ default: false
55
+ }),
56
+ "package-manager": Flags.string({
57
+ env: "SHOPIFY_HYDROGEN_FLAG_PACKAGE_MANAGER",
58
+ hidden: true,
59
+ options: ["npm", "yarn", "pnpm", "unknown"]
53
60
  })
54
61
  };
55
62
  async run() {
56
- const {
57
- flags: { markets, ..._flags }
58
- } = await this.parse(Init);
59
- const flags = { ..._flags, i18n: markets };
60
- if (flags.i18n && !I18N_CHOICES.includes(flags.i18n)) {
61
- throw new AbortError(
62
- `Invalid URL structure strategy: ${flags.i18n}. Must be one of ${I18N_CHOICES.join(", ")}`
63
- );
64
- }
65
- if (flags.styling && !STYLING_CHOICES.includes(flags.styling)) {
66
- throw new AbortError(
67
- `Invalid styling strategy: ${flags.styling}. Must be one of ${STYLING_CHOICES.join(", ")}`
68
- );
69
- }
63
+ const { flags } = await this.parse(Init);
70
64
  await runInit(flagsToCamelObject(flags));
71
65
  }
72
66
  }
73
- async function runInit(options = parseProcessFlags(process.argv, FLAG_MAP)) {
67
+ async function runInit({
68
+ markets,
69
+ ...options
70
+ } = parseProcessFlags(
71
+ process.argv,
72
+ FLAG_MAP
73
+ )) {
74
74
  supressNodeExperimentalWarnings();
75
+ if (!options.i18n && markets) {
76
+ options.i18n = markets;
77
+ }
78
+ if (options.i18n && !I18N_CHOICES.includes(options.i18n)) {
79
+ throw new AbortError(
80
+ `Invalid URL structure strategy: ${options.i18n}. Must be one of ${I18N_CHOICES.join(", ")}`
81
+ );
82
+ }
75
83
  options.git ??= true;
84
+ if (options.quickstart) {
85
+ options.i18n ??= "none";
86
+ options.installDeps ??= true;
87
+ options.language ??= "js";
88
+ options.mockShop ??= true;
89
+ options.path ??= "./hydrogen-quickstart";
90
+ options.routes ??= true;
91
+ options.shortcut ??= true;
92
+ }
76
93
  const showUpgrade = await checkHydrogenVersion(
77
94
  // Resolving the CLI package from a local directory might fail because
78
95
  // this code could be run from a global dependency (e.g. on `npm create`).
@@ -81,14 +98,15 @@ async function runInit(options = parseProcessFlags(process.argv, FLAG_MAP)) {
81
98
  "cli"
82
99
  );
83
100
  if (showUpgrade) {
84
- const packageManager = packageManagerFromUserAgent();
101
+ const packageManager = options.packageManager ?? packageManagerFromUserAgent();
85
102
  showUpgrade(
86
103
  packageManager === "unknown" ? "" : `Please use the latest version with \`${packageManager} create @shopify/hydrogen@latest\``
87
104
  );
88
105
  }
89
106
  const controller = new AbortController();
90
107
  try {
91
- return options.template ? await setupRemoteTemplate(options, controller) : await setupLocalStarterTemplate(options, controller);
108
+ const template = options.template;
109
+ return template ? await setupRemoteTemplate({ ...options, template }, controller) : await setupLocalStarterTemplate(options, controller);
92
110
  } catch (error) {
93
111
  controller.abort();
94
112
  throw error;
@@ -11,9 +11,14 @@ import { waitForJob } from '../../lib/graphql/admin/fetch-job.js';
11
11
  import { titleize } from '../../lib/string.js';
12
12
  import { getCliCommand } from '../../lib/shell.js';
13
13
  import { login } from '../../lib/auth.js';
14
- import { handleStorefrontSelection } from '../../lib/onboarding/common.js';
14
+ import { handleStorefrontSelection, generateRandomName } from '../../lib/onboarding/common.js';
15
15
 
16
16
  class Link extends Command {
17
+ static descriptionWithMarkdown = `Links your local development environment to a remote Hydrogen storefront. You can link an unlimited number of development environments to a single Hydrogen storefront.
18
+
19
+ Linking to a Hydrogen storefront enables you to run [dev](https://shopify.dev/docs/api/shopify-cli/hydrogen/hydrogen-dev) and automatically inject your linked Hydrogen storefront's environment variables directly into the server runtime.
20
+
21
+ After you run the \`link\` command, you can access the [env list](https://shopify.dev/docs/api/shopify-cli/hydrogen/hydrogen-env-list), [env pull](https://shopify.dev/docs/api/shopify-cli/hydrogen/hydrogen-env-pull), and [unlink](https://shopify.dev/docs/api/shopify-cli/hydrogen/hydrogen-unlink) commands.`;
17
22
  static description = "Link a local project to one of your shop's Hydrogen storefronts.";
18
23
  static flags = {
19
24
  ...commonFlags.force,
@@ -58,7 +63,8 @@ async function runLink({
58
63
  async function linkStorefront(root, session, config, {
59
64
  force = false,
60
65
  flagStorefront,
61
- cliCommand
66
+ cliCommand,
67
+ storefronts
62
68
  }) {
63
69
  if (!config.shop) {
64
70
  throw new AbortError("No shop found in local config, login first.");
@@ -71,7 +77,9 @@ async function linkStorefront(root, session, config, {
71
77
  return;
72
78
  }
73
79
  }
74
- const storefronts = await getStorefronts(session);
80
+ if (!storefronts) {
81
+ storefronts = await getStorefronts(session);
82
+ }
75
83
  let selectedStorefront;
76
84
  if (flagStorefront) {
77
85
  selectedStorefront = storefronts.find(
@@ -96,17 +104,28 @@ async function linkStorefront(root, session, config, {
96
104
  } else {
97
105
  selectedStorefront = await handleStorefrontSelection(storefronts);
98
106
  if (!selectedStorefront) {
99
- selectedStorefront = await createNewStorefront(root, session);
107
+ selectedStorefront = await createNewStorefront(
108
+ root,
109
+ session,
110
+ storefronts
111
+ );
100
112
  }
101
113
  }
102
114
  await setStorefront(root, selectedStorefront);
103
115
  return selectedStorefront;
104
116
  }
105
- async function createNewStorefront(root, session) {
117
+ async function createNewStorefront(root, session, storefronts) {
106
118
  const projectDirectory = basename(root);
119
+ let defaultProjectName = titleize(projectDirectory);
120
+ const nameAlreadyUsed = storefronts.some(
121
+ ({ title }) => title === defaultProjectName
122
+ );
123
+ if (nameAlreadyUsed) {
124
+ defaultProjectName = generateRandomName();
125
+ }
107
126
  const projectName = await renderTextPrompt({
108
127
  message: "New storefront name",
109
- defaultValue: titleize(projectDirectory)
128
+ defaultValue: defaultProjectName
110
129
  });
111
130
  let storefront;
112
131
  let jobId;
@@ -11,6 +11,7 @@ import { login } from '../../lib/auth.js';
11
11
  import { getCliCommand } from '../../lib/shell.js';
12
12
 
13
13
  class List extends Command {
14
+ static descriptionWithMarkdown = "Lists all remote Hydrogen storefronts available to link to your local development environment.";
14
15
  static description = "Returns a list of Hydrogen storefronts available on a given shop.";
15
16
  static flags = {
16
17
  ...commonFlags.path
@@ -3,6 +3,7 @@ import { commonFlags } from '../../lib/flags.js';
3
3
  import { login, renderLoginSuccess } from '../../lib/auth.js';
4
4
 
5
5
  class Login extends Command {
6
+ static descriptionWithMarkdown = "Logs in to the specified shop and saves the shop domain to the project.";
6
7
  static description = "Login to your Shopify account.";
7
8
  static flags = {
8
9
  ...commonFlags.path,
@@ -4,6 +4,7 @@ import { commonFlags } from '../../lib/flags.js';
4
4
  import { logout } from '../../lib/auth.js';
5
5
 
6
6
  class Logout extends Command {
7
+ static descriptionWithMarkdown = "Log out from the current shop.";
7
8
  static description = "Logout of your local session.";
8
9
  static flags = {
9
10
  ...commonFlags.path
@@ -1,25 +1,27 @@
1
1
  import Command from '@shopify/cli-kit/node/base-command';
2
- import { muteDevLogs } from '../../lib/log.js';
3
- import { getProjectPaths } from '../../lib/remix-config.js';
4
- import { commonFlags, deprecated, flagsToCamelObject } from '../../lib/flags.js';
2
+ import { setH2OVerbose, isH2Verbose, muteDevLogs } from '../../lib/log.js';
3
+ import { getProjectPaths, hasRemixConfigFile } from '../../lib/remix-config.js';
4
+ import { commonFlags, deprecated, flagsToCamelObject, DEFAULT_APP_PORT } from '../../lib/flags.js';
5
5
  import { startMiniOxygen } from '../../lib/mini-oxygen/index.js';
6
6
  import { getAllEnvironmentVariables } from '../../lib/environment-variables.js';
7
7
  import { getConfig } from '../../lib/shopify-config.js';
8
8
  import { findPort } from '../../lib/find-port.js';
9
- import { fileExists } from '@shopify/cli-kit/node/fs';
10
9
  import { joinPath } from '@shopify/cli-kit/node/path';
11
10
  import { getViteConfig } from '../../lib/vite-config.js';
12
11
 
13
12
  class Preview extends Command {
13
+ static descriptionWithMarkdown = "Runs a server in your local development environment that serves your Hydrogen app's production build. Requires running the [build](https://shopify.dev/docs/api/shopify-cli/hydrogen/hydrogen-build) command first.";
14
14
  static description = "Runs a Hydrogen storefront in an Oxygen worker for production.";
15
15
  static flags = {
16
16
  ...commonFlags.path,
17
17
  ...commonFlags.port,
18
18
  worker: deprecated("--worker", { isBoolean: true }),
19
19
  ...commonFlags.legacyRuntime,
20
+ ...commonFlags.env,
20
21
  ...commonFlags.envBranch,
21
22
  ...commonFlags.inspectorPort,
22
- ...commonFlags.debug
23
+ ...commonFlags.debug,
24
+ ...commonFlags.verbose
23
25
  };
24
26
  async run() {
25
27
  const { flags } = await this.parse(Preview);
@@ -32,31 +34,44 @@ async function runPreview({
32
34
  port: appPort,
33
35
  path: appPath,
34
36
  legacyRuntime = false,
37
+ env: envHandle,
35
38
  envBranch,
36
39
  inspectorPort,
37
- debug
40
+ debug,
41
+ verbose
38
42
  }) {
39
43
  if (!process.env.NODE_ENV)
40
44
  process.env.NODE_ENV = "production";
41
- muteDevLogs({ workerReload: false });
42
- let { root, buildPathWorkerFile, buildPathClient } = getProjectPaths(appPath);
43
- if (!await fileExists(joinPath(root, buildPathWorkerFile))) {
45
+ if (verbose)
46
+ setH2OVerbose();
47
+ if (!isH2Verbose())
48
+ muteDevLogs();
49
+ let { root, buildPath, buildPathWorkerFile, buildPathClient } = getProjectPaths(appPath);
50
+ if (!await hasRemixConfigFile(root)) {
44
51
  const maybeResult = await getViteConfig(root).catch(() => null);
45
- if (maybeResult)
46
- buildPathWorkerFile = maybeResult.serverOutFile;
52
+ buildPathWorkerFile = maybeResult?.serverOutFile ?? joinPath(buildPath, "server", "index.js");
47
53
  }
48
54
  const { shop, storefront } = await getConfig(root);
49
55
  const fetchRemote = !!shop && !!storefront?.id;
50
- const env = await getAllEnvironmentVariables({ root, fetchRemote, envBranch });
51
- appPort = legacyRuntime ? appPort : await findPort(appPort);
52
- inspectorPort = debug ? await findPort(inspectorPort) : inspectorPort;
56
+ const { allVariables, logInjectedVariables } = await getAllEnvironmentVariables(
57
+ {
58
+ root,
59
+ fetchRemote,
60
+ envBranch,
61
+ envHandle
62
+ }
63
+ );
64
+ if (!appPort) {
65
+ appPort = await findPort(DEFAULT_APP_PORT);
66
+ }
53
67
  const assetsPort = legacyRuntime ? 0 : await findPort(appPort + 100);
68
+ logInjectedVariables();
54
69
  const miniOxygen = await startMiniOxygen(
55
70
  {
56
71
  root,
57
- port: appPort,
72
+ appPort,
58
73
  assetsPort,
59
- env,
74
+ env: allVariables,
60
75
  buildPathClient,
61
76
  buildPathWorkerFile,
62
77
  inspectorPort,
@@ -4,10 +4,12 @@ import Command from '@shopify/cli-kit/node/base-command';
4
4
  import { renderTasks, renderSuccess } from '@shopify/cli-kit/node/ui';
5
5
  import { getPackageManager, installNodeModules } from '@shopify/cli-kit/node/node-package-manager';
6
6
  import { Args } from '@oclif/core';
7
- import { getRemixConfig } from '../../../lib/remix-config.js';
7
+ import { hasRemixConfigFile, getRemixConfig } from '../../../lib/remix-config.js';
8
8
  import { SETUP_CSS_STRATEGIES, renderCssPrompt, setupCssStrategy, CSS_STRATEGY_NAME_MAP } from '../../../lib/setups/css/index.js';
9
+ import { AbortError } from '@shopify/cli-kit/node/error';
9
10
 
10
11
  class SetupCSS extends Command {
12
+ static descriptionWithMarkdown = "Adds support for certain CSS strategies to your project.";
11
13
  static description = "Setup CSS strategies for your project.";
12
14
  static flags = {
13
15
  ...commonFlags.path,
@@ -37,6 +39,11 @@ async function runSetupCSS({
37
39
  force = false,
38
40
  installDeps = true
39
41
  }) {
42
+ if (!await hasRemixConfigFile(directory)) {
43
+ throw new AbortError(
44
+ "No remix.config.js file found. This command is not supported in Vite projects."
45
+ );
46
+ }
40
47
  const remixConfigPromise = getRemixConfig(directory);
41
48
  const strategy = flagStrategy ? flagStrategy : await renderCssPrompt();
42
49
  const remixConfig = await remixConfigPromise;
@@ -7,6 +7,7 @@ import { getRemixConfig } from '../../../lib/remix-config.js';
7
7
  import { SETUP_I18N_STRATEGIES, renderI18nPrompt, setupI18nStrategy, I18N_STRATEGY_NAME_MAP } from '../../../lib/setups/i18n/index.js';
8
8
 
9
9
  class SetupMarkets extends Command {
10
+ static descriptionWithMarkdown = "Adds support for multiple [markets](https://shopify.dev/docs/custom-storefronts/hydrogen/markets) to your project by using the URL structure.";
10
11
  static description = "Setup support for multiple markets in your project.";
11
12
  static flags = {
12
13
  ...commonFlags.path