@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
@@ -0,0 +1,786 @@
1
+ import { createRequire } from 'node:module';
2
+ import { fileURLToPath } from 'node:url';
3
+ import { execa } from 'execa';
4
+ import { vi, beforeEach, beforeAll, afterAll, describe, it, expect } from 'vitest';
5
+ import { inTemporaryDirectory, writeFile, fileExists } from '@shopify/cli-kit/node/fs';
6
+ import { joinPath } from '@shopify/cli-kit/node/path';
7
+ import { renderSelectPrompt, renderConfirmationPrompt, renderTasks } from '@shopify/cli-kit/node/ui';
8
+ import { mockAndCaptureOutput } from '@shopify/cli-kit/node/testing/output';
9
+ import { runUpgrade, getChangelog, getHydrogenVersion, getAvailableUpgrades, buildUpgradeCommandArgs, getAbsoluteVersion, getSelectedRelease, getCummulativeRelease, displayConfirmation, upgradeNodeModules } from './upgrade.js';
10
+
11
+ vi.mock("../../lib/shell.js");
12
+ vi.mock("@shopify/cli-kit/node/session");
13
+ vi.mock("@shopify/cli-kit/node/ui", async () => {
14
+ const original = await vi.importActual("@shopify/cli-kit/node/ui");
15
+ return {
16
+ ...original,
17
+ renderTasks: vi.fn(() => Promise.resolve()),
18
+ renderSelectPrompt: vi.fn(() => Promise.resolve()),
19
+ renderConfirmationPrompt: vi.fn(() => Promise.resolve(false))
20
+ };
21
+ });
22
+ const outputMock = mockAndCaptureOutput();
23
+ beforeEach(() => {
24
+ vi.restoreAllMocks();
25
+ vi.resetModules();
26
+ vi.clearAllMocks();
27
+ outputMock.clear();
28
+ });
29
+ beforeAll(() => {
30
+ process.env.FORCE_CHANGELOG_SOURCE = "local";
31
+ });
32
+ afterAll(() => {
33
+ delete process.env.FORCE_CHANGELOG_SOURCE;
34
+ });
35
+ function createOutdatedSkeletonPackageJson() {
36
+ const require2 = createRequire(import.meta.url);
37
+ const packageJson = require2(fileURLToPath(
38
+ new URL("../../../../../templates/skeleton/package.json", import.meta.url)
39
+ ));
40
+ if (!packageJson)
41
+ throw new Error("Could not parse package.json");
42
+ if (!packageJson?.dependencies)
43
+ throw new Error("Could not parse package.json dependencies");
44
+ if (!packageJson?.devDependencies)
45
+ throw new Error("Could not parse package.json devDependencies");
46
+ packageJson.dependencies["@shopify/hydrogen"] = "^2023.1.6";
47
+ packageJson.dependencies["@remix-run/react"] = "1.12.0";
48
+ packageJson.devDependencies["@shopify/cli-hydrogen"] = "^4.0.8";
49
+ packageJson.devDependencies["@shopify/remix-oxygen"] = "^1.0.3";
50
+ packageJson.devDependencies["@remix-run/dev"] = "1.12.0";
51
+ packageJson.devDependencies["typescript"] = "^4.9.5";
52
+ return packageJson;
53
+ }
54
+ async function inTemporaryHydrogenRepo(cb, {
55
+ cleanGitRepo,
56
+ packageJson
57
+ } = {
58
+ cleanGitRepo: true,
59
+ packageJson: null
60
+ }) {
61
+ return inTemporaryDirectory(async (tmpDir) => {
62
+ await execa("git", ["init"], { cwd: tmpDir });
63
+ if (packageJson) {
64
+ const packageJsonPath = joinPath(tmpDir, "package.json");
65
+ await writeFile(packageJsonPath, JSON.stringify(packageJson));
66
+ expect(await fileExists(packageJsonPath)).toBeTruthy();
67
+ }
68
+ expect(await fileExists(joinPath(tmpDir, "/.git/config"))).toBeTruthy();
69
+ if (cleanGitRepo) {
70
+ await execa("git", ["add", "package.json"], { cwd: tmpDir });
71
+ if (process.env.NODE_ENV === "test" && process.env.CI) {
72
+ await execa("git", ["config", "user.email", "test@hydrogen.shop"], {
73
+ cwd: tmpDir
74
+ });
75
+ await execa("git", ["config", "user.name", "Hydrogen Test"], {
76
+ cwd: tmpDir
77
+ });
78
+ }
79
+ await execa("git", ["commit", "-m", "initial commit"], { cwd: tmpDir });
80
+ }
81
+ await cb(tmpDir);
82
+ });
83
+ }
84
+ describe("upgrade", async () => {
85
+ const OUTDATED_HYDROGEN_PACKAGE_JSON = createOutdatedSkeletonPackageJson();
86
+ describe("checkIsGitRepo", () => {
87
+ it("renders an error message when not in a git repo", async () => {
88
+ await inTemporaryDirectory(async (appPath) => {
89
+ await expect(runUpgrade({ appPath })).rejects.toThrowError(
90
+ "git repository"
91
+ );
92
+ });
93
+ });
94
+ });
95
+ describe("checkDirtyGitBranch", () => {
96
+ it("renders error message if the target git repo is dirty", async () => {
97
+ await inTemporaryHydrogenRepo(
98
+ async (appPath) => {
99
+ await expect(runUpgrade({ appPath })).rejects.toThrowError("clean git");
100
+ },
101
+ { cleanGitRepo: false, packageJson: OUTDATED_HYDROGEN_PACKAGE_JSON }
102
+ );
103
+ });
104
+ });
105
+ describe("getHydrogenVersion", () => {
106
+ it("throws if no package.json is found", async () => {
107
+ await inTemporaryHydrogenRepo(
108
+ async (appPath) => {
109
+ await expect(runUpgrade({ appPath })).rejects.toThrowError(
110
+ "valid package.json"
111
+ );
112
+ },
113
+ { packageJson: null }
114
+ );
115
+ });
116
+ it("throws if no hydrogen version is found in package.json", async () => {
117
+ await inTemporaryHydrogenRepo(
118
+ async (appPath) => {
119
+ await expect(runUpgrade({ appPath })).rejects.toThrowError(
120
+ "version in package.json"
121
+ );
122
+ },
123
+ {
124
+ cleanGitRepo: true,
125
+ packageJson: {
126
+ name: "hello-world",
127
+ dependencies: {}
128
+ }
129
+ }
130
+ );
131
+ });
132
+ it("returns the current hydrogen version from the package.json", async () => {
133
+ await inTemporaryHydrogenRepo(
134
+ async (appPath) => {
135
+ const hydrogen = await getHydrogenVersion({ appPath });
136
+ expect(hydrogen).toBeDefined();
137
+ expect(hydrogen.currentVersion).toMatch("^2023.1.6");
138
+ expect(hydrogen.currentDependencies).toMatchObject({
139
+ ...OUTDATED_HYDROGEN_PACKAGE_JSON.dependencies,
140
+ ...OUTDATED_HYDROGEN_PACKAGE_JSON.devDependencies
141
+ });
142
+ },
143
+ {
144
+ cleanGitRepo: true,
145
+ packageJson: OUTDATED_HYDROGEN_PACKAGE_JSON
146
+ }
147
+ );
148
+ });
149
+ it('exists when run over a prerelease "next" version', async () => {
150
+ await inTemporaryHydrogenRepo(
151
+ async (appPath) => {
152
+ await expect(runUpgrade({ appPath })).rejects.toThrowError(
153
+ "prerelease"
154
+ );
155
+ },
156
+ {
157
+ cleanGitRepo: true,
158
+ packageJson: {
159
+ dependencies: {
160
+ "@shopify/hydrogen": "0.0.0-next-74ea1db-20231120210149"
161
+ }
162
+ }
163
+ }
164
+ );
165
+ });
166
+ });
167
+ describe.skip("fetchChangelog", () => {
168
+ it("fetches the latest changelog from the hydrogen repo", async () => {
169
+ });
170
+ it("renders an error message if the changelog could not be fetched", async () => {
171
+ });
172
+ });
173
+ describe("getAvailableUpgrades", async () => {
174
+ it('renders "already in the latest version" success message if no upgrades are available', async () => {
175
+ const { releases } = await getChangelog();
176
+ await inTemporaryHydrogenRepo(
177
+ async (appPath) => {
178
+ await runUpgrade({ appPath });
179
+ expect(outputMock.info()).toMatch(
180
+ / success.+ latest Hydrogen version/is
181
+ );
182
+ },
183
+ {
184
+ cleanGitRepo: true,
185
+ packageJson: {
186
+ dependencies: {
187
+ // @ts-expect-error - we know this release version exists
188
+ "@shopify/hydrogen": releases[0].version
189
+ }
190
+ }
191
+ }
192
+ );
193
+ });
194
+ it("returns available upgrades and uniqueAvailableUpgrades if they exist", async () => {
195
+ const { releases } = await getChangelog();
196
+ await inTemporaryHydrogenRepo(
197
+ async (appPath) => {
198
+ const current = await getHydrogenVersion({ appPath });
199
+ const availableUpgrades = getAvailableUpgrades({
200
+ releases,
201
+ ...current
202
+ });
203
+ const uniqueAvailableUpgrades = releases.slice(0, 2).reduce((acc, release) => {
204
+ if (acc[release.version])
205
+ return acc;
206
+ return {
207
+ ...acc,
208
+ [release.version]: release
209
+ };
210
+ }, {});
211
+ expect(availableUpgrades).toMatchObject({
212
+ availableUpgrades: releases.slice(0, 2),
213
+ uniqueAvailableUpgrades
214
+ });
215
+ },
216
+ {
217
+ cleanGitRepo: true,
218
+ packageJson: {
219
+ dependencies: {
220
+ // @ts-ignore
221
+ "@shopify/hydrogen": releases[2].version
222
+ }
223
+ }
224
+ }
225
+ );
226
+ });
227
+ });
228
+ describe("getSelectedRelease", () => {
229
+ it("prioritizes a passed target --version over a select prompt if available", async () => {
230
+ await inTemporaryHydrogenRepo(
231
+ async (appPath) => {
232
+ const { releases } = await getChangelog();
233
+ const current = await getHydrogenVersion({ appPath });
234
+ expect(current?.currentVersion).toBeDefined();
235
+ const { availableUpgrades } = getAvailableUpgrades({
236
+ ...current,
237
+ releases
238
+ });
239
+ await expect(
240
+ getSelectedRelease({
241
+ availableUpgrades,
242
+ // @ts-ignore - we know this release version exists
243
+ currentVersion: current.currentVersion,
244
+ // OUTDATED_HYDROGEN_PACKAGE_JSON.dependencies['@shopify/hydrogen'],
245
+ targetVersion: "2023.7.10"
246
+ })
247
+ ).resolves.toMatchObject({
248
+ version: "2023.7.10"
249
+ });
250
+ expect(renderSelectPrompt).not.toHaveBeenCalled();
251
+ },
252
+ {
253
+ cleanGitRepo: true,
254
+ packageJson: OUTDATED_HYDROGEN_PACKAGE_JSON
255
+ }
256
+ );
257
+ });
258
+ it("prompts if a passed target --version is not a valid upgradable version", async () => {
259
+ await inTemporaryHydrogenRepo(
260
+ async (appPath) => {
261
+ const { releases } = await getChangelog();
262
+ const current = await getHydrogenVersion({ appPath });
263
+ expect(current?.currentVersion).toBeDefined();
264
+ const { availableUpgrades } = getAvailableUpgrades({
265
+ ...current,
266
+ releases
267
+ });
268
+ vi.mocked(renderSelectPrompt).mockImplementationOnce(
269
+ ({ choices }) => Promise.resolve(choices[0]?.value)
270
+ );
271
+ await expect(
272
+ getSelectedRelease({
273
+ availableUpgrades,
274
+ currentVersion: current.currentVersion,
275
+ targetVersion: "2023.1.5"
276
+ // fails because this version is in the past
277
+ })
278
+ ).resolves.toMatchObject(availableUpgrades[0]);
279
+ expect(renderSelectPrompt).toHaveBeenCalled();
280
+ },
281
+ {
282
+ cleanGitRepo: true,
283
+ packageJson: OUTDATED_HYDROGEN_PACKAGE_JSON
284
+ }
285
+ );
286
+ });
287
+ it("prompts to select a release if no target --version is passed", async () => {
288
+ await inTemporaryHydrogenRepo(
289
+ async (appPath) => {
290
+ const { releases } = await getChangelog();
291
+ const previousRelease = releases[1];
292
+ const latestRelease = releases[0];
293
+ const current = await getHydrogenVersion({ appPath });
294
+ expect(current?.currentVersion).toBeDefined();
295
+ const { availableUpgrades } = getAvailableUpgrades({
296
+ ...current,
297
+ releases
298
+ });
299
+ vi.mocked(renderSelectPrompt).mockImplementationOnce(
300
+ ({ choices }) => Promise.resolve(choices[0]?.value)
301
+ );
302
+ await expect(
303
+ getSelectedRelease({
304
+ availableUpgrades,
305
+ currentVersion: previousRelease.version
306
+ })
307
+ ).resolves.toMatchObject(availableUpgrades[0]);
308
+ expect(renderSelectPrompt).toHaveBeenCalledWith({
309
+ message: expect.stringContaining(previousRelease.version),
310
+ choices: expect.arrayContaining([
311
+ {
312
+ label: expect.stringContaining(latestRelease.version),
313
+ value: latestRelease
314
+ }
315
+ ]),
316
+ defaultValue: latestRelease
317
+ });
318
+ },
319
+ {
320
+ cleanGitRepo: true,
321
+ packageJson: OUTDATED_HYDROGEN_PACKAGE_JSON
322
+ }
323
+ );
324
+ });
325
+ });
326
+ describe("getCummulativeRelease", () => {
327
+ it("returns the correct fixes and features for a release range thats outdated", async () => {
328
+ await inTemporaryHydrogenRepo(
329
+ async (appPath) => {
330
+ const { releases } = await getChangelog();
331
+ const current = await getHydrogenVersion({ appPath });
332
+ expect(current?.currentVersion).toBeDefined();
333
+ const { availableUpgrades } = getAvailableUpgrades({
334
+ ...current,
335
+ releases
336
+ });
337
+ const selectedRelease = releases.find(
338
+ (release) => release.version === "2023.4.1"
339
+ );
340
+ const { features, fixes } = getCummulativeRelease({
341
+ availableUpgrades,
342
+ ...current,
343
+ // @ts-ignore - we know this release version exists
344
+ selectedRelease
345
+ });
346
+ expect(features).toMatchObject(CUMMLATIVE_RELEASE.features);
347
+ expect(fixes).toMatchObject(CUMMLATIVE_RELEASE.fixes);
348
+ },
349
+ {
350
+ cleanGitRepo: true,
351
+ packageJson: OUTDATED_HYDROGEN_PACKAGE_JSON
352
+ }
353
+ );
354
+ });
355
+ });
356
+ describe("displayConfirmation", () => {
357
+ it("renders a confirmation prompt to continue or return to the previous menu", async () => {
358
+ await inTemporaryHydrogenRepo(
359
+ async () => {
360
+ const { releases } = await getChangelog();
361
+ const selectedRelease = releases.find(
362
+ (release) => release.version === "2023.10.0"
363
+ );
364
+ await expect(
365
+ displayConfirmation({
366
+ cumulativeRelease: CUMMLATIVE_RELEASE,
367
+ selectedRelease
368
+ })
369
+ ).resolves.toEqual(false);
370
+ const info = outputMock.info();
371
+ expect(info).toMatch("Included in this upgrade");
372
+ [...CUMMLATIVE_RELEASE.features, ...CUMMLATIVE_RELEASE.fixes].forEach(
373
+ (feat) => (
374
+ // Cut the string to avoid matching the banner
375
+ expect(info).toMatch(feat.title.slice(0, 15))
376
+ )
377
+ );
378
+ expect(renderConfirmationPrompt).toHaveBeenCalledWith({
379
+ message: `Are you sure you want to upgrade to ${selectedRelease.version}?`,
380
+ cancellationMessage: `No, choose another version`,
381
+ defaultValue: true
382
+ });
383
+ },
384
+ {
385
+ cleanGitRepo: true,
386
+ packageJson: OUTDATED_HYDROGEN_PACKAGE_JSON
387
+ }
388
+ );
389
+ });
390
+ });
391
+ describe("upgradeNodeModules", () => {
392
+ it("runs the upgrade command task", async () => {
393
+ await inTemporaryHydrogenRepo(
394
+ async (appPath) => {
395
+ const { releases } = await getChangelog();
396
+ const selectedRelease = releases.find(
397
+ (release) => release.version === "2023.10.0"
398
+ );
399
+ const currentDependencies = {
400
+ ...OUTDATED_HYDROGEN_PACKAGE_JSON.dependencies,
401
+ ...OUTDATED_HYDROGEN_PACKAGE_JSON.devDependencies
402
+ };
403
+ await upgradeNodeModules({
404
+ appPath,
405
+ selectedRelease,
406
+ currentDependencies
407
+ });
408
+ expect(renderTasks).toHaveBeenCalled();
409
+ },
410
+ {
411
+ cleanGitRepo: true,
412
+ packageJson: OUTDATED_HYDROGEN_PACKAGE_JSON
413
+ }
414
+ );
415
+ });
416
+ it("builds the upgrade command args", async () => {
417
+ const { releases } = await getChangelog();
418
+ const selectedRelease = releases.find(
419
+ (release) => release.version === "2023.10.0"
420
+ );
421
+ const currentDependencies = {
422
+ ...OUTDATED_HYDROGEN_PACKAGE_JSON.dependencies,
423
+ ...OUTDATED_HYDROGEN_PACKAGE_JSON.devDependencies
424
+ };
425
+ const result = [
426
+ "@shopify/hydrogen@2023.10.0",
427
+ "@shopify/cli-hydrogen@6.0.0",
428
+ "@shopify/remix-oxygen@2.0.0",
429
+ "@remix-run/react@2.1.0",
430
+ "@remix-run/dev@2.1.0",
431
+ "typescript@5.2.2"
432
+ ];
433
+ const args = buildUpgradeCommandArgs({
434
+ selectedRelease,
435
+ currentDependencies
436
+ });
437
+ expect(args).toEqual(expect.arrayContaining(result));
438
+ });
439
+ it("upgrades and syncs up all available Remix deps if they are out-of-date", async () => {
440
+ const { releases } = await getChangelog();
441
+ const selectedRelease = releases.find(
442
+ (release) => release.version === "2023.10.0"
443
+ );
444
+ const currentDependencies = {
445
+ ...OUTDATED_HYDROGEN_PACKAGE_JSON.dependencies,
446
+ "@remix-run/react": "1.3.0",
447
+ ...OUTDATED_HYDROGEN_PACKAGE_JSON.devDependencies,
448
+ "@remix-run/dev": "1.2.0",
449
+ "@remix-run/css-bundle": "1.7.0"
450
+ };
451
+ const result = [
452
+ "@shopify/cli-hydrogen@6.0.0",
453
+ "@shopify/hydrogen@2023.10.0",
454
+ "@shopify/remix-oxygen@2.0.0",
455
+ "typescript@5.2.2",
456
+ "@remix-run/react@2.1.0",
457
+ "@remix-run/server-runtime@2.1.0",
458
+ "@remix-run/dev@2.1.0",
459
+ "@remix-run/eslint-config@2.1.0",
460
+ "@remix-run/css-bundle@2.1.0"
461
+ ];
462
+ const args = buildUpgradeCommandArgs({
463
+ selectedRelease,
464
+ currentDependencies
465
+ });
466
+ expect(args).toEqual(result);
467
+ });
468
+ it("upgrades all available Remix deps if they are out-of-date", async () => {
469
+ const { releases } = await getChangelog();
470
+ const selectedRelease = releases.find(
471
+ (release) => release.version === "2023.10.0"
472
+ );
473
+ const currentDependencies = {
474
+ ...OUTDATED_HYDROGEN_PACKAGE_JSON.dependencies,
475
+ "@remix-run/react": "1.8.0",
476
+ ...OUTDATED_HYDROGEN_PACKAGE_JSON.devDependencies,
477
+ "@remix-run/dev": "1.8.0",
478
+ "@remix-run/css-bundle": "1.8.0"
479
+ };
480
+ const result = [
481
+ "@shopify/cli-hydrogen@6.0.0",
482
+ "@shopify/hydrogen@2023.10.0",
483
+ "@shopify/remix-oxygen@2.0.0",
484
+ "typescript@5.2.2",
485
+ "@remix-run/react@2.1.0",
486
+ "@remix-run/server-runtime@2.1.0",
487
+ "@remix-run/dev@2.1.0",
488
+ "@remix-run/eslint-config@2.1.0",
489
+ "@remix-run/css-bundle@2.1.0"
490
+ ];
491
+ const args = buildUpgradeCommandArgs({
492
+ selectedRelease,
493
+ currentDependencies
494
+ });
495
+ expect(args).toEqual(result);
496
+ });
497
+ it("does not upgrade Remix deps if they are more up-to-date", async () => {
498
+ const { releases } = await getChangelog();
499
+ const selectedRelease = releases.find(
500
+ (release) => release.version === "2023.10.0"
501
+ );
502
+ const currentDependencies = {
503
+ ...OUTDATED_HYDROGEN_PACKAGE_JSON.dependencies,
504
+ "@remix-run/react": "2.2.0",
505
+ ...OUTDATED_HYDROGEN_PACKAGE_JSON.devDependencies,
506
+ "@remix-run/dev": "2.2.0"
507
+ };
508
+ const result = [
509
+ "@shopify/cli-hydrogen@6.0.0",
510
+ "@shopify/hydrogen@2023.10.0",
511
+ "@shopify/remix-oxygen@2.0.0",
512
+ "typescript@5.2.2"
513
+ ];
514
+ const args = buildUpgradeCommandArgs({
515
+ selectedRelease,
516
+ currentDependencies
517
+ });
518
+ expect(args).toEqual(result);
519
+ });
520
+ it("does not install an optional dependency that was not installed", async () => {
521
+ const { releases } = await getChangelog();
522
+ const selectedRelease = Object.create(
523
+ // @ts-ignore - we know this release version exists
524
+ releases.find((release) => release.version === "2023.10.0")
525
+ );
526
+ selectedRelease.dependenciesMeta = {
527
+ typescript: {
528
+ required: false
529
+ }
530
+ };
531
+ const currentDependencies = {
532
+ ...OUTDATED_HYDROGEN_PACKAGE_JSON.dependencies,
533
+ "@remix-run/react": "2.1.0",
534
+ ...OUTDATED_HYDROGEN_PACKAGE_JSON.devDependencies,
535
+ "@remix-run/dev": "2.1.0"
536
+ };
537
+ delete currentDependencies["typescript"];
538
+ const result = [
539
+ "@shopify/cli-hydrogen@6.0.0",
540
+ "@shopify/hydrogen@2023.10.0",
541
+ "@shopify/remix-oxygen@2.0.0"
542
+ ];
543
+ const args = buildUpgradeCommandArgs({
544
+ selectedRelease,
545
+ currentDependencies
546
+ });
547
+ expect(args).toEqual(result);
548
+ });
549
+ it("adds a required dependency that was not installed", async () => {
550
+ const { releases } = await getChangelog();
551
+ const selectedRelease = releases.find(
552
+ (release) => release.version === "2023.10.0"
553
+ );
554
+ const currentDependencies = {
555
+ ...OUTDATED_HYDROGEN_PACKAGE_JSON.dependencies,
556
+ "@remix-run/react": "2.1.0",
557
+ ...OUTDATED_HYDROGEN_PACKAGE_JSON.devDependencies,
558
+ "@remix-run/dev": "2.1.0"
559
+ };
560
+ delete currentDependencies["typescript"];
561
+ const result = [
562
+ "@shopify/cli-hydrogen@6.0.0",
563
+ "@shopify/hydrogen@2023.10.0",
564
+ "@shopify/remix-oxygen@2.0.0",
565
+ `typescript@${getAbsoluteVersion(
566
+ // @ts-ignore - we know this release version exists
567
+ selectedRelease.devDependencies.typescript
568
+ )}`
569
+ ];
570
+ const args = buildUpgradeCommandArgs({
571
+ selectedRelease,
572
+ currentDependencies
573
+ });
574
+ expect(args).toEqual(expect.arrayContaining(result));
575
+ });
576
+ it("does not upgrade a required dependency that is further up-to-date", async () => {
577
+ const { releases } = await getChangelog();
578
+ const selectedRelease = releases.find(
579
+ (release) => release.version === "2023.10.0"
580
+ );
581
+ const currentDependencies = {
582
+ ...OUTDATED_HYDROGEN_PACKAGE_JSON.dependencies,
583
+ "@remix-run/react": "2.1.0",
584
+ ...OUTDATED_HYDROGEN_PACKAGE_JSON.devDependencies,
585
+ "@remix-run/dev": "2.1.0",
586
+ typescript: "5.3.0"
587
+ // more up-to-date than that of 2023.10.0
588
+ };
589
+ const result = [
590
+ "@shopify/cli-hydrogen@6.0.0",
591
+ "@shopify/hydrogen@2023.10.0",
592
+ "@shopify/remix-oxygen@2.0.0"
593
+ ];
594
+ const args = buildUpgradeCommandArgs({
595
+ selectedRelease,
596
+ currentDependencies
597
+ });
598
+ expect(args).toEqual(expect.arrayContaining(result));
599
+ });
600
+ it("does not upgrade @next dependencies", async () => {
601
+ const { releases } = await getChangelog();
602
+ const selectedRelease = releases.find(
603
+ (release) => release.version === "2023.10.0"
604
+ );
605
+ const currentDependencies = {
606
+ ...OUTDATED_HYDROGEN_PACKAGE_JSON.dependencies,
607
+ "@remix-run/react": "2.1.0",
608
+ ...OUTDATED_HYDROGEN_PACKAGE_JSON.devDependencies,
609
+ "@remix-run/dev": "2.1.0",
610
+ "@shopify/hydrogen": "next"
611
+ };
612
+ const result = [
613
+ "@shopify/cli-hydrogen@6.0.0",
614
+ "@shopify/remix-oxygen@2.0.0",
615
+ `typescript@${getAbsoluteVersion(
616
+ // @ts-ignore - we know this release version exists
617
+ selectedRelease.devDependencies.typescript
618
+ )}`
619
+ ];
620
+ const args = buildUpgradeCommandArgs({
621
+ selectedRelease,
622
+ currentDependencies
623
+ });
624
+ expect(args).toEqual(result);
625
+ });
626
+ });
627
+ });
628
+ const CUMMLATIVE_RELEASE = {
629
+ fixes: [
630
+ {
631
+ title: "Add a default Powered-By: Shopify-Hydrogen header",
632
+ pr: "https://github.com/Shopify/hydrogen/pull/872",
633
+ id: "872",
634
+ steps: [
635
+ {
636
+ title: " It can be disabled by passing poweredByHeader: false in the configuration object of createRequestHandler",
637
+ code: "YGBgdHMKaW1wb3J0IHtjcmVhdGVSZXF1ZXN0SGFuZGxlcn0gZnJvbSAnQHNob3BpZnkvcmVtaXgtb3h5Z2VuJzsKCmV4cG9ydCBkZWZhdWx0IHsKICBhc3luYyBmZXRjaChyZXF1ZXN0KSB7CiAgICAvLyAuLi4KICAgIGNvbnN0IGhhbmRsZVJlcXVlc3QgPSBjcmVhdGVSZXF1ZXN0SGFuZGxlcih7CiAgICAgIC8vIC4uLiBvdGhlciBwcm9wZXJ0aWVzIGluY2x1ZGVkCiAgICAgIHBvd2VyZWRCeUhlYWRlcjogZmFsc2UsCiAgICB9KTsKICAgIC8vIC4uLgogIH0sCn07CmBgYA",
638
+ file: "server.ts"
639
+ }
640
+ ]
641
+ },
642
+ {
643
+ title: "Updated CLI prompts",
644
+ pr: "https://github.com/Shopify/hydrogen/pull/733",
645
+ id: "733",
646
+ steps: [
647
+ {
648
+ title: "Update package.json",
649
+ code: "YGBgZGlmZgoiZGVwZW5kZW5jaWVzIjogewotICAiQHNob3BpZnkvY2xpIjogIjMueC54IiwKKyAgIkBzaG9waWZ5L2NsaSI6ICIzLjQ1LjAiLAp9CmBgYA==",
650
+ file: "package.json"
651
+ }
652
+ ]
653
+ },
654
+ {
655
+ title: "Added support for the Remix future flags v2_meta, v2_errorBoundary and v2_routeConvention to the generate command",
656
+ pr: "https://github.com/Shopify/hydrogen/pull/756",
657
+ id: "756"
658
+ },
659
+ {
660
+ title: "Update virtual route to use Remix V2 route name conventions",
661
+ pr: "https://github.com/Shopify/hydrogen/pull/792",
662
+ id: "792"
663
+ },
664
+ {
665
+ title: "Update internal Remix dependencies to 1.15.0",
666
+ pr: "https://github.com/Shopify/hydrogen/pull/728",
667
+ id: "728",
668
+ docs: "https://github.com/remix-run/remix/releases/tag/remix%401.15.0"
669
+ },
670
+ {
671
+ title: "Improve type safety in SEO data generators",
672
+ pr: "https://github.com/Shopify/hydrogen/pull/763",
673
+ id: "763"
674
+ },
675
+ {
676
+ title: "Stop hydrating with requestIdleCallback",
677
+ pr: "https://github.com/Shopify/hydrogen/pull/667",
678
+ id: "667"
679
+ },
680
+ {
681
+ title: "Fix active cart session event in Live View",
682
+ pr: "https://github.com/Shopify/hydrogen/pull/614",
683
+ id: "614",
684
+ steps: [
685
+ {
686
+ title: "Introducing getStorefrontHeaders that collects the required Shopify headers for making a Storefront API call.",
687
+ code: "YGBgdHMKKyBpbXBvcnQge2dldFN0b3JlZnJvbnRIZWFkZXJzfSBmcm9tICdAc2hvcGlmeS9yZW1peC1veHlnZW4nOwppbXBvcnQge2NyZWF0ZVN0b3JlZnJvbnRDbGllbnQsIHN0b3JlZnJvbnRSZWRpcmVjdH0gZnJvbSAnQHNob3BpZnkvaHlkcm9nZW4nOwoKZXhwb3J0IGRlZmF1bHQgewogIGFzeW5jIGZldGNoKAogICAgcmVxdWVzdDogUmVxdWVzdCwKICAgIGVudjogRW52LAogICAgZXhlY3V0aW9uQ29udGV4dDogRXhlY3V0aW9uQ29udGV4dCwKICApOiBQcm9taXNlPFJlc3BvbnNlPiB7CgogICAgY29uc3Qge3N0b3JlZnJvbnR9ID0gY3JlYXRlU3RvcmVmcm9udENsaWVudCh7CiAgICAgIGNhY2hlLAogICAgICB3YWl0VW50aWwsCi0gICAgIGJ1eWVySXA6IGdldEJ1eWVySXAocmVxdWVzdCksCiAgICAgIGkxOG46IHtsYW5ndWFnZTogJ0VOJywgY291bnRyeTogJ1VTJ30sCiAgICAgIHB1YmxpY1N0b3JlZnJvbnRUb2tlbjogZW52LlBVQkxJQ19TVE9SRUZST05UX0FQSV9UT0tFTiwKICAgICAgcHJpdmF0ZVN0b3JlZnJvbnRUb2tlbjogZW52LlBSSVZBVEVfU1RPUkVGUk9OVF9BUElfVE9LRU4sCiAgICAgIHN0b3JlRG9tYWluOiBgaHR0cHM6Ly8ke2Vudi5QVUJMSUNfU1RPUkVfRE9NQUlOfWAsCiAgICAgIHN0b3JlZnJvbnRBcGlWZXJzaW9uOiBlbnYuUFVCTElDX1NUT1JFRlJPTlRfQVBJX1ZFUlNJT04gfHwgJzIwMjMtMDEnLAogICAgICBzdG9yZWZyb250SWQ6IGVudi5QVUJMSUNfU1RPUkVGUk9OVF9JRCwKLSAgICAgcmVxdWVzdEdyb3VwSWQ6IHJlcXVlc3QuaGVhZGVycy5nZXQoJ3JlcXVlc3QtaWQnKSwKKyAgICAgc3RvcmVmcm9udEhlYWRlcnM6IGdldFN0b3JlZnJvbnRIZWFkZXJzKHJlcXVlc3QpLAogICAgfSk7CmBgYA==",
688
+ file: "server.ts"
689
+ }
690
+ ]
691
+ }
692
+ ],
693
+ features: [
694
+ {
695
+ title: "Add command to pull environment variables from a Hydrogen storefront",
696
+ pr: "https://github.com/Shopify/hydrogen/pull/809",
697
+ id: "809"
698
+ },
699
+ {
700
+ title: "New --debug flag for the dev command that attaches a Node inspector to the development server",
701
+ pr: "https://github.com/Shopify/hydrogen/pull/869",
702
+ id: "869"
703
+ },
704
+ {
705
+ title: "Add new commands for merchants to be able to list and link Hydrogen storefronts",
706
+ pr: "https://github.com/Shopify/hydrogen/pull/784",
707
+ id: "784"
708
+ },
709
+ {
710
+ title: "Added parseGid() utility",
711
+ pr: "https://github.com/Shopify/hydrogen/pull/845",
712
+ id: "845",
713
+ steps: [
714
+ {
715
+ title: "Example usage",
716
+ code: "YGBgdHMKaW1wb3J0IHtwYXJzZUdpZH0gZnJvbSAnQHNob3BpZnkvaHlkcm9nZW4tcmVhY3QnOwoKY29uc3Qge2lkLCByZXNvdXJjZX0gPSBwYXJzZUdpZCgnZ2lkOi8vc2hvcGlmeS9PcmRlci8xMjMnKTsKCmNvbnNvbGUubG9nKGlkKTsgLy8gMTIzCmNvbnNvbGUubG9nKHJlc291cmNlKTsgLy8gT3JkZXIKYGBg"
717
+ }
718
+ ]
719
+ },
720
+ {
721
+ title: "Added a new shortcut command that creates a global h2 alias for the Hydrogen CLI",
722
+ pr: "https://github.com/Shopify/hydrogen/pull/679",
723
+ id: "679",
724
+ steps: [
725
+ {
726
+ title: "Create the h2 alias",
727
+ code: "YGBgYmFzaApucHggc2hvcGlmeSBoeWRyb2dlbiBzaG9ydGN1dApgYGA="
728
+ },
729
+ {
730
+ title: "After that, you can run commands using the new alias:",
731
+ code: "YGBgYmFzaApoMiBnZW5lcmF0ZSByb3V0ZSBob21lCmgyIGcgciBob21lICMgU2FtZSBhcyB0aGUgYWJvdmUKaDIgY2hlY2sgcm91dGVzCmBgYA=="
732
+ }
733
+ ]
734
+ },
735
+ {
736
+ title: "Add an experimental createWithCache_unstable",
737
+ info: "This utility creates a function similar to useQuery from Hydrogen v1. Use this utility to query third-party APIs and apply custom cache options",
738
+ pr: "https://github.com/Shopify/hydrogen/pull/600",
739
+ id: "600",
740
+ steps: [
741
+ {
742
+ title: "To setup the utility, update your server.ts",
743
+ file: "server.ts",
744
+ code: "YGBgdHMKaW1wb3J0IHsKICBjcmVhdGVTdG9yZWZyb250Q2xpZW50LAogIGNyZWF0ZVdpdGhDYWNoZV91bnN0YWJsZSwKICBDYWNoZUxvbmcsCn0gZnJvbSAnQHNob3BpZnkvaHlkcm9nZW4nOwoKLy8gLi4uCgogIGNvbnN0IGNhY2hlID0gYXdhaXQgY2FjaGVzLm9wZW4oJ2h5ZHJvZ2VuJyk7CiAgY29uc3Qgd2l0aENhY2hlID0gY3JlYXRlV2l0aENhY2hlX3Vuc3RhYmxlKHtjYWNoZSwgd2FpdFVudGlsfSk7CgogIC8vIENyZWF0ZSBjdXN0b20gdXRpbGl0aWVzIHRvIHF1ZXJ5IHRoaXJkLXBhcnR5IEFQSXM6CiAgY29uc3QgZmV0Y2hNeUNNUyA9IChxdWVyeSkgPT4gewogICAgLy8gUHJlZml4IHRoZSBjYWNoZSBrZXkgYW5kIG1ha2UgaXQgdW5pcXVlIGJhc2VkIG9uIGFyZ3VtZW50cy4KICAgIHJldHVybiB3aXRoQ2FjaGUoWydteS1jbXMnLCBxdWVyeV0sIENhY2hlTG9uZygpLCAoKSA9PiB7CiAgICAgIGNvbnN0IGNtc0RhdGEgPSBhd2FpdCAoYXdhaXQgZmV0Y2goJ215LWNtcy5jb20vYXBpJywgewogICAgICAgIG1ldGhvZDogJ1BPU1QnLAogICAgICAgIGJvZHk6IHF1ZXJ5CiAgICAgIH0pKS5qc29uKCk7CgogICAgICBjb25zdCBuZXh0UGFnZSA9IChhd2FpdCBmZXRjaCgnbXktY21zLmNvbS9hcGknLCB7CiAgICAgICAgbWV0aG9kOiAnUE9TVCcsCiAgICAgICAgYm9keTogY21zRGF0YTEubmV4dFBhZ2VRdWVyeSwKICAgICAgfSkpLmpzb24oKTsKCiAgICAgIHJldHVybiB7Li4uY21zRGF0YSwgbmV4dFBhZ2V9CiAgICB9KTsKICB9OwoKICBjb25zdCBoYW5kbGVSZXF1ZXN0ID0gY3JlYXRlUmVxdWVzdEhhbmRsZXIoewogICAgYnVpbGQ6IHJlbWl4QnVpbGQsCiAgICBtb2RlOiBwcm9jZXNzLmVudi5OT0RFX0VOViwKICAgIGdldExvYWRDb250ZXh0OiAoKSA9PiAoewogICAgICBzZXNzaW9uLAogICAgICB3YWl0VW50aWwsCiAgICAgIHN0b3JlZnJvbnQsCiAgICAgIGVudiwKICAgICAgZmV0Y2hNeUNNUywKICAgIH0pLAogIH0pOwpgYGA="
745
+ }
746
+ ]
747
+ },
748
+ {
749
+ title: "Update Remix to 1.14.0",
750
+ pr: "https://github.com/Shopify/hydrogen/pull/599",
751
+ id: "599"
752
+ },
753
+ {
754
+ title: "Added Cache-Control defaults to all the demo store routes",
755
+ pr: "https://github.com/Shopify/hydrogen/pull/599",
756
+ id: "599"
757
+ },
758
+ {
759
+ title: "Added new loader API for setting SEO tags within route module",
760
+ pr: "https://github.com/Shopify/hydrogen/pull/591",
761
+ id: "591"
762
+ },
763
+ {
764
+ title: "ShopPayButton component now can receive a storeDomain",
765
+ pr: "https://github.com/Shopify/hydrogen/pull/645",
766
+ id: "645"
767
+ },
768
+ {
769
+ title: "Added robots option to SEO config that allows users granular control over the robots meta tag.",
770
+ pr: "https://github.com/Shopify/hydrogen/pull/572",
771
+ id: "572",
772
+ steps: [
773
+ {
774
+ title: "Example usage",
775
+ code: "YGBgdHMKZXhwb3J0IGhhbmRsZSA9IHsKICBzZW86IHsKICAgIHJvYm90czogewogICAgICBub0luZGV4OiBmYWxzZSwKICAgICAgbm9Gb2xsb3c6IGZhbHNlLAogICAgfQogIH0KfQpgYGA=",
776
+ file: "All files that use SEO config"
777
+ }
778
+ ]
779
+ },
780
+ {
781
+ title: "Added decoding prop to the SpreadMedia component",
782
+ pr: "https://github.com/Shopify/hydrogen/pull/642",
783
+ id: "642"
784
+ }
785
+ ]
786
+ };