@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.
- package/dist/commands/hydrogen/build-vite.js +19 -10
- package/dist/commands/hydrogen/build.js +10 -2
- package/dist/commands/hydrogen/check.js +1 -0
- package/dist/commands/hydrogen/codegen.js +1 -0
- package/dist/commands/hydrogen/customer-account/push.js +170 -0
- package/dist/commands/hydrogen/debug/cpu.js +3 -0
- package/dist/commands/hydrogen/deploy.js +121 -36
- package/dist/commands/hydrogen/dev-vite.js +128 -59
- package/dist/commands/hydrogen/dev.js +108 -51
- package/dist/commands/hydrogen/env/list.js +7 -8
- package/dist/commands/hydrogen/env/pull.js +17 -1
- package/dist/commands/hydrogen/env/{push__unstable.js → push.js} +23 -50
- package/dist/commands/hydrogen/generate/route.js +1 -0
- package/dist/commands/hydrogen/init.js +45 -17
- package/dist/commands/hydrogen/link.js +20 -4
- package/dist/commands/hydrogen/list.js +1 -0
- package/dist/commands/hydrogen/login.js +1 -0
- package/dist/commands/hydrogen/logout.js +1 -0
- package/dist/commands/hydrogen/preview.js +31 -16
- package/dist/commands/hydrogen/setup/css.js +8 -1
- package/dist/commands/hydrogen/setup/markets.js +1 -0
- package/dist/commands/hydrogen/setup/vite.js +244 -138
- package/dist/commands/hydrogen/setup.js +21 -22
- package/dist/commands/hydrogen/shortcut.js +10 -0
- package/dist/commands/hydrogen/unlink.js +1 -0
- package/dist/commands/hydrogen/upgrade.js +2 -1
- package/dist/generator-templates/assets/vite/package.json +3 -4
- package/dist/generator-templates/assets/vite/vite.config.js +10 -2
- package/dist/generator-templates/starter/CHANGELOG.md +89 -0
- package/dist/generator-templates/starter/README.md +3 -44
- package/dist/generator-templates/starter/app/graphql/customer-account/CustomerDetailsQuery.ts +1 -0
- package/dist/generator-templates/starter/app/lib/fragments.ts +2 -0
- package/dist/generator-templates/starter/app/root.tsx +2 -5
- package/dist/generator-templates/starter/app/routes/account.orders._index.tsx +1 -1
- package/dist/generator-templates/starter/app/routes/account.tsx +1 -1
- package/dist/generator-templates/starter/app/routes/collections.all.tsx +160 -0
- package/dist/generator-templates/starter/app/routes/products.$handle.tsx +1 -2
- package/dist/generator-templates/starter/customer-accountapi.generated.d.ts +6 -3
- package/dist/generator-templates/starter/{remix.env.d.ts → env.d.ts} +8 -2
- package/dist/generator-templates/starter/package.json +14 -9
- package/dist/generator-templates/starter/server.ts +2 -1
- package/dist/generator-templates/starter/storefrontapi.generated.d.ts +59 -3
- package/dist/generator-templates/starter/vite.config.ts +21 -0
- package/dist/{commands/hydrogen/init.d.ts → init.d.ts} +11 -3
- package/dist/lib/check-lockfile.js +12 -18
- package/dist/lib/codegen.js +37 -13
- package/dist/lib/common.js +50 -0
- package/dist/lib/cpu-profiler.js +4 -1
- package/dist/lib/dev-shared.js +97 -0
- package/dist/lib/environment-variables.js +51 -30
- package/dist/lib/file.js +8 -1
- package/dist/lib/flags.js +37 -16
- package/dist/lib/graphql/admin/customer-application-update.js +29 -0
- package/dist/lib/graphql/admin/get-oxygen-data.js +1 -0
- package/dist/lib/graphql/admin/list-environments.js +1 -0
- package/dist/lib/graphql/admin/pull-variables.js +4 -4
- package/dist/lib/graphql/admin/test-helper.js +37 -0
- package/dist/lib/log.js +86 -13
- package/dist/lib/mini-oxygen/common.js +19 -33
- package/dist/lib/mini-oxygen/index.js +6 -2
- package/dist/lib/mini-oxygen/node.js +43 -31
- package/dist/lib/mini-oxygen/workerd.js +72 -165
- package/dist/lib/missing-routes.js +1 -1
- package/dist/lib/onboarding/common.js +82 -70
- package/dist/lib/onboarding/local.js +19 -9
- package/dist/lib/onboarding/remote.js +35 -30
- package/dist/lib/package-managers.js +24 -0
- package/dist/lib/remix-config.js +17 -1
- package/dist/lib/request-events.js +6 -1
- package/dist/lib/setups/i18n/replacers.js +9 -6
- package/dist/lib/setups/routes/generate.js +1 -0
- package/dist/lib/shell.js +2 -1
- package/dist/lib/shopify-config.js +19 -1
- package/dist/lib/template-diff.js +36 -15
- package/dist/lib/template-downloader.js +35 -5
- package/dist/lib/transpile/morph/typedefs.js +5 -2
- package/dist/lib/transpile/project.js +8 -4
- package/dist/lib/tunneling.js +44 -0
- package/dist/lib/virtual-routes.js +1 -1
- package/dist/lib/vite-config.js +39 -9
- package/oclif.manifest.json +711 -498
- package/package.json +32 -24
- package/dist/commands/hydrogen/deploy.test.js +0 -553
- package/dist/commands/hydrogen/env/list.test.js +0 -148
- package/dist/commands/hydrogen/env/pull.test.js +0 -207
- package/dist/commands/hydrogen/env/push__unstable.test.js +0 -383
- package/dist/commands/hydrogen/generate/route.test.js +0 -43
- package/dist/commands/hydrogen/init.test.js +0 -641
- package/dist/commands/hydrogen/link.test.js +0 -187
- package/dist/commands/hydrogen/list.test.js +0 -111
- package/dist/commands/hydrogen/setup.test.js +0 -61
- package/dist/commands/hydrogen/shortcut.test.js +0 -30
- package/dist/commands/hydrogen/unlink.test.js +0 -36
- package/dist/commands/hydrogen/upgrade.test.js +0 -786
- package/dist/generator-templates/starter/remix.config.js +0 -24
- package/dist/lib/auth.test.js +0 -157
- package/dist/lib/check-lockfile.test.js +0 -81
- package/dist/lib/check-version.test.js +0 -86
- package/dist/lib/environment-variables.test.js +0 -149
- package/dist/lib/file.test.js +0 -68
- package/dist/lib/flags.test.js +0 -43
- package/dist/lib/get-oxygen-deployment-data.test.js +0 -120
- package/dist/lib/gid.test.js +0 -15
- package/dist/lib/graphql/admin/client.test.js +0 -76
- package/dist/lib/graphql/admin/create-storefront.test.js +0 -64
- package/dist/lib/graphql/admin/link-storefront.test.js +0 -38
- package/dist/lib/graphql/admin/list-environments.test.js +0 -44
- package/dist/lib/graphql/admin/list-storefronts.test.js +0 -44
- package/dist/lib/graphql/admin/pull-variables.test.js +0 -43
- package/dist/lib/graphql/business-platform/user-account.test.js +0 -80
- package/dist/lib/log.test.js +0 -92
- package/dist/lib/mini-oxygen/assets.js +0 -134
- package/dist/lib/mini-oxygen/mini-oxygen.test.js +0 -214
- package/dist/lib/mini-oxygen/workerd-inspector-logs.js +0 -227
- package/dist/lib/mini-oxygen/workerd-inspector-proxy.js +0 -200
- package/dist/lib/mini-oxygen/workerd-inspector.js +0 -219
- package/dist/lib/missing-routes.test.js +0 -45
- package/dist/lib/remix-version-check.test.js +0 -39
- package/dist/lib/remix-version-interop.test.js +0 -13
- package/dist/lib/setups/i18n/domains.test.js +0 -39
- package/dist/lib/setups/i18n/replacers.test.js +0 -261
- package/dist/lib/setups/i18n/subdomains.test.js +0 -39
- package/dist/lib/setups/i18n/subfolders.test.js +0 -39
- package/dist/lib/setups/routes/generate.test.js +0 -296
- package/dist/lib/shell.test.js +0 -111
- package/dist/lib/shopify-config.test.js +0 -199
- package/dist/lib/string.test.js +0 -16
- package/dist/lib/virtual-routes.test.js +0 -49
- package/dist/lib/vite/hydrogen-middleware.js +0 -82
- package/dist/lib/vite/mini-oxygen.js +0 -152
- package/dist/lib/vite/plugins.d.ts +0 -27
- package/dist/lib/vite/plugins.js +0 -139
- package/dist/lib/vite/shared.js +0 -10
- package/dist/lib/vite/utils.js +0 -55
- package/dist/lib/vite/worker-entry.js +0 -1518
- /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
|
-
});
|