@shopify/cli-hydrogen 7.1.2 → 8.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (136) hide show
  1. package/dist/commands/hydrogen/build-vite.js +19 -10
  2. package/dist/commands/hydrogen/build.js +10 -2
  3. package/dist/commands/hydrogen/check.js +1 -0
  4. package/dist/commands/hydrogen/codegen.js +1 -0
  5. package/dist/commands/hydrogen/customer-account/push.js +170 -0
  6. package/dist/commands/hydrogen/debug/cpu.js +3 -0
  7. package/dist/commands/hydrogen/deploy.js +121 -36
  8. package/dist/commands/hydrogen/dev-vite.js +128 -59
  9. package/dist/commands/hydrogen/dev.js +108 -51
  10. package/dist/commands/hydrogen/env/list.js +7 -8
  11. package/dist/commands/hydrogen/env/pull.js +17 -1
  12. package/dist/commands/hydrogen/env/{push__unstable.js → push.js} +23 -50
  13. package/dist/commands/hydrogen/generate/route.js +1 -0
  14. package/dist/commands/hydrogen/init.js +45 -17
  15. package/dist/commands/hydrogen/link.js +20 -4
  16. package/dist/commands/hydrogen/list.js +1 -0
  17. package/dist/commands/hydrogen/login.js +1 -0
  18. package/dist/commands/hydrogen/logout.js +1 -0
  19. package/dist/commands/hydrogen/preview.js +31 -16
  20. package/dist/commands/hydrogen/setup/css.js +8 -1
  21. package/dist/commands/hydrogen/setup/markets.js +1 -0
  22. package/dist/commands/hydrogen/setup/vite.js +244 -138
  23. package/dist/commands/hydrogen/setup.js +21 -22
  24. package/dist/commands/hydrogen/shortcut.js +10 -0
  25. package/dist/commands/hydrogen/unlink.js +1 -0
  26. package/dist/commands/hydrogen/upgrade.js +2 -1
  27. package/dist/generator-templates/assets/vite/package.json +3 -4
  28. package/dist/generator-templates/assets/vite/vite.config.js +10 -2
  29. package/dist/generator-templates/starter/CHANGELOG.md +89 -0
  30. package/dist/generator-templates/starter/README.md +3 -44
  31. package/dist/generator-templates/starter/app/graphql/customer-account/CustomerDetailsQuery.ts +1 -0
  32. package/dist/generator-templates/starter/app/lib/fragments.ts +2 -0
  33. package/dist/generator-templates/starter/app/root.tsx +2 -5
  34. package/dist/generator-templates/starter/app/routes/account.orders._index.tsx +1 -1
  35. package/dist/generator-templates/starter/app/routes/account.tsx +1 -1
  36. package/dist/generator-templates/starter/app/routes/collections.all.tsx +160 -0
  37. package/dist/generator-templates/starter/app/routes/products.$handle.tsx +1 -2
  38. package/dist/generator-templates/starter/customer-accountapi.generated.d.ts +6 -3
  39. package/dist/generator-templates/starter/{remix.env.d.ts → env.d.ts} +8 -2
  40. package/dist/generator-templates/starter/package.json +14 -9
  41. package/dist/generator-templates/starter/server.ts +2 -1
  42. package/dist/generator-templates/starter/storefrontapi.generated.d.ts +59 -3
  43. package/dist/generator-templates/starter/vite.config.ts +21 -0
  44. package/dist/{commands/hydrogen/init.d.ts → init.d.ts} +11 -3
  45. package/dist/lib/check-lockfile.js +12 -18
  46. package/dist/lib/codegen.js +37 -13
  47. package/dist/lib/common.js +50 -0
  48. package/dist/lib/cpu-profiler.js +4 -1
  49. package/dist/lib/dev-shared.js +97 -0
  50. package/dist/lib/environment-variables.js +51 -30
  51. package/dist/lib/file.js +8 -1
  52. package/dist/lib/flags.js +37 -16
  53. package/dist/lib/graphql/admin/customer-application-update.js +29 -0
  54. package/dist/lib/graphql/admin/get-oxygen-data.js +1 -0
  55. package/dist/lib/graphql/admin/list-environments.js +1 -0
  56. package/dist/lib/graphql/admin/pull-variables.js +4 -4
  57. package/dist/lib/graphql/admin/test-helper.js +37 -0
  58. package/dist/lib/log.js +86 -13
  59. package/dist/lib/mini-oxygen/common.js +19 -33
  60. package/dist/lib/mini-oxygen/index.js +6 -2
  61. package/dist/lib/mini-oxygen/node.js +43 -31
  62. package/dist/lib/mini-oxygen/workerd.js +72 -165
  63. package/dist/lib/missing-routes.js +1 -1
  64. package/dist/lib/onboarding/common.js +82 -70
  65. package/dist/lib/onboarding/local.js +19 -9
  66. package/dist/lib/onboarding/remote.js +35 -30
  67. package/dist/lib/package-managers.js +24 -0
  68. package/dist/lib/remix-config.js +17 -1
  69. package/dist/lib/request-events.js +6 -1
  70. package/dist/lib/setups/i18n/replacers.js +9 -6
  71. package/dist/lib/setups/routes/generate.js +1 -0
  72. package/dist/lib/shell.js +2 -1
  73. package/dist/lib/shopify-config.js +19 -1
  74. package/dist/lib/template-diff.js +36 -15
  75. package/dist/lib/template-downloader.js +35 -5
  76. package/dist/lib/transpile/morph/typedefs.js +5 -2
  77. package/dist/lib/transpile/project.js +8 -4
  78. package/dist/lib/tunneling.js +44 -0
  79. package/dist/lib/virtual-routes.js +1 -1
  80. package/dist/lib/vite-config.js +39 -9
  81. package/oclif.manifest.json +711 -498
  82. package/package.json +32 -24
  83. package/dist/commands/hydrogen/deploy.test.js +0 -553
  84. package/dist/commands/hydrogen/env/list.test.js +0 -148
  85. package/dist/commands/hydrogen/env/pull.test.js +0 -207
  86. package/dist/commands/hydrogen/env/push__unstable.test.js +0 -383
  87. package/dist/commands/hydrogen/generate/route.test.js +0 -43
  88. package/dist/commands/hydrogen/init.test.js +0 -641
  89. package/dist/commands/hydrogen/link.test.js +0 -187
  90. package/dist/commands/hydrogen/list.test.js +0 -111
  91. package/dist/commands/hydrogen/setup.test.js +0 -61
  92. package/dist/commands/hydrogen/shortcut.test.js +0 -30
  93. package/dist/commands/hydrogen/unlink.test.js +0 -36
  94. package/dist/commands/hydrogen/upgrade.test.js +0 -786
  95. package/dist/generator-templates/starter/remix.config.js +0 -24
  96. package/dist/lib/auth.test.js +0 -157
  97. package/dist/lib/check-lockfile.test.js +0 -81
  98. package/dist/lib/check-version.test.js +0 -86
  99. package/dist/lib/environment-variables.test.js +0 -149
  100. package/dist/lib/file.test.js +0 -68
  101. package/dist/lib/flags.test.js +0 -43
  102. package/dist/lib/get-oxygen-deployment-data.test.js +0 -120
  103. package/dist/lib/gid.test.js +0 -15
  104. package/dist/lib/graphql/admin/client.test.js +0 -76
  105. package/dist/lib/graphql/admin/create-storefront.test.js +0 -64
  106. package/dist/lib/graphql/admin/link-storefront.test.js +0 -38
  107. package/dist/lib/graphql/admin/list-environments.test.js +0 -44
  108. package/dist/lib/graphql/admin/list-storefronts.test.js +0 -44
  109. package/dist/lib/graphql/admin/pull-variables.test.js +0 -43
  110. package/dist/lib/graphql/business-platform/user-account.test.js +0 -80
  111. package/dist/lib/log.test.js +0 -92
  112. package/dist/lib/mini-oxygen/assets.js +0 -134
  113. package/dist/lib/mini-oxygen/mini-oxygen.test.js +0 -214
  114. package/dist/lib/mini-oxygen/workerd-inspector-logs.js +0 -227
  115. package/dist/lib/mini-oxygen/workerd-inspector-proxy.js +0 -200
  116. package/dist/lib/mini-oxygen/workerd-inspector.js +0 -219
  117. package/dist/lib/missing-routes.test.js +0 -45
  118. package/dist/lib/remix-version-check.test.js +0 -39
  119. package/dist/lib/remix-version-interop.test.js +0 -13
  120. package/dist/lib/setups/i18n/domains.test.js +0 -39
  121. package/dist/lib/setups/i18n/replacers.test.js +0 -261
  122. package/dist/lib/setups/i18n/subdomains.test.js +0 -39
  123. package/dist/lib/setups/i18n/subfolders.test.js +0 -39
  124. package/dist/lib/setups/routes/generate.test.js +0 -296
  125. package/dist/lib/shell.test.js +0 -111
  126. package/dist/lib/shopify-config.test.js +0 -199
  127. package/dist/lib/string.test.js +0 -16
  128. package/dist/lib/virtual-routes.test.js +0 -49
  129. package/dist/lib/vite/hydrogen-middleware.js +0 -82
  130. package/dist/lib/vite/mini-oxygen.js +0 -152
  131. package/dist/lib/vite/plugins.d.ts +0 -27
  132. package/dist/lib/vite/plugins.js +0 -139
  133. package/dist/lib/vite/shared.js +0 -10
  134. package/dist/lib/vite/utils.js +0 -55
  135. package/dist/lib/vite/worker-entry.js +0 -1518
  136. /package/dist/generator-templates/starter/{.eslintrc.js → .eslintrc.cjs} +0 -0
