@shopify/cli-hydrogen 5.1.1 → 5.1.2
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.
- package/dist/commands/hydrogen/build.js +6 -4
- package/dist/commands/hydrogen/check.js +1 -1
- package/dist/commands/hydrogen/codegen-unstable.js +3 -3
- package/dist/commands/hydrogen/init.test.js +197 -51
- package/dist/commands/hydrogen/setup.js +2 -2
- package/dist/commands/hydrogen/setup.test.js +62 -0
- package/dist/generator-templates/starter/package.json +4 -4
- package/dist/lib/file.test.js +4 -5
- package/dist/lib/onboarding/remote.js +8 -3
- package/dist/lib/setups/routes/generate.test.js +10 -11
- package/oclif.manifest.json +1 -1
- package/package.json +5 -5
|
@@ -42,12 +42,12 @@ class Build extends Command {
|
|
|
42
42
|
await runBuild({
|
|
43
43
|
...flagsToCamelObject(flags),
|
|
44
44
|
useCodegen: flags["codegen-unstable"],
|
|
45
|
-
|
|
45
|
+
directory
|
|
46
46
|
});
|
|
47
47
|
}
|
|
48
48
|
}
|
|
49
49
|
async function runBuild({
|
|
50
|
-
|
|
50
|
+
directory,
|
|
51
51
|
useCodegen = false,
|
|
52
52
|
codegenConfigPath,
|
|
53
53
|
sourcemap = false,
|
|
@@ -56,7 +56,7 @@ async function runBuild({
|
|
|
56
56
|
if (!process.env.NODE_ENV) {
|
|
57
57
|
process.env.NODE_ENV = "production";
|
|
58
58
|
}
|
|
59
|
-
const { root, buildPath, buildPathClient, buildPathWorkerFile, publicPath } = getProjectPaths(
|
|
59
|
+
const { root, buildPath, buildPathClient, buildPathWorkerFile, publicPath } = getProjectPaths(directory);
|
|
60
60
|
await Promise.all([checkLockfileStatus(root), muteRemixLogs()]);
|
|
61
61
|
console.time(LOG_WORKER_BUILT);
|
|
62
62
|
outputInfo(`
|
|
@@ -125,7 +125,9 @@ This build is missing ${missingRoutes.length} route${missingRoutes.length > 1 ?
|
|
|
125
125
|
);
|
|
126
126
|
}
|
|
127
127
|
}
|
|
128
|
-
process.
|
|
128
|
+
if (!process.env.SHOPIFY_UNIT_TEST) {
|
|
129
|
+
process.exit(0);
|
|
130
|
+
}
|
|
129
131
|
}
|
|
130
132
|
async function copyPublicFiles(publicPath, buildPathClient) {
|
|
131
133
|
return copyFile(publicPath, buildPathClient);
|
|
@@ -29,17 +29,17 @@ class Codegen extends Command {
|
|
|
29
29
|
const directory = flags.path ? path.resolve(flags.path) : process.cwd();
|
|
30
30
|
await runCodegen({
|
|
31
31
|
...flagsToCamelObject(flags),
|
|
32
|
-
|
|
32
|
+
directory
|
|
33
33
|
});
|
|
34
34
|
}
|
|
35
35
|
}
|
|
36
36
|
async function runCodegen({
|
|
37
|
-
|
|
37
|
+
directory,
|
|
38
38
|
codegenConfigPath,
|
|
39
39
|
forceSfapiVersion,
|
|
40
40
|
watch
|
|
41
41
|
}) {
|
|
42
|
-
const { root } = getProjectPaths(
|
|
42
|
+
const { root } = getProjectPaths(directory);
|
|
43
43
|
const remixConfig = await getRemixConfig(root);
|
|
44
44
|
console.log("");
|
|
45
45
|
const generatedFiles = await codegen({
|
|
@@ -1,39 +1,30 @@
|
|
|
1
1
|
import { fileURLToPath } from 'node:url';
|
|
2
2
|
import { vi, describe, beforeEach, it, expect } from 'vitest';
|
|
3
|
-
import { temporaryDirectoryTask } from 'tempy';
|
|
4
3
|
import { runInit } from './init.js';
|
|
5
4
|
import { exec } from '@shopify/cli-kit/node/system';
|
|
6
5
|
import { mockAndCaptureOutput } from '@shopify/cli-kit/node/testing/output';
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
6
|
+
import { writeFile, inTemporaryDirectory, readFile, isDirectory, removeFile, fileExists } from '@shopify/cli-kit/node/fs';
|
|
7
|
+
import { joinPath, basename } from '@shopify/cli-kit/node/path';
|
|
9
8
|
import { checkHydrogenVersion } from '../../lib/check-version.js';
|
|
10
9
|
import { handleProjectLocation } from '../../lib/onboarding/common.js';
|
|
11
10
|
import glob from 'fast-glob';
|
|
12
11
|
import { getSkeletonSourceDir } from '../../lib/build.js';
|
|
13
12
|
import { execAsync } from '../../lib/process.js';
|
|
14
13
|
import { rmdir, symlink } from 'fs-extra';
|
|
14
|
+
import { runCheckRoutes } from './check.js';
|
|
15
|
+
import { runCodegen } from './codegen-unstable.js';
|
|
16
|
+
import { runBuild } from './build.js';
|
|
15
17
|
|
|
18
|
+
const { renderTasksHook } = vi.hoisted(() => ({ renderTasksHook: vi.fn() }));
|
|
19
|
+
vi.mock("../../lib/check-version.js");
|
|
16
20
|
vi.mock("../../lib/template-downloader.js", async () => ({
|
|
17
|
-
getLatestTemplates: () => Promise.resolve({
|
|
21
|
+
getLatestTemplates: () => Promise.resolve({
|
|
22
|
+
version: "",
|
|
23
|
+
templatesDir: fileURLToPath(
|
|
24
|
+
new URL("../../../../../templates", import.meta.url)
|
|
25
|
+
)
|
|
26
|
+
})
|
|
18
27
|
}));
|
|
19
|
-
vi.mock(
|
|
20
|
-
"@shopify/cli-kit/node/node-package-manager",
|
|
21
|
-
async (importOriginal) => {
|
|
22
|
-
const original = await importOriginal();
|
|
23
|
-
return {
|
|
24
|
-
...original,
|
|
25
|
-
installNodeModules: vi.fn(),
|
|
26
|
-
getPackageManager: () => Promise.resolve("npm"),
|
|
27
|
-
packageManagerUsedForCreating: () => Promise.resolve("npm")
|
|
28
|
-
};
|
|
29
|
-
}
|
|
30
|
-
);
|
|
31
|
-
vi.mock("../../lib/check-version.js");
|
|
32
|
-
const { renderTasksHook } = vi.hoisted(() => {
|
|
33
|
-
return {
|
|
34
|
-
renderTasksHook: vi.fn()
|
|
35
|
-
};
|
|
36
|
-
});
|
|
37
28
|
vi.mock("@shopify/cli-kit/node/ui", async () => {
|
|
38
29
|
const original = await vi.importActual("@shopify/cli-kit/node/ui");
|
|
39
30
|
return {
|
|
@@ -48,6 +39,30 @@ vi.mock("@shopify/cli-kit/node/ui", async () => {
|
|
|
48
39
|
})
|
|
49
40
|
};
|
|
50
41
|
});
|
|
42
|
+
vi.mock(
|
|
43
|
+
"@shopify/cli-kit/node/node-package-manager",
|
|
44
|
+
async (importOriginal) => {
|
|
45
|
+
const original = await importOriginal();
|
|
46
|
+
return {
|
|
47
|
+
...original,
|
|
48
|
+
getPackageManager: () => Promise.resolve("npm"),
|
|
49
|
+
packageManagerUsedForCreating: () => Promise.resolve("npm"),
|
|
50
|
+
installNodeModules: vi.fn(async ({ directory }) => {
|
|
51
|
+
renderTasksHook.mockImplementationOnce(async () => {
|
|
52
|
+
await writeFile(`${directory}/package-lock.json`, "{}");
|
|
53
|
+
});
|
|
54
|
+
await rmdir(joinPath(directory, "node_modules")).catch(() => {
|
|
55
|
+
});
|
|
56
|
+
await symlink(
|
|
57
|
+
fileURLToPath(
|
|
58
|
+
new URL("../../../../../node_modules", import.meta.url)
|
|
59
|
+
),
|
|
60
|
+
joinPath(directory, "node_modules")
|
|
61
|
+
);
|
|
62
|
+
})
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
);
|
|
51
66
|
vi.mock("../../lib/onboarding/common.js", async (importOriginal) => {
|
|
52
67
|
const original = await importOriginal();
|
|
53
68
|
return Object.keys(original).reduce((acc, item) => {
|
|
@@ -65,10 +80,11 @@ describe("init", () => {
|
|
|
65
80
|
const outputMock = mockAndCaptureOutput();
|
|
66
81
|
beforeEach(() => {
|
|
67
82
|
vi.clearAllMocks();
|
|
83
|
+
vi.unstubAllEnvs();
|
|
68
84
|
outputMock.clear();
|
|
69
85
|
});
|
|
70
86
|
it("checks Hydrogen version", async () => {
|
|
71
|
-
await
|
|
87
|
+
await inTemporaryDirectory(async (tmpDir) => {
|
|
72
88
|
const showUpgradeMock = vi.fn((param) => ({
|
|
73
89
|
currentVersion: "1.0.0",
|
|
74
90
|
newVersion: "1.0.1"
|
|
@@ -83,9 +99,89 @@ describe("init", () => {
|
|
|
83
99
|
);
|
|
84
100
|
});
|
|
85
101
|
});
|
|
102
|
+
describe("remote templates", () => {
|
|
103
|
+
it("throws for unknown templates", async () => {
|
|
104
|
+
await inTemporaryDirectory(async (tmpDir) => {
|
|
105
|
+
await expect(
|
|
106
|
+
runInit({
|
|
107
|
+
path: tmpDir,
|
|
108
|
+
git: false,
|
|
109
|
+
language: "ts",
|
|
110
|
+
template: "https://github.com/some/repo"
|
|
111
|
+
})
|
|
112
|
+
).rejects.toThrow("supported");
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
it("creates basic projects", async () => {
|
|
116
|
+
await inTemporaryDirectory(async (tmpDir) => {
|
|
117
|
+
await runInit({
|
|
118
|
+
path: tmpDir,
|
|
119
|
+
git: false,
|
|
120
|
+
language: "ts",
|
|
121
|
+
template: "hello-world"
|
|
122
|
+
});
|
|
123
|
+
const helloWorldFiles = await glob("**/*", {
|
|
124
|
+
cwd: getSkeletonSourceDir().replace("skeleton", "hello-world"),
|
|
125
|
+
ignore: ["**/node_modules/**", "**/dist/**"]
|
|
126
|
+
});
|
|
127
|
+
const projectFiles = await glob("**/*", { cwd: tmpDir });
|
|
128
|
+
const nonAppFiles = helloWorldFiles.filter(
|
|
129
|
+
(item) => !item.startsWith("app/")
|
|
130
|
+
);
|
|
131
|
+
expect(projectFiles).toEqual(expect.arrayContaining(nonAppFiles));
|
|
132
|
+
expect(projectFiles).toContain("app/root.tsx");
|
|
133
|
+
expect(projectFiles).toContain("app/entry.client.tsx");
|
|
134
|
+
expect(projectFiles).toContain("app/entry.server.tsx");
|
|
135
|
+
expect(projectFiles).not.toContain("app/components/Layout.tsx");
|
|
136
|
+
expect(projectFiles).not.toContain("app/routes/_index.tsx");
|
|
137
|
+
await expect(readFile(`${tmpDir}/package.json`)).resolves.toMatch(
|
|
138
|
+
`"name": "hello-world"`
|
|
139
|
+
);
|
|
140
|
+
const output = outputMock.info();
|
|
141
|
+
expect(output).toMatch("success");
|
|
142
|
+
expect(output).not.toMatch("warning");
|
|
143
|
+
expect(output).not.toMatch("Routes");
|
|
144
|
+
expect(output).toMatch(/Language:\s*TypeScript/);
|
|
145
|
+
expect(output).toMatch("Help");
|
|
146
|
+
expect(output).toMatch("Next steps");
|
|
147
|
+
expect(output).toMatch(
|
|
148
|
+
/Run `cd .*? &&[^\w]*?npm[^\w]*?install[^\w]*?&&[^\w]*?npm[^\w]*?run[^\w]*?dev`/ims
|
|
149
|
+
);
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
it("transpiles projects to JS", async () => {
|
|
153
|
+
await inTemporaryDirectory(async (tmpDir) => {
|
|
154
|
+
await runInit({
|
|
155
|
+
path: tmpDir,
|
|
156
|
+
git: false,
|
|
157
|
+
language: "js",
|
|
158
|
+
template: "hello-world"
|
|
159
|
+
});
|
|
160
|
+
const helloWorldFiles = await glob("**/*", {
|
|
161
|
+
cwd: getSkeletonSourceDir().replace("skeleton", "hello-world"),
|
|
162
|
+
ignore: ["**/node_modules/**", "**/dist/**"]
|
|
163
|
+
});
|
|
164
|
+
const projectFiles = await glob("**/*", { cwd: tmpDir });
|
|
165
|
+
expect(projectFiles).toEqual(
|
|
166
|
+
expect.arrayContaining(
|
|
167
|
+
helloWorldFiles.filter((item) => !item.endsWith(".d.ts")).map(
|
|
168
|
+
(item) => item.replace(/\.ts(x)?$/, ".js$1").replace(/tsconfig\.json$/, "jsconfig.json")
|
|
169
|
+
)
|
|
170
|
+
)
|
|
171
|
+
);
|
|
172
|
+
await expect(readFile(`${tmpDir}/server.js`)).resolves.toMatch(
|
|
173
|
+
/export default {\n\s+async fetch\(\s*request,\s*env,\s*executionContext,?\s*\)/
|
|
174
|
+
);
|
|
175
|
+
const output = outputMock.info();
|
|
176
|
+
expect(output).toMatch("success");
|
|
177
|
+
expect(output).not.toMatch("warning");
|
|
178
|
+
expect(output).toMatch(/Language:\s*JavaScript/);
|
|
179
|
+
});
|
|
180
|
+
});
|
|
181
|
+
});
|
|
86
182
|
describe("local templates", () => {
|
|
87
183
|
it("creates basic projects", async () => {
|
|
88
|
-
await
|
|
184
|
+
await inTemporaryDirectory(async (tmpDir) => {
|
|
89
185
|
await runInit({
|
|
90
186
|
path: tmpDir,
|
|
91
187
|
git: false,
|
|
@@ -129,7 +225,7 @@ describe("init", () => {
|
|
|
129
225
|
});
|
|
130
226
|
});
|
|
131
227
|
it("creates projects with route files", async () => {
|
|
132
|
-
await
|
|
228
|
+
await inTemporaryDirectory(async (tmpDir) => {
|
|
133
229
|
await runInit({ path: tmpDir, git: false, routes: true, language: "ts" });
|
|
134
230
|
const skeletonFiles = await glob("**/*", {
|
|
135
231
|
cwd: getSkeletonSourceDir(),
|
|
@@ -152,7 +248,7 @@ describe("init", () => {
|
|
|
152
248
|
});
|
|
153
249
|
});
|
|
154
250
|
it("transpiles projects to JS", async () => {
|
|
155
|
-
await
|
|
251
|
+
await inTemporaryDirectory(async (tmpDir) => {
|
|
156
252
|
await runInit({ path: tmpDir, git: false, routes: true, language: "js" });
|
|
157
253
|
const skeletonFiles = await glob("**/*", {
|
|
158
254
|
cwd: getSkeletonSourceDir(),
|
|
@@ -182,7 +278,7 @@ describe("init", () => {
|
|
|
182
278
|
});
|
|
183
279
|
describe("styling libraries", () => {
|
|
184
280
|
it("scaffolds Tailwind CSS", async () => {
|
|
185
|
-
await
|
|
281
|
+
await inTemporaryDirectory(async (tmpDir) => {
|
|
186
282
|
await runInit({
|
|
187
283
|
path: tmpDir,
|
|
188
284
|
git: false,
|
|
@@ -207,7 +303,7 @@ describe("init", () => {
|
|
|
207
303
|
});
|
|
208
304
|
});
|
|
209
305
|
it("scaffolds CSS Modules", async () => {
|
|
210
|
-
await
|
|
306
|
+
await inTemporaryDirectory(async (tmpDir) => {
|
|
211
307
|
await runInit({
|
|
212
308
|
path: tmpDir,
|
|
213
309
|
git: false,
|
|
@@ -229,7 +325,7 @@ describe("init", () => {
|
|
|
229
325
|
});
|
|
230
326
|
});
|
|
231
327
|
it("scaffolds Vanilla Extract", async () => {
|
|
232
|
-
await
|
|
328
|
+
await inTemporaryDirectory(async (tmpDir) => {
|
|
233
329
|
await runInit({
|
|
234
330
|
path: tmpDir,
|
|
235
331
|
git: false,
|
|
@@ -253,7 +349,7 @@ describe("init", () => {
|
|
|
253
349
|
});
|
|
254
350
|
describe("i18n strategies", () => {
|
|
255
351
|
it("scaffolds i18n with domains strategy", async () => {
|
|
256
|
-
await
|
|
352
|
+
await inTemporaryDirectory(async (tmpDir) => {
|
|
257
353
|
await runInit({
|
|
258
354
|
path: tmpDir,
|
|
259
355
|
git: false,
|
|
@@ -273,7 +369,7 @@ describe("init", () => {
|
|
|
273
369
|
});
|
|
274
370
|
});
|
|
275
371
|
it("scaffolds i18n with subdomains strategy", async () => {
|
|
276
|
-
await
|
|
372
|
+
await inTemporaryDirectory(async (tmpDir) => {
|
|
277
373
|
await runInit({
|
|
278
374
|
path: tmpDir,
|
|
279
375
|
git: false,
|
|
@@ -293,7 +389,7 @@ describe("init", () => {
|
|
|
293
389
|
});
|
|
294
390
|
});
|
|
295
391
|
it("scaffolds i18n with subfolders strategy", async () => {
|
|
296
|
-
await
|
|
392
|
+
await inTemporaryDirectory(async (tmpDir) => {
|
|
297
393
|
await runInit({
|
|
298
394
|
path: tmpDir,
|
|
299
395
|
git: false,
|
|
@@ -315,10 +411,7 @@ describe("init", () => {
|
|
|
315
411
|
});
|
|
316
412
|
describe("git", () => {
|
|
317
413
|
it("initializes a git repository and creates initial commits", async () => {
|
|
318
|
-
await
|
|
319
|
-
renderTasksHook.mockImplementationOnce(async () => {
|
|
320
|
-
await writeFile(`${tmpDir}/package-lock.json`, "{}");
|
|
321
|
-
});
|
|
414
|
+
await inTemporaryDirectory(async (tmpDir) => {
|
|
322
415
|
await runInit({
|
|
323
416
|
path: tmpDir,
|
|
324
417
|
git: true,
|
|
@@ -345,10 +438,7 @@ describe("init", () => {
|
|
|
345
438
|
});
|
|
346
439
|
describe("project validity", () => {
|
|
347
440
|
it("typechecks the project", async () => {
|
|
348
|
-
await
|
|
349
|
-
renderTasksHook.mockImplementationOnce(async () => {
|
|
350
|
-
await writeFile(`${tmpDir}/package-lock.json`, "{}");
|
|
351
|
-
});
|
|
441
|
+
await inTemporaryDirectory(async (tmpDir) => {
|
|
352
442
|
await runInit({
|
|
353
443
|
path: tmpDir,
|
|
354
444
|
git: true,
|
|
@@ -358,21 +448,77 @@ describe("init", () => {
|
|
|
358
448
|
routes: true,
|
|
359
449
|
installDeps: true
|
|
360
450
|
});
|
|
361
|
-
await rmdir(joinPath(tmpDir, "node_modules")).catch(() => {
|
|
362
|
-
});
|
|
363
|
-
await symlink(
|
|
364
|
-
fileURLToPath(
|
|
365
|
-
new URL("../../../../../node_modules", import.meta.url)
|
|
366
|
-
),
|
|
367
|
-
joinPath(tmpDir, "node_modules")
|
|
368
|
-
);
|
|
369
451
|
await expect(
|
|
370
|
-
exec("npm", ["run", "typecheck"], {
|
|
371
|
-
cwd: tmpDir
|
|
372
|
-
})
|
|
452
|
+
exec("npm", ["run", "typecheck"], { cwd: tmpDir })
|
|
373
453
|
).resolves.not.toThrow();
|
|
374
454
|
});
|
|
375
455
|
});
|
|
456
|
+
it("contains all standard routes", async () => {
|
|
457
|
+
await inTemporaryDirectory(async (tmpDir) => {
|
|
458
|
+
await runInit({
|
|
459
|
+
path: tmpDir,
|
|
460
|
+
git: true,
|
|
461
|
+
language: "ts",
|
|
462
|
+
i18n: "subfolders",
|
|
463
|
+
routes: true,
|
|
464
|
+
installDeps: true
|
|
465
|
+
});
|
|
466
|
+
outputMock.clear();
|
|
467
|
+
await runCheckRoutes({ directory: tmpDir });
|
|
468
|
+
const output = outputMock.info();
|
|
469
|
+
expect(output).toMatch("success");
|
|
470
|
+
});
|
|
471
|
+
});
|
|
472
|
+
it("supports codegen", async () => {
|
|
473
|
+
await inTemporaryDirectory(async (tmpDir) => {
|
|
474
|
+
await runInit({
|
|
475
|
+
path: tmpDir,
|
|
476
|
+
git: true,
|
|
477
|
+
language: "ts",
|
|
478
|
+
routes: true,
|
|
479
|
+
installDeps: true
|
|
480
|
+
});
|
|
481
|
+
outputMock.clear();
|
|
482
|
+
const codegenFile = `${tmpDir}/storefrontapi.generated.d.ts`;
|
|
483
|
+
const codegenFromTemplate = await readFile(codegenFile);
|
|
484
|
+
expect(codegenFromTemplate).toBeTruthy();
|
|
485
|
+
await removeFile(codegenFile);
|
|
486
|
+
expect(fileExists(codegenFile)).resolves.toBeFalsy();
|
|
487
|
+
await expect(runCodegen({ directory: tmpDir })).resolves.not.toThrow();
|
|
488
|
+
const output = outputMock.info();
|
|
489
|
+
expect(output).toMatch("success");
|
|
490
|
+
await expect(readFile(codegenFile)).resolves.toEqual(
|
|
491
|
+
codegenFromTemplate
|
|
492
|
+
);
|
|
493
|
+
});
|
|
494
|
+
});
|
|
495
|
+
it("builds the generated project", async () => {
|
|
496
|
+
await inTemporaryDirectory(async (tmpDir) => {
|
|
497
|
+
await runInit({
|
|
498
|
+
path: tmpDir,
|
|
499
|
+
git: true,
|
|
500
|
+
language: "ts",
|
|
501
|
+
styling: "postcss",
|
|
502
|
+
i18n: "subfolders",
|
|
503
|
+
routes: true,
|
|
504
|
+
installDeps: true
|
|
505
|
+
});
|
|
506
|
+
outputMock.clear();
|
|
507
|
+
vi.stubEnv("NODE_ENV", "production");
|
|
508
|
+
await expect(runBuild({ directory: tmpDir })).resolves.not.toThrow();
|
|
509
|
+
const expectedBundlePath = "dist/worker/index.js";
|
|
510
|
+
const output = outputMock.output();
|
|
511
|
+
expect(output).toMatch(expectedBundlePath);
|
|
512
|
+
expect(
|
|
513
|
+
fileExists(joinPath(tmpDir, expectedBundlePath))
|
|
514
|
+
).resolves.toBeTruthy();
|
|
515
|
+
const mb = Number(
|
|
516
|
+
output.match(/index\.js\s+([\d.]+)\s+MB/)?.[1] || ""
|
|
517
|
+
);
|
|
518
|
+
expect(mb).toBeGreaterThan(0);
|
|
519
|
+
expect(mb).toBeLessThan(1);
|
|
520
|
+
});
|
|
521
|
+
});
|
|
376
522
|
});
|
|
377
523
|
});
|
|
378
524
|
});
|
|
@@ -44,7 +44,7 @@ async function runSetup(options) {
|
|
|
44
44
|
}
|
|
45
45
|
}
|
|
46
46
|
];
|
|
47
|
-
const i18nStrategy = options.
|
|
47
|
+
const i18nStrategy = options.markets ? options.markets : await renderI18nPrompt({
|
|
48
48
|
abortSignal: controller.signal,
|
|
49
49
|
extraChoices: { none: "Set up later" }
|
|
50
50
|
});
|
|
@@ -130,4 +130,4 @@ async function runSetup(options) {
|
|
|
130
130
|
);
|
|
131
131
|
}
|
|
132
132
|
|
|
133
|
-
export { Setup as default };
|
|
133
|
+
export { Setup as default, runSetup };
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { fileURLToPath } from 'node:url';
|
|
2
|
+
import { vi, describe, beforeEach, it, expect } from 'vitest';
|
|
3
|
+
import { inTemporaryDirectory, copyFile, fileExists, readFile } from '@shopify/cli-kit/node/fs';
|
|
4
|
+
import { joinPath } from '@shopify/cli-kit/node/path';
|
|
5
|
+
import { mockAndCaptureOutput } from '@shopify/cli-kit/node/testing/output';
|
|
6
|
+
import { runSetup } from './setup.js';
|
|
7
|
+
import { renderConfirmationPrompt } from '@shopify/cli-kit/node/ui';
|
|
8
|
+
|
|
9
|
+
vi.mock("../../lib/shell.js");
|
|
10
|
+
vi.mock("@shopify/cli-kit/node/ui", async () => {
|
|
11
|
+
const original = await vi.importActual("@shopify/cli-kit/node/ui");
|
|
12
|
+
return {
|
|
13
|
+
...original,
|
|
14
|
+
renderConfirmationPrompt: vi.fn(),
|
|
15
|
+
renderSelectPrompt: vi.fn(),
|
|
16
|
+
renderTextPrompt: vi.fn(),
|
|
17
|
+
renderInfo: vi.fn()
|
|
18
|
+
};
|
|
19
|
+
});
|
|
20
|
+
describe("setup", () => {
|
|
21
|
+
const outputMock = mockAndCaptureOutput();
|
|
22
|
+
beforeEach(() => {
|
|
23
|
+
vi.resetAllMocks();
|
|
24
|
+
});
|
|
25
|
+
beforeEach(() => {
|
|
26
|
+
outputMock.clear();
|
|
27
|
+
});
|
|
28
|
+
it("sets up an i18n strategy and generates routes", async () => {
|
|
29
|
+
await inTemporaryDirectory(async (tmpDir) => {
|
|
30
|
+
await copyFile(
|
|
31
|
+
fileURLToPath(
|
|
32
|
+
new URL("../../../../../templates/hello-world", import.meta.url)
|
|
33
|
+
),
|
|
34
|
+
tmpDir
|
|
35
|
+
);
|
|
36
|
+
await expect(
|
|
37
|
+
fileExists(joinPath(tmpDir, "app/routes/_index.tsx"))
|
|
38
|
+
).resolves.toBeFalsy();
|
|
39
|
+
vi.mocked(renderConfirmationPrompt).mockResolvedValueOnce(true);
|
|
40
|
+
await expect(
|
|
41
|
+
runSetup({
|
|
42
|
+
directory: tmpDir,
|
|
43
|
+
markets: "subfolders",
|
|
44
|
+
installDeps: false
|
|
45
|
+
})
|
|
46
|
+
).resolves.not.toThrow();
|
|
47
|
+
await expect(
|
|
48
|
+
fileExists(joinPath(tmpDir, "app/routes/($locale)._index.tsx"))
|
|
49
|
+
).resolves.toBeTruthy();
|
|
50
|
+
const serverFile = await readFile(`${tmpDir}/server.ts`);
|
|
51
|
+
expect(serverFile).toMatch(/i18n: getLocaleFromRequest\(request\),/);
|
|
52
|
+
expect(serverFile).toMatch(/url.pathname/);
|
|
53
|
+
const output = outputMock.info();
|
|
54
|
+
expect(output).toMatch("success");
|
|
55
|
+
expect(output).not.toMatch("warning");
|
|
56
|
+
expect(output).toMatch(/Markets:\s*Subfolders/);
|
|
57
|
+
expect(output).toMatch("Routes");
|
|
58
|
+
expect(output).toMatch("Home (/ & /:catchAll)");
|
|
59
|
+
expect(output).toMatch("Account (/account/*)");
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
});
|
|
@@ -15,9 +15,9 @@
|
|
|
15
15
|
"dependencies": {
|
|
16
16
|
"@remix-run/react": "1.19.1",
|
|
17
17
|
"@shopify/cli": "3.48.0",
|
|
18
|
-
"@shopify/cli-hydrogen": "^5.1.
|
|
19
|
-
"@shopify/hydrogen": "^2023.7.
|
|
20
|
-
"@shopify/remix-oxygen": "^1.1.
|
|
18
|
+
"@shopify/cli-hydrogen": "^5.1.2",
|
|
19
|
+
"@shopify/hydrogen": "^2023.7.2",
|
|
20
|
+
"@shopify/remix-oxygen": "^1.1.3",
|
|
21
21
|
"graphql": "^16.6.0",
|
|
22
22
|
"graphql-tag": "^2.12.6",
|
|
23
23
|
"isbot": "^3.6.6",
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
},
|
|
27
27
|
"devDependencies": {
|
|
28
28
|
"@remix-run/dev": "1.19.1",
|
|
29
|
-
"@shopify/oxygen-workers-types": "^3.17.
|
|
29
|
+
"@shopify/oxygen-workers-types": "^3.17.3",
|
|
30
30
|
"@shopify/prettier-config": "^1.1.2",
|
|
31
31
|
"@total-typescript/ts-reset": "^0.4.2",
|
|
32
32
|
"@types/eslint": "^8.4.10",
|
package/dist/lib/file.test.js
CHANGED
|
@@ -1,13 +1,12 @@
|
|
|
1
|
-
import { temporaryDirectoryTask } from 'tempy';
|
|
2
1
|
import { describe, it, expect } from 'vitest';
|
|
3
2
|
import { replaceFileContent, findFileWithExtension } from './file.js';
|
|
4
3
|
import { resolvePath } from '@shopify/cli-kit/node/path';
|
|
5
|
-
import { writeFile, readFile, mkdir } from '@shopify/cli-kit/node/fs';
|
|
4
|
+
import { inTemporaryDirectory, writeFile, readFile, mkdir } from '@shopify/cli-kit/node/fs';
|
|
6
5
|
|
|
7
6
|
describe("File utils", () => {
|
|
8
7
|
describe("replaceFileContent", () => {
|
|
9
8
|
it("replaces the content of a file and formats it", async () => {
|
|
10
|
-
await
|
|
9
|
+
await inTemporaryDirectory(async (tmpDir) => {
|
|
11
10
|
const filepath = resolvePath(tmpDir, "index.js");
|
|
12
11
|
await writeFile(
|
|
13
12
|
filepath,
|
|
@@ -24,7 +23,7 @@ describe("File utils", () => {
|
|
|
24
23
|
});
|
|
25
24
|
describe("findFileWithExtension", () => {
|
|
26
25
|
it("ignores missing files", async () => {
|
|
27
|
-
await
|
|
26
|
+
await inTemporaryDirectory(async (tmpDir) => {
|
|
28
27
|
expect(findFileWithExtension(tmpDir, "nope")).resolves.toEqual({
|
|
29
28
|
filepath: void 0,
|
|
30
29
|
extension: void 0,
|
|
@@ -33,7 +32,7 @@ describe("File utils", () => {
|
|
|
33
32
|
});
|
|
34
33
|
});
|
|
35
34
|
it("finds the file with its corresponding extension and astType", async () => {
|
|
36
|
-
await
|
|
35
|
+
await inTemporaryDirectory(async (tmpDir) => {
|
|
37
36
|
await writeFile(resolvePath(tmpDir, "first.js"), "content");
|
|
38
37
|
await writeFile(resolvePath(tmpDir, "second.tsx"), "content");
|
|
39
38
|
await writeFile(resolvePath(tmpDir, "third.mjs"), "content");
|
|
@@ -3,7 +3,7 @@ import { copyFile } from '@shopify/cli-kit/node/fs';
|
|
|
3
3
|
import { joinPath } from '@shopify/cli-kit/node/path';
|
|
4
4
|
import { renderTasks, renderInfo } from '@shopify/cli-kit/node/ui';
|
|
5
5
|
import { getLatestTemplates } from '../template-downloader.js';
|
|
6
|
-
import { handleProjectLocation, createAbortHandler, handleLanguage, createInitialCommit, handleDependencies, renderProjectReady } from './common.js';
|
|
6
|
+
import { handleProjectLocation, createAbortHandler, handleLanguage, createInitialCommit, handleDependencies, commitAll, renderProjectReady } from './common.js';
|
|
7
7
|
|
|
8
8
|
async function setupRemoteTemplate(options, controller) {
|
|
9
9
|
const isOfficialTemplate = options.template === "demo-store" || options.template === "hello-world";
|
|
@@ -33,7 +33,9 @@ async function setupRemoteTemplate(options, controller) {
|
|
|
33
33
|
controller,
|
|
34
34
|
options.language
|
|
35
35
|
);
|
|
36
|
-
backgroundWorkPromise = backgroundWorkPromise.then(() => transpileProject().catch(abort)).then(
|
|
36
|
+
backgroundWorkPromise = backgroundWorkPromise.then(() => transpileProject().catch(abort)).then(
|
|
37
|
+
() => options.git ? createInitialCommit(project.directory) : void 0
|
|
38
|
+
);
|
|
37
39
|
const { packageManager, shouldInstallDeps, installDeps } = await handleDependencies(
|
|
38
40
|
project.directory,
|
|
39
41
|
controller,
|
|
@@ -73,10 +75,13 @@ async function setupRemoteTemplate(options, controller) {
|
|
|
73
75
|
});
|
|
74
76
|
}
|
|
75
77
|
await renderTasks(tasks);
|
|
78
|
+
if (options.git) {
|
|
79
|
+
await commitAll(project.directory, "Lockfile");
|
|
80
|
+
}
|
|
76
81
|
await renderProjectReady(project, setupSummary);
|
|
77
82
|
if (isOfficialTemplate) {
|
|
78
83
|
renderInfo({
|
|
79
|
-
headline: `Your project will display inventory from the Hydrogen Demo Store.`,
|
|
84
|
+
headline: `Your project will display inventory from ${options.template === "demo-store" ? "the Hydrogen Demo Store" : "Mock.shop"}.`,
|
|
80
85
|
body: `To connect this project to your Shopify store\u2019s inventory, update \`${project.name}/.env\` with your store ID and Storefront API key.`
|
|
81
86
|
});
|
|
82
87
|
}
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { describe, beforeEach, vi, it, expect } from 'vitest';
|
|
2
|
-
import { temporaryDirectoryTask } from 'tempy';
|
|
3
2
|
import { getResolvedRoutes, generateRoutes, generateProjectFile } from './generate.js';
|
|
4
3
|
import { renderConfirmationPrompt } from '@shopify/cli-kit/node/ui';
|
|
5
|
-
import { fileExists, readFile, mkdir, writeFile } from '@shopify/cli-kit/node/fs';
|
|
4
|
+
import { inTemporaryDirectory, fileExists, readFile, mkdir, writeFile } from '@shopify/cli-kit/node/fs';
|
|
6
5
|
import { joinPath, dirname } from '@shopify/cli-kit/node/path';
|
|
7
6
|
import { getTemplateAppFile } from '../../../lib/build.js';
|
|
8
7
|
import { getRemixConfig } from '../../remix-config.js';
|
|
@@ -21,7 +20,7 @@ describe("generate/route", () => {
|
|
|
21
20
|
expect(
|
|
22
21
|
resolvedRouteFiles.find((item) => /account_?\.login/.test(item))
|
|
23
22
|
).toBeTruthy();
|
|
24
|
-
await
|
|
23
|
+
await inTemporaryDirectory(async (tmpDir) => {
|
|
25
24
|
const directories = await createHydrogenFixture(tmpDir, {
|
|
26
25
|
files: [
|
|
27
26
|
["jsconfig.json", JSON.stringify({ compilerOptions: { test: "js" } })],
|
|
@@ -51,7 +50,7 @@ describe("generate/route", () => {
|
|
|
51
50
|
});
|
|
52
51
|
});
|
|
53
52
|
it("figures out the locale if a home route already exists", async () => {
|
|
54
|
-
await
|
|
53
|
+
await inTemporaryDirectory(async (tmpDir) => {
|
|
55
54
|
const route = "routes/pages.$handle";
|
|
56
55
|
const directories = await createHydrogenFixture(tmpDir, {
|
|
57
56
|
files: [
|
|
@@ -89,7 +88,7 @@ describe("generate/route", () => {
|
|
|
89
88
|
});
|
|
90
89
|
describe("generateProjectFile", () => {
|
|
91
90
|
it("generates a route file for Remix v1", async () => {
|
|
92
|
-
await
|
|
91
|
+
await inTemporaryDirectory(async (tmpDir) => {
|
|
93
92
|
const route = "routes/pages.$handle";
|
|
94
93
|
const directories = await createHydrogenFixture(tmpDir, {
|
|
95
94
|
files: [],
|
|
@@ -107,7 +106,7 @@ describe("generate/route", () => {
|
|
|
107
106
|
});
|
|
108
107
|
});
|
|
109
108
|
it("generates a route file for Remix v2", async () => {
|
|
110
|
-
await
|
|
109
|
+
await inTemporaryDirectory(async (tmpDir) => {
|
|
111
110
|
const route = "routes/custom.path.$handle._index";
|
|
112
111
|
const directories = await createHydrogenFixture(tmpDir, {
|
|
113
112
|
files: [],
|
|
@@ -123,7 +122,7 @@ describe("generate/route", () => {
|
|
|
123
122
|
});
|
|
124
123
|
});
|
|
125
124
|
it("generates route files with locale prefix", async () => {
|
|
126
|
-
await
|
|
125
|
+
await inTemporaryDirectory(async (tmpDir) => {
|
|
127
126
|
const routeCode = `const str = 'hello world'`;
|
|
128
127
|
const directories = await createHydrogenFixture(tmpDir, {
|
|
129
128
|
files: [],
|
|
@@ -174,7 +173,7 @@ describe("generate/route", () => {
|
|
|
174
173
|
});
|
|
175
174
|
});
|
|
176
175
|
it("produces a typescript file when typescript argument is true", async () => {
|
|
177
|
-
await
|
|
176
|
+
await inTemporaryDirectory(async (tmpDir) => {
|
|
178
177
|
const route = "routes/pages.$handle";
|
|
179
178
|
const directories = await createHydrogenFixture(tmpDir, {
|
|
180
179
|
files: [],
|
|
@@ -191,7 +190,7 @@ describe("generate/route", () => {
|
|
|
191
190
|
});
|
|
192
191
|
});
|
|
193
192
|
it("prompts the user if there the file already exists", async () => {
|
|
194
|
-
await
|
|
193
|
+
await inTemporaryDirectory(async (tmpDir) => {
|
|
195
194
|
vi.mocked(renderConfirmationPrompt).mockImplementationOnce(
|
|
196
195
|
async () => true
|
|
197
196
|
);
|
|
@@ -212,7 +211,7 @@ describe("generate/route", () => {
|
|
|
212
211
|
});
|
|
213
212
|
});
|
|
214
213
|
it("does not prompt the user if the force property is true", async () => {
|
|
215
|
-
await
|
|
214
|
+
await inTemporaryDirectory(async (tmpDir) => {
|
|
216
215
|
vi.mocked(renderConfirmationPrompt).mockImplementationOnce(
|
|
217
216
|
async () => true
|
|
218
217
|
);
|
|
@@ -229,7 +228,7 @@ describe("generate/route", () => {
|
|
|
229
228
|
});
|
|
230
229
|
});
|
|
231
230
|
it("generates all the route dependencies", async () => {
|
|
232
|
-
await
|
|
231
|
+
await inTemporaryDirectory(async (tmpDir) => {
|
|
233
232
|
const templates = [
|
|
234
233
|
[
|
|
235
234
|
"routes/pages.$pageHandle.tsx",
|
package/oclif.manifest.json
CHANGED
package/package.json
CHANGED
|
@@ -4,12 +4,12 @@
|
|
|
4
4
|
"access": "public",
|
|
5
5
|
"@shopify:registry": "https://registry.npmjs.org"
|
|
6
6
|
},
|
|
7
|
-
"version": "5.1.
|
|
7
|
+
"version": "5.1.2",
|
|
8
8
|
"license": "MIT",
|
|
9
9
|
"type": "module",
|
|
10
10
|
"scripts": {
|
|
11
11
|
"build": "rm -rf dist && tsup --config ./tsup.config.ts && node scripts/build-check.mjs",
|
|
12
|
-
"dev": "
|
|
12
|
+
"dev": "tsup --watch --config ./tsup.config.ts",
|
|
13
13
|
"typecheck": "tsc --noEmit",
|
|
14
14
|
"generate:manifest": "node scripts/generate-manifest.mjs",
|
|
15
15
|
"test": "cross-env SHOPIFY_UNIT_TEST=1 vitest run --test-timeout=15000",
|
|
@@ -22,14 +22,14 @@
|
|
|
22
22
|
"@types/prettier": "^2.7.2",
|
|
23
23
|
"@types/recursive-readdir": "^2.2.1",
|
|
24
24
|
"@types/tar-fs": "^2.0.1",
|
|
25
|
-
"
|
|
25
|
+
"@vitest/coverage-v8": "^0.33.0",
|
|
26
26
|
"type-fest": "^3.6.0",
|
|
27
27
|
"vitest": "^0.33.0"
|
|
28
28
|
},
|
|
29
29
|
"peerDependencies": {
|
|
30
30
|
"@remix-run/react": "1.19.1",
|
|
31
|
-
"@shopify/hydrogen-react": "^2023.7.
|
|
32
|
-
"@shopify/remix-oxygen": "^1.1.
|
|
31
|
+
"@shopify/hydrogen-react": "^2023.7.1",
|
|
32
|
+
"@shopify/remix-oxygen": "^1.1.3"
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
35
|
"@ast-grep/napi": "^0.5.3",
|