@shopify/cli-hydrogen 7.1.2 → 8.0.0

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 (136) 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 +3 -0
  7. package/dist/commands/hydrogen/deploy.js +121 -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 +7 -8
  11. package/dist/commands/hydrogen/env/pull.js +17 -1
  12. package/dist/commands/hydrogen/env/{push__unstable.js → push.js} +23 -50
  13. package/dist/commands/hydrogen/generate/route.js +1 -0
  14. package/dist/commands/hydrogen/init.js +45 -17
  15. package/dist/commands/hydrogen/link.js +20 -4
  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 -22
  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 +10 -2
  29. package/dist/generator-templates/starter/CHANGELOG.md +89 -0
  30. package/dist/generator-templates/starter/README.md +3 -44
  31. package/dist/generator-templates/starter/app/graphql/customer-account/CustomerDetailsQuery.ts +1 -0
  32. package/dist/generator-templates/starter/app/lib/fragments.ts +2 -0
  33. package/dist/generator-templates/starter/app/root.tsx +2 -5
  34. package/dist/generator-templates/starter/app/routes/account.orders._index.tsx +1 -1
  35. package/dist/generator-templates/starter/app/routes/account.tsx +1 -1
  36. package/dist/generator-templates/starter/app/routes/collections.all.tsx +160 -0
  37. package/dist/generator-templates/starter/app/routes/products.$handle.tsx +1 -2
  38. package/dist/generator-templates/starter/customer-accountapi.generated.d.ts +6 -3
  39. package/dist/generator-templates/starter/{remix.env.d.ts → env.d.ts} +8 -2
  40. package/dist/generator-templates/starter/package.json +14 -9
  41. package/dist/generator-templates/starter/server.ts +2 -1
  42. package/dist/generator-templates/starter/storefrontapi.generated.d.ts +59 -3
  43. package/dist/generator-templates/starter/vite.config.ts +21 -0
  44. package/dist/{commands/hydrogen/init.d.ts → init.d.ts} +11 -3
  45. package/dist/lib/check-lockfile.js +12 -18
  46. package/dist/lib/codegen.js +37 -13
  47. package/dist/lib/common.js +50 -0
  48. package/dist/lib/cpu-profiler.js +4 -1
  49. package/dist/lib/dev-shared.js +97 -0
  50. package/dist/lib/environment-variables.js +51 -30
  51. package/dist/lib/file.js +8 -1
  52. package/dist/lib/flags.js +37 -16
  53. package/dist/lib/graphql/admin/customer-application-update.js +29 -0
  54. package/dist/lib/graphql/admin/get-oxygen-data.js +1 -0
  55. package/dist/lib/graphql/admin/list-environments.js +1 -0
  56. package/dist/lib/graphql/admin/pull-variables.js +4 -4
  57. package/dist/lib/graphql/admin/test-helper.js +37 -0
  58. package/dist/lib/log.js +86 -13
  59. package/dist/lib/mini-oxygen/common.js +19 -33
  60. package/dist/lib/mini-oxygen/index.js +6 -2
  61. package/dist/lib/mini-oxygen/node.js +43 -31
  62. package/dist/lib/mini-oxygen/workerd.js +72 -165
  63. package/dist/lib/missing-routes.js +1 -1
  64. package/dist/lib/onboarding/common.js +82 -70
  65. package/dist/lib/onboarding/local.js +19 -9
  66. package/dist/lib/onboarding/remote.js +35 -30
  67. package/dist/lib/package-managers.js +24 -0
  68. package/dist/lib/remix-config.js +17 -1
  69. package/dist/lib/request-events.js +6 -1
  70. package/dist/lib/setups/i18n/replacers.js +9 -6
  71. package/dist/lib/setups/routes/generate.js +1 -0
  72. package/dist/lib/shell.js +2 -1
  73. package/dist/lib/shopify-config.js +19 -1
  74. package/dist/lib/template-diff.js +36 -15
  75. package/dist/lib/template-downloader.js +35 -5
  76. package/dist/lib/transpile/morph/typedefs.js +5 -2
  77. package/dist/lib/transpile/project.js +8 -4
  78. package/dist/lib/tunneling.js +44 -0
  79. package/dist/lib/virtual-routes.js +1 -1
  80. package/dist/lib/vite-config.js +39 -9
  81. package/oclif.manifest.json +711 -498
  82. package/package.json +32 -24
  83. package/dist/commands/hydrogen/deploy.test.js +0 -553
  84. package/dist/commands/hydrogen/env/list.test.js +0 -148
  85. package/dist/commands/hydrogen/env/pull.test.js +0 -207
  86. package/dist/commands/hydrogen/env/push__unstable.test.js +0 -383
  87. package/dist/commands/hydrogen/generate/route.test.js +0 -43
  88. package/dist/commands/hydrogen/init.test.js +0 -641
  89. package/dist/commands/hydrogen/link.test.js +0 -187
  90. package/dist/commands/hydrogen/list.test.js +0 -111
  91. package/dist/commands/hydrogen/setup.test.js +0 -61
  92. package/dist/commands/hydrogen/shortcut.test.js +0 -30
  93. package/dist/commands/hydrogen/unlink.test.js +0 -36
  94. package/dist/commands/hydrogen/upgrade.test.js +0 -786
  95. package/dist/generator-templates/starter/remix.config.js +0 -24
  96. package/dist/lib/auth.test.js +0 -157
  97. package/dist/lib/check-lockfile.test.js +0 -81
  98. package/dist/lib/check-version.test.js +0 -86
  99. package/dist/lib/environment-variables.test.js +0 -149
  100. package/dist/lib/file.test.js +0 -68
  101. package/dist/lib/flags.test.js +0 -43
  102. package/dist/lib/get-oxygen-deployment-data.test.js +0 -120
  103. package/dist/lib/gid.test.js +0 -15
  104. package/dist/lib/graphql/admin/client.test.js +0 -76
  105. package/dist/lib/graphql/admin/create-storefront.test.js +0 -64
  106. package/dist/lib/graphql/admin/link-storefront.test.js +0 -38
  107. package/dist/lib/graphql/admin/list-environments.test.js +0 -44
  108. package/dist/lib/graphql/admin/list-storefronts.test.js +0 -44
  109. package/dist/lib/graphql/admin/pull-variables.test.js +0 -43
  110. package/dist/lib/graphql/business-platform/user-account.test.js +0 -80
  111. package/dist/lib/log.test.js +0 -92
  112. package/dist/lib/mini-oxygen/assets.js +0 -134
  113. package/dist/lib/mini-oxygen/mini-oxygen.test.js +0 -214
  114. package/dist/lib/mini-oxygen/workerd-inspector-logs.js +0 -227
  115. package/dist/lib/mini-oxygen/workerd-inspector-proxy.js +0 -200
  116. package/dist/lib/mini-oxygen/workerd-inspector.js +0 -219
  117. package/dist/lib/missing-routes.test.js +0 -45
  118. package/dist/lib/remix-version-check.test.js +0 -39
  119. package/dist/lib/remix-version-interop.test.js +0 -13
  120. package/dist/lib/setups/i18n/domains.test.js +0 -39
  121. package/dist/lib/setups/i18n/replacers.test.js +0 -261
  122. package/dist/lib/setups/i18n/subdomains.test.js +0 -39
  123. package/dist/lib/setups/i18n/subfolders.test.js +0 -39
  124. package/dist/lib/setups/routes/generate.test.js +0 -296
  125. package/dist/lib/shell.test.js +0 -111
  126. package/dist/lib/shopify-config.test.js +0 -199
  127. package/dist/lib/string.test.js +0 -16
  128. package/dist/lib/virtual-routes.test.js +0 -49
  129. package/dist/lib/vite/hydrogen-middleware.js +0 -82
  130. package/dist/lib/vite/mini-oxygen.js +0 -152
  131. package/dist/lib/vite/plugins.d.ts +0 -27
  132. package/dist/lib/vite/plugins.js +0 -139
  133. package/dist/lib/vite/shared.js +0 -10
  134. package/dist/lib/vite/utils.js +0 -55
  135. package/dist/lib/vite/worker-entry.js +0 -1518
  136. /package/dist/generator-templates/starter/{.eslintrc.js → .eslintrc.cjs} +0 -0
