@shopify/cli-hydrogen 6.0.2 → 7.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/hydrogen/build.js +40 -78
- package/dist/commands/hydrogen/codegen.js +8 -3
- package/dist/commands/hydrogen/deploy.js +173 -37
- package/dist/commands/hydrogen/deploy.test.js +192 -20
- package/dist/commands/hydrogen/dev.js +56 -31
- package/dist/commands/hydrogen/init.js +1 -1
- package/dist/commands/hydrogen/init.test.js +155 -53
- package/dist/commands/hydrogen/link.js +5 -21
- package/dist/commands/hydrogen/link.test.js +10 -10
- package/dist/commands/hydrogen/preview.js +22 -11
- package/dist/commands/hydrogen/setup.js +0 -4
- package/dist/commands/hydrogen/setup.test.js +0 -1
- package/dist/commands/hydrogen/shortcut.js +1 -0
- package/dist/commands/hydrogen/upgrade.js +720 -0
- package/dist/commands/hydrogen/upgrade.test.js +786 -0
- package/dist/generator-templates/starter/.graphqlrc.yml +12 -1
- package/dist/generator-templates/starter/CHANGELOG.md +126 -0
- package/dist/generator-templates/starter/README.md +23 -0
- package/dist/generator-templates/starter/app/components/Cart.tsx +1 -1
- package/dist/generator-templates/starter/app/components/Footer.tsx +3 -1
- package/dist/generator-templates/starter/app/components/Header.tsx +5 -1
- package/dist/generator-templates/starter/app/components/Layout.tsx +14 -11
- package/dist/generator-templates/starter/app/components/Search.tsx +1 -1
- package/dist/generator-templates/starter/app/graphql/customer-account/CustomerAddressMutations.ts +61 -0
- package/dist/generator-templates/starter/app/graphql/customer-account/CustomerDetailsQuery.ts +39 -0
- package/dist/generator-templates/starter/app/graphql/customer-account/CustomerOrderQuery.ts +87 -0
- package/dist/generator-templates/starter/app/graphql/customer-account/CustomerOrdersQuery.ts +58 -0
- package/dist/generator-templates/starter/app/graphql/customer-account/CustomerUpdateMutation.ts +24 -0
- package/dist/generator-templates/starter/app/lib/fragments.ts +102 -0
- package/dist/generator-templates/starter/app/lib/session.ts +67 -0
- package/dist/generator-templates/starter/app/root.tsx +11 -45
- package/dist/generator-templates/starter/app/routes/[robots.txt].tsx +0 -27
- package/dist/generator-templates/starter/app/routes/account.$.tsx +8 -4
- package/dist/generator-templates/starter/app/routes/account._index.tsx +5 -0
- package/dist/generator-templates/starter/app/routes/account.addresses.tsx +215 -206
- package/dist/generator-templates/starter/app/routes/account.orders.$id.tsx +56 -163
- package/dist/generator-templates/starter/app/routes/account.orders._index.tsx +32 -109
- package/dist/generator-templates/starter/app/routes/account.profile.tsx +40 -180
- package/dist/generator-templates/starter/app/routes/account.tsx +20 -135
- package/dist/generator-templates/starter/app/routes/account_.authorize.tsx +5 -0
- package/dist/generator-templates/starter/app/routes/account_.login.tsx +3 -140
- package/dist/generator-templates/starter/app/routes/account_.logout.tsx +5 -24
- package/dist/generator-templates/starter/app/routes/cart.tsx +7 -5
- package/dist/generator-templates/starter/app/routes/collections.$handle.tsx +1 -1
- package/dist/generator-templates/starter/app/routes/products.$handle.tsx +2 -2
- package/dist/generator-templates/starter/app/routes/search.tsx +1 -1
- package/dist/generator-templates/starter/customer-accountapi.generated.d.ts +506 -0
- package/dist/generator-templates/starter/package.json +11 -10
- package/dist/generator-templates/starter/remix.config.js +4 -0
- package/dist/generator-templates/starter/remix.env.d.ts +6 -11
- package/dist/generator-templates/starter/server.ts +24 -167
- package/dist/generator-templates/starter/storefrontapi.generated.d.ts +104 -881
- package/dist/hooks/init.js +4 -4
- package/dist/lib/auth.js +5 -10
- package/dist/lib/build.js +6 -1
- package/dist/lib/bundle/analyzer.js +36 -26
- package/dist/lib/check-lockfile.js +1 -0
- package/dist/lib/codegen.js +59 -18
- package/dist/lib/defer.js +12 -0
- package/dist/lib/file.js +52 -3
- package/dist/lib/flags.js +27 -9
- package/dist/lib/get-oxygen-deployment-data.test.js +4 -2
- package/dist/lib/graphql/admin/client.test.js +2 -2
- package/dist/lib/graphql/admin/get-oxygen-data.js +1 -0
- package/dist/lib/log.js +32 -14
- package/dist/lib/mini-oxygen/assets.js +118 -0
- package/dist/lib/mini-oxygen/common.js +2 -1
- package/dist/lib/mini-oxygen/index.js +7 -5
- package/dist/lib/mini-oxygen/mini-oxygen.test.js +214 -0
- package/dist/lib/mini-oxygen/node.js +19 -5
- package/dist/lib/mini-oxygen/workerd-inspector-logs.js +227 -0
- package/dist/lib/mini-oxygen/workerd-inspector-proxy.js +200 -0
- package/dist/lib/mini-oxygen/workerd-inspector.js +62 -235
- package/dist/lib/mini-oxygen/workerd.js +74 -50
- package/dist/lib/missing-routes.js +6 -3
- package/dist/lib/onboarding/common.js +40 -9
- package/dist/lib/onboarding/local.js +19 -11
- package/dist/lib/onboarding/remote.js +48 -28
- package/dist/lib/render-errors.js +2 -0
- package/dist/lib/request-events.js +65 -31
- package/dist/lib/setups/css/assets.js +1 -46
- package/dist/lib/setups/css/css-modules.js +3 -2
- package/dist/lib/setups/css/postcss.js +4 -2
- package/dist/lib/setups/css/tailwind.js +4 -2
- package/dist/lib/setups/css/vanilla-extract.js +3 -2
- package/dist/lib/setups/i18n/replacers.test.js +56 -38
- package/dist/lib/shell.js +1 -1
- package/dist/lib/template-diff.js +89 -0
- package/dist/lib/template-downloader.js +3 -2
- package/dist/lib/transpile/project.js +1 -1
- package/dist/virtual-routes/assets/debug-network.css +592 -0
- package/dist/virtual-routes/assets/favicon-dark.svg +20 -0
- package/dist/virtual-routes/components/FlameChartWrapper.jsx +8 -10
- package/dist/virtual-routes/components/IconClose.jsx +38 -0
- package/dist/virtual-routes/components/IconDiscard.jsx +44 -0
- package/dist/virtual-routes/components/RequestDetails.jsx +179 -0
- package/dist/virtual-routes/components/RequestTable.jsx +92 -0
- package/dist/virtual-routes/components/RequestWaterfall.jsx +151 -0
- package/dist/virtual-routes/lib/useDebugNetworkServer.jsx +176 -0
- package/dist/virtual-routes/routes/subrequest-profiler.jsx +243 -0
- package/oclif.manifest.json +134 -59
- package/package.json +18 -26
- package/dist/generator-templates/starter/app/routes/account_.activate.$id.$activationToken.tsx +0 -161
- package/dist/generator-templates/starter/app/routes/account_.recover.tsx +0 -129
- package/dist/generator-templates/starter/app/routes/account_.register.tsx +0 -207
- package/dist/generator-templates/starter/app/routes/account_.reset.$id.$resetToken.tsx +0 -136
- package/dist/virtual-routes/routes/debug-network.jsx +0 -289
- /package/dist/generator-templates/starter/app/{utils.ts → lib/variants.ts} +0 -0
|
@@ -3,17 +3,19 @@ import { vi, describe, beforeEach, it, expect } from 'vitest';
|
|
|
3
3
|
import { runInit } from './init.js';
|
|
4
4
|
import { exec } from '@shopify/cli-kit/node/system';
|
|
5
5
|
import { mockAndCaptureOutput } from '@shopify/cli-kit/node/testing/output';
|
|
6
|
+
import { readAndParsePackageJson } from '@shopify/cli-kit/node/node-package-manager';
|
|
6
7
|
import { writeFile, inTemporaryDirectory, readFile, isDirectory, removeFile, fileExists } from '@shopify/cli-kit/node/fs';
|
|
7
8
|
import { joinPath, basename } from '@shopify/cli-kit/node/path';
|
|
8
9
|
import { checkHydrogenVersion } from '../../lib/check-version.js';
|
|
9
10
|
import { handleProjectLocation } from '../../lib/onboarding/common.js';
|
|
10
11
|
import glob from 'fast-glob';
|
|
11
|
-
import { getSkeletonSourceDir } from '../../lib/build.js';
|
|
12
|
+
import { getRepoNodeModules, getSkeletonSourceDir } from '../../lib/build.js';
|
|
12
13
|
import { execAsync } from '../../lib/process.js';
|
|
13
|
-
import {
|
|
14
|
+
import { remove, createSymlink } from 'fs-extra/esm';
|
|
14
15
|
import { runCheckRoutes } from './check.js';
|
|
15
16
|
import { runCodegen } from './codegen.js';
|
|
16
17
|
import { runBuild } from './build.js';
|
|
18
|
+
import { runDev } from './dev.js';
|
|
17
19
|
|
|
18
20
|
const { renderTasksHook } = vi.hoisted(() => ({ renderTasksHook: vi.fn() }));
|
|
19
21
|
vi.mock("../../lib/check-version.js");
|
|
@@ -22,6 +24,9 @@ vi.mock("../../lib/template-downloader.js", async () => ({
|
|
|
22
24
|
version: "",
|
|
23
25
|
templatesDir: fileURLToPath(
|
|
24
26
|
new URL("../../../../../templates", import.meta.url)
|
|
27
|
+
),
|
|
28
|
+
examplesDir: fileURLToPath(
|
|
29
|
+
new URL("../../../../../examples", import.meta.url)
|
|
25
30
|
)
|
|
26
31
|
})
|
|
27
32
|
}));
|
|
@@ -51,12 +56,10 @@ vi.mock(
|
|
|
51
56
|
renderTasksHook.mockImplementationOnce(async () => {
|
|
52
57
|
await writeFile(`${directory}/package-lock.json`, "{}");
|
|
53
58
|
});
|
|
54
|
-
await
|
|
59
|
+
await remove(joinPath(directory, "node_modules")).catch(() => {
|
|
55
60
|
});
|
|
56
|
-
await
|
|
57
|
-
|
|
58
|
-
new URL("../../../../../node_modules", import.meta.url)
|
|
59
|
-
),
|
|
61
|
+
await createSymlink(
|
|
62
|
+
await getRepoNodeModules(),
|
|
60
63
|
joinPath(directory, "node_modules")
|
|
61
64
|
);
|
|
62
65
|
})
|
|
@@ -101,6 +104,8 @@ describe("init", () => {
|
|
|
101
104
|
});
|
|
102
105
|
describe("remote templates", () => {
|
|
103
106
|
it("throws for unknown templates", async () => {
|
|
107
|
+
const processExit = vi.spyOn(process, "exit").mockImplementationOnce(() => {
|
|
108
|
+
});
|
|
104
109
|
await inTemporaryDirectory(async (tmpDir) => {
|
|
105
110
|
await expect(
|
|
106
111
|
runInit({
|
|
@@ -109,8 +114,11 @@ describe("init", () => {
|
|
|
109
114
|
language: "ts",
|
|
110
115
|
template: "https://github.com/some/repo"
|
|
111
116
|
})
|
|
112
|
-
).
|
|
117
|
+
).resolves;
|
|
113
118
|
});
|
|
119
|
+
expect(outputMock.error()).toMatch("--template");
|
|
120
|
+
expect(processExit).toHaveBeenCalledWith(1);
|
|
121
|
+
processExit.mockRestore();
|
|
114
122
|
});
|
|
115
123
|
it("creates basic projects", async () => {
|
|
116
124
|
await inTemporaryDirectory(async (tmpDir) => {
|
|
@@ -120,20 +128,20 @@ describe("init", () => {
|
|
|
120
128
|
language: "ts",
|
|
121
129
|
template: "hello-world"
|
|
122
130
|
});
|
|
123
|
-
const
|
|
131
|
+
const templateFiles = await glob("**/*", {
|
|
124
132
|
cwd: getSkeletonSourceDir().replace("skeleton", "hello-world"),
|
|
125
133
|
ignore: ["**/node_modules/**", "**/dist/**"]
|
|
126
134
|
});
|
|
127
|
-
const
|
|
128
|
-
const nonAppFiles =
|
|
135
|
+
const resultFiles = await glob("**/*", { cwd: tmpDir });
|
|
136
|
+
const nonAppFiles = templateFiles.filter(
|
|
129
137
|
(item) => !item.startsWith("app/")
|
|
130
138
|
);
|
|
131
|
-
expect(
|
|
132
|
-
expect(
|
|
133
|
-
expect(
|
|
134
|
-
expect(
|
|
135
|
-
expect(
|
|
136
|
-
expect(
|
|
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");
|
|
137
145
|
await expect(readFile(`${tmpDir}/package.json`)).resolves.toMatch(
|
|
138
146
|
`"name": "hello-world"`
|
|
139
147
|
);
|
|
@@ -150,6 +158,62 @@ describe("init", () => {
|
|
|
150
158
|
);
|
|
151
159
|
});
|
|
152
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 templateFiles = await glob("**/*", { ignore, cwd: templatePath });
|
|
175
|
+
const exampleFiles = await glob("**/*", { ignore, cwd: examplePath });
|
|
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
|
+
});
|
|
153
217
|
it("transpiles projects to JS", async () => {
|
|
154
218
|
await inTemporaryDirectory(async (tmpDir) => {
|
|
155
219
|
await runInit({
|
|
@@ -158,14 +222,14 @@ describe("init", () => {
|
|
|
158
222
|
language: "js",
|
|
159
223
|
template: "hello-world"
|
|
160
224
|
});
|
|
161
|
-
const
|
|
225
|
+
const templateFiles = await glob("**/*", {
|
|
162
226
|
cwd: getSkeletonSourceDir().replace("skeleton", "hello-world"),
|
|
163
227
|
ignore: ["**/node_modules/**", "**/dist/**"]
|
|
164
228
|
});
|
|
165
|
-
const
|
|
166
|
-
expect(
|
|
229
|
+
const resultFiles = await glob("**/*", { cwd: tmpDir });
|
|
230
|
+
expect(resultFiles).toEqual(
|
|
167
231
|
expect.arrayContaining(
|
|
168
|
-
|
|
232
|
+
templateFiles.filter((item) => !item.endsWith(".d.ts")).map(
|
|
169
233
|
(item) => item.replace(/\.ts(x)?$/, ".js$1").replace(/tsconfig\.json$/, "jsconfig.json")
|
|
170
234
|
)
|
|
171
235
|
)
|
|
@@ -189,20 +253,20 @@ describe("init", () => {
|
|
|
189
253
|
language: "ts",
|
|
190
254
|
mockShop: true
|
|
191
255
|
});
|
|
192
|
-
const
|
|
256
|
+
const templateFiles = await glob("**/*", {
|
|
193
257
|
cwd: getSkeletonSourceDir(),
|
|
194
258
|
ignore: ["**/node_modules/**", "**/dist/**"]
|
|
195
259
|
});
|
|
196
|
-
const
|
|
197
|
-
const nonAppFiles =
|
|
260
|
+
const resultFiles = await glob("**/*", { cwd: tmpDir });
|
|
261
|
+
const nonAppFiles = templateFiles.filter(
|
|
198
262
|
(item) => !item.startsWith("app/")
|
|
199
263
|
);
|
|
200
|
-
expect(
|
|
201
|
-
expect(
|
|
202
|
-
expect(
|
|
203
|
-
expect(
|
|
204
|
-
expect(
|
|
205
|
-
expect(
|
|
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");
|
|
206
270
|
await expect(readFile(`${tmpDir}/server.ts`)).resolves.toEqual(
|
|
207
271
|
await readFile(`${getSkeletonSourceDir()}/server.ts`)
|
|
208
272
|
);
|
|
@@ -229,13 +293,13 @@ describe("init", () => {
|
|
|
229
293
|
it("creates projects with route files", async () => {
|
|
230
294
|
await inTemporaryDirectory(async (tmpDir) => {
|
|
231
295
|
await runInit({ path: tmpDir, git: false, routes: true, language: "ts" });
|
|
232
|
-
const
|
|
296
|
+
const templateFiles = await glob("**/*", {
|
|
233
297
|
cwd: getSkeletonSourceDir(),
|
|
234
298
|
ignore: ["**/node_modules/**", "**/dist/**"]
|
|
235
299
|
});
|
|
236
|
-
const
|
|
237
|
-
expect(
|
|
238
|
-
expect(
|
|
300
|
+
const resultFiles = await glob("**/*", { cwd: tmpDir });
|
|
301
|
+
expect(resultFiles).toEqual(expect.arrayContaining(templateFiles));
|
|
302
|
+
expect(resultFiles).toContain("app/routes/_index.tsx");
|
|
239
303
|
await expect(readFile(`${tmpDir}/server.ts`)).resolves.toEqual(
|
|
240
304
|
await readFile(`${getSkeletonSourceDir()}/server.ts`)
|
|
241
305
|
);
|
|
@@ -252,19 +316,19 @@ describe("init", () => {
|
|
|
252
316
|
it("transpiles projects to JS", async () => {
|
|
253
317
|
await inTemporaryDirectory(async (tmpDir) => {
|
|
254
318
|
await runInit({ path: tmpDir, git: false, routes: true, language: "js" });
|
|
255
|
-
const
|
|
319
|
+
const templateFiles = await glob("**/*", {
|
|
256
320
|
cwd: getSkeletonSourceDir(),
|
|
257
321
|
ignore: ["**/node_modules/**", "**/dist/**"]
|
|
258
322
|
});
|
|
259
|
-
const
|
|
260
|
-
expect(
|
|
323
|
+
const resultFiles = await glob("**/*", { cwd: tmpDir });
|
|
324
|
+
expect(resultFiles).toEqual(
|
|
261
325
|
expect.arrayContaining(
|
|
262
|
-
|
|
326
|
+
templateFiles.filter((item) => !item.endsWith(".d.ts")).map(
|
|
263
327
|
(item) => item.replace(/\.ts(x)?$/, ".js$1").replace(/tsconfig\.json$/, "jsconfig.json")
|
|
264
328
|
)
|
|
265
329
|
)
|
|
266
330
|
);
|
|
267
|
-
expect(
|
|
331
|
+
expect(resultFiles).toContain("app/routes/_index.jsx");
|
|
268
332
|
await expect(readFile(`${tmpDir}/server.js`)).resolves.toMatch(
|
|
269
333
|
/export default {\n\s+\/\*\*.*?\*\/\n\s+async fetch\(\s*request,\s*env,\s*executionContext,?\s*\)/s
|
|
270
334
|
);
|
|
@@ -356,8 +420,8 @@ describe("init", () => {
|
|
|
356
420
|
i18n: "domains",
|
|
357
421
|
routes: true
|
|
358
422
|
});
|
|
359
|
-
const
|
|
360
|
-
expect(
|
|
423
|
+
const resultFiles = await glob("**/*", { cwd: tmpDir });
|
|
424
|
+
expect(resultFiles).toContain("app/routes/_index.tsx");
|
|
361
425
|
const serverFile = await readFile(`${tmpDir}/server.ts`);
|
|
362
426
|
expect(serverFile).toMatch(/i18n: getLocaleFromRequest\(request\),/);
|
|
363
427
|
expect(serverFile).toMatch(/domain = url.hostname/);
|
|
@@ -376,8 +440,8 @@ describe("init", () => {
|
|
|
376
440
|
i18n: "subdomains",
|
|
377
441
|
routes: true
|
|
378
442
|
});
|
|
379
|
-
const
|
|
380
|
-
expect(
|
|
443
|
+
const resultFiles = await glob("**/*", { cwd: tmpDir });
|
|
444
|
+
expect(resultFiles).toContain("app/routes/_index.tsx");
|
|
381
445
|
const serverFile = await readFile(`${tmpDir}/server.ts`);
|
|
382
446
|
expect(serverFile).toMatch(/i18n: getLocaleFromRequest\(request\),/);
|
|
383
447
|
expect(serverFile).toMatch(/firstSubdomain = url.hostname/);
|
|
@@ -396,8 +460,8 @@ describe("init", () => {
|
|
|
396
460
|
i18n: "subfolders",
|
|
397
461
|
routes: true
|
|
398
462
|
});
|
|
399
|
-
const
|
|
400
|
-
expect(
|
|
463
|
+
const resultFiles = await glob("**/*", { cwd: tmpDir });
|
|
464
|
+
expect(resultFiles).toContain("app/routes/($locale)._index.tsx");
|
|
401
465
|
const serverFile = await readFile(`${tmpDir}/server.ts`);
|
|
402
466
|
expect(serverFile).toMatch(/i18n: getLocaleFromRequest\(request\),/);
|
|
403
467
|
expect(serverFile).toMatch(/url.pathname/);
|
|
@@ -519,18 +583,56 @@ describe("init", () => {
|
|
|
519
583
|
expect(output).toMatch("Complete analysis: file://");
|
|
520
584
|
const clientAnalysisPath = "dist/worker/client-bundle-analyzer.html";
|
|
521
585
|
const workerAnalysisPath = "dist/worker/worker-bundle-analyzer.html";
|
|
522
|
-
expect(
|
|
586
|
+
await expect(
|
|
523
587
|
fileExists(joinPath(tmpDir, clientAnalysisPath))
|
|
524
588
|
).resolves.toBeTruthy();
|
|
525
|
-
expect(
|
|
589
|
+
await expect(
|
|
526
590
|
fileExists(joinPath(tmpDir, workerAnalysisPath))
|
|
527
591
|
).resolves.toBeTruthy();
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
);
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
);
|
|
592
|
+
await expect(
|
|
593
|
+
readFile(joinPath(tmpDir, clientAnalysisPath))
|
|
594
|
+
).resolves.toMatch(/globalThis\.METAFILE = '.+';/g);
|
|
595
|
+
await expect(
|
|
596
|
+
readFile(joinPath(tmpDir, workerAnalysisPath))
|
|
597
|
+
).resolves.toMatch(/globalThis\.METAFILE = '.+';/g);
|
|
598
|
+
});
|
|
599
|
+
});
|
|
600
|
+
it("runs dev in the generated project", async () => {
|
|
601
|
+
await inTemporaryDirectory(async (tmpDir) => {
|
|
602
|
+
await runInit({
|
|
603
|
+
path: tmpDir,
|
|
604
|
+
git: true,
|
|
605
|
+
language: "ts",
|
|
606
|
+
styling: "postcss",
|
|
607
|
+
i18n: "subfolders",
|
|
608
|
+
routes: true,
|
|
609
|
+
installDeps: true
|
|
610
|
+
});
|
|
611
|
+
outputMock.clear();
|
|
612
|
+
const port = 1337;
|
|
613
|
+
const { close } = await runDev({
|
|
614
|
+
path: tmpDir,
|
|
615
|
+
port,
|
|
616
|
+
inspectorPort: 9e3,
|
|
617
|
+
disableVirtualRoutes: true,
|
|
618
|
+
disableVersionCheck: true
|
|
619
|
+
});
|
|
620
|
+
try {
|
|
621
|
+
await vi.waitFor(
|
|
622
|
+
() => expect(outputMock.output()).toMatch("success"),
|
|
623
|
+
{ timeout: 5e3 }
|
|
624
|
+
);
|
|
625
|
+
expect(outputMock.output()).toMatch(/View Hydrogen app/i);
|
|
626
|
+
await expect(
|
|
627
|
+
fileExists(joinPath(tmpDir, "dist", "worker", "index.js"))
|
|
628
|
+
).resolves.toBeTruthy();
|
|
629
|
+
const response = await fetch(`http://localhost:${port}`);
|
|
630
|
+
expect(response.status).toEqual(200);
|
|
631
|
+
expect(response.headers.get("content-type")).toEqual("text/html");
|
|
632
|
+
await expect(response.text()).resolves.toMatch("Mock.shop");
|
|
633
|
+
} finally {
|
|
634
|
+
await close();
|
|
635
|
+
}
|
|
534
636
|
});
|
|
535
637
|
});
|
|
536
638
|
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Flags } from '@oclif/core';
|
|
2
2
|
import Command from '@shopify/cli-kit/node/base-command';
|
|
3
3
|
import { basename } from '@shopify/cli-kit/node/path';
|
|
4
|
-
import { renderSuccess, renderConfirmationPrompt, renderWarning,
|
|
4
|
+
import { renderSuccess, renderConfirmationPrompt, renderWarning, renderTextPrompt, renderTasks } from '@shopify/cli-kit/node/ui';
|
|
5
5
|
import { AbortError } from '@shopify/cli-kit/node/error';
|
|
6
6
|
import { commonFlags } from '../../lib/flags.js';
|
|
7
7
|
import { getStorefronts } from '../../lib/graphql/admin/link-storefront.js';
|
|
@@ -11,6 +11,7 @@ import { waitForJob } from '../../lib/graphql/admin/fetch-job.js';
|
|
|
11
11
|
import { titleize } from '../../lib/string.js';
|
|
12
12
|
import { getCliCommand } from '../../lib/shell.js';
|
|
13
13
|
import { login } from '../../lib/auth.js';
|
|
14
|
+
import { handleStorefrontSelection } from '../../lib/onboarding/common.js';
|
|
14
15
|
|
|
15
16
|
class Link extends Command {
|
|
16
17
|
static description = "Link a local project to one of your shop's Hydrogen storefronts.";
|
|
@@ -93,29 +94,12 @@ async function linkStorefront(root, session, config, {
|
|
|
93
94
|
return;
|
|
94
95
|
}
|
|
95
96
|
} else {
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
label: "Create a new storefront",
|
|
99
|
-
value: null
|
|
100
|
-
},
|
|
101
|
-
...storefronts.map(({ id, title, productionUrl }) => ({
|
|
102
|
-
label: `${title} (${productionUrl})`,
|
|
103
|
-
value: id
|
|
104
|
-
}))
|
|
105
|
-
];
|
|
106
|
-
const storefrontId = await renderSelectPrompt({
|
|
107
|
-
message: "Select a Hydrogen storefront to link",
|
|
108
|
-
choices
|
|
109
|
-
});
|
|
110
|
-
if (storefrontId) {
|
|
111
|
-
selectedStorefront = storefronts.find(({ id }) => id === storefrontId);
|
|
112
|
-
} else {
|
|
97
|
+
selectedStorefront = await handleStorefrontSelection(storefronts);
|
|
98
|
+
if (!selectedStorefront) {
|
|
113
99
|
selectedStorefront = await createNewStorefront(root, session);
|
|
114
100
|
}
|
|
115
101
|
}
|
|
116
|
-
|
|
117
|
-
await setStorefront(root, selectedStorefront);
|
|
118
|
-
}
|
|
102
|
+
await setStorefront(root, selectedStorefront);
|
|
119
103
|
return selectedStorefront;
|
|
120
104
|
}
|
|
121
105
|
async function createNewStorefront(root, session) {
|
|
@@ -42,6 +42,8 @@ describe("link", () => {
|
|
|
42
42
|
...FULL_SHOPIFY_CONFIG,
|
|
43
43
|
storefront: void 0
|
|
44
44
|
};
|
|
45
|
+
const expectedStorefrontName = "New Storefront";
|
|
46
|
+
const expectedJobId = "gid://shopify/Job/1";
|
|
45
47
|
beforeEach(async () => {
|
|
46
48
|
vi.mocked(login).mockResolvedValue({
|
|
47
49
|
session: ADMIN_SESSION,
|
|
@@ -55,6 +57,14 @@ describe("link", () => {
|
|
|
55
57
|
}
|
|
56
58
|
]);
|
|
57
59
|
vi.mocked(renderSelectPrompt).mockResolvedValue(FULL_SHOPIFY_CONFIG.shop);
|
|
60
|
+
vi.mocked(createStorefront).mockResolvedValue({
|
|
61
|
+
storefront: {
|
|
62
|
+
id: "gid://shopify/HydrogenStorefront/1",
|
|
63
|
+
title: expectedStorefrontName,
|
|
64
|
+
productionUrl: "https://example.com"
|
|
65
|
+
},
|
|
66
|
+
jobId: expectedJobId
|
|
67
|
+
});
|
|
58
68
|
});
|
|
59
69
|
afterEach(() => {
|
|
60
70
|
vi.resetAllMocks();
|
|
@@ -100,18 +110,8 @@ describe("link", () => {
|
|
|
100
110
|
});
|
|
101
111
|
});
|
|
102
112
|
describe("when you want to link a new Hydrogen storefront", () => {
|
|
103
|
-
const expectedStorefrontName = "New Storefront";
|
|
104
|
-
const expectedJobId = "gid://shopify/Job/1";
|
|
105
113
|
beforeEach(async () => {
|
|
106
114
|
vi.mocked(renderSelectPrompt).mockResolvedValue(null);
|
|
107
|
-
vi.mocked(createStorefront).mockResolvedValue({
|
|
108
|
-
storefront: {
|
|
109
|
-
id: "gid://shopify/HydrogenStorefront/1",
|
|
110
|
-
title: expectedStorefrontName,
|
|
111
|
-
productionUrl: "https://example.com"
|
|
112
|
-
},
|
|
113
|
-
jobId: expectedJobId
|
|
114
|
-
});
|
|
115
115
|
});
|
|
116
116
|
it("chooses to create a new storefront given the directory path", async () => {
|
|
117
117
|
await runLink({ path: "my-path" });
|
|
@@ -1,32 +1,37 @@
|
|
|
1
1
|
import Command from '@shopify/cli-kit/node/base-command';
|
|
2
2
|
import { muteDevLogs } from '../../lib/log.js';
|
|
3
3
|
import { getProjectPaths } from '../../lib/remix-config.js';
|
|
4
|
-
import { commonFlags,
|
|
4
|
+
import { commonFlags, deprecated, flagsToCamelObject } from '../../lib/flags.js';
|
|
5
5
|
import { startMiniOxygen } from '../../lib/mini-oxygen/index.js';
|
|
6
6
|
import { getAllEnvironmentVariables } from '../../lib/environment-variables.js';
|
|
7
7
|
import { getConfig } from '../../lib/shopify-config.js';
|
|
8
|
+
import { findPort } from '../../lib/find-port.js';
|
|
8
9
|
|
|
9
10
|
class Preview extends Command {
|
|
10
11
|
static description = "Runs a Hydrogen storefront in an Oxygen worker for production.";
|
|
11
12
|
static flags = {
|
|
12
13
|
path: commonFlags.path,
|
|
13
14
|
port: commonFlags.port,
|
|
14
|
-
|
|
15
|
-
|
|
15
|
+
worker: deprecated("--worker", { isBoolean: true }),
|
|
16
|
+
"legacy-runtime": commonFlags.legacyRuntime,
|
|
17
|
+
"env-branch": commonFlags.envBranch,
|
|
18
|
+
"inspector-port": commonFlags.inspectorPort,
|
|
19
|
+
debug: commonFlags.debug
|
|
16
20
|
};
|
|
17
21
|
async run() {
|
|
18
22
|
const { flags } = await this.parse(Preview);
|
|
19
23
|
await runPreview({
|
|
20
|
-
...flagsToCamelObject(flags)
|
|
21
|
-
workerRuntime: flags["worker-unstable"]
|
|
24
|
+
...flagsToCamelObject(flags)
|
|
22
25
|
});
|
|
23
26
|
}
|
|
24
27
|
}
|
|
25
28
|
async function runPreview({
|
|
26
|
-
port
|
|
29
|
+
port: appPort,
|
|
27
30
|
path: appPath,
|
|
28
|
-
|
|
29
|
-
envBranch
|
|
31
|
+
legacyRuntime = false,
|
|
32
|
+
envBranch,
|
|
33
|
+
inspectorPort,
|
|
34
|
+
debug
|
|
30
35
|
}) {
|
|
31
36
|
if (!process.env.NODE_ENV)
|
|
32
37
|
process.env.NODE_ENV = "production";
|
|
@@ -35,15 +40,21 @@ async function runPreview({
|
|
|
35
40
|
const { shop, storefront } = await getConfig(root);
|
|
36
41
|
const fetchRemote = !!shop && !!storefront?.id;
|
|
37
42
|
const env = await getAllEnvironmentVariables({ root, fetchRemote, envBranch });
|
|
43
|
+
appPort = legacyRuntime ? appPort : await findPort(appPort);
|
|
44
|
+
inspectorPort = debug ? await findPort(inspectorPort) : inspectorPort;
|
|
45
|
+
const assetsPort = legacyRuntime ? 0 : await findPort(appPort + 100);
|
|
38
46
|
const miniOxygen = await startMiniOxygen(
|
|
39
47
|
{
|
|
40
48
|
root,
|
|
41
|
-
port,
|
|
49
|
+
port: appPort,
|
|
50
|
+
assetsPort,
|
|
51
|
+
env,
|
|
42
52
|
buildPathClient,
|
|
43
53
|
buildPathWorkerFile,
|
|
44
|
-
|
|
54
|
+
inspectorPort,
|
|
55
|
+
debug
|
|
45
56
|
},
|
|
46
|
-
|
|
57
|
+
legacyRuntime
|
|
47
58
|
);
|
|
48
59
|
miniOxygen.showBanner({ mode: "preview" });
|
|
49
60
|
}
|
|
@@ -8,7 +8,6 @@ import { renderI18nPrompt, setupI18nStrategy } from '../../lib/setups/i18n/index
|
|
|
8
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
|
-
import { generateProjectFile } from '../../lib/setups/routes/generate.js';
|
|
12
11
|
import { getTemplateAppFile } from '../../lib/build.js';
|
|
13
12
|
|
|
14
13
|
class Setup extends Command {
|
|
@@ -57,9 +56,6 @@ async function runSetup(options) {
|
|
|
57
56
|
const typescript = !!remixConfig.tsconfigPath;
|
|
58
57
|
backgroundWorkPromise = backgroundWorkPromise.then(
|
|
59
58
|
() => Promise.all([
|
|
60
|
-
// When starting from hello-world, the server entry point won't
|
|
61
|
-
// include all the cart logic from skeleton, so we need to copy it.
|
|
62
|
-
generateProjectFile("../server.ts", { ...remixConfig, typescript }),
|
|
63
59
|
...typescript ? [
|
|
64
60
|
copyFile(
|
|
65
61
|
getTemplateAppFile("../remix.env.d.ts"),
|