@@ -1,641 +0,0 @@
1
- import { fileURLToPath } from 'node:url';
2
- import { vi, describe, beforeEach, it, expect } from 'vitest';
3
- import { runInit } from './init.js';
4
- import { exec } from '@shopify/cli-kit/node/system';
5
- import { mockAndCaptureOutput } from '@shopify/cli-kit/node/testing/output';
6
- import { readAndParsePackageJson } from '@shopify/cli-kit/node/node-package-manager';
7
- import { writeFile, inTemporaryDirectory, readFile, isDirectory, removeFile, fileExists } from '@shopify/cli-kit/node/fs';
8
- import { joinPath, basename } from '@shopify/cli-kit/node/path';
9
- import { checkHydrogenVersion } from '../../lib/check-version.js';
10
- import { handleProjectLocation } from '../../lib/onboarding/common.js';
11
- import glob from 'fast-glob';
12
- import { getRepoNodeModules, getSkeletonSourceDir } from '../../lib/build.js';
13
- import { execAsync } from '../../lib/process.js';
14
- import { remove, createSymlink } from 'fs-extra/esm';
15
- import { runCheckRoutes } from './check.js';
16
- import { runCodegen } from './codegen.js';
17
- import { runBuild } from './build.js';
18
- import { runDev } from './dev.js';
19
-
20
- const { renderTasksHook } = vi.hoisted(() => ({ renderTasksHook: vi.fn() }));
21
- vi.mock("../../lib/check-version.js");
22
- vi.mock("../../lib/template-downloader.js", async () => ({
23
- getLatestTemplates: () => Promise.resolve({
24
- version: "",
25
- templatesDir: fileURLToPath(
26
- new URL("../../../../../templates", import.meta.url)
27
- ),
28
- examplesDir: fileURLToPath(
29
- new URL("../../../../../examples", import.meta.url)
30
- )
31
- })
32
- }));
33
- vi.mock("@shopify/cli-kit/node/ui", async () => {
34
- const original = await vi.importActual("@shopify/cli-kit/node/ui");
35
- return {
36
- ...original,
37
- renderConfirmationPrompt: vi.fn(),
38
- renderSelectPrompt: vi.fn(),
39
- renderTextPrompt: vi.fn(),
40
- renderInfo: vi.fn(),
41
- renderTasks: vi.fn(async (args) => {
42
- await original.renderTasks(args);
43
- renderTasksHook();
44
- })
45
- };
46
- });
47
- vi.mock(
48
- "@shopify/cli-kit/node/node-package-manager",
49
- async (importOriginal) => {
50
- const original = await importOriginal();
51
- return {
52
- ...original,
53
- getPackageManager: () => Promise.resolve("npm"),
54
- packageManagerFromUserAgent: () => "npm",
55
- installNodeModules: vi.fn(async ({ directory }) => {
56
- renderTasksHook.mockImplementationOnce(async () => {
57
- await writeFile(`${directory}/package-lock.json`, "{}");
58
- });
59
- await remove(joinPath(directory, "node_modules")).catch(() => {
60
- });
61
- await createSymlink(
62
- await getRepoNodeModules(),
63
- joinPath(directory, "node_modules")
64
- );
65
- })
66
- };
67
- }
68
- );
69
- vi.mock("../../lib/onboarding/common.js", async (importOriginal) => {
70
- const original = await importOriginal();
71
- return Object.keys(original).reduce((acc, item) => {
72
- const key = item;
73
- const value = original[key];
74
- if (typeof value === "function") {
75
- acc[key] = vi.fn(value);
76
- } else {
77
- acc[key] = value;
78
- }
79
- return acc;
80
- }, {});
81
- });
82
- describe("init", () => {
83
- const outputMock = mockAndCaptureOutput();
84
- beforeEach(() => {
85
- vi.clearAllMocks();
86
- vi.unstubAllEnvs();
87
- outputMock.clear();
88
- });
89
- it("checks Hydrogen version", async () => {
90
- await inTemporaryDirectory(async (tmpDir) => {
91
- const showUpgradeMock = vi.fn((param) => ({
92
- currentVersion: "1.0.0",
93
- newVersion: "1.0.1"
94
- }));
95
- vi.mocked(checkHydrogenVersion).mockResolvedValueOnce(showUpgradeMock);
96
- vi.mocked(handleProjectLocation).mockResolvedValueOnce(void 0);
97
- const project = await runInit({ path: tmpDir, git: false });
98
- expect(project).toBeFalsy();
99
- expect(checkHydrogenVersion).toHaveBeenCalledOnce();
100
- expect(showUpgradeMock).toHaveBeenCalledWith(
101
- expect.stringContaining("npm create @shopify/hydrogen@latest")
102
- );
103
- });
104
- });
105
- describe("remote templates", () => {
106
- it("throws for unknown templates", async () => {
107
- const processExit = vi.spyOn(process, "exit").mockImplementationOnce(() => {
108
- });
109
- await inTemporaryDirectory(async (tmpDir) => {
110
- await expect(
111
- runInit({
112
- path: tmpDir,
113
- git: false,
114
- language: "ts",
115
- template: "https://github.com/some/repo"
116
- })
117
- ).resolves.ok;
118
- });
119
- await vi.waitFor(() => expect(outputMock.error()).toMatch("--template"));
120
- expect(processExit).toHaveBeenCalledWith(1);
121
- processExit.mockRestore();
122
- });
123
- it("creates basic projects", async () => {
124
- await inTemporaryDirectory(async (tmpDir) => {
125
- await runInit({
126
- path: tmpDir,
127
- git: false,
128
- language: "ts",
129
- template: "hello-world"
130
- });
131
- const templateFiles = await glob("**/*", {
132
- cwd: getSkeletonSourceDir().replace("skeleton", "hello-world"),
133
- ignore: ["**/node_modules/**", "**/dist/**"]
134
- });
135
- const resultFiles = await glob("**/*", { cwd: tmpDir });
136
- const nonAppFiles = templateFiles.filter(
137
- (item) => !item.startsWith("app/")
138
- );
139
- expect(resultFiles).toEqual(expect.arrayContaining(nonAppFiles));
140
- expect(resultFiles).toContain("app/root.tsx");
141
- expect(resultFiles).toContain("app/entry.client.tsx");
142
- expect(resultFiles).toContain("app/entry.server.tsx");
143
- expect(resultFiles).not.toContain("app/components/Layout.tsx");
144
- expect(resultFiles).not.toContain("app/routes/_index.tsx");
145
- await expect(readFile(`${tmpDir}/package.json`)).resolves.toMatch(
146
- `"name": "hello-world"`
147
- );
148
- const output = outputMock.info();
149
- expect(output).toMatch("success");
150
- expect(output).not.toMatch("warning");
151
- expect(output).not.toMatch("Routes");
152
- expect(output).toMatch(/Language:\s*TypeScript/);
153
- expect(output).toMatch("Help");
154
- expect(output).toMatch("Next steps");
155
- expect(output).toMatch(
156
- // Output contains banner characters. USe [^\w]*? to match them.
157
- /Run `cd .*? &&[^\w]*?npm[^\w]*?install[^\w]*?&&[^\w]*?npm[^\w]*?run[^\w]*?dev`/ims
158
- );
159
- });
160
- });
161
- it("applies diff for examples", async () => {
162
- await inTemporaryDirectory(async (tmpDir) => {
163
- const exampleName = "third-party-queries-caching";
164
- await runInit({
165
- path: tmpDir,
166
- git: false,
167
- language: "ts",
168
- template: exampleName
169
- });
170
- const templatePath = getSkeletonSourceDir();
171
- const examplePath = templatePath.replace("templates", "examples").replace("skeleton", exampleName);
172
- const ignore = ["**/node_modules/**", "**/dist/**"];
173
- const resultFiles = await glob("**/*", { ignore, cwd: tmpDir });
174
- const exampleFiles = await glob("**/*", { ignore, cwd: examplePath });
175
- const templateFiles = (await glob("**/*", { ignore, cwd: templatePath })).filter((item) => !item.endsWith("CHANGELOG.md"));
176
- expect(resultFiles).toEqual(
177
- expect.arrayContaining([
178
- .../* @__PURE__ */ new Set([...templateFiles, ...exampleFiles])
179
- ])
180
- );
181
- const templatePkgJson = await readAndParsePackageJson(
182
- `${templatePath}/package.json`
183
- );
184
- const examplePkgJson = await readAndParsePackageJson(
185
- `${examplePath}/package.json`
186
- );
187
- const resultPkgJson = await readAndParsePackageJson(
188
- `${tmpDir}/package.json`
189
- );
190
- expect(resultPkgJson.name).toMatch(exampleName);
191
- expect(resultPkgJson.scripts).toEqual(
192
- expect.objectContaining(templatePkgJson.scripts)
193
- );
194
- expect(resultPkgJson.dependencies).toEqual(
195
- expect.objectContaining({
196
- ...templatePkgJson.dependencies,
197
- ...examplePkgJson.dependencies
198
- })
199
- );
200
- expect(resultPkgJson.devDependencies).toEqual(
201
- expect.objectContaining({
202
- ...templatePkgJson.devDependencies,
203
- ...examplePkgJson.devDependencies
204
- })
205
- );
206
- expect(resultPkgJson.peerDependencies).toEqual(
207
- expect.objectContaining({
208
- ...templatePkgJson.peerDependencies,
209
- ...examplePkgJson.peerDependencies
210
- })
211
- );
212
- expect(await readFile(joinPath(templatePath, "tsconfig.json"))).toEqual(
213
- await readFile(joinPath(tmpDir, "tsconfig.json"))
214
- );
215
- });
216
- });
217
- it("transpiles projects to JS", async () => {
218
- await inTemporaryDirectory(async (tmpDir) => {
219
- await runInit({
220
- path: tmpDir,
221
- git: false,
222
- language: "js",
223
- template: "hello-world"
224
- });
225
- const templateFiles = await glob("**/*", {
226
- cwd: getSkeletonSourceDir().replace("skeleton", "hello-world"),
227
- ignore: ["**/node_modules/**", "**/dist/**"]
228
- });
229
- const resultFiles = await glob("**/*", { cwd: tmpDir });
230
- expect(resultFiles).toEqual(
231
- expect.arrayContaining(
232
- templateFiles.filter((item) => !item.endsWith(".d.ts")).map(
233
- (item) => item.replace(/\.ts(x)?$/, ".js$1").replace(/tsconfig\.json$/, "jsconfig.json")
234
- )
235
- )
236
- );
237
- await expect(readFile(`${tmpDir}/server.js`)).resolves.toMatch(
238
- /export default {\n\s+\/\*\*.*?\*\/\n\s+async fetch\(\s*request,\s*env,\s*executionContext,?\s*\)/s
239
- );
240
- const output = outputMock.info();
241
- expect(output).toMatch("success");
242
- expect(output).not.toMatch("warning");
243
- expect(output).toMatch(/Language:\s*JavaScript/);
244
- });
245
- });
246
- });
247
- describe("local templates", () => {
248
- it("creates basic projects", async () => {
249
- await inTemporaryDirectory(async (tmpDir) => {
250
- await runInit({
251
- path: tmpDir,
252
- git: false,
253
- language: "ts",
254
- mockShop: true
255
- });
256
- const templateFiles = await glob("**/*", {
257
- cwd: getSkeletonSourceDir(),
258
- ignore: ["**/node_modules/**", "**/dist/**"]
259
- });
260
- const resultFiles = await glob("**/*", { cwd: tmpDir });
261
- const nonAppFiles = templateFiles.filter(
262
- (item) => !item.startsWith("app/")
263
- );
264
- expect(resultFiles).toEqual(expect.arrayContaining(nonAppFiles));
265
- expect(resultFiles).toContain("app/root.tsx");
266
- expect(resultFiles).toContain("app/entry.client.tsx");
267
- expect(resultFiles).toContain("app/entry.server.tsx");
268
- expect(resultFiles).toContain("app/components/Layout.tsx");
269
- expect(resultFiles).not.toContain("app/routes/_index.tsx");
270
- await expect(readFile(`${tmpDir}/server.ts`)).resolves.toEqual(
271
- await readFile(`${getSkeletonSourceDir()}/server.ts`)
272
- );
273
- await expect(readFile(`${tmpDir}/package.json`)).resolves.toMatch(
274
- `"name": "${basename(tmpDir)}"`
275
- );
276
- await expect(readFile(`${tmpDir}/.env`)).resolves.toMatch(
277
- `PUBLIC_STORE_DOMAIN="mock.shop"`
278
- );
279
- const output = outputMock.info();
280
- expect(output).toMatch("success");
281
- expect(output).not.toMatch("warning");
282
- expect(output).toMatch(basename(tmpDir));
283
- expect(output).not.toMatch("Routes");
284
- expect(output).toMatch(/Language:\s*TypeScript/);
285
- expect(output).toMatch("Help");
286
- expect(output).toMatch("Next steps");
287
- expect(output).toMatch(
288
- // Output contains banner characters. USe [^\w]*? to match them.
289
- /Run `cd .*? &&[^\w]*?npm[^\w]*?install[^\w]*?&&[^\w]*?npm[^\w]*?run[^\w]*?dev`/ims
290
- );
291
- });
292
- });
293
- it("creates projects with route files", async () => {
294
- await inTemporaryDirectory(async (tmpDir) => {
295
- await runInit({ path: tmpDir, git: false, routes: true, language: "ts" });
296
- const templateFiles = await glob("**/*", {
297
- cwd: getSkeletonSourceDir(),
298
- ignore: ["**/node_modules/**", "**/dist/**"]
299
- });
300
- const resultFiles = await glob("**/*", { cwd: tmpDir });
301
- expect(resultFiles).toEqual(expect.arrayContaining(templateFiles));
302
- expect(resultFiles).toContain("app/routes/_index.tsx");
303
- await expect(readFile(`${tmpDir}/server.ts`)).resolves.toEqual(
304
- await readFile(`${getSkeletonSourceDir()}/server.ts`)
305
- );
306
- const output = outputMock.info();
307
- expect(output).toMatch("success");
308
- expect(output).not.toMatch("warning");
309
- expect(output).toMatch(basename(tmpDir));
310
- expect(output).toMatch(/Language:\s*TypeScript/);
311
- expect(output).toMatch("Routes");
312
- expect(output).toMatch("Home (/ & /:catchAll)");
313
- expect(output).toMatch("Account (/account/*)");
314
- });
315
- });
316
- it("transpiles projects to JS", async () => {
317
- await inTemporaryDirectory(async (tmpDir) => {
318
- await runInit({ path: tmpDir, git: false, routes: true, language: "js" });
319
- const templateFiles = await glob("**/*", {
320
- cwd: getSkeletonSourceDir(),
321
- ignore: ["**/node_modules/**", "**/dist/**"]
322
- });
323
- const resultFiles = await glob("**/*", { cwd: tmpDir });
324
- expect(resultFiles).toEqual(
325
- expect.arrayContaining(
326
- templateFiles.filter((item) => !item.endsWith(".d.ts")).map(
327
- (item) => item.replace(/\.ts(x)?$/, ".js$1").replace(/tsconfig\.json$/, "jsconfig.json")
328
- )
329
- )
330
- );
331
- expect(resultFiles).toContain("app/routes/_index.jsx");
332
- await expect(readFile(`${tmpDir}/server.js`)).resolves.toMatch(
333
- /export default {\n\s+\/\*\*.*?\*\/\n\s+async fetch\(\s*request,\s*env,\s*executionContext,?\s*\)/s
334
- );
335
- const output = outputMock.info();
336
- expect(output).toMatch("success");
337
- expect(output).not.toMatch("warning");
338
- expect(output).toMatch(basename(tmpDir));
339
- expect(output).toMatch(/Language:\s*JavaScript/);
340
- expect(output).toMatch("Routes");
341
- expect(output).toMatch("Home (/ & /:catchAll)");
342
- expect(output).toMatch("Account (/account/*)");
343
- });
344
- });
345
- describe("styling libraries", () => {
346
- it("scaffolds Tailwind CSS", async () => {
347
- await inTemporaryDirectory(async (tmpDir) => {
348
- await runInit({
349
- path: tmpDir,
350
- git: false,
351
- language: "ts",
352
- styling: "tailwind"
353
- });
354
- await expect(
355
- readFile(`${tmpDir}/app/styles/tailwind.css`)
356
- ).resolves.toMatch(/@tailwind base;/);
357
- const rootFile = await readFile(`${tmpDir}/app/root.tsx`);
358
- await expect(rootFile).toMatch(/import tailwindCss from/);
359
- await expect(rootFile).toMatch(
360
- /export function links\(\) \{.*?return \[.*\{rel: 'stylesheet', href: tailwindCss\}/ims
361
- );
362
- const output = outputMock.info();
363
- expect(output).toMatch("success");
364
- expect(output).not.toMatch("warning");
365
- expect(output).toMatch(/Styling:\s*Tailwind/);
366
- });
367
- });
368
- it("scaffolds CSS Modules", async () => {
369
- await inTemporaryDirectory(async (tmpDir) => {
370
- await runInit({
371
- path: tmpDir,
372
- git: false,
373
- language: "ts",
374
- styling: "css-modules"
375
- });
376
- await expect(readFile(`${tmpDir}/package.json`)).resolves.toMatch(
377
- `"@remix-run/css-bundle": "`
378
- );
379
- const rootFile = await readFile(`${tmpDir}/app/root.tsx`);
380
- await expect(rootFile).toMatch(/import {cssBundleHref} from/);
381
- await expect(rootFile).toMatch(
382
- /export function links\(\) \{.*?return \[.*\{rel: 'stylesheet', href: cssBundleHref\}/ims
383
- );
384
- const output = outputMock.info();
385
- expect(output).toMatch("success");
386
- expect(output).not.toMatch("warning");
387
- expect(output).toMatch(/Styling:\s*CSS Modules/);
388
- });
389
- });
390
- it("scaffolds Vanilla Extract", async () => {
391
- await inTemporaryDirectory(async (tmpDir) => {
392
- await runInit({
393
- path: tmpDir,
394
- git: false,
395
- language: "ts",
396
- styling: "vanilla-extract"
397
- });
398
- const packageJson = await readFile(`${tmpDir}/package.json`);
399
- expect(packageJson).toMatch(/"@remix-run\/css-bundle": "/);
400
- expect(packageJson).toMatch(/"@vanilla-extract\/css": "/);
401
- const rootFile = await readFile(`${tmpDir}/app/root.tsx`);
402
- await expect(rootFile).toMatch(/import {cssBundleHref} from/);
403
- await expect(rootFile).toMatch(
404
- /export function links\(\) \{.*?return \[.*\{rel: 'stylesheet', href: cssBundleHref\}/ims
405
- );
406
- const output = outputMock.info();
407
- expect(output).toMatch("success");
408
- expect(output).not.toMatch("warning");
409
- expect(output).toMatch(/Styling:\s*Vanilla Extract/);
410
- });
411
- });
412
- });
413
- describe("i18n strategies", () => {
414
- it("scaffolds i18n with domains strategy", async () => {
415
- await inTemporaryDirectory(async (tmpDir) => {
416
- await runInit({
417
- path: tmpDir,
418
- git: false,
419
- language: "ts",
420
- i18n: "domains",
421
- routes: true
422
- });
423
- const resultFiles = await glob("**/*", { cwd: tmpDir });
424
- expect(resultFiles).toContain("app/routes/_index.tsx");
425
- const serverFile = await readFile(`${tmpDir}/server.ts`);
426
- expect(serverFile).toMatch(/i18n: getLocaleFromRequest\(request\),/);
427
- expect(serverFile).toMatch(/domain = url.hostname/);
428
- const output = outputMock.info();
429
- expect(output).toMatch("success");
430
- expect(output).not.toMatch("warning");
431
- expect(output).toMatch(/Markets:\s*Top-level domains/);
432
- });
433
- });
434
- it("scaffolds i18n with subdomains strategy", async () => {
435
- await inTemporaryDirectory(async (tmpDir) => {
436
- await runInit({
437
- path: tmpDir,
438
- git: false,
439
- language: "ts",
440
- i18n: "subdomains",
441
- routes: true
442
- });
443
- const resultFiles = await glob("**/*", { cwd: tmpDir });
444
- expect(resultFiles).toContain("app/routes/_index.tsx");
445
- const serverFile = await readFile(`${tmpDir}/server.ts`);
446
- expect(serverFile).toMatch(/i18n: getLocaleFromRequest\(request\),/);
447
- expect(serverFile).toMatch(/firstSubdomain = url.hostname/);
448
- const output = outputMock.info();
449
- expect(output).toMatch("success");
450
- expect(output).not.toMatch("warning");
451
- expect(output).toMatch(/Markets:\s*Subdomains/);
452
- });
453
- });
454
- it("scaffolds i18n with subfolders strategy", async () => {
455
- await inTemporaryDirectory(async (tmpDir) => {
456
- await runInit({
457
- path: tmpDir,
458
- git: false,
459
- language: "ts",
460
- i18n: "subfolders",
461
- routes: true
462
- });
463
- const resultFiles = await glob("**/*", { cwd: tmpDir });
464
- expect(resultFiles).toContain("app/routes/($locale)._index.tsx");
465
- expect(resultFiles).toContain("app/routes/($locale).tsx");
466
- const serverFile = await readFile(`${tmpDir}/server.ts`);
467
- expect(serverFile).toMatch(/i18n: getLocaleFromRequest\(request\),/);
468
- expect(serverFile).toMatch(/url.pathname/);
469
- const output = outputMock.info();
470
- expect(output).toMatch("success");
471
- expect(output).not.toMatch("warning");
472
- expect(output).toMatch(/Markets:\s*Subfolders/);
473
- });
474
- });
475
- });
476
- describe("git", () => {
477
- it("initializes a git repository and creates initial commits", async () => {
478
- await inTemporaryDirectory(async (tmpDir) => {
479
- await runInit({
480
- path: tmpDir,
481
- git: true,
482
- language: "js",
483
- styling: "tailwind",
484
- i18n: "domains",
485
- routes: true,
486
- installDeps: true
487
- });
488
- expect(isDirectory(`${tmpDir}/.git`)).resolves.toBeTruthy();
489
- const { stdout: gitLog } = await execAsync(`git log --oneline`, {
490
- cwd: tmpDir
491
- });
492
- expect(gitLog.split("\n")).toEqual(
493
- expect.arrayContaining([
494
- expect.stringContaining("Lockfile"),
495
- expect.stringContaining("Generate routes for core functionality"),
496
- expect.stringContaining("Setup Tailwind"),
497
- expect.stringContaining("Scaffold Storefront")
498
- ])
499
- );
500
- });
501
- });
502
- });
503
- describe("project validity", () => {
504
- it("typechecks the project", async () => {
505
- await inTemporaryDirectory(async (tmpDir) => {
506
- await runInit({
507
- path: tmpDir,
508
- git: true,
509
- language: "ts",
510
- styling: "tailwind",
511
- i18n: "subfolders",
512
- routes: true,
513
- installDeps: true
514
- });
515
- await expect(
516
- exec("npm", ["run", "typecheck"], { cwd: tmpDir })
517
- ).resolves.not.toThrow();
518
- });
519
- });
520
- it("contains all standard routes", async () => {
521
- await inTemporaryDirectory(async (tmpDir) => {
522
- await runInit({
523
- path: tmpDir,
524
- git: true,
525
- language: "ts",
526
- i18n: "subfolders",
527
- routes: true,
528
- installDeps: true
529
- });
530
- outputMock.clear();
531
- await runCheckRoutes({ directory: tmpDir });
532
- const output = outputMock.info();
533
- expect(output).toMatch("success");
534
- });
535
- });
536
- it("supports codegen", async () => {
537
- await inTemporaryDirectory(async (tmpDir) => {
538
- await runInit({
539
- path: tmpDir,
540
- git: true,
541
- language: "ts",
542
- routes: true,
543
- installDeps: true
544
- });
545
- outputMock.clear();
546
- const codegenFile = `${tmpDir}/storefrontapi.generated.d.ts`;
547
- const codegenFromTemplate = await readFile(codegenFile);
548
- expect(codegenFromTemplate).toBeTruthy();
549
- await removeFile(codegenFile);
550
- expect(fileExists(codegenFile)).resolves.toBeFalsy();
551
- await expect(runCodegen({ directory: tmpDir })).resolves.not.toThrow();
552
- const output = outputMock.info();
553
- expect(output).toMatch("success");
554
- await expect(readFile(codegenFile)).resolves.toEqual(
555
- codegenFromTemplate
556
- );
557
- });
558
- });
559
- it("builds the generated project", async () => {
560
- await inTemporaryDirectory(async (tmpDir) => {
561
- await runInit({
562
- path: tmpDir,
563
- git: true,
564
- language: "ts",
565
- styling: "postcss",
566
- i18n: "subfolders",
567
- routes: true,
568
- installDeps: true
569
- });
570
- outputMock.clear();
571
- vi.stubEnv("NODE_ENV", "production");
572
- await expect(runBuild({ directory: tmpDir })).resolves.not.toThrow();
573
- const expectedBundlePath = "dist/worker/index.js";
574
- const output = outputMock.output();
575
- expect(output).toMatch(expectedBundlePath);
576
- expect(
577
- fileExists(joinPath(tmpDir, expectedBundlePath))
578
- ).resolves.toBeTruthy();
579
- const mb = Number(
580
- output.match(/index\.js\s+([\d.]+)\s+MB/)?.[1] || ""
581
- );
582
- expect(mb).toBeGreaterThan(0);
583
- expect(mb).toBeLessThan(1);
584
- expect(output).toMatch("Complete analysis: file://");
585
- const clientAnalysisPath = "dist/worker/client-bundle-analyzer.html";
586
- const workerAnalysisPath = "dist/worker/worker-bundle-analyzer.html";
587
- await expect(
588
- fileExists(joinPath(tmpDir, clientAnalysisPath))
589
- ).resolves.toBeTruthy();
590
- await expect(
591
- fileExists(joinPath(tmpDir, workerAnalysisPath))
592
- ).resolves.toBeTruthy();
593
- await expect(
594
- readFile(joinPath(tmpDir, clientAnalysisPath))
595
- ).resolves.toMatch(/globalThis\.METAFILE = '.+';/g);
596
- await expect(
597
- readFile(joinPath(tmpDir, workerAnalysisPath))
598
- ).resolves.toMatch(/globalThis\.METAFILE = '.+';/g);
599
- });
600
- });
601
- it("runs dev in the generated project", async () => {
602
- await inTemporaryDirectory(async (tmpDir) => {
603
- await runInit({
604
- path: tmpDir,
605
- git: true,
606
- language: "ts",
607
- styling: "postcss",
608
- i18n: "subfolders",
609
- routes: true,
610
- installDeps: true
611
- });
612
- outputMock.clear();
613
- const port = 1337;
614
- const { close } = await runDev({
615
- path: tmpDir,
616
- port,
617
- inspectorPort: 9e3,
618
- disableVirtualRoutes: true,
619
- disableVersionCheck: true
620
- });
621
- try {
622
- await vi.waitFor(
623
- () => expect(outputMock.output()).toMatch("success"),
624
- { timeout: 5e3 }
625
- );
626
- expect(outputMock.output()).toMatch(/View Hydrogen app/i);
627
- await expect(
628
- fileExists(joinPath(tmpDir, "dist", "worker", "index.js"))
629
- ).resolves.toBeTruthy();
630
- const response = await fetch(`http://localhost:${port}`);
631
- expect(response.status).toEqual(200);
632
- expect(response.headers.get("content-type")).toEqual("text/html");
633
- await expect(response.text()).resolves.toMatch("Mock.shop");
634
- } finally {
635
- await close();
636
- }
637
- });
638
- });
639
- });
640
- });
641
- });