@shopify/cli-hydrogen 5.1.0 → 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 +11 -8
- package/dist/commands/hydrogen/check.js +3 -3
- package/dist/commands/hydrogen/codegen-unstable.js +4 -4
- package/dist/commands/hydrogen/dev.js +12 -6
- package/dist/commands/hydrogen/init.test.js +197 -51
- package/dist/commands/hydrogen/preview.js +1 -1
- package/dist/commands/hydrogen/setup/css.js +1 -1
- package/dist/commands/hydrogen/setup/markets.js +1 -1
- package/dist/commands/hydrogen/setup.js +3 -3
- package/dist/commands/hydrogen/setup.test.js +62 -0
- package/dist/generator-templates/starter/app/root.tsx +7 -13
- package/dist/generator-templates/starter/app/styles/app.css +1 -1
- package/dist/generator-templates/starter/package.json +7 -7
- package/dist/lib/file.test.js +4 -5
- package/dist/lib/log.js +32 -1
- package/dist/lib/onboarding/remote.js +8 -3
- package/dist/lib/remix-config.js +135 -0
- package/dist/lib/remix-version-check.js +51 -0
- package/dist/lib/remix-version-check.test.js +38 -0
- package/dist/lib/remix-version-interop.js +2 -2
- package/dist/lib/remix-version-interop.test.js +1 -1
- package/dist/lib/setups/routes/generate.js +1 -1
- package/dist/lib/setups/routes/generate.test.js +12 -13
- package/oclif.manifest.json +1 -1
- package/package.json +10 -11
- package/dist/lib/config.js +0 -141
|
@@ -5,11 +5,11 @@ import { rmdir, fileSize, glob, removeFile, copyFile } from '@shopify/cli-kit/no
|
|
|
5
5
|
import { resolvePath, relativePath, joinPath } from '@shopify/cli-kit/node/path';
|
|
6
6
|
import { getPackageManager } from '@shopify/cli-kit/node/node-package-manager';
|
|
7
7
|
import colors from '@shopify/cli-kit/node/colors';
|
|
8
|
-
import { getProjectPaths, getRemixConfig } from '../../lib/config.js';
|
|
8
|
+
import { getProjectPaths, getRemixConfig, assertOxygenChecks } from '../../lib/remix-config.js';
|
|
9
9
|
import { commonFlags, deprecated, flagsToCamelObject } from '../../lib/flags.js';
|
|
10
10
|
import { checkLockfileStatus } from '../../lib/check-lockfile.js';
|
|
11
11
|
import { findMissingRoutes } from '../../lib/missing-routes.js';
|
|
12
|
-
import {
|
|
12
|
+
import { muteRemixLogs, createRemixLogger } from '../../lib/log.js';
|
|
13
13
|
import { codegen } from '../../lib/codegen.js';
|
|
14
14
|
|
|
15
15
|
const LOG_WORKER_BUILT = "\u{1F4E6} Worker built";
|
|
@@ -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,8 +56,8 @@ 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(
|
|
60
|
-
await checkLockfileStatus(root);
|
|
59
|
+
const { root, buildPath, buildPathClient, buildPathWorkerFile, publicPath } = getProjectPaths(directory);
|
|
60
|
+
await Promise.all([checkLockfileStatus(root), muteRemixLogs()]);
|
|
61
61
|
console.time(LOG_WORKER_BUILT);
|
|
62
62
|
outputInfo(`
|
|
63
63
|
\u{1F3D7}\uFE0F Building in ${process.env.NODE_ENV} mode...`);
|
|
@@ -68,15 +68,16 @@ async function runBuild({
|
|
|
68
68
|
import('@remix-run/dev/dist/compiler/fileWatchCache.js'),
|
|
69
69
|
rmdir(buildPath, { force: true })
|
|
70
70
|
]);
|
|
71
|
+
assertOxygenChecks(remixConfig);
|
|
71
72
|
await Promise.all([
|
|
72
73
|
copyPublicFiles(publicPath, buildPathClient),
|
|
73
74
|
build({
|
|
74
75
|
config: remixConfig,
|
|
75
76
|
options: {
|
|
76
77
|
mode: process.env.NODE_ENV,
|
|
77
|
-
onWarning: warnOnce,
|
|
78
78
|
sourcemap
|
|
79
79
|
},
|
|
80
|
+
logger: createRemixLogger(),
|
|
80
81
|
fileWatchCache: createFileWatchCache()
|
|
81
82
|
}).catch((thrown) => {
|
|
82
83
|
logThrown(thrown);
|
|
@@ -124,7 +125,9 @@ This build is missing ${missingRoutes.length} route${missingRoutes.length > 1 ?
|
|
|
124
125
|
);
|
|
125
126
|
}
|
|
126
127
|
}
|
|
127
|
-
process.
|
|
128
|
+
if (!process.env.SHOPIFY_UNIT_TEST) {
|
|
129
|
+
process.exit(0);
|
|
130
|
+
}
|
|
128
131
|
}
|
|
129
132
|
async function copyPublicFiles(publicPath, buildPathClient) {
|
|
130
133
|
return copyFile(publicPath, buildPathClient);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import Command from '@shopify/cli-kit/node/base-command';
|
|
2
2
|
import { resolvePath } from '@shopify/cli-kit/node/path';
|
|
3
3
|
import { commonFlags } from '../../lib/flags.js';
|
|
4
|
-
import { getRemixConfig } from '../../lib/config.js';
|
|
4
|
+
import { getRemixConfig } from '../../lib/remix-config.js';
|
|
5
5
|
import { logMissingRoutes, findMissingRoutes } from '../../lib/missing-routes.js';
|
|
6
6
|
import { Args } from '@oclif/core';
|
|
7
7
|
|
|
@@ -29,8 +29,8 @@ class GenerateRoute extends Command {
|
|
|
29
29
|
}
|
|
30
30
|
}
|
|
31
31
|
async function runCheckRoutes({ directory }) {
|
|
32
|
-
const remixConfig = await getRemixConfig(directory
|
|
32
|
+
const remixConfig = await getRemixConfig(directory);
|
|
33
33
|
logMissingRoutes(findMissingRoutes(remixConfig));
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
export { GenerateRoute as default };
|
|
36
|
+
export { GenerateRoute as default, runCheckRoutes };
|
|
@@ -2,7 +2,7 @@ import path from 'path';
|
|
|
2
2
|
import Command from '@shopify/cli-kit/node/base-command';
|
|
3
3
|
import { renderSuccess } from '@shopify/cli-kit/node/ui';
|
|
4
4
|
import { Flags } from '@oclif/core';
|
|
5
|
-
import { getProjectPaths, getRemixConfig } from '../../lib/config.js';
|
|
5
|
+
import { getProjectPaths, getRemixConfig } from '../../lib/remix-config.js';
|
|
6
6
|
import { commonFlags, flagsToCamelObject } from '../../lib/flags.js';
|
|
7
7
|
import { codegen } from '../../lib/codegen.js';
|
|
8
8
|
|
|
@@ -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({
|
|
@@ -5,8 +5,8 @@ import { fileExists } from '@shopify/cli-kit/node/fs';
|
|
|
5
5
|
import { renderFatalError } from '@shopify/cli-kit/node/ui';
|
|
6
6
|
import colors from '@shopify/cli-kit/node/colors';
|
|
7
7
|
import { copyPublicFiles } from './build.js';
|
|
8
|
-
import { getProjectPaths, getRemixConfig } from '../../lib/config.js';
|
|
9
|
-
import { muteDevLogs,
|
|
8
|
+
import { getProjectPaths, assertOxygenChecks, getRemixConfig } from '../../lib/remix-config.js';
|
|
9
|
+
import { muteDevLogs, muteRemixLogs, createRemixLogger, enhanceH2Logs } from '../../lib/log.js';
|
|
10
10
|
import { commonFlags, deprecated, flagsToCamelObject } from '../../lib/flags.js';
|
|
11
11
|
import Command from '@shopify/cli-kit/node/base-command';
|
|
12
12
|
import { Flags } from '@oclif/core';
|
|
@@ -16,6 +16,7 @@ import { addVirtualRoutes } from '../../lib/virtual-routes.js';
|
|
|
16
16
|
import { spawnCodegenProcess } from '../../lib/codegen.js';
|
|
17
17
|
import { getAllEnvironmentVariables } from '../../lib/environment-variables.js';
|
|
18
18
|
import { getConfig } from '../../lib/shopify-config.js';
|
|
19
|
+
import { checkRemixVersions } from '../../lib/remix-version-check.js';
|
|
19
20
|
|
|
20
21
|
const LOG_REBUILDING = "\u{1F9F1} Rebuilding...";
|
|
21
22
|
const LOG_REBUILT = "\u{1F680} Rebuilt";
|
|
@@ -67,6 +68,7 @@ async function runDev({
|
|
|
67
68
|
if (!process.env.NODE_ENV)
|
|
68
69
|
process.env.NODE_ENV = "development";
|
|
69
70
|
muteDevLogs();
|
|
71
|
+
await muteRemixLogs();
|
|
70
72
|
if (debug)
|
|
71
73
|
(await import('node:inspector')).open();
|
|
72
74
|
const { root, publicPath, buildPathClient, buildPathWorkerFile } = getProjectPaths(appPath);
|
|
@@ -86,7 +88,11 @@ async function runDev({
|
|
|
86
88
|
return [fileRelative, path.resolve(root, fileRelative)];
|
|
87
89
|
};
|
|
88
90
|
const serverBundleExists = () => fileExists(buildPathWorkerFile);
|
|
89
|
-
const { shop, storefront } = await
|
|
91
|
+
const [remixConfig, { shop, storefront }] = await Promise.all([
|
|
92
|
+
reloadConfig(),
|
|
93
|
+
getConfig(root)
|
|
94
|
+
]);
|
|
95
|
+
assertOxygenChecks(remixConfig);
|
|
90
96
|
const fetchRemote = !!shop && !!storefront?.id;
|
|
91
97
|
const envPromise = getAllEnvironmentVariables({ root, fetchRemote, envBranch });
|
|
92
98
|
const [{ watch }, { createFileWatchCache }] = await Promise.all([
|
|
@@ -120,11 +126,11 @@ View GraphiQL API browser: ${graphiqlUrl}`)]
|
|
|
120
126
|
if (useCodegen) {
|
|
121
127
|
spawnCodegenProcess({ ...remixConfig, configFilePath: codegenConfigPath });
|
|
122
128
|
}
|
|
129
|
+
checkRemixVersions();
|
|
123
130
|
const showUpgrade = await checkingHydrogenVersion;
|
|
124
131
|
if (showUpgrade)
|
|
125
132
|
showUpgrade();
|
|
126
133
|
}
|
|
127
|
-
const remixConfig = await reloadConfig();
|
|
128
134
|
const fileWatchCache = createFileWatchCache();
|
|
129
135
|
let skipRebuildLogs = false;
|
|
130
136
|
await watch(
|
|
@@ -132,10 +138,10 @@ View GraphiQL API browser: ${graphiqlUrl}`)]
|
|
|
132
138
|
config: remixConfig,
|
|
133
139
|
options: {
|
|
134
140
|
mode: process.env.NODE_ENV,
|
|
135
|
-
onWarning: warnOnce,
|
|
136
141
|
sourcemap
|
|
137
142
|
},
|
|
138
|
-
fileWatchCache
|
|
143
|
+
fileWatchCache,
|
|
144
|
+
logger: createRemixLogger()
|
|
139
145
|
},
|
|
140
146
|
{
|
|
141
147
|
reloadConfig,
|
|
@@ -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
|
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import Command from '@shopify/cli-kit/node/base-command';
|
|
2
2
|
import { muteDevLogs } from '../../lib/log.js';
|
|
3
|
-
import { getProjectPaths } from '../../lib/config.js';
|
|
3
|
+
import { getProjectPaths } from '../../lib/remix-config.js';
|
|
4
4
|
import { commonFlags } from '../../lib/flags.js';
|
|
5
5
|
import { startMiniOxygen } from '../../lib/mini-oxygen.js';
|
|
6
6
|
|
|
@@ -4,7 +4,7 @@ import Command from '@shopify/cli-kit/node/base-command';
|
|
|
4
4
|
import { renderTasks, renderSuccess } from '@shopify/cli-kit/node/ui';
|
|
5
5
|
import { getPackageManager, installNodeModules } from '@shopify/cli-kit/node/node-package-manager';
|
|
6
6
|
import { Args } from '@oclif/core';
|
|
7
|
-
import { getRemixConfig } from '../../../lib/config.js';
|
|
7
|
+
import { getRemixConfig } from '../../../lib/remix-config.js';
|
|
8
8
|
import { SETUP_CSS_STRATEGIES, renderCssPrompt, setupCssStrategy, CSS_STRATEGY_NAME_MAP } from '../../../lib/setups/css/index.js';
|
|
9
9
|
|
|
10
10
|
class SetupCSS extends Command {
|
|
@@ -3,7 +3,7 @@ import { commonFlags, flagsToCamelObject } from '../../../lib/flags.js';
|
|
|
3
3
|
import Command from '@shopify/cli-kit/node/base-command';
|
|
4
4
|
import { renderTasks, renderSuccess } from '@shopify/cli-kit/node/ui';
|
|
5
5
|
import { Args } from '@oclif/core';
|
|
6
|
-
import { getRemixConfig } from '../../../lib/config.js';
|
|
6
|
+
import { getRemixConfig } from '../../../lib/remix-config.js';
|
|
7
7
|
import { SETUP_I18N_STRATEGIES, renderI18nPrompt, setupI18nStrategy, I18N_STRATEGY_NAME_MAP } from '../../../lib/setups/i18n/index.js';
|
|
8
8
|
|
|
9
9
|
class SetupMarkets extends Command {
|
|
@@ -5,7 +5,7 @@ import { resolvePath, basename } from '@shopify/cli-kit/node/path';
|
|
|
5
5
|
import { copyFile } from '@shopify/cli-kit/node/fs';
|
|
6
6
|
import { commonFlags, overrideFlag, flagsToCamelObject } from '../../lib/flags.js';
|
|
7
7
|
import { renderI18nPrompt, setupI18nStrategy } from '../../lib/setups/i18n/index.js';
|
|
8
|
-
import { getRemixConfig } from '../../lib/config.js';
|
|
8
|
+
import { getRemixConfig } from '../../lib/remix-config.js';
|
|
9
9
|
import { handleRouteGeneration, generateProjectEntries, handleCliShortcut, renderProjectReady } from '../../lib/onboarding/common.js';
|
|
10
10
|
import { getCliCommand } from '../../lib/shell.js';
|
|
11
11
|
import { generateProjectFile } from '../../lib/setups/routes/generate.js';
|
|
@@ -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
|
+
});
|