@shopify/cli-hydrogen 6.1.1 → 7.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 (100) hide show
  1. package/dist/commands/hydrogen/build.js +40 -78
  2. package/dist/commands/hydrogen/codegen.js +8 -3
  3. package/dist/commands/hydrogen/deploy.js +311 -21
  4. package/dist/commands/hydrogen/deploy.test.js +410 -0
  5. package/dist/commands/hydrogen/dev.js +30 -15
  6. package/dist/commands/hydrogen/init.js +1 -1
  7. package/dist/commands/hydrogen/init.test.js +155 -53
  8. package/dist/commands/hydrogen/link.js +5 -21
  9. package/dist/commands/hydrogen/link.test.js +10 -10
  10. package/dist/commands/hydrogen/preview.js +7 -6
  11. package/dist/commands/hydrogen/setup.js +17 -23
  12. package/dist/commands/hydrogen/setup.test.js +0 -1
  13. package/dist/commands/hydrogen/upgrade.js +15 -0
  14. package/dist/generator-templates/starter/.graphqlrc.yml +12 -1
  15. package/dist/generator-templates/starter/CHANGELOG.md +66 -0
  16. package/dist/generator-templates/starter/README.md +23 -0
  17. package/dist/generator-templates/starter/app/components/Cart.tsx +1 -1
  18. package/dist/generator-templates/starter/app/components/Header.tsx +5 -1
  19. package/dist/generator-templates/starter/app/components/Layout.tsx +1 -1
  20. package/dist/generator-templates/starter/app/components/Search.tsx +1 -1
  21. package/dist/generator-templates/starter/app/graphql/customer-account/CustomerAddressMutations.ts +61 -0
  22. package/dist/generator-templates/starter/app/graphql/customer-account/CustomerDetailsQuery.ts +39 -0
  23. package/dist/generator-templates/starter/app/graphql/customer-account/CustomerOrderQuery.ts +87 -0
  24. package/dist/generator-templates/starter/app/graphql/customer-account/CustomerOrdersQuery.ts +58 -0
  25. package/dist/generator-templates/starter/app/graphql/customer-account/CustomerUpdateMutation.ts +24 -0
  26. package/dist/generator-templates/starter/app/lib/fragments.ts +102 -0
  27. package/dist/generator-templates/starter/app/lib/session.ts +67 -0
  28. package/dist/generator-templates/starter/app/root.tsx +11 -45
  29. package/dist/generator-templates/starter/app/routes/account.$.tsx +8 -4
  30. package/dist/generator-templates/starter/app/routes/account._index.tsx +5 -0
  31. package/dist/generator-templates/starter/app/routes/account.addresses.tsx +215 -206
  32. package/dist/generator-templates/starter/app/routes/account.orders.$id.tsx +56 -163
  33. package/dist/generator-templates/starter/app/routes/account.orders._index.tsx +32 -109
  34. package/dist/generator-templates/starter/app/routes/account.profile.tsx +40 -180
  35. package/dist/generator-templates/starter/app/routes/account.tsx +20 -135
  36. package/dist/generator-templates/starter/app/routes/account_.authorize.tsx +5 -0
  37. package/dist/generator-templates/starter/app/routes/account_.login.tsx +3 -140
  38. package/dist/generator-templates/starter/app/routes/account_.logout.tsx +5 -24
  39. package/dist/generator-templates/starter/app/routes/cart.tsx +7 -5
  40. package/dist/generator-templates/starter/app/routes/collections.$handle.tsx +1 -1
  41. package/dist/generator-templates/starter/app/routes/products.$handle.tsx +2 -2
  42. package/dist/generator-templates/starter/app/routes/search.tsx +1 -1
  43. package/dist/generator-templates/starter/customer-accountapi.generated.d.ts +506 -0
  44. package/dist/generator-templates/starter/package.json +11 -11
  45. package/dist/generator-templates/starter/remix.config.js +4 -0
  46. package/dist/generator-templates/starter/remix.env.d.ts +8 -11
  47. package/dist/generator-templates/starter/server.ts +24 -167
  48. package/dist/generator-templates/starter/storefrontapi.generated.d.ts +104 -881
  49. package/dist/hooks/init.js +4 -4
  50. package/dist/lib/auth.js +5 -10
  51. package/dist/lib/build.js +6 -1
  52. package/dist/lib/bundle/analyzer.js +36 -26
  53. package/dist/lib/codegen.js +59 -26
  54. package/dist/lib/defer.js +12 -0
  55. package/dist/lib/file.js +55 -3
  56. package/dist/lib/flags.js +15 -8
  57. package/dist/lib/get-oxygen-deployment-data.test.js +4 -2
  58. package/dist/lib/graphiql-url.js +3 -0
  59. package/dist/lib/graphql/admin/client.test.js +2 -2
  60. package/dist/lib/graphql/admin/get-oxygen-data.js +1 -0
  61. package/dist/lib/log.js +31 -14
  62. package/dist/lib/mini-oxygen/assets.js +17 -1
  63. package/dist/lib/mini-oxygen/index.js +4 -5
  64. package/dist/lib/mini-oxygen/mini-oxygen.test.js +214 -0
  65. package/dist/lib/mini-oxygen/node.js +4 -2
  66. package/dist/lib/mini-oxygen/workerd-inspector-logs.js +2 -2
  67. package/dist/lib/mini-oxygen/workerd.js +27 -10
  68. package/dist/lib/missing-routes.js +6 -3
  69. package/dist/lib/onboarding/common.js +44 -12
  70. package/dist/lib/onboarding/local.js +26 -18
  71. package/dist/lib/onboarding/remote.js +50 -29
  72. package/dist/lib/request-events.js +65 -31
  73. package/dist/lib/setups/css/assets.js +1 -46
  74. package/dist/lib/setups/css/css-modules.js +3 -2
  75. package/dist/lib/setups/css/postcss.js +4 -2
  76. package/dist/lib/setups/css/tailwind.js +4 -2
  77. package/dist/lib/setups/css/vanilla-extract.js +3 -2
  78. package/dist/lib/setups/i18n/replacers.test.js +54 -38
  79. package/dist/lib/shell.js +1 -1
  80. package/dist/lib/template-diff.js +99 -0
  81. package/dist/lib/template-downloader.js +3 -2
  82. package/dist/lib/transpile/project.js +1 -1
  83. package/dist/virtual-routes/assets/debug-network.css +592 -0
  84. package/dist/virtual-routes/assets/favicon-dark.svg +20 -0
  85. package/dist/virtual-routes/components/FlameChartWrapper.jsx +8 -10
  86. package/dist/virtual-routes/components/IconClose.jsx +38 -0
  87. package/dist/virtual-routes/components/IconDiscard.jsx +44 -0
  88. package/dist/virtual-routes/components/RequestDetails.jsx +179 -0
  89. package/dist/virtual-routes/components/RequestTable.jsx +92 -0
  90. package/dist/virtual-routes/components/RequestWaterfall.jsx +151 -0
  91. package/dist/virtual-routes/lib/useDebugNetworkServer.jsx +176 -0
  92. package/dist/virtual-routes/routes/subrequest-profiler.jsx +243 -0
  93. package/oclif.manifest.json +54 -61
  94. package/package.json +15 -12
  95. package/dist/generator-templates/starter/app/routes/account_.activate.$id.$activationToken.tsx +0 -161
  96. package/dist/generator-templates/starter/app/routes/account_.recover.tsx +0 -129
  97. package/dist/generator-templates/starter/app/routes/account_.register.tsx +0 -207
  98. package/dist/generator-templates/starter/app/routes/account_.reset.$id.$resetToken.tsx +0 -136
  99. package/dist/virtual-routes/routes/debug-network.jsx +0 -289
  100. /package/dist/generator-templates/starter/app/{utils.ts → lib/variants.ts} +0 -0
@@ -1,22 +1,22 @@
1
1
  import { Flags } from '@oclif/core';
2
2
  import Command from '@shopify/cli-kit/node/base-command';
3
- import { outputInfo, outputWarn, outputContent, outputToken } from '@shopify/cli-kit/node/output';
3
+ import { outputInfo, outputContent, outputToken, outputWarn } from '@shopify/cli-kit/node/output';
4
4
  import { rmdir, fileSize, glob, readFile, writeFile, fileExists, copyFile } from '@shopify/cli-kit/node/fs';
5
- import { resolvePath, joinPath, relativePath } from '@shopify/cli-kit/node/path';
5
+ import { resolvePath, relativePath, joinPath } from '@shopify/cli-kit/node/path';
6
6
  import { getPackageManager } from '@shopify/cli-kit/node/node-package-manager';
7
7
  import colors from '@shopify/cli-kit/node/colors';
8
8
  import { getProjectPaths, getRemixConfig, handleRemixImportFail, assertOxygenChecks } from '../../lib/remix-config.js';
9
- import { commonFlags, deprecated, flagsToCamelObject } from '../../lib/flags.js';
9
+ import { commonFlags, flagsToCamelObject } from '../../lib/flags.js';
10
10
  import { checkLockfileStatus } from '../../lib/check-lockfile.js';