@@ -1,5 +1,5 @@
1
1
  import Command from '@shopify/cli-kit/node/base-command';
2
- import { outputWarn } from '@shopify/cli-kit/node/output';
2
+ import { collectLog, outputWarn } from '@shopify/cli-kit/node/output';
3
3
  import { removeFile, fileSize } from '@shopify/cli-kit/node/fs';
4
4
  import { resolvePath, joinPath } from '@shopify/cli-kit/node/path';
5
5
  import { getPackageManager } from '@shopify/cli-kit/node/node-package-manager';
@@ -23,6 +23,7 @@ class Build extends Command {
23
23
  ...commonFlags.codegen,
24
24
  ...commonFlags.diff
25
25
  };
26
+ static hidden = true;
26
27
  async run() {
27
28
  const { flags } = await this.parse(Build);
28
29
  const originalDirectory = flags.path ? resolvePath(flags.path) : process.cwd();
@@ -57,20 +58,28 @@ async function runViteBuild({
57
58
  if (lockfileCheck) {
58
59
  await checkLockfileStatus(root, isCI());
59
60
  }
60
- const {
61
- userViteConfig,
62
- remixConfig,
63
- clientOutDir,
64
- serverOutDir,
65
- serverOutFile
66
- } = await getViteConfig(root);
61
+ const [
62
+ vite,
63
+ { userViteConfig, remixConfig, clientOutDir, serverOutDir, serverOutFile }
64
+ ] = await Promise.all([
65
+ // Avoid static imports because this file is imported by `deploy` command,
66
+ // which must have a hard dependency on 'vite'.
67
+ import('vite'),
68
+ getViteConfig(root, ssrEntry)
69
+ ]);
70
+ const customLogger = vite.createLogger();
71
+ if (process.env.SHOPIFY_UNIT_TEST) {
72
+ customLogger.info = (msg) => collectLog("info", msg);
73
+ customLogger.warn = (msg) => collectLog("warn", msg);
74
+ customLogger.error = (msg) => collectLog("error", msg);
75
+ }
67
76
  const serverMinify = userViteConfig.build?.minify ?? true;
68
77
  const commonConfig = {
69
78
  root,
70
79
  mode: process.env.NODE_ENV,
71
- base: assetPath
80
+ base: assetPath,
81
+ customLogger
72
82
  };
73
- const vite = await import('vite');
74
83
  await vite.build({
75
84
  ...commonConfig,
76
85
  build: {
@@ -14,10 +14,12 @@ import { codegen } from '../../lib/codegen.js';
14
14
  import { buildBundleAnalysis, getBundleAnalysisSummary } from '../../lib/bundle/analyzer.js';
15
15
  import { isCI } from '../../lib/is-ci.js';
16
16
  import { prepareDiffDirectory, copyDiffBuild } from '../../lib/template-diff.js';
17
+ import { hasViteConfig } from '../../lib/vite-config.js';
17
18
 
18
19
  const LOG_WORKER_BUILT = "\u{1F4E6} Worker built";
19
20
  const WORKER_BUILD_SIZE_LIMIT = 5;
20
21
  class Build extends Command {
22
+ static descriptionWithMarkdown = `Builds a Hydrogen storefront for production. The client and app worker files are compiled to a \`/dist\` folder in your Hydrogen project directory.`;
21
23
  static description = "Builds a Hydrogen storefront for production.";
22
24
  static flags = {
23
25
  ...commonFlags.path,
@@ -39,11 +41,17 @@ class Build extends Command {
39
41
  if (flags.diff) {
40
42
  directory = await prepareDiffDirectory(originalDirectory, false);
41
43
  }
42
- await runBuild({
44
+ const buildParams = {
43
45
  ...flagsToCamelObject(flags),
44
46
  useCodegen: flags.codegen,
45
47
  directory
46
- });
48
+ };
49
+ if (await hasViteConfig(directory ?? process.cwd())) {
50
+ const { runViteBuild } = await import('./build-vite.js');
51
+ await runViteBuild(buildParams);
52
+ } else {
53
+ await runBuild(buildParams);
54
+ }
47
55
  if (flags.diff) {
48
56
  await copyDiffBuild(directory, originalDirectory);
49
57
  }
@@ -6,6 +6,7 @@ import { logMissingRoutes, findMissingRoutes } from '../../lib/missing-routes.js
6
6
  import { Args } from '@oclif/core';
7
7
 
8
8
  class GenerateRoute extends Command {
9
+ static descriptionWithMarkdown = `Checks whether your Hydrogen app includes a set of standard Shopify routes.`;
9
10
  static description = "Returns diagnostic information about a Hydrogen storefront.";
10
11
  static flags = {
11
12
  ...commonFlags.path
@@ -8,6 +8,7 @@ import { commonFlags, flagsToCamelObject } from '../../lib/flags.js';
8
8
  import { codegen } from '../../lib/codegen.js';
9
9
 
10
10
  class Codegen extends Command {
11
+ static descriptionWithMarkdown = "Automatically generates GraphQL types for your project\u2019s Storefront API queries.";
11
12
  static description = "Generate types for the Storefront API queries found in your project.";
12
13
  static flags = {
13
14
  ...commonFlags.path,
@@ -0,0 +1,170 @@
1
+ import Command from '@shopify/cli-kit/node/base-command';
2
+ import { Flags } from '@oclif/core';
3
+ import { AbortError } from '@shopify/cli-kit/node/error';
4
+ import { outputDebug } from '@shopify/cli-kit/node/output';
5
+ import { linkStorefront } from '../link.js';
6
+ import { commonFlags, flagsToCamelObject } from '../../../lib/flags.js';
7
+ import { getCliCommand } from '../../../lib/shell.js';
8
+ import { login } from '../../../lib/auth.js';
9
+ import { setCustomerAccountConfig, getConfig } from '../../../lib/shopify-config.js';
10
+ import { replaceCustomerApplicationUrls } from '../../../lib/graphql/admin/customer-application-update.js';
11
+
12
+ class CustomerAccountPush extends Command {
13
+ static description = "Push project configuration to admin";
14
+ static flags = {
15
+ ...commonFlags.path,
16
+ "storefront-id": Flags.string({
17
+ description: "The id of the storefront the configuration should be pushed to. Must start with 'gid://shopify/HydrogenStorefront/'"
18
+ }),
19
+ "dev-origin": Flags.string({
20
+ description: "The development domain of your application.",
21
+ required: true
22
+ }),
23
+ "relative-redirect-uri": Flags.string({
24
+ description: "The relative url of allowed callback url for Customer Account API OAuth flow. Default is '/account/authorize'"
25
+ }),
26
+ "relative-logout-uri": Flags.string({
27
+ description: "The relative url of allowed url that will be redirected to post-logout for Customer Account API OAuth flow. Default to nothing."
28
+ })
29
+ };
30
+ async run() {
31
+ const { flags } = await this.parse(CustomerAccountPush);
32
+ await runCustomerAccountPush({ ...flagsToCamelObject(flags) });
33
+ }
34
+ }
35
+ async function runCustomerAccountPush({
36
+ path: root = process.cwd(),
37
+ storefrontId: storefrontIdFromFlag,
38
+ devOrigin,
39
+ redirectUriRelativeUrl = "/account/authorize",
40
+ logoutUriRelativeUrl
41
+ }) {
42
+ const storefrontId = await getStorefrontId(root, storefrontIdFromFlag);
43
+ try {
44
+ if (!storefrontId) {
45
+ throw new Error(
46
+ "No storefrontId was found in --storefront-id flag or .shopify/project.json"
47
+ );
48
+ }
49
+ const redirectUri = redirectUriRelativeUrl ? new URL(redirectUriRelativeUrl, devOrigin).toString() : devOrigin;
50
+ const javascriptOrigin = devOrigin;
51
+ const logoutUri = logoutUriRelativeUrl ? new URL(logoutUriRelativeUrl, devOrigin).toString() : devOrigin;
52
+ if (!redirectUri && !javascriptOrigin && !logoutUri) {
53
+ return;
54
+ }
55
+ const { session, config } = await login(root);
56
+ const customerAccountConfig = config?.storefront?.customerAccountConfig;
57
+ const { success, userErrors } = await replaceCustomerApplicationUrls(
58
+ session,
59
+ storefrontId,
60
+ {
61
+ redirectUri: {
62
+ add: redirectUri ? [redirectUri] : void 0,
63
+ removeRegex: customerAccountConfig?.redirectUri
64
+ },
65
+ javascriptOrigin: {
66
+ add: javascriptOrigin ? [javascriptOrigin] : void 0,
67
+ removeRegex: customerAccountConfig?.javascriptOrigin
68
+ },
69
+ logoutUris: {
70
+ add: logoutUri ? [logoutUri] : void 0,
71
+ removeRegex: customerAccountConfig?.logoutUri
72
+ }
73
+ }
74
+ );
75
+ if (!success || userErrors.length) {
76
+ const error = new Error(
77
+ "Customer Account Application setup update fail."
78
+ );
79
+ error.userErrors = userErrors;
80
+ throw error;
81
+ }
82
+ await setCustomerAccountConfig(root, {
83
+ redirectUri,
84
+ javascriptOrigin,
85
+ logoutUri
86
+ });
87
+ return () => cleanupCustomerApplicationUrls(session, storefrontId, {
88
+ redirectUri,
89
+ javascriptOrigin,
90
+ logoutUri
91
+ });
92
+ } catch (error) {
93
+ let confidentialAccessFound = false;
94
+ const errors = error?.userErrors?.length ? error.userErrors.map(
95
+ (value) => {
96
+ if (/Javascript origin is not allowed for this application type/i.test(
97
+ value.message
98
+ )) {
99
+ confidentialAccessFound = true;
100
+ }
101
+ return `${value.field}: ${value.message}`;
102
+ }
103
+ ) : error.message ? [error.message] : [];
104
+ const nextSteps = [
105
+ {
106
+ link: {
107
+ label: "Manually update application setup",
108
+ url: "https://shopify.dev/docs/custom-storefronts/building-with-the-customer-account-api/hydrogen#update-the-application-setup"
109
+ }
110
+ }
111
+ ];
112
+ if (confidentialAccessFound) {
113
+ nextSteps.unshift({
114
+ link: {
115
+ label: "Enable Public access for Hydrogen",
116
+ url: "https://shopify.dev/docs/custom-storefronts/building-with-the-customer-account-api/getting-started#step-1-enable-customer-account-api-access"
117
+ }
118
+ });
119
+ }
120
+ throw new AbortError(
121
+ "Customer Account Application setup update fail.",
122
+ errors.length ? [
123
+ {
124
+ list: {
125
+ title: "The following error occurs during update:",
126
+ items: errors.map(
127
+ (value, index) => `${index != 0 ? "\n\n" : ""}${value}`
128
+ )
129
+ }
130
+ }
131
+ ] : void 0,
132
+ nextSteps
133
+ );
134
+ }
135
+ }
136
+ async function cleanupCustomerApplicationUrls(session, storefrontId, customerAccountConfig = {}) {
137
+ if (!Object.values(customerAccountConfig).some(Boolean))
138
+ return;
139
+ outputDebug(
140
+ `Cleaning up Customer Application url "${customerAccountConfig.redirectUri}"`
141
+ );
142
+ await replaceCustomerApplicationUrls(session, storefrontId, {
143
+ redirectUri: { removeRegex: customerAccountConfig?.redirectUri },
144
+ javascriptOrigin: { removeRegex: customerAccountConfig?.javascriptOrigin },
145
+ logoutUris: { removeRegex: customerAccountConfig?.logoutUri }
146
+ }).catch((error) => {
147
+ outputDebug(
148
+ `Failed to clean up Customer Application url "${customerAccountConfig.redirectUri}":
149
+ ${error?.message}`
150
+ );
151
+ });
152
+ }
153
+ async function getStorefrontId(root, storefrontIdFromFlag) {
154
+ if (storefrontIdFromFlag)
155
+ return storefrontIdFromFlag;
156
+ const { session, config } = await login(root);
157
+ if (config.storefront?.id)
158
+ return config.storefront.id;
159
+ const cliCommand = await getCliCommand();
160
+ const linkedStore = await linkStorefront(root, session, config, {
161
+ cliCommand
162
+ });
163
+ if (!linkedStore) {
164
+ return;
165
+ }
166
+ const newConfig = await getConfig(root);
167
+ return newConfig.storefront?.id;
168
+ }
169
+
170
+ export { CustomerAccountPush as default, getStorefrontId, runCustomerAccountPush };
@@ -12,6 +12,9 @@ import { createCpuStartupProfiler } from '../../../lib/cpu-profiler.js';
12
12
 
13
13
  const DEFAULT_OUTPUT_PATH = "startup.cpuprofile";
14
14
  class DebugCpu extends Command {
15
+ static descriptionWithMarkdown = `Builds the app and runs the resulting code to profile the server startup time, watching for changes. This command can be used to [debug slow app startup times](https://shopify.dev/docs/custom-storefronts/hydrogen/debugging/cpu-startup) that cause failed deployments in Oxygen.
16
+
17
+ The profiling results are written to a \`.cpuprofile\` file that can be viewed with certain tools such as [Flame Chart Visualizer for Visual Studio Code](https://marketplace.visualstudio.com/items?itemName=ms-vscode.vscode-js-profile-flame).`;
15
18
  static description = "Builds and profiles the server startup time the app.";
16
19
  static flags = {
17
20
  ...commonFlags.path,
@@ -7,15 +7,19 @@ import { AbortError } from '@shopify/cli-kit/node/error';
7
7
  import { writeFile } from '@shopify/cli-kit/node/fs';
8
8
  import { ensureIsClean, getLatestGitCommit, GitDirectoryNotCleanError } from '@shopify/cli-kit/node/git';
9
9
  import { resolvePath, relativePath } from '@shopify/cli-kit/node/path';
10
- import { renderWarning, renderSelectPrompt, renderSuccess, renderTasks } from '@shopify/cli-kit/node/ui';
10
+ import { renderWarning, renderSelectPrompt, renderConfirmationPrompt, renderSuccess, renderTasks } from '@shopify/cli-kit/node/ui';
11
11
  import { ciPlatform } from '@shopify/cli-kit/node/context/local';
12
12
  import { parseToken, createDeploy } from '@shopify/oxygen-cli/deploy';
13
+ import { findEnvironmentOrThrow, findEnvironmentByBranchOrThrow, orderEnvironmentsBySafety, createEnvironmentCliChoiceLabel } from '../../lib/common.js';
14
+ import { execAsync } from '../../lib/process.js';
13
15
  import { commonFlags, flagsToCamelObject } from '../../lib/flags.js';
14
16
  import { getOxygenDeploymentData } from '../../lib/get-oxygen-deployment-data.js';
15
17
  import { runBuild } from './build.js';
16
18
  import { runViteBuild } from './build-vite.js';
17
19
  import { getViteConfig } from '../../lib/vite-config.js';
18
20
  import { prepareDiffDirectory } from '../../lib/template-diff.js';
21
+ import { hasRemixConfigFile } from '../../lib/remix-config.js';
22
+ import { packageManagers } from '../../lib/package-managers.js';
19
23
 
20
24
  const DEPLOY_OUTPUT_FILE_HANDLE = "h2_deploy_log.json";
21
25
  const deploymentLogger = (message, level = "info") => {
@@ -24,12 +28,12 @@ const deploymentLogger = (message, level = "info") => {
24
28
  }
25
29
  };
26
30
  class Deploy extends Command {
31
+ static descriptionWithMarkdown = `Builds and deploys your Hydrogen storefront to Oxygen. Requires an Oxygen deployment token to be set with the \`--token\` flag or an environment variable (\`SHOPIFY_HYDROGEN_DEPLOYMENT_TOKEN\`). If the storefront is [linked](https://shopify.dev/docs/api/shopify-cli/hydrogen/hydrogen-link) then the Oxygen deployment token for the linked storefront will be used automatically.`;
27
32
  static description = "Builds and deploys a Hydrogen storefront to Oxygen.";
28
33
  static flags = {
29
- "env-branch": Flags.string({
30
- description: "Environment branch (tag) for environment to deploy to.",
31
- required: false
32
- }),
34
+ ...commonFlags.entry,
35
+ ...commonFlags.env,
36
+ ...commonFlags.envBranch,
33
37
  "env-file": Flags.string({
34
38
  description: "Path to an environment file to override existing environment variables for the deployment.",
35
39
  required: false
@@ -114,7 +118,6 @@ class Deploy extends Command {
114
118
  return {
115
119
  ...camelFlags,
116
120
  defaultEnvironment: flags.preview,
117
- environmentTag: flags["env-branch"],
118
121
  environmentFile: flags["env-file"],
119
122
  path: flags.path ? resolvePath(flags.path) : process.cwd()
120
123
  };
@@ -143,7 +146,8 @@ async function runDeploy(options) {
143
146
  authBypassToken: generateAuthBypassToken,
144
147
  buildCommand,
145
148
  defaultEnvironment,
146
- environmentTag,
149
+ env: envHandle,
150
+ envBranch,
147
151
  environmentFile,
148
152
  force: forceOnUncommitedChanges,
149
153
  noVerify,
@@ -153,7 +157,8 @@ async function runDeploy(options) {
153
157
  shop,
154
158
  metadataUrl,
155
159
  metadataUser,
156
- metadataVersion
160
+ metadataVersion,
161
+ entry: ssrEntry
157
162
  } = options;
158
163
  let { metadataDescription } = options;
159
164
  let isCleanGit = true;
@@ -164,13 +169,34 @@ async function runDeploy(options) {
164
169
  isCleanGit = false;
165
170
  }
166
171
  if (!forceOnUncommitedChanges && !isCleanGit) {
167
- throw new AbortError("Uncommitted changes detected.", null, [
172
+ let errorMessage = "Uncommitted changes detected";
173
+ let changedFiles = void 0;
174
+ const nextSteps = [
168
175
  [
169
- "Commit your changes before deploying or use the ",
176
+ "Commit your changes before deploying or use the",
170
177
  { command: "--force" },
171
- " flag to deploy with uncommitted changes."
178
+ "flag to deploy with uncommitted changes."
172
179
  ]
173
- ]);
180
+ ];
181
+ try {
182
+ changedFiles = (await execAsync("git status -s", { cwd: root })).stdout;
183
+ } catch {
184
+ }
185
+ if (changedFiles) {
186
+ errorMessage += `:
187
+
188
+ ${changedFiles.trimEnd()}`;
189
+ packageManagers.forEach(({ name, lockfile, installCommand }) => {
190
+ if (changedFiles.includes(lockfile)) {
191
+ nextSteps.push([
192
+ `If you are using ${name}, try running`,
193
+ { command: installCommand },
194
+ `to avoid changes to ${lockfile}.`
195
+ ]);
196
+ }
197
+ });
198
+ }
199
+ throw new AbortError(errorMessage, null, nextSteps);
174
200
  }
175
201
  }
176
202
  const isCI = ciPlatform().isCI;
@@ -178,7 +204,7 @@ async function runDeploy(options) {
178
204
  let branch;
179
205
  let commitHash;
180
206
  let deploymentData;
181
- let deploymentEnvironmentTag = void 0;
207
+ let userChosenEnvironmentTag = void 0;
182
208
  let gitCommit;
183
209
  try {
184
210
  gitCommit = await getLatestGitCommit(root);
@@ -212,6 +238,14 @@ async function runDeploy(options) {
212
238
  })
213
239
  );
214
240
  }
241
+ let userProvidedEnvironmentTag = null;
242
+ let isPreview = false;
243
+ if (isCI && envHandle) {
244
+ throw new AbortError(
245
+ "Can't specify an environment handle in CI",
246
+ "Environments are automatically picked up by the current Git branch."
247
+ );
248
+ }
215
249
  if (!isCI) {
216
250
  deploymentData = await getOxygenDeploymentData({
217
251
  root,
@@ -221,6 +255,20 @@ async function runDeploy(options) {
221
255
  return;
222
256
  }
223
257
  token = token || deploymentData.oxygenDeploymentToken;
258
+ if (envHandle) {
259
+ userProvidedEnvironmentTag = findEnvironmentOrThrow(
260
+ deploymentData.environments || [],
261
+ envHandle
262
+ ).branch;
263
+ if (userProvidedEnvironmentTag === null) {
264
+ isPreview = true;
265
+ }
266
+ } else if (envBranch) {
267
+ userProvidedEnvironmentTag = findEnvironmentByBranchOrThrow(
268
+ deploymentData.environments || [],
269
+ envBranch
270
+ ).branch;
271
+ }
224
272
  }
225
273
  if (!token) {
226
274
  const errMessage = isCI ? [
@@ -230,20 +278,21 @@ async function runDeploy(options) {
230
278
  ] : `Could not obtain an Oxygen deployment token, please try again or contact Shopify support.`;
231
279
  throw new AbortError(errMessage);
232
280
  }
233
- if (!isCI && !defaultEnvironment && !environmentTag && deploymentData?.environments) {
281
+ if (!isCI && !defaultEnvironment && !envHandle && !envBranch && deploymentData?.environments) {
234
282
  if (deploymentData.environments.length > 1) {
235
- const choices = [
236
- ...deploymentData.environments.map(({ name, branch: branch2, type }) => ({
237
- label: name,
238
- // The preview environment will never have an associated branch so
239
- // we're using a custom string here to identify it later in our code.
240
- // Using a period at the end of the value is an invalid branch name
241
- // in Git so we can be sure that this won't conflict with a merchant's
242
- // repository.
243
- value: type === "PREVIEW" ? "shopify-preview-environment." : branch2
244
- }))
245
- ];
246
- deploymentEnvironmentTag = await renderSelectPrompt({
283
+ const environments = orderEnvironmentsBySafety(
284
+ deploymentData.environments
285
+ );
286
+ const choices = environments.map(({ name, branch: branch2, handle, type }) => ({
287
+ label: createEnvironmentCliChoiceLabel(name, handle, branch2),
288
+ // The preview environment will never have an associated branch so
289
+ // we're using a custom string here to identify it later in our code.
290
+ // Using a period at the end of the value is an invalid branch name
291
+ // in Git so we can be sure that this won't conflict with a merchant's
292
+ // repository.
293
+ value: type === "PREVIEW" ? "shopify-preview-environment." : branch2
294
+ }));
295
+ userChosenEnvironmentTag = await renderSelectPrompt({
247
296
  message: "Select an environment to deploy to",
248
297
  choices,
249
298
  defaultValue: branch
@@ -262,18 +311,22 @@ async function runDeploy(options) {
262
311
  );
263
312
  }
264
313
  let fallbackEnvironmentTag = branch;
265
- let isPreview = false;
266
- if (deploymentEnvironmentTag === "shopify-preview-environment.") {
314
+ if (userChosenEnvironmentTag === "shopify-preview-environment.") {
267
315
  fallbackEnvironmentTag = void 0;
268
- deploymentEnvironmentTag = void 0;
316
+ userChosenEnvironmentTag = void 0;
269
317
  isPreview = true;
270
318
  }
271
319
  let assetsDir = "dist/client";
272
320
  let workerDir = "dist/worker";
273
- const maybeVite = await getViteConfig(root).catch(() => null);
274
- if (maybeVite) {
275
- assetsDir = relativePath(root, maybeVite.clientOutDir);
276
- workerDir = relativePath(root, maybeVite.serverOutDir);
321
+ const isClassicCompiler = await hasRemixConfigFile(root);
322
+ if (!isClassicCompiler) {
323
+ const viteConfig = await getViteConfig(root, ssrEntry).catch(() => null);
324
+ if (viteConfig) {
325
+ assetsDir = relativePath(root, viteConfig.clientOutDir);
326
+ workerDir = relativePath(root, viteConfig.serverOutDir);
327
+ } else {
328
+ workerDir = "dist/server";
329
+ }
277
330
  }
278
331
  const config = {
279
332
  assetsDir,
@@ -281,7 +334,7 @@ async function runDeploy(options) {
281
334
  deploymentUrl,
282
335
  defaultEnvironment: defaultEnvironment || isPreview,
283
336
  deploymentToken: parseToken(token),
284
- environmentTag: environmentTag || deploymentEnvironmentTag || fallbackEnvironmentTag,
337
+ environmentTag: userProvidedEnvironmentTag || userChosenEnvironmentTag || fallbackEnvironmentTag,
285
338
  generateAuthBypassToken,
286
339
  verificationMaxDuration: 180,
287
340
  metadata: {
@@ -297,6 +350,37 @@ async function runDeploy(options) {
297
350
  workerDir,
298
351
  overriddenEnvironmentVariables
299
352
  };
353
+ if (!isCI && (userProvidedEnvironmentTag || userChosenEnvironmentTag || config.defaultEnvironment)) {
354
+ let chosenEnvironment = null;
355
+ if (config.defaultEnvironment) {
356
+ chosenEnvironment = findEnvironmentOrThrow(
357
+ deploymentData.environments,
358
+ "preview"
359
+ );
360
+ } else if (config.environmentTag) {
361
+ chosenEnvironment = findEnvironmentByBranchOrThrow(
362
+ deploymentData.environments,
363
+ config.environmentTag
364
+ );
365
+ }
366
+ let confirmationMessage = "Creating a deployment";
367
+ if (chosenEnvironment) {
368
+ confirmationMessage += ` against ${createEnvironmentCliChoiceLabel(
369
+ chosenEnvironment.name,
370
+ chosenEnvironment.handle,
371
+ chosenEnvironment.branch
372
+ )}`;
373
+ }
374
+ const confirmPush = await renderConfirmationPrompt({
375
+ confirmationMessage: "Yes, confirm deploy",
376
+ cancellationMessage: "No, cancel deploy",
377
+ message: outputContent`${confirmationMessage}
378
+
379
+ Continue?`.value
380
+ });
381
+ if (!confirmPush)
382
+ return;
383
+ }
300
384
  let resolveUpload;
301
385
  const uploadPromise = new Promise((resolve) => {
302
386
  resolveUpload = resolve;
@@ -352,13 +436,14 @@ async function runDeploy(options) {
352
436
  outputInfo(
353
437
  outputContent`${colors.whiteBright("Building project...")}`.value
354
438
  );
355
- const build = maybeVite ? runViteBuild : runBuild;
439
+ const build = isClassicCompiler ? runBuild : runViteBuild;
356
440
  await build({
357
441
  directory: root,
358
442
  assetPath,
359
443
  lockfileCheck,
360
444
  sourcemap: true,
361
- useCodegen: false
445
+ useCodegen: false,
446
+ entry: ssrEntry
362
447
  });
363
448
  };
364
449
  }