@shopify/cli-hydrogen 6.0.2 → 7.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 (108) 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 +173 -37
  4. package/dist/commands/hydrogen/deploy.test.js +192 -20
  5. package/dist/commands/hydrogen/dev.js +56 -31
  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 +22 -11
  11. package/dist/commands/hydrogen/setup.js +0 -4
  12. package/dist/commands/hydrogen/setup.test.js +0 -1
  13. package/dist/commands/hydrogen/shortcut.js +1 -0
  14. package/dist/commands/hydrogen/upgrade.js +720 -0
  15. package/dist/commands/hydrogen/upgrade.test.js +786 -0
  16. package/dist/generator-templates/starter/.graphqlrc.yml +12 -1
  17. package/dist/generator-templates/starter/CHANGELOG.md +126 -0
  18. package/dist/generator-templates/starter/README.md +23 -0
  19. package/dist/generator-templates/starter/app/components/Cart.tsx +1 -1
  20. package/dist/generator-templates/starter/app/components/Footer.tsx +3 -1
  21. package/dist/generator-templates/starter/app/components/Header.tsx +5 -1
  22. package/dist/generator-templates/starter/app/components/Layout.tsx +14 -11
  23. package/dist/generator-templates/starter/app/components/Search.tsx +1 -1
  24. package/dist/generator-templates/starter/app/graphql/customer-account/CustomerAddressMutations.ts +61 -0
  25. package/dist/generator-templates/starter/app/graphql/customer-account/CustomerDetailsQuery.ts +39 -0
  26. package/dist/generator-templates/starter/app/graphql/customer-account/CustomerOrderQuery.ts +87 -0
  27. package/dist/generator-templates/starter/app/graphql/customer-account/CustomerOrdersQuery.ts +58 -0
  28. package/dist/generator-templates/starter/app/graphql/customer-account/CustomerUpdateMutation.ts +24 -0
  29. package/dist/generator-templates/starter/app/lib/fragments.ts +102 -0
  30. package/dist/generator-templates/starter/app/lib/session.ts +67 -0
  31. package/dist/generator-templates/starter/app/root.tsx +11 -45
  32. package/dist/generator-templates/starter/app/routes/[robots.txt].tsx +0 -27
  33. package/dist/generator-templates/starter/app/routes/account.$.tsx +8 -4
  34. package/dist/generator-templates/starter/app/routes/account._index.tsx +5 -0
  35. package/dist/generator-templates/starter/app/routes/account.addresses.tsx +215 -206
  36. package/dist/generator-templates/starter/app/routes/account.orders.$id.tsx +56 -163
  37. package/dist/generator-templates/starter/app/routes/account.orders._index.tsx +32 -109
  38. package/dist/generator-templates/starter/app/routes/account.profile.tsx +40 -180
  39. package/dist/generator-templates/starter/app/routes/account.tsx +20 -135
  40. package/dist/generator-templates/starter/app/routes/account_.authorize.tsx +5 -0
  41. package/dist/generator-templates/starter/app/routes/account_.login.tsx +3 -140
  42. package/dist/generator-templates/starter/app/routes/account_.logout.tsx +5 -24
  43. package/dist/generator-templates/starter/app/routes/cart.tsx +7 -5
  44. package/dist/generator-templates/starter/app/routes/collections.$handle.tsx +1 -1
  45. package/dist/generator-templates/starter/app/routes/products.$handle.tsx +2 -2
  46. package/dist/generator-templates/starter/app/routes/search.tsx +1 -1
  47. package/dist/generator-templates/starter/customer-accountapi.generated.d.ts +506 -0
  48. package/dist/generator-templates/starter/package.json +11 -10
  49. package/dist/generator-templates/starter/remix.config.js +4 -0
  50. package/dist/generator-templates/starter/remix.env.d.ts +6 -11
  51. package/dist/generator-templates/starter/server.ts +24 -167
  52. package/dist/generator-templates/starter/storefrontapi.generated.d.ts +104 -881
  53. package/dist/hooks/init.js +4 -4
  54. package/dist/lib/auth.js +5 -10
  55. package/dist/lib/build.js +6 -1
  56. package/dist/lib/bundle/analyzer.js +36 -26
  57. package/dist/lib/check-lockfile.js +1 -0
  58. package/dist/lib/codegen.js +59 -18
  59. package/dist/lib/defer.js +12 -0
  60. package/dist/lib/file.js +52 -3
  61. package/dist/lib/flags.js +27 -9
  62. package/dist/lib/get-oxygen-deployment-data.test.js +4 -2
  63. package/dist/lib/graphql/admin/client.test.js +2 -2
  64. package/dist/lib/graphql/admin/get-oxygen-data.js +1 -0
  65. package/dist/lib/log.js +32 -14
  66. package/dist/lib/mini-oxygen/assets.js +118 -0
  67. package/dist/lib/mini-oxygen/common.js +2 -1
  68. package/dist/lib/mini-oxygen/index.js +7 -5
  69. package/dist/lib/mini-oxygen/mini-oxygen.test.js +214 -0
  70. package/dist/lib/mini-oxygen/node.js +19 -5
  71. package/dist/lib/mini-oxygen/workerd-inspector-logs.js +227 -0
  72. package/dist/lib/mini-oxygen/workerd-inspector-proxy.js +200 -0
  73. package/dist/lib/mini-oxygen/workerd-inspector.js +62 -235
  74. package/dist/lib/mini-oxygen/workerd.js +74 -50
  75. package/dist/lib/missing-routes.js +6 -3
  76. package/dist/lib/onboarding/common.js +40 -9
  77. package/dist/lib/onboarding/local.js +19 -11
  78. package/dist/lib/onboarding/remote.js +48 -28
  79. package/dist/lib/render-errors.js +2 -0
  80. package/dist/lib/request-events.js +65 -31
  81. package/dist/lib/setups/css/assets.js +1 -46
  82. package/dist/lib/setups/css/css-modules.js +3 -2
  83. package/dist/lib/setups/css/postcss.js +4 -2
  84. package/dist/lib/setups/css/tailwind.js +4 -2
  85. package/dist/lib/setups/css/vanilla-extract.js +3 -2
  86. package/dist/lib/setups/i18n/replacers.test.js +56 -38
  87. package/dist/lib/shell.js +1 -1
  88. package/dist/lib/template-diff.js +89 -0
  89. package/dist/lib/template-downloader.js +3 -2
  90. package/dist/lib/transpile/project.js +1 -1
  91. package/dist/virtual-routes/assets/debug-network.css +592 -0
  92. package/dist/virtual-routes/assets/favicon-dark.svg +20 -0
  93. package/dist/virtual-routes/components/FlameChartWrapper.jsx +8 -10
  94. package/dist/virtual-routes/components/IconClose.jsx +38 -0
  95. package/dist/virtual-routes/components/IconDiscard.jsx +44 -0
  96. package/dist/virtual-routes/components/RequestDetails.jsx +179 -0
  97. package/dist/virtual-routes/components/RequestTable.jsx +92 -0
  98. package/dist/virtual-routes/components/RequestWaterfall.jsx +151 -0
  99. package/dist/virtual-routes/lib/useDebugNetworkServer.jsx +176 -0
  100. package/dist/virtual-routes/routes/subrequest-profiler.jsx +243 -0
  101. package/oclif.manifest.json +134 -59
  102. package/package.json +18 -26
  103. package/dist/generator-templates/starter/app/routes/account_.activate.$id.$activationToken.tsx +0 -161
  104. package/dist/generator-templates/starter/app/routes/account_.recover.tsx +0 -129
  105. package/dist/generator-templates/starter/app/routes/account_.register.tsx +0 -207
  106. package/dist/generator-templates/starter/app/routes/account_.reset.$id.$resetToken.tsx +0 -136
  107. package/dist/virtual-routes/routes/debug-network.jsx +0 -289
  108. /package/dist/generator-templates/starter/app/{utils.ts → lib/variants.ts} +0 -0
@@ -2,14 +2,18 @@ import { vi, describe, expect, beforeEach, afterEach, it } from 'vitest';
2
2
  import { login } from '../../lib/auth.js';
3
3
  import { getStorefronts } from '../../lib/graphql/admin/link-storefront.js';
4
4
  import { AbortError } from '@shopify/cli-kit/node/error';
5
- import { renderSelectPrompt, renderSuccess, renderFatalError } from '@shopify/cli-kit/node/ui';
6
- import { getLatestGitCommit } from '@shopify/cli-kit/node/git';
5
+ import { writeFile } from '@shopify/cli-kit/node/fs';
6
+ import { renderSelectPrompt, renderSuccess, renderWarning, renderFatalError } from '@shopify/cli-kit/node/ui';
7
+ import { ensureIsClean, GitDirectoryNotCleanError, getLatestGitCommit } from '@shopify/cli-kit/node/git';
7
8
  import { oxygenDeploy, deploymentLogger } from './deploy.js';
8
9
  import { getOxygenDeploymentData } from '../../lib/get-oxygen-deployment-data.js';
9
10
  import { createDeploy, parseToken } from '@shopify/oxygen-cli/deploy';
11
+ import { ciPlatform } from '@shopify/cli-kit/node/context/local';
10
12
 
11
13
  vi.mock("../../lib/get-oxygen-deployment-data.js");
12
14
  vi.mock("@shopify/oxygen-cli/deploy");
15
+ vi.mock("@shopify/cli-kit/node/fs");
16
+ vi.mock("@shopify/cli-kit/node/context/local");
13
17
  vi.mock("../../lib/auth.js");
14
18
  vi.mock("../../lib/shopify-config.js");
15
19
  vi.mock("../../lib/graphql/admin/link-storefront.js");
@@ -30,17 +34,16 @@ vi.mock("@shopify/cli-kit/node/ui", async () => {
30
34
  renderFatalError: vi.fn(),
31
35
  renderSelectPrompt: vi.fn(),
32
36
  renderSuccess: vi.fn(),
33
- renderTasks: vi.fn()
37
+ renderTasks: vi.fn(),
38
+ renderWarning: vi.fn()
34
39
  };
35
40
  });
36
41
  vi.mock("@shopify/cli-kit/node/git", async () => {
42
+ const actual = await vi.importActual("@shopify/cli-kit/node/git");
37
43
  return {
38
- getLatestGitCommit: vi.fn()
39
- };
40
- });
41
- vi.mock("@shopify/cli-kit/node/context/local", async () => {
42
- return {
43
- ciPlatform: () => ({ isCI: false })
44
+ ...actual,
45
+ getLatestGitCommit: vi.fn(),
46
+ ensureIsClean: vi.fn()
44
47
  };
45
48
  });
46
49
  describe("deploy", () => {
@@ -63,9 +66,12 @@ describe("deploy", () => {
63
66
  };
64
67
  const originalExit = process.exit;
65
68
  const deployParams = {
69
+ authBypassToken: true,
70
+ defaultEnvironment: false,
71
+ force: false,
72
+ noJsonOutput: false,
66
73
  path: "./",
67
74
  shop: "snowdevil.myshopify.com",
68
- publicDeployment: false,
69
75
  metadataUrl: "https://example.com",
70
76
  metadataUser: "user",
71
77
  metadataVersion: "1.0.0"
@@ -82,15 +88,16 @@ describe("deploy", () => {
82
88
  const expectedConfig = {
83
89
  assetsDir: "dist/client",
84
90
  bugsnag: true,
91
+ defaultEnvironment: false,
85
92
  deploymentUrl: "https://oxygen.shopifyapps.com",
86
93
  deploymentToken: mockToken,
94
+ generateAuthBypassToken: true,
87
95
  verificationMaxDuration: 180,
88
96
  metadata: {
89
97
  url: deployParams.metadataUrl,
90
98
  user: deployParams.metadataUser,
91
99
  version: deployParams.metadataVersion
92
100
  },
93
- publicDeployment: deployParams.publicDeployment,
94
101
  skipVerification: false,
95
102
  rootPath: deployParams.path,
96
103
  skipBuild: false,
@@ -99,10 +106,13 @@ describe("deploy", () => {
99
106
  };
100
107
  const expectedHooks = {
101
108
  buildFunction: expect.any(Function),
109
+ onDeploymentCompleted: expect.any(Function),
110
+ onDeploymentFailed: expect.any(Function),
111
+ onDeploymentCompletedVerificationError: expect.any(Function),
102
112
  onVerificationComplete: expect.any(Function),
113
+ onVerificationError: expect.any(Function),
103
114
  onUploadFilesStart: expect.any(Function),
104
115
  onUploadFilesComplete: expect.any(Function),
105
- onVerificationError: expect.any(Function),
106
116
  onUploadFilesError: expect.any(Function)
107
117
  };
108
118
  beforeEach(async () => {
@@ -111,6 +121,7 @@ describe("deploy", () => {
111
121
  session: ADMIN_SESSION,
112
122
  config: UNLINKED_SHOPIFY_CONFIG
113
123
  });
124
+ vi.mocked(ciPlatform).mockReturnValue({ isCI: false });
114
125
  vi.mocked(getStorefronts).mockResolvedValue([
115
126
  {
116
127
  ...FULL_SHOPIFY_CONFIG.storefront,
@@ -119,9 +130,10 @@ describe("deploy", () => {
119
130
  }
120
131
  ]);
121
132
  vi.mocked(renderSelectPrompt).mockResolvedValue(FULL_SHOPIFY_CONFIG.shop);
122
- vi.mocked(createDeploy).mockResolvedValue(
123
- "https://a-lovely-deployment.com"
124
- );
133
+ vi.mocked(createDeploy).mockResolvedValue({
134
+ authBypassToken: "some-token",
135
+ url: "https://a-lovely-deployment.com"
136
+ });
125
137
  vi.mocked(getOxygenDeploymentData).mockResolvedValue({
126
138
  oxygenDeploymentToken: "some-encoded-token",
127
139
  environments: []
@@ -149,6 +161,81 @@ describe("deploy", () => {
149
161
  });
150
162
  expect(vi.mocked(renderSuccess)).toHaveBeenCalled;
151
163
  });
164
+ it("errors when there are uncommited changes", async () => {
165
+ vi.mocked(ensureIsClean).mockRejectedValue(
166
+ new GitDirectoryNotCleanError("Uncommitted changes")
167
+ );
168
+ await expect(oxygenDeploy(deployParams)).rejects.toThrowError(
169
+ "Uncommitted changes detected"
170
+ );
171
+ expect(vi.mocked(createDeploy)).not.toHaveBeenCalled;
172
+ });
173
+ it("proceeds with warning and modified description when there are uncommited changes and the force flag is used", async () => {
174
+ vi.mocked(ensureIsClean).mockRejectedValue(
175
+ new GitDirectoryNotCleanError("Uncommitted changes")
176
+ );
177
+ vi.mocked(getLatestGitCommit).mockResolvedValue({
178
+ hash: "123",
179
+ message: "test commit",
180
+ date: "2021-01-01",
181
+ author_name: "test author",
182
+ author_email: "test@author.com",
183
+ body: "test body",
184
+ refs: "HEAD -> main"
185
+ });
186
+ await oxygenDeploy({
187
+ ...deployParams,
188
+ force: true
189
+ });
190
+ expect(vi.mocked(renderWarning)).toHaveBeenCalledWith({
191
+ headline: "No deployment description provided",
192
+ body: expect.anything()
193
+ });
194
+ expect(vi.mocked(createDeploy)).toHaveBeenCalledWith({
195
+ config: {
196
+ ...expectedConfig,
197
+ environmentTag: "main",
198
+ metadata: {
199
+ ...expectedConfig.metadata,
200
+ description: "123 with additional changes"
201
+ }
202
+ },
203
+ hooks: expectedHooks,
204
+ logger: deploymentLogger
205
+ });
206
+ });
207
+ it("proceeds with provided description without warning when there are uncommited changes and the force flag is used", async () => {
208
+ vi.mocked(ensureIsClean).mockRejectedValue(
209
+ new GitDirectoryNotCleanError("Uncommitted changes")
210
+ );
211
+ vi.mocked(getLatestGitCommit).mockResolvedValue({
212
+ hash: "123",
213
+ message: "test commit",
214
+ date: "2021-01-01",
215
+ author_name: "test author",
216
+ author_email: "test@author.com",
217
+ body: "test body",
218
+ refs: "HEAD -> main"
219
+ });
220
+ await oxygenDeploy({
221
+ ...deployParams,
222
+ force: true,
223
+ metadataDescription: "cool new stuff"
224
+ });
225
+ expect(vi.mocked(renderWarning)).not.toHaveBeenCalled;
226
+ expect(vi.mocked(createDeploy)).toHaveBeenCalledWith({
227
+ config: {
228
+ ...expectedConfig,
229
+ environmentTag: "main",
230
+ metadata: {
231
+ ...expectedConfig.metadata,
232
+ description: "cool new stuff"
233
+ }
234
+ },
235
+ hooks: expectedHooks,
236
+ logger: deploymentLogger
237
+ });
238
+ });
152
239
  it("calls createDeploy with the checked out branch name", async () => {
153
240
  vi.mocked(getLatestGitCommit).mockResolvedValue({
154
241
  hash: "123",
@@ -171,19 +258,77 @@ describe("deploy", () => {
171
258
  vi.mocked(getOxygenDeploymentData).mockResolvedValue({
172
259
  oxygenDeploymentToken: "some-encoded-token",
173
260
  environments: [
174
- { name: "production", branch: "main" },
175
- { name: "preview", branch: "staging" }
261
+ { name: "Production", branch: "main", type: "PRODUCTION" },
262
+ { name: "Preview", branch: null, type: "PREVIEW" }
176
263
  ]
177
264
  });
178
265
  await oxygenDeploy(deployParams);
179
266
  expect(vi.mocked(renderSelectPrompt)).toHaveBeenCalledWith({
180
267
  message: "Select an environment to deploy to",
181
268
  choices: [
182
- { label: "production", value: "main" },
183
- { label: "preview", value: "staging" }
269
+ { label: "Production", value: "main" },
270
+ { label: "Preview", value: "shopify-preview-environment." }
184
271
  ]
185
272
  });
186
273
  });
274
+ describe("when Preview is selected", () => {
275
+ it("calls createDeploy with defaultEnvironment and an undefined environmentTag", async () => {
276
+ vi.mocked(getLatestGitCommit).mockResolvedValue({
277
+ hash: "123",
278
+ message: "test commit",
279
+ date: "2021-01-01",
280
+ author_name: "test author",
281
+ author_email: "test@author.com",
282
+ body: "test body",
283
+ refs: "HEAD -> main"
284
+ });
285
+ vi.mocked(getOxygenDeploymentData).mockResolvedValue({
286
+ oxygenDeploymentToken: "some-encoded-token",
287
+ environments: [
288
+ { name: "Production", branch: "main", type: "PRODUCTION" },
289
+ { name: "Preview", branch: null, type: "PREVIEW" }
290
+ ]
291
+ });
292
+ vi.mocked(renderSelectPrompt).mockResolvedValue(
293
+ "shopify-preview-environment."
294
+ );
295
+ await oxygenDeploy(deployParams);
296
+ expect(vi.mocked(createDeploy)).toHaveBeenCalledWith({
297
+ config: {
298
+ ...expectedConfig,
299
+ defaultEnvironment: true,
300
+ environmentTag: void 0
301
+ },
302
+ hooks: expectedHooks,
303
+ logger: deploymentLogger
304
+ });
305
+ });
306
+ });
307
+ it("writes a file with JSON content in CI environments", async () => {
308
+ vi.mocked(ciPlatform).mockReturnValue({
309
+ isCI: true,
310
+ name: "github",
311
+ metadata: {}
312
+ });
313
+ const ciDeployParams = {
314
+ ...deployParams,
315
+ token: "some-token",
316
+ metadataDescription: "cool new stuff",
317
+ generateAuthBypassToken: true
318
+ };
319
+ await oxygenDeploy(ciDeployParams);
320
+ expect(vi.mocked(writeFile)).toHaveBeenCalledWith(
321
+ "h2_deploy_log.json",
322
+ JSON.stringify({
323
+ authBypassToken: "some-token",
324
+ url: "https://a-lovely-deployment.com"
325
+ })
326
+ );
327
+ vi.mocked(writeFile).mockClear();
328
+ ciDeployParams.noJsonOutput = true;
329
+ await oxygenDeploy(ciDeployParams);
330
+ expect(vi.mocked(writeFile)).not.toHaveBeenCalled();
331
+ });
187
332
  it("handles error during uploadFiles", async () => {
188
333
  const mockRenderFatalError = vi.fn();
189
334
  vi.mocked(renderFatalError).mockImplementation(mockRenderFatalError);
@@ -209,7 +354,7 @@ describe("deploy", () => {
209
354
  }
210
355
  }
211
356
  });
212
- it("handles error during deployment verification", async () => {
357
+ it("handles error during deployment routability verification", async () => {
213
358
  const mockRenderFatalError = vi.fn();
214
359
  vi.mocked(renderFatalError).mockImplementation(mockRenderFatalError);
215
360
  const error = new Error("Cloudflare is down!");
@@ -235,4 +380,31 @@ describe("deploy", () => {
235
380
  }
236
381
  }
237
382
  });
383
+ it("handles error during deployment completion verification", async () => {
384
+ const mockRenderFatalError = vi.fn();
385
+ vi.mocked(renderFatalError).mockImplementation(mockRenderFatalError);
386
+ vi.mocked(createDeploy).mockImplementation((options) => {
387
+ options.hooks?.onUploadFilesStart?.();
388
+ options.hooks?.onUploadFilesComplete?.();
389
+ options.hooks?.onDeploymentCompletedVerificationStart?.();
390
+ options.hooks?.onDeploymentFailed?.({
391
+ status: "oh shit",
392
+ url: "https://a-lovely-deployment.com"
393
+ });
394
+ return new Promise((_resolve, reject) => {
395
+ reject();
396
+ });
397
+ });
398
+ try {
399
+ await oxygenDeploy(deployParams);
400
+ expect(true).toBe(false);
401
+ } catch (err) {
402
+ if (err instanceof AbortError) {
403
+ expect(err.message).toBe("oh shit");
404
+ expect(err.tryMessage).toBe("Retrying the deployement may succeed.");
405
+ } else {
406
+ expect(true).toBe(false);
407
+ }
408
+ }
409
+ });
238
410
  });
@@ -1,5 +1,5 @@
1
- import path from 'path';
2
- import fs from 'fs/promises';
1
+ import path from 'node:path';
2
+ import fs from 'node:fs/promises';
3
3
  import { outputInfo, outputDebug } from '@shopify/cli-kit/node/output';
4
4
  import { fileExists } from '@shopify/cli-kit/node/fs';
5
5
  import { renderFatalError } from '@shopify/cli-kit/node/ui';
@@ -7,11 +7,10 @@ import colors from '@shopify/cli-kit/node/colors';
7
7
  import { copyPublicFiles } from './build.js';
8
8
  import { getProjectPaths, assertOxygenChecks, handleRemixImportFail, getRemixConfig } from '../../lib/remix-config.js';
9
9
  import { muteDevLogs, createRemixLogger, enhanceH2Logs } from '../../lib/log.js';
10
- import { commonFlags, overrideFlag, deprecated, flagsToCamelObject, DEFAULT_PORT } from '../../lib/flags.js';
10
+ import { commonFlags, deprecated, overrideFlag, flagsToCamelObject } from '../../lib/flags.js';
11
11
  import Command from '@shopify/cli-kit/node/base-command';
12
12
  import { Flags } from '@oclif/core';
13
- import { startMiniOxygen } from '../../lib/mini-oxygen/index.js';
14
- import { checkHydrogenVersion } from '../../lib/check-version.js';
13
+ import { buildAssetsUrl, startMiniOxygen } from '../../lib/mini-oxygen/index.js';
15
14
  import { addVirtualRoutes } from '../../lib/virtual-routes.js';
16
15
  import { spawnCodegenProcess } from '../../lib/codegen.js';
17
16
  import { getAllEnvironmentVariables } from '../../lib/environment-variables.js';
@@ -19,6 +18,9 @@ import { getConfig } from '../../lib/shopify-config.js';
19
18
  import { setupLiveReload } from '../../lib/live-reload.js';
20
19
  import { checkRemixVersions } from '../../lib/remix-version-check.js';
21
20
  import { getGraphiQLUrl } from '../../lib/graphiql-url.js';
21
+ import { displayDevUpgradeNotice } from './upgrade.js';
22
+ import { findPort } from '../../lib/find-port.js';
23
+ import { prepareDiffDirectory } from '../../lib/template-diff.js';
22
24
 
23
25
  const LOG_REBUILDING = "\u{1F9F1} Rebuilding...";
24
26
  const LOG_REBUILT = "\u{1F680} Rebuilt";
@@ -27,7 +29,8 @@ class Dev extends Command {
27
29
  static flags = {
28
30
  path: commonFlags.path,
29
31
  port: commonFlags.port,
30
- ["worker-unstable"]: commonFlags.workerRuntime,
32
+ worker: deprecated("--worker", { isBoolean: true }),
33
+ "legacy-runtime": commonFlags.legacyRuntime,
31
34
  codegen: overrideFlag(commonFlags.codegen, {
32
35
  description: commonFlags.codegen.description + " It updates the types on file save."
33
36
  }),
@@ -38,43 +41,45 @@ class Dev extends Command {
38
41
  env: "SHOPIFY_HYDROGEN_FLAG_DISABLE_VIRTUAL_ROUTES",
39
42
  default: false
40
43
  }),
41
- debug: Flags.boolean({
42
- description: "Attaches a Node inspector",
43
- env: "SHOPIFY_HYDROGEN_FLAG_DEBUG",
44
- default: false
44
+ debug: commonFlags.debug,
45
+ "inspector-port": commonFlags.inspectorPort,
46
+ ["env-branch"]: commonFlags.envBranch,
47
+ ["disable-version-check"]: Flags.boolean({
48
+ description: "Skip the version check when running `hydrogen dev`",
49
+ default: false,
50
+ required: false
45
51
  }),
46
- host: deprecated("--host")(),
47
- ["env-branch"]: commonFlags.envBranch
52
+ diff: commonFlags.diff
48
53
  };
49
54
  async run() {
50
55
  const { flags } = await this.parse(Dev);
51
- const directory = flags.path ? path.resolve(flags.path) : process.cwd();
56
+ let directory = flags.path ? path.resolve(flags.path) : process.cwd();
57
+ if (flags.diff) {
58
+ directory = await prepareDiffDirectory(directory, true);
59
+ }
52
60
  await runDev({
53
61
  ...flagsToCamelObject(flags),
54
- useCodegen: flags.codegen,
55
- workerRuntime: flags["worker-unstable"],
56
62
  path: directory
57
63
  });
58
64
  }
59
65
  }
60
66
  async function runDev({
61
- port: portFlag = DEFAULT_PORT,
67
+ port: appPort,
62
68
  path: appPath,
63
- useCodegen = false,
64
- workerRuntime = false,
69
+ codegen: useCodegen = false,
70
+ legacyRuntime = false,
65
71
  codegenConfigPath,
66
72
  disableVirtualRoutes,
67
73
  envBranch,
68
74
  debug = false,
69
- sourcemap = true
75
+ sourcemap = true,
76
+ disableVersionCheck = false,
77
+ inspectorPort
70
78
  }) {
71
79
  if (!process.env.NODE_ENV)
72
80
  process.env.NODE_ENV = "development";
73
81
  muteDevLogs();
74
- if (debug)
75
- (await import('node:inspector')).open();
76
82
  const { root, publicPath, buildPathClient, buildPathWorkerFile } = getProjectPaths(appPath);
77
- const checkingHydrogenVersion = checkHydrogenVersion(root);
78
83
  const copyingFiles = copyPublicFiles(publicPath, buildPathClient);
79
84
  const reloadConfig = async () => {
80
85
  const config = await getRemixConfig(root);
@@ -90,6 +95,12 @@ async function runDev({
90
95
  return [fileRelative, path.resolve(root, fileRelative)];
91
96
  };
92
97
  const serverBundleExists = () => fileExists(buildPathWorkerFile);
98
+ inspectorPort = debug ? await findPort(inspectorPort) : inspectorPort;
99
+ appPort = legacyRuntime ? appPort : await findPort(appPort);
100
+ const assetsPort = legacyRuntime ? 0 : await findPort(appPort + 100);
101
+ if (assetsPort) {
102
+ process.env.HYDROGEN_ASSET_BASE_URL = buildAssetsUrl(assetsPort);
103
+ }
93
104
  const [remixConfig, { shop, storefront }] = await Promise.all([
94
105
  reloadConfig(),
95
106
  getConfig(root)
@@ -106,19 +117,23 @@ async function runDev({
106
117
  let initialBuildStartTimeMs = Date.now();
107
118
  const liveReload = await setupLiveReload(remixConfig.dev?.port ?? 8002) ;
108
119
  let miniOxygen;
120
+ let codegenProcess;
109
121
  async function safeStartMiniOxygen() {
110
122
  if (miniOxygen)
111
123
  return;
112
124
  miniOxygen = await startMiniOxygen(
113
125
  {
114
126
  root,
115
- port: portFlag,
127
+ debug,
128
+ assetsPort,
129
+ inspectorPort,
130
+ port: appPort,
116
131
  watch: !liveReload,
117
132
  buildPathWorkerFile,
118
133
  buildPathClient,
119
134
  env: await envPromise
120
135
  },
121
- workerRuntime
136
+ legacyRuntime
122
137
  );
123
138
  enhanceH2Logs({ host: miniOxygen.listeningAt, ...remixConfig });
124
139
  miniOxygen.showBanner({
@@ -134,21 +149,24 @@ View GraphiQL API browser: ${getGraphiQLUrl({
134
149
  ),
135
150
  colors.dim(
136
151
  `
137
- View server-side network requests: ${miniOxygen.listeningAt}/debug-network`
152
+ View server network requests: ${miniOxygen.listeningAt}/subrequest-profiler`
138
153
  )
139
154
  ]
140
155
  });
141
156
  if (useCodegen) {
142
- spawnCodegenProcess({ ...remixConfig, configFilePath: codegenConfigPath });
157
+ codegenProcess = spawnCodegenProcess({
158
+ ...remixConfig,
159
+ configFilePath: codegenConfigPath
160
+ });
143
161
  }
144
162
  checkRemixVersions();
145
- const showUpgrade = await checkingHydrogenVersion;
146
- if (showUpgrade)
147
- showUpgrade();
163
+ if (!disableVersionCheck) {
164
+ displayDevUpgradeNotice({ targetPath: appPath });
165
+ }
148
166
  }
149
167
  const fileWatchCache = createFileWatchCache();
150
168
  let skipRebuildLogs = false;
151
- await watch(
169
+ const closeWatcher = await watch(
152
170
  {
153
171
  config: remixConfig,
154
172
  options: {
@@ -184,6 +202,7 @@ View server-side network requests: ${miniOxygen.listeningAt}/debug-network`
184
202
  name: "BuildError",
185
203
  type: 0,
186
204
  message: "MiniOxygen cannot start because the server bundle has not been generated.",
205
+ skipOclifErrorHandling: true,
187
206
  tryMessage: "This is likely due to an error in your app and Remix is unable to compile. Try fixing the app and MiniOxygen will start."
188
207
  });
189
208
  }
@@ -240,6 +259,12 @@ View server-side network requests: ${miniOxygen.listeningAt}/debug-network`
240
259
  }
241
260
  }
242
261
  );
262
+ return {
263
+ async close() {
264
+ codegenProcess?.kill(0);
265
+ await Promise.all([closeWatcher(), miniOxygen?.close()]);
266
+ }
267
+ };
243
268
  }
244
269
 
245
- export { Dev as default };
270
+ export { Dev as default, runDev };
@@ -27,7 +27,7 @@ class Init extends Command {
27
27
  env: "SHOPIFY_HYDROGEN_FLAG_LANGUAGE"
28
28
  }),
29
29
  template: Flags.string({
30
- description: "Sets the template to use. Pass `demo-store` for a fully-featured store template or `hello-world` for a barebones project.",
30
+ description: "Scaffolds project based on an existing template or example from the Hydrogen repository.",
31
31
  env: "SHOPIFY_HYDROGEN_FLAG_TEMPLATE"
32
32
  }),
33
33
  "install-deps": commonFlags.installDeps,