11
11
  import { findMissingRoutes } from '../../lib/missing-routes.js';
12
12
  import { muteRemixLogs, createRemixLogger } from '../../lib/log.js';
13
13
  import { codegen } from '../../lib/codegen.js';
14
- import { hasMetafile, buildBundleAnalysis, getBundleAnalysisSummary } from '../../lib/bundle/analyzer.js';
15
- import { AbortError } from '@shopify/cli-kit/node/error';
14
+ import { buildBundleAnalysis, getBundleAnalysisSummary } from '../../lib/bundle/analyzer.js';
16
15
  import { isCI } from '../../lib/is-ci.js';
16
+ import { prepareDiffDirectory, copyDiffBuild } from '../../lib/template-diff.js';
17
17
 
18
18
  const LOG_WORKER_BUILT = "\u{1F4E6} Worker built";
19
- const MAX_WORKER_BUNDLE_SIZE = 10;
19
+ const WORKER_BUILD_SIZE_LIMIT = 5;
20
20
  class Build extends Command {
21
21
  static description = "Builds a Hydrogen storefront for production.";
22
22
  static flags = {
@@ -44,18 +44,24 @@ class Build extends Command {
44
44
  }),
45
45
  codegen: commonFlags.codegen,
46
46
  "codegen-config-path": commonFlags.codegenConfigPath,
47
- base: deprecated("--base")(),
48
- entry: deprecated("--entry")(),
49
- target: deprecated("--target")()
47
+ diff: commonFlags.diff
50
48
  };
51
49
  async run() {
52
50
  const { flags } = await this.parse(Build);
53
- const directory = flags.path ? resolvePath(flags.path) : process.cwd();
51
+ const originalDirectory = flags.path ? resolvePath(flags.path) : process.cwd();
52
+ let directory = originalDirectory;
53
+ if (flags.diff) {
54
+ directory = await prepareDiffDirectory(originalDirectory, false);
55
+ }
54
56
  await runBuild({
55
57
  ...flagsToCamelObject(flags),
56
58
  useCodegen: flags.codegen,
57
59
  directory
58
60
  });
61
+ if (flags.diff) {
62
+ await copyDiffBuild(directory, originalDirectory);
63
+ }
64
+ process.exit(0);
59
65
  }
60
66
  }
61
67
  async function runBuild({
@@ -104,28 +110,37 @@ async function runBuild({
104
110
  fileWatchCache: createFileWatchCache()
105
111
  }).catch((thrown) => {
106
112
  logThrown(thrown);
107
- process.exit(1);
113
+ if (process.env.SHOPIFY_UNIT_TEST) {
114
+ throw thrown;
115
+ } else {
116
+ process.exit(1);
117
+ }
108
118
  }),
109
119
  useCodegen && codegen({ ...remixConfig, configFilePath: codegenConfigPath })
110
120
  ]);
111
121
  if (process.env.NODE_ENV !== "development") {
112
122
  console.timeEnd(LOG_WORKER_BUILT);
123
+ const bundleAnalysisPath = await buildBundleAnalysis(buildPath);
113
124
  const sizeMB = await fileSize(buildPathWorkerFile) / (1024 * 1024);
114
- if (await hasMetafile(buildPath)) {
115
- await writeBundleAnalysis(
116
- buildPath,
117
- root,
118
- buildPathWorkerFile,
119
- sizeMB,
120
- bundleStats,
121
- remixConfig
125
+ const formattedSize = colors.yellow(sizeMB.toFixed(2) + " MB");
126
+ outputInfo(
127
+ outputContent` ${colors.dim(
128
+ relativePath(root, buildPathWorkerFile)
129
+ )} ${bundleAnalysisPath ? outputToken.link(formattedSize, bundleAnalysisPath) : formattedSize}\n`
130
+ );
131
+ if (bundleStats && bundleAnalysisPath) {
132
+ outputInfo(
133
+ outputContent`${await getBundleAnalysisSummary(buildPathWorkerFile) || "\n"}\n │\n └─── ${outputToken.link(
134
+ "Complete analysis: " + bundleAnalysisPath,
135
+ bundleAnalysisPath
136
+ )}\n\n`
122
137
  );
123
- } else {
124
- await writeSimpleBuildStatus(
125
- root,
126
- buildPathWorkerFile,
127
- sizeMB,
128
- remixConfig
138
+ }
139
+ if (sizeMB >= WORKER_BUILD_SIZE_LIMIT) {
140
+ outputWarn(
141
+ `\u{1F6A8} Smaller worker bundles are faster to deploy and run.${remixConfig.serverMinify ? "" : "\n Minify your bundle by adding `serverMinify: true` to remix.config.js."}
142
+ Learn more about optimizing your worker bundle file: https://h2o.fyi/debugging/bundle-size
143
+ `
129
144
  );
130
145
  }
131
146
  }
@@ -145,9 +160,6 @@ This build is missing ${missingRoutes.length} route${missingRoutes.length > 1 ?
145
160
  if (process.env.NODE_ENV !== "development") {
146
161
  await cleanClientSourcemaps(buildPathClient);
147
162
  }
148
- if (!process.env.SHOPIFY_UNIT_TEST && !assetPath) {
149
- process.exit(0);
150
- }
151
163
  }
152
164
  async function cleanClientSourcemaps(buildPathClient) {
153
165
  const bundleFiles = await glob(joinPath(buildPathClient, "**/*.js"));
@@ -161,56 +173,6 @@ async function cleanClientSourcemaps(buildPathClient) {
161
173
  })
162
174
  );
163
175
  }
164
- async function writeBundleAnalysis(buildPath, root, buildPathWorkerFile, sizeMB, bundleStats, remixConfig) {
165
- const bundleAnalysisPath = await buildBundleAnalysis(buildPath);
166
- outputInfo(
167
- outputContent` ${colors.dim(
168
- relativePath(root, buildPathWorkerFile)
169
- )} ${outputToken.link(
170
- colors.yellow(sizeMB.toFixed(2) + " MB"),
171
- bundleAnalysisPath
172
- )}\n`
173
- );
174
- if (bundleStats && sizeMB < MAX_WORKER_BUNDLE_SIZE) {
175
- outputInfo(
176
- outputContent`${await getBundleAnalysisSummary(buildPathWorkerFile) || "\n"}\n │\n └─── ${outputToken.link(
177
- "Complete analysis: " + bundleAnalysisPath,
178
- bundleAnalysisPath
179
- )}\n\n`
180
- );
181
- }
182
- if (sizeMB >= MAX_WORKER_BUNDLE_SIZE) {
183
- throw new AbortError(
184
- "\u{1F6A8} Worker bundle exceeds 10 MB! Oxygen has a maximum worker bundle size of 10 MB.",
185
- outputContent`See the bundle analysis for a breakdown of what is contributing to the bundle size:\n${outputToken.link(
186
- bundleAnalysisPath,
187
- bundleAnalysisPath
188
- )}`
189
- );
190
- } else if (sizeMB >= 5) {
191
- outputWarn(
192
- `\u{1F6A8} Worker bundle exceeds 5 MB! This can delay your worker response.${remixConfig.serverMinify ? "" : " Minify your bundle by adding `serverMinify: true` to remix.config.js."}
193
- `
194
- );
195
- }
196
- }
197
- async function writeSimpleBuildStatus(root, buildPathWorkerFile, sizeMB, remixConfig) {
198
- outputInfo(
199
- outputContent` ${colors.dim(
200
- relativePath(root, buildPathWorkerFile)
201
- )} ${colors.yellow(sizeMB.toFixed(2) + " MB")}\n`
202
- );
203
- if (sizeMB >= MAX_WORKER_BUNDLE_SIZE) {
204
- throw new AbortError(
205
- "\u{1F6A8} Worker bundle exceeds 10 MB! Oxygen has a maximum worker bundle size of 10 MB."
206
- );
207
- } else if (sizeMB >= 5) {
208
- outputWarn(
209
- `\u{1F6A8} Worker bundle exceeds 5 MB! This can delay your worker response.${remixConfig.serverMinify ? "" : " Minify your bundle by adding `serverMinify: true` to remix.config.js."}
210
- `
211
- );
212
- }
213
- }
214
176
  async function copyPublicFiles(publicPath, buildPathClient) {
215
177
  if (!await fileExists(publicPath)) {
216
178
  return;
@@ -1,6 +1,7 @@
1
1
  import path from 'path';
2
2
  import Command from '@shopify/cli-kit/node/base-command';
3
3
  import { renderSuccess } from '@shopify/cli-kit/node/ui';
4
+ import colors from '@shopify/cli-kit/node/colors';
4
5
  import { Flags } from '@oclif/core';
5
6
  import { getProjectPaths, getRemixConfig } from '../../lib/remix-config.js';
6
7
  import { commonFlags, flagsToCamelObject } from '../../lib/flags.js';
@@ -24,8 +25,6 @@ class Codegen extends Command {
24
25
  default: false
25
26
  })
26
27
  };
27
- static aliases = ["codegen-unstable"];
28
- static deprecateAliases = true;
29
28
  async run() {
30
29
  const { flags } = await this.parse(Codegen);
31
30
  const directory = flags.path ? path.resolve(flags.path) : process.cwd();
@@ -53,7 +52,13 @@ async function runCodegen({
53
52
  if (!watch) {
54
53
  renderSuccess({
55
54
  headline: "Generated types for GraphQL:",
56
- body: generatedFiles.map((file) => `- ${file}`).join("\n")
55
+ body: {
56
+ list: {
57
+ items: Object.entries(generatedFiles).map(
58
+ ([key, value]) => key + "\n" + value.map((item) => colors.dim(`- ${item}`)).join("\n")
59
+ )
60
+ }
61
+ }
57
62
  });
58
63
  }
59
64
  }
@@ -1,8 +1,17 @@
1
1
  import { Flags } from '@oclif/core';
2
2
  import Command from '@shopify/cli-kit/node/base-command';
3
- import { outputWarn } from '@shopify/cli-kit/node/output';
4
- import { renderWarning } from '@shopify/cli-kit/node/ui';
5
- import { commonFlags } from '../../lib/flags.js';
3
+ import colors from '@shopify/cli-kit/node/colors';
4
+ import { outputWarn, outputInfo, outputContent } from '@shopify/cli-kit/node/output';
5
+ import { AbortError } from '@shopify/cli-kit/node/error';
6
+ import { writeFile } from '@shopify/cli-kit/node/fs';
7
+ import { ensureIsClean, getLatestGitCommit, GitDirectoryNotCleanError } from '@shopify/cli-kit/node/git';
8
+ import { resolvePath } from '@shopify/cli-kit/node/path';
9
+ import { renderFatalError, renderWarning, renderSelectPrompt, renderSuccess, renderTasks } from '@shopify/cli-kit/node/ui';
10
+ import { ciPlatform } from '@shopify/cli-kit/node/context/local';
11
+ import { parseToken, createDeploy } from '@shopify/oxygen-cli/deploy';
12
+ import { commonFlags, flagsToCamelObject } from '../../lib/flags.js';
13
+ import { getOxygenDeploymentData } from '../../lib/get-oxygen-deployment-data.js';
14
+ import { runBuild } from './build.js';
6
15
 
7
16
  const deploymentLogger = (message, level = "info") => {
8
17
  if (level === "error" || level === "warn") {
@@ -10,12 +19,17 @@ const deploymentLogger = (message, level = "info") => {
10
19
  }
11
20
  };
12
21
  class Deploy extends Command {
22
+ static description = "Builds and deploys a Hydrogen storefront to Oxygen.";
13
23
  static flags = {
14
24
  "env-branch": Flags.string({
15
- char: "e",
16
- description: "Environment branch (tag) for environment to deploy to",
25
+ description: "Environment branch (tag) for environment to deploy to.",
17
26
  required: false
18
27
  }),
28
+ preview: Flags.boolean({
29
+ description: "Deploys to the Preview environment. Overrides --env-branch and Git metadata.",
30
+ required: false,
31
+ default: false
32
+ }),
19
33
  force: Flags.boolean({
20
34
  char: "f",
21
35
  description: "Forces a deployment to proceed if there are uncommited changes in its Git repository.",
@@ -23,22 +37,21 @@ class Deploy extends Command {
23
37
  env: "SHOPIFY_HYDROGEN_FLAG_FORCE",
24
38
  required: false
25
39
  }),
26
- path: commonFlags.path,
27
- shop: commonFlags.shop,
28
- "public-deployment": Flags.boolean({
29
- env: "SHOPIFY_HYDROGEN_FLAG_PUBLIC_DEPLOYMENT",
30
- description: "Marks a preview deployment as publicly accessible.",
40
+ "auth-bypass-token": Flags.boolean({
41
+ description: "Generate an authentication bypass token, which can be used to perform end-to-end tests against the deployment.",
31
42
  required: false,
32
43
  default: false
33
44
  }),
45
+ path: commonFlags.path,
46
+ shop: commonFlags.shop,
34
47
  "no-json-output": Flags.boolean({
35
- description: "Prevents the command from creating a JSON file containing the deployment URL (in CI environments).",
48
+ description: "Prevents the command from creating a JSON file containing the deployment URL in CI environments.",
36
49
  required: false,
37
50
  default: false
38
51
  }),
39
52
  token: Flags.string({
40
53
  char: "t",
41
- description: "Oxygen deployment token",
54
+ description: "Oxygen deployment token. Defaults to the linked storefront's token if available.",
42
55
  env: "SHOPIFY_HYDROGEN_DEPLOYMENT_TOKEN",
43
56
  required: false
44
57
  }),
@@ -48,9 +61,9 @@ class Deploy extends Command {
48
61
  env: "SHOPIFY_HYDROGEN_FLAG_METADATA_DESCRIPTION"
49
62
  }),
50
63
  "metadata-url": Flags.string({
51
- description: "URL that links to the deployment. Will be saved and displayed in the Shopify admin",
52
64
  required: false,
53
- env: "SHOPIFY_HYDROGEN_FLAG_METADATA_URL"
65
+ env: "SHOPIFY_HYDROGEN_FLAG_METADATA_URL",
66
+ hidden: true
54
67
  }),
55
68
  "metadata-user": Flags.string({
56
69
  description: "User that initiated the deployment. Will be saved and displayed in the Shopify admin",
@@ -58,20 +71,297 @@ class Deploy extends Command {
58
71
  env: "SHOPIFY_HYDROGEN_FLAG_METADATA_USER"
59
72
  }),
60
73
  "metadata-version": Flags.string({
61
- description: "A version identifier for the deployment. Will be saved and displayed in the Shopify admin",
62
74
  required: false,
63
- env: "SHOPIFY_HYDROGEN_FLAG_METADATA_VERSION"
75
+ env: "SHOPIFY_HYDROGEN_FLAG_METADATA_VERSION",
76
+ hidden: true
64
77
  })
65
78
  };
66
- static hidden = true;
67
79
  async run() {
80
+ const { flags } = await this.parse(Deploy);
81
+ const deploymentOptions = this.flagsToOxygenDeploymentOptions(flags);
82
+ await oxygenDeploy(deploymentOptions).catch((error) => {
83
+ renderFatalError(error);
84
+ process.exit(1);
85
+ }).finally(() => {
86
+ process.exit(0);
87
+ });
88
+ }
89
+ flagsToOxygenDeploymentOptions(flags) {
90
+ const camelFlags = flagsToCamelObject(flags);
91
+ return {
92
+ ...camelFlags,
93
+ defaultEnvironment: flags.preview,
94
+ environmentTag: flags["env-branch"],
95
+ path: flags.path ? resolvePath(flags.path) : process.cwd()
96
+ };
97
+ }
98
+ }
99
+ function createUnexpectedAbortError(message) {
100
+ return new AbortError(
101
+ message || "The deployment failed due to an unexpected error.",
102
+ "Retrying the deployement may succeed.",
103
+ [
104
+ [
105
+ "If the issue persits, please check the",
106
+ {
107
+ link: {
108
+ label: "Shopify status page",
109
+ url: "https://status.shopify.com/"
110
+ }
111
+ },
112
+ "for any known issues."
113
+ ]
114
+ ]
115
+ );
116
+ }
117
+ async function oxygenDeploy(options) {
118
+ const {
119
+ authBypassToken: generateAuthBypassToken,
120
+ defaultEnvironment,
121
+ environmentTag,
122
+ force: forceOnUncommitedChanges,
123
+ noJsonOutput,
124
+ path,
125
+ shop,
126
+ metadataUrl,
127
+ metadataUser,
128
+ metadataVersion
129
+ } = options;
130
+ let { metadataDescription } = options;
131
+ let isCleanGit = true;
132
+ try {
133
+ await ensureIsClean(path);
134
+ } catch (error) {
135
+ if (error instanceof GitDirectoryNotCleanError) {
136
+ isCleanGit = false;
137
+ }
138
+ if (!forceOnUncommitedChanges && !isCleanGit) {
139
+ throw new AbortError("Uncommitted changes detected.", null, [
140
+ [
141
+ "Commit your changes before deploying or use the ",
142
+ { command: "--force" },
143
+ " flag to deploy with uncommitted changes."
144
+ ]
145
+ ]);
146
+ }
147
+ }
148
+ const isCI = ciPlatform().isCI;
149
+ let token = options.token;
150
+ let branch;
151
+ let commitHash;
152
+ let deploymentData;
153
+ let deploymentEnvironmentTag = void 0;
154
+ let gitCommit;
155
+ try {
156
+ gitCommit = await getLatestGitCommit(path);
157
+ branch = (/HEAD -> ([^,]*)/.exec(gitCommit.refs) || [])[1];
158
+ commitHash = gitCommit.hash;
159
+ } catch (error) {
160
+ outputWarn("Could not retrieve Git history.");
161
+ branch = void 0;
162
+ }
163
+ if (!metadataDescription && !isCleanGit) {
68
164
  renderWarning({
69
- body: "Deploy command unavailable.",
70
- nextSteps: [
71
- "To use the deploy command, upgrade cli-hydrogen to v7.0.0+."
165
+ headline: "No deployment description provided",
166
+ body: [
167
+ "Deploying uncommited changes, but no description has been provided. Use the ",
168
+ { command: "--metadata-description" },
169
+ "flag to provide a description. If no description is provided, the description defaults to ",
170
+ { userInput: "<sha> with additional changes" },
171
+ " using the SHA of the last commit."
72
172
  ]
73
173
  });
174
+ metadataDescription = `${commitHash} with additional changes`;
175
+ }
176
+ if (!isCI) {
177
+ deploymentData = await getOxygenDeploymentData({
178
+ root: path,
179
+ flagShop: shop
180
+ });
181
+ if (!deploymentData) {
182
+ return;
183
+ }
184
+ token = token || deploymentData.oxygenDeploymentToken;
185
+ }
186
+ if (!token) {
187
+ const errMessage = isCI ? [
188
+ "No deployment token provided. Use the ",
189
+ { command: "--token" },
190
+ " flag to provide a token."
191
+ ] : `Could not obtain an Oxygen deployment token, please try again or contact Shopify support.`;
192
+ throw new AbortError(errMessage);
193
+ }
194
+ if (!isCI && !defaultEnvironment && !environmentTag && deploymentData?.environments) {
195
+ if (deploymentData.environments.length > 1) {
196
+ const choices = [
197
+ ...deploymentData.environments.map(({ name, branch: branch2, type }) => ({
198
+ label: name,
199
+ // The preview environment will never have an associated branch so
200
+ // we're using a custom string here to identify it later in our code.
201
+ // Using a period at the end of the value is an invalid branch name
202
+ // in Git so we can be sure that this won't conflict with a merchant's
203
+ // repository.
204
+ value: type === "PREVIEW" ? "shopify-preview-environment." : branch2
205
+ }))
206
+ ];
207
+ deploymentEnvironmentTag = await renderSelectPrompt({
208
+ message: "Select an environment to deploy to",
209
+ choices,
210
+ defaultValue: branch
211
+ });
212
+ } else {
213
+ outputInfo(
214
+ `Using current checked out branch ${branch} as environment tag`
215
+ );
216
+ }
217
+ }
218
+ let deploymentUrl = "https://oxygen.shopifyapps.com";
219
+ if (process.env.UNSAFE_SHOPIFY_HYDROGEN_DEPLOYMENT_URL) {
220
+ deploymentUrl = process.env.UNSAFE_SHOPIFY_HYDROGEN_DEPLOYMENT_URL;
221
+ outputWarn(
222
+ "Using a custom deployment service. Don't do this in production!"
223
+ );
74
224
  }
225
+ let fallbackEnvironmentTag = branch;
226
+ let isPreview = false;
227
+ if (deploymentEnvironmentTag === "shopify-preview-environment.") {
228
+ fallbackEnvironmentTag = void 0;
229
+ deploymentEnvironmentTag = void 0;
230
+ isPreview = true;
231
+ }
232
+ const config = {
233
+ assetsDir: "dist/client",
234
+ bugsnag: true,
235
+ deploymentUrl,
236
+ defaultEnvironment: defaultEnvironment || isPreview,
237
+ deploymentToken: parseToken(token),
238
+ environmentTag: environmentTag || deploymentEnvironmentTag || fallbackEnvironmentTag,
239
+ generateAuthBypassToken,
240
+ verificationMaxDuration: 180,
241
+ metadata: {
242
+ ...metadataDescription ? { description: metadataDescription } : {},
243
+ ...metadataUrl ? { url: metadataUrl } : {},
244
+ ...metadataUser ? { user: metadataUser } : {},
245
+ ...metadataVersion ? { version: metadataVersion } : {}
246
+ },
247
+ skipVerification: false,
248
+ rootPath: path,
249
+ skipBuild: false,
250
+ workerOnly: false,
251
+ workerDir: "dist/worker"
252
+ };
253
+ let resolveUpload;
254
+ const uploadPromise = new Promise((resolve) => {
255
+ resolveUpload = resolve;
256
+ });
257
+ let resolveRoutableCheck;
258
+ const routableCheckPromise = new Promise((resolve) => {
259
+ resolveRoutableCheck = resolve;
260
+ });
261
+ let resolveDeploymentCompletedVerification;
262
+ const deploymentCompletedVerificationPromise = new Promise(
263
+ (resolve) => {
264
+ resolveDeploymentCompletedVerification = resolve;
265
+ }
266
+ );
267
+ let deployError = null;
268
+ let resolveDeploy;
269
+ let rejectDeploy;
270
+ const deployPromise = new Promise((resolve, reject) => {
271
+ resolveDeploy = resolve;
272
+ rejectDeploy = reject;
273
+ });
274
+ const hooks = {
275
+ buildFunction: async (assetPath) => {
276
+ outputInfo(
277
+ outputContent`${colors.whiteBright("Building project...")}`.value
278
+ );
279
+ await runBuild({
280
+ directory: path,
281
+ assetPath,
282
+ sourcemap: true,
283
+ useCodegen: false
284
+ });
285
+ },
286
+ onDeploymentCompleted: () => resolveDeploymentCompletedVerification(),
287
+ onVerificationComplete: () => resolveRoutableCheck(),
288
+ onDeploymentCompletedVerificationError() {
289
+ deployError = new AbortError(
290
+ "Unable to verify the deployment was completed successfully",
291
+ "Please verify the deployment status in the Shopify Admin and retry deploying if necessary."
292
+ );
293
+ },
294
+ onDeploymentFailed: (details) => {
295
+ deployError = createUnexpectedAbortError(details.error || details.status);
296
+ },
297
+ onUploadFilesStart: () => uploadStart(),
298
+ onUploadFilesComplete: () => resolveUpload(),
299
+ onVerificationError: (error) => {
300
+ deployError = new AbortError(
301
+ error.message,
302
+ "Please verify the deployment status in the Shopify Admin and retry deploying if necessary."
303
+ );
304
+ },
305
+ onUploadFilesError: (error) => {
306
+ deployError = new AbortError(
307
+ error.message,
308
+ "Check your connection and try again. If the problem persists, try again later or contact support."
309
+ );
310
+ }
311
+ };
312
+ const uploadStart = async () => {
313
+ outputInfo(
314
+ outputContent`${colors.whiteBright("Deploying to Oxygen..\n")}`.value
315
+ );
316
+ await renderTasks([
317
+ {
318
+ title: "Uploading files",
319
+ task: async () => await uploadPromise
320
+ },
321
+ {
322
+ title: "Verifying deployment has been completed",
323
+ task: async () => await deploymentCompletedVerificationPromise
324
+ },
325
+ {
326
+ title: "Verifying deployment is routable",
327
+ task: async () => await routableCheckPromise
328
+ }
329
+ ]);
330
+ };
331
+ await createDeploy({ config, hooks, logger: deploymentLogger }).then(async (completedDeployment) => {
332
+ if (!completedDeployment) {
333
+ rejectDeploy(createUnexpectedAbortError());
334
+ return;
335
+ }
336
+ const nextSteps = [
337
+ [
338
+ "Open",
339
+ { link: { url: completedDeployment.url } },
340
+ `in your browser to view your deployment.`
341
+ ]
342
+ ];
343
+ if (completedDeployment?.authBypassToken) {
344
+ nextSteps.push([
345
+ "Use the",
346
+ { subdued: completedDeployment.authBypassToken },
347
+ "token to perform end-to-end tests against the deployment."
348
+ ]);
349
+ }
350
+ renderSuccess({
351
+ body: ["Successfully deployed to Oxygen"],
352
+ nextSteps
353
+ });
354
+ if (isCI && !noJsonOutput) {
355
+ await writeFile(
356
+ "h2_deploy_log.json",
357
+ JSON.stringify(completedDeployment)
358
+ );
359
+ }
360
+ resolveDeploy();
361
+ }).catch((error) => {
362
+ rejectDeploy(deployError || error);
363
+ });
364
+ return deployPromise;
75
365
  }
76
366
 
77
- export { Deploy as default, deploymentLogger };
367
+ export { Deploy as default, deploymentLogger, oxygenDeploy };