@shopify/cli-hydrogen 6.1.0 → 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 +107 -35
- package/dist/commands/hydrogen/deploy.test.js +83 -13
- package/dist/commands/hydrogen/dev.js +30 -15
- 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 +7 -6
- package/dist/commands/hydrogen/setup.js +0 -4
- package/dist/commands/hydrogen/setup.test.js +0 -1
- package/dist/commands/hydrogen/upgrade.js +15 -0
- package/dist/generator-templates/starter/.graphqlrc.yml +12 -1
- package/dist/generator-templates/starter/CHANGELOG.md +56 -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/Header.tsx +5 -1
- package/dist/generator-templates/starter/app/components/Layout.tsx +1 -1
- 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/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 -11
- package/dist/generator-templates/starter/remix.config.js +4 -0
- package/dist/generator-templates/starter/remix.env.d.ts +4 -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/codegen.js +58 -18
- package/dist/lib/defer.js +12 -0
- package/dist/lib/file.js +52 -3
- package/dist/lib/flags.js +15 -8
- 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 +31 -14
- package/dist/lib/mini-oxygen/index.js +4 -5
- package/dist/lib/mini-oxygen/mini-oxygen.test.js +214 -0
- package/dist/lib/mini-oxygen/node.js +4 -2
- package/dist/lib/mini-oxygen/workerd-inspector-logs.js +2 -2
- package/dist/lib/mini-oxygen/workerd.js +27 -10
- 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/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 +54 -38
- 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 +54 -61
- package/package.json +14 -11
- 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
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
2
|
+
import { transformWithEsbuild } from 'vite';
|
|
3
|
+
import { startMiniOxygen, buildAssetsUrl } from './index.js';
|
|
4
|
+
import { joinPath } from '@shopify/cli-kit/node/path';
|
|
5
|
+
import { inTemporaryDirectory, removeFile, touchFile, writeFile } from '@shopify/cli-kit/node/fs';
|
|
6
|
+
import getPort from 'get-port';
|
|
7
|
+
|
|
8
|
+
describe("MiniOxygen Worker Runtime", () => {
|
|
9
|
+
it("receives HTML from test worker", async () => {
|
|
10
|
+
await withFixtures(
|
|
11
|
+
async ({ writeHandler }) => {
|
|
12
|
+
await writeHandler(() => {
|
|
13
|
+
return new Response("<html><body>Hello, world</body></html>", {
|
|
14
|
+
headers: { "content-type": "text/html" }
|
|
15
|
+
});
|
|
16
|
+
});
|
|
17
|
+
},
|
|
18
|
+
async ({ fetch: fetch2 }) => {
|
|
19
|
+
const response = await fetch2("/");
|
|
20
|
+
expect(response.headers.get("content-type")).toEqual("text/html");
|
|
21
|
+
await expect(response.text()).resolves.toEqual(
|
|
22
|
+
"<html><body>Hello, world</body></html>"
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
);
|
|
26
|
+
});
|
|
27
|
+
it("reloads script", async () => {
|
|
28
|
+
await withFixtures(
|
|
29
|
+
async ({ writeHandler }) => {
|
|
30
|
+
await writeHandler((req, env) => new Response("foo"));
|
|
31
|
+
},
|
|
32
|
+
async ({ fetch: fetch2, writeHandler, miniOxygen }) => {
|
|
33
|
+
let response = await fetch2("/");
|
|
34
|
+
await expect(response.text()).resolves.toEqual("foo");
|
|
35
|
+
await writeHandler((req, env) => new Response("bar"));
|
|
36
|
+
await miniOxygen.reload();
|
|
37
|
+
response = await fetch2("/");
|
|
38
|
+
await expect(response.text()).resolves.toEqual("bar");
|
|
39
|
+
}
|
|
40
|
+
);
|
|
41
|
+
});
|
|
42
|
+
it("reloads environment variables", async () => {
|
|
43
|
+
await withFixtures(
|
|
44
|
+
async ({ writeHandler }) => {
|
|
45
|
+
await writeHandler((req, env) => new Response(env.TEST));
|
|
46
|
+
return { env: { TEST: "foo" } };
|
|
47
|
+
},
|
|
48
|
+
async ({ fetch: fetch2, miniOxygen }) => {
|
|
49
|
+
let response = await fetch2("/");
|
|
50
|
+
await expect(response.text()).resolves.toEqual("foo");
|
|
51
|
+
await miniOxygen.reload({ env: { TEST: "bar" } });
|
|
52
|
+
response = await fetch2("/");
|
|
53
|
+
await expect(response.text()).resolves.toEqual("bar");
|
|
54
|
+
}
|
|
55
|
+
);
|
|
56
|
+
});
|
|
57
|
+
it("serves a static asset via proxy", async () => {
|
|
58
|
+
await withFixtures(
|
|
59
|
+
async ({ writeHandler, writeAsset }) => {
|
|
60
|
+
await writeAsset(
|
|
61
|
+
"star.svg",
|
|
62
|
+
'<svg><polygon points="100,10 40,198 190,78 10,78 160,198" style="fill:gold;"/></svg>'
|
|
63
|
+
);
|
|
64
|
+
await writeHandler(() => new Response("ok"));
|
|
65
|
+
},
|
|
66
|
+
async ({ fetch: fetch2, fetchAsset }) => {
|
|
67
|
+
const asset = await (await fetchAsset("/star.svg")).text();
|
|
68
|
+
expect(asset).toEqual(
|
|
69
|
+
'<svg><polygon points="100,10 40,198 190,78 10,78 160,198" style="fill:gold;"/></svg>'
|
|
70
|
+
);
|
|
71
|
+
const response = await fetch2("/star.svg");
|
|
72
|
+
const result = await response.text();
|
|
73
|
+
expect(response.headers.get("content-type")).toEqual("image/svg+xml");
|
|
74
|
+
expect(result).toEqual(asset);
|
|
75
|
+
}
|
|
76
|
+
);
|
|
77
|
+
});
|
|
78
|
+
it("adds Oxygen request headers", async () => {
|
|
79
|
+
await withFixtures(
|
|
80
|
+
async ({ writeHandler }) => {
|
|
81
|
+
await writeHandler(
|
|
82
|
+
(req) => new Response(
|
|
83
|
+
JSON.stringify(Object.fromEntries(req.headers.entries())),
|
|
84
|
+
{ headers: { "content-type": "application/json" } }
|
|
85
|
+
)
|
|
86
|
+
);
|
|
87
|
+
},
|
|
88
|
+
async ({ fetch: fetch2 }) => {
|
|
89
|
+
const response = await fetch2("/");
|
|
90
|
+
await expect(response.json()).resolves.toMatchObject({
|
|
91
|
+
"request-id": expect.stringMatching(/^[a-z0-9-]{36}$/),
|
|
92
|
+
"oxygen-buyer-ip": "127.0.0.1"
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
);
|
|
96
|
+
});
|
|
97
|
+
it("applies sourcemaps to error stack traces", async () => {
|
|
98
|
+
await withFixtures(
|
|
99
|
+
async ({ writeHandler }) => {
|
|
100
|
+
await writeHandler(
|
|
101
|
+
() => {
|
|
102
|
+
function doStuff() {
|
|
103
|
+
throw new Error("test");
|
|
104
|
+
}
|
|
105
|
+
try {
|
|
106
|
+
doStuff();
|
|
107
|
+
} catch (error) {
|
|
108
|
+
console.error(error);
|
|
109
|
+
throw error;
|
|
110
|
+
}
|
|
111
|
+
return new Response("ok");
|
|
112
|
+
},
|
|
113
|
+
{ sourcemap: true }
|
|
114
|
+
);
|
|
115
|
+
},
|
|
116
|
+
async ({ fetch: fetch2, miniOxygen, miniOxygenOptions }) => {
|
|
117
|
+
const spy = vi.spyOn(console, "error").mockImplementation((error) => {
|
|
118
|
+
});
|
|
119
|
+
const response = await fetch2("/");
|
|
120
|
+
expect(response.status).toEqual(500);
|
|
121
|
+
await expect(response.text()).resolves.toEqual("Error: test");
|
|
122
|
+
await vi.waitFor(
|
|
123
|
+
() => expect(spy.mock.calls.length).toBeGreaterThan(1)
|
|
124
|
+
// At least 2 calls
|
|
125
|
+
);
|
|
126
|
+
expect(spy, "Logged with sourcemaps").toHaveBeenCalledWith(
|
|
127
|
+
expect.objectContaining({
|
|
128
|
+
stack: expect.stringMatching(
|
|
129
|
+
// Shows `doStuff` and the offending line by mapping
|
|
130
|
+
// the minified code with sourcemaps:
|
|
131
|
+
/Error: test\nthrow new Error\("test"\);\n.*at doStuff \(/s
|
|
132
|
+
)
|
|
133
|
+
})
|
|
134
|
+
);
|
|
135
|
+
expect(spy).toHaveBeenCalledWith(new Error("test"));
|
|
136
|
+
spy.mockClear();
|
|
137
|
+
await removeFile(miniOxygenOptions.buildPathWorkerFile + ".map");
|
|
138
|
+
await miniOxygen.reload();
|
|
139
|
+
await fetch2("/");
|
|
140
|
+
await vi.waitFor(
|
|
141
|
+
() => expect(spy.mock.calls.length).toBeGreaterThan(1)
|
|
142
|
+
// At least 2 calls
|
|
143
|
+
);
|
|
144
|
+
expect(spy, "Logged without sourcemaps").toHaveBeenCalledWith(
|
|
145
|
+
expect.objectContaining({
|
|
146
|
+
stack: expect.stringMatching(
|
|
147
|
+
// Doesn't show `doStuff` because it's minified
|
|
148
|
+
/Error: test\n\s+at \w .*at Object\.fetch/s
|
|
149
|
+
)
|
|
150
|
+
})
|
|
151
|
+
);
|
|
152
|
+
expect(spy).toHaveBeenCalledWith(new Error("test"));
|
|
153
|
+
spy.mockRestore();
|
|
154
|
+
}
|
|
155
|
+
);
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
function withFixtures(setup, runTest) {
|
|
159
|
+
return inTemporaryDirectory(async (tmpDir) => {
|
|
160
|
+
const relativeDistClient = joinPath("dist", "client");
|
|
161
|
+
const relativeDistWorker = joinPath("dist", "worker");
|
|
162
|
+
const relativeWorkerEntry = joinPath(relativeDistWorker, "index.js");
|
|
163
|
+
const writeFixture = async (filename, content) => {
|
|
164
|
+
const filepath = joinPath(tmpDir, filename);
|
|
165
|
+
await touchFile(filepath);
|
|
166
|
+
await writeFile(filepath, content);
|
|
167
|
+
};
|
|
168
|
+
const writeAsset = (filepath, content) => writeFixture(joinPath(relativeDistClient, filepath), content);
|
|
169
|
+
const writeHandler = async (handler, { sourcemap = false } = {}) => {
|
|
170
|
+
let code = `export default { fetch: ${handler.toString()} }`;
|
|
171
|
+
if (sourcemap) {
|
|
172
|
+
const result = await transformWithEsbuild(code, relativeWorkerEntry, {
|
|
173
|
+
minify: true,
|
|
174
|
+
sourcemap: true
|
|
175
|
+
});
|
|
176
|
+
code = result.code;
|
|
177
|
+
await writeFixture(
|
|
178
|
+
relativeWorkerEntry + ".map",
|
|
179
|
+
JSON.stringify(result.map)
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
await writeFixture(relativeWorkerEntry, code);
|
|
183
|
+
};
|
|
184
|
+
const optionsFromSetup = await setup({
|
|
185
|
+
writeFixture,
|
|
186
|
+
writeAsset,
|
|
187
|
+
writeHandler
|
|
188
|
+
});
|
|
189
|
+
const miniOxygenOptions = {
|
|
190
|
+
root: tmpDir,
|
|
191
|
+
port: await getPort(),
|
|
192
|
+
buildPathWorkerFile: joinPath(tmpDir, relativeWorkerEntry),
|
|
193
|
+
buildPathClient: joinPath(tmpDir, relativeDistClient),
|
|
194
|
+
inspectorPort: 9229,
|
|
195
|
+
assetsPort: 1347,
|
|
196
|
+
env: {},
|
|
197
|
+
...optionsFromSetup
|
|
198
|
+
};
|
|
199
|
+
const miniOxygen = await startMiniOxygen(miniOxygenOptions);
|
|
200
|
+
try {
|
|
201
|
+
await runTest({
|
|
202
|
+
writeFixture,
|
|
203
|
+
writeHandler,
|
|
204
|
+
writeAsset,
|
|
205
|
+
miniOxygen,
|
|
206
|
+
miniOxygenOptions,
|
|
207
|
+
fetch: (pathname) => fetch(miniOxygen.listeningAt + pathname),
|
|
208
|
+
fetchAsset: (pathname) => fetch(buildAssetsUrl(miniOxygenOptions.assetsPort) + pathname)
|
|
209
|
+
});
|
|
210
|
+
} finally {
|
|
211
|
+
await miniOxygen.close();
|
|
212
|
+
}
|
|
213
|
+
});
|
|
214
|
+
}
|
|
@@ -2,10 +2,10 @@ import { randomUUID } from 'node:crypto';
|
|
|
2
2
|
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
3
3
|
import { readFile } from '@shopify/cli-kit/node/fs';
|
|
4
4
|
import { renderSuccess } from '@shopify/cli-kit/node/ui';
|
|
5
|
-
import { startServer, Request } from '@shopify/mini-oxygen';
|
|
5
|
+
import { Response, startServer, Request } from '@shopify/mini-oxygen';
|
|
6
6
|
import { DEFAULT_PORT } from '../flags.js';
|
|
7
7
|
import { OXYGEN_HEADERS_MAP, logRequestLine } from './common.js';
|
|
8
|
-
import { handleDebugNetworkRequest, H2O_BINDING_NAME
|
|
8
|
+
import { setConstructors, createLogRequestEvent, handleDebugNetworkRequest, H2O_BINDING_NAME } from '../request-events.js';
|
|
9
9
|
|
|
10
10
|
async function startNodeServer({
|
|
11
11
|
port = DEFAULT_PORT,
|
|
@@ -21,6 +21,8 @@ async function startNodeServer({
|
|
|
21
21
|
return [key, value.defaultValue];
|
|
22
22
|
})
|
|
23
23
|
);
|
|
24
|
+
setConstructors({ Response });
|
|
25
|
+
const logRequestEvent = createLogRequestEvent();
|
|
24
26
|
const asyncLocalStorage = new AsyncLocalStorage();
|
|
25
27
|
const serviceBindings = {
|
|
26
28
|
[H2O_BINDING_NAME]: {
|
|
@@ -179,12 +179,12 @@ function formatStructuredError(sourceMapConsumer, message, frames) {
|
|
|
179
179
|
lines.push(message);
|
|
180
180
|
frames?.forEach(({ functionName, lineNumber, columnNumber }, i) => {
|
|
181
181
|
try {
|
|
182
|
-
if (lineNumber) {
|
|
182
|
+
if (typeof lineNumber === "number") {
|
|
183
183
|
const pos = sourceMapConsumer.originalPositionFor({
|
|
184
184
|
line: lineNumber + 1,
|
|
185
185
|
column: columnNumber
|
|
186
186
|
});
|
|
187
|
-
if (i === 0 && pos.source && pos.line) {
|
|
187
|
+
if (i === 0 && pos.source && pos.line !== null) {
|
|
188
188
|
const fileSource = sourceMapConsumer.sourceContentFor(pos.source);
|
|
189
189
|
const fileSourceLine = fileSource?.split("\n")[pos.line - 1] || "";
|
|
190
190
|
lines.push(fileSourceLine.trim());
|
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { Response, Miniflare, fetch, Request, NoOpLog } from 'miniflare';
|
|
1
|
+
import { Response, Miniflare, Request, fetch, NoOpLog } from 'miniflare';
|
|
3
2
|
import { resolvePath, dirname } from '@shopify/cli-kit/node/path';
|
|
4
3
|
import { readFile } from '@shopify/cli-kit/node/fs';
|
|
5
4
|
import { renderSuccess } from '@shopify/cli-kit/node/ui';
|
|
5
|
+
import { outputContent, outputToken } from '@shopify/cli-kit/node/output';
|
|
6
|
+
import colors from '@shopify/cli-kit/node/colors';
|
|
6
7
|
import { createInspectorConnector } from './workerd-inspector.js';
|
|
7
8
|
import { findPort } from '../find-port.js';
|
|
8
9
|
import { OXYGEN_HEADERS_MAP, logRequestLine } from './common.js';
|
|
9
|
-
import { setConstructors, handleDebugNetworkRequest, H2O_BINDING_NAME,
|
|
10
|
+
import { setConstructors, handleDebugNetworkRequest, H2O_BINDING_NAME, createLogRequestEvent } from '../request-events.js';
|
|
10
11
|
import { STATIC_ASSET_EXTENSIONS, createAssetsServer, buildAssetsUrl } from './assets.js';
|
|
11
12
|
|
|
12
13
|
const PRIVATE_WORKERD_INSPECTOR_PORT = 9222;
|
|
@@ -33,6 +34,13 @@ async function startWorkerdServer({
|
|
|
33
34
|
const absoluteBundlePath = resolvePath(root, buildPathWorkerFile);
|
|
34
35
|
const handleAssets = createAssetHandler(assetsPort);
|
|
35
36
|
const staticAssetExtensions = STATIC_ASSET_EXTENSIONS.slice();
|
|
37
|
+
let stringifiedOxygenHandler = miniOxygenHandler.toString();
|
|
38
|
+
if (process.env.NODE_ENV === "test") {
|
|
39
|
+
stringifiedOxygenHandler = stringifiedOxygenHandler.replace(
|
|
40
|
+
/\w*vite_ssr_import[\w\d]*\./g,
|
|
41
|
+
""
|
|
42
|
+
);
|
|
43
|
+
}
|
|
36
44
|
const buildMiniOxygenOptions = async () => ({
|
|
37
45
|
cf: false,
|
|
38
46
|
verbose: false,
|
|
@@ -41,11 +49,13 @@ async function startWorkerdServer({
|
|
|
41
49
|
log: new NoOpLog(),
|
|
42
50
|
liveReload: watch,
|
|
43
51
|
host: "localhost",
|
|
52
|
+
handleRuntimeStdio() {
|
|
53
|
+
},
|
|
44
54
|
workers: [
|
|
45
55
|
{
|
|
46
56
|
name: "mini-oxygen",
|
|
47
57
|
modules: true,
|
|
48
|
-
script: `export default { fetch: ${
|
|
58
|
+
script: `export default { fetch: ${stringifiedOxygenHandler} }`,
|
|
49
59
|
bindings: {
|
|
50
60
|
staticAssetExtensions,
|
|
51
61
|
oxygenHeadersMap
|
|
@@ -71,7 +81,7 @@ async function startWorkerdServer({
|
|
|
71
81
|
compatibilityDate: "2022-10-31",
|
|
72
82
|
bindings: { ...env },
|
|
73
83
|
serviceBindings: {
|
|
74
|
-
[H2O_BINDING_NAME]:
|
|
84
|
+
[H2O_BINDING_NAME]: createLogRequestEvent({ absoluteBundlePath })
|
|
75
85
|
}
|
|
76
86
|
}
|
|
77
87
|
]
|
|
@@ -107,10 +117,14 @@ async function startWorkerdServer({
|
|
|
107
117
|
},
|
|
108
118
|
showBanner(options) {
|
|
109
119
|
console.log("");
|
|
110
|
-
const
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
120
|
+
const isVSCode = process.env.TERM_PROGRAM === "vscode";
|
|
121
|
+
const debuggingDocsLink = "https://h2o.fyi/debugging/server-code" + (isVSCode ? "#visual-studio-code" : "#step-2-attach-a-debugger");
|
|
122
|
+
const debuggerMessage = outputContent`\n\nDebugging enabled on port ${String(
|
|
123
|
+
publicInspectorPort
|
|
124
|
+
)}.\nAttach a ${outputToken.link(
|
|
125
|
+
colors.yellow(isVSCode ? "VSCode debugger" : "debugger"),
|
|
126
|
+
debuggingDocsLink
|
|
127
|
+
)} or open DevTools in http://localhost:${String(publicInspectorPort)}.`.value;
|
|
114
128
|
renderSuccess({
|
|
115
129
|
headline: `${options?.headlinePrefix ?? ""}MiniOxygen (Worker Runtime) ${options?.mode ?? "development"} server running.`,
|
|
116
130
|
body: [
|
|
@@ -179,7 +193,10 @@ function createAssetHandler(assetsPort) {
|
|
|
179
193
|
return async (request) => {
|
|
180
194
|
return fetch(
|
|
181
195
|
new Request(
|
|
182
|
-
request.url.replace(
|
|
196
|
+
request.url.replace(
|
|
197
|
+
new URL(request.url).origin + "/",
|
|
198
|
+
assetsServerOrigin
|
|
199
|
+
),
|
|
183
200
|
request
|
|
184
201
|
)
|
|
185
202
|
);
|
|
@@ -23,12 +23,15 @@ const REQUIRED_ROUTES = [
|
|
|
23
23
|
// 'discount/:discountCode', => Handled in storefrontRedirect
|
|
24
24
|
"account",
|
|
25
25
|
"account/login",
|
|
26
|
-
"account/register",
|
|
27
26
|
// 'account/addresses',
|
|
28
27
|
// 'account/orders',
|
|
29
28
|
"account/orders/:orderId",
|
|
30
|
-
|
|
31
|
-
"account/
|
|
29
|
+
// -- Added for CAAPI:
|
|
30
|
+
"account/authorize"
|
|
31
|
+
// -- These were removed when migrating to CAAPI:
|
|
32
|
+
// 'account/register',
|
|
33
|
+
// 'account/reset/:id/:token',
|
|
34
|
+
// 'account/activate/:id/:token',
|
|
32
35
|
// 'password',
|
|
33
36
|
// 'opening_soon',
|
|
34
37
|
];
|
|
@@ -16,6 +16,7 @@ import { transpileProject } from '../transpile/index.js';
|
|
|
16
16
|
import { renderCssPrompt, setupCssStrategy, CSS_STRATEGY_NAME_MAP } from '../setups/css/index.js';
|
|
17
17
|
import { renderRoutePrompt, generateRoutes, generateProjectFile } from '../setups/routes/generate.js';
|
|
18
18
|
import { execAsync } from '../process.js';
|
|
19
|
+
import { getStorefronts } from '../graphql/admin/link-storefront.js';
|
|
19
20
|
|
|
20
21
|
const LANGUAGES = {
|
|
21
22
|
js: "JavaScript",
|
|
@@ -68,7 +69,7 @@ async function handleRouteGeneration(controller, flagRoutes) {
|
|
|
68
69
|
}
|
|
69
70
|
function generateProjectEntries(options) {
|
|
70
71
|
return Promise.all(
|
|
71
|
-
["root", "entry.server", "entry.client"].map(
|
|
72
|
+
["root", "entry.server", "entry.client", "../server.ts"].map(
|
|
72
73
|
(filename) => generateProjectFile(filename, options)
|
|
73
74
|
)
|
|
74
75
|
);
|
|
@@ -108,12 +109,41 @@ async function handleCliShortcut(controller, cliCommand, flagShortcut) {
|
|
|
108
109
|
async function handleStorefrontLink(controller) {
|
|
109
110
|
const { session, config } = await login();
|
|
110
111
|
renderLoginSuccess(config);
|
|
111
|
-
const
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
112
|
+
const storefronts = await getStorefronts(session);
|
|
113
|
+
let selectedStorefront = await handleStorefrontSelection(storefronts);
|
|
114
|
+
let title;
|
|
115
|
+
if (selectedStorefront) {
|
|
116
|
+
title = selectedStorefront.title;
|
|
117
|
+
} else {
|
|
118
|
+
title = await renderTextPrompt({
|
|
119
|
+
message: "New storefront name",
|
|
120
|
+
defaultValue: titleize(config.shopName),
|
|
121
|
+
abortSignal: controller.signal
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
return {
|
|
125
|
+
...config,
|
|
126
|
+
id: selectedStorefront?.id,
|
|
127
|
+
title,
|
|
128
|
+
session
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
async function handleStorefrontSelection(storefronts) {
|
|
132
|
+
const choices = [
|
|
133
|
+
{
|
|
134
|
+
label: "Create a new storefront",
|
|
135
|
+
value: null
|
|
136
|
+
},
|
|
137
|
+
...storefronts.map(({ id, title, productionUrl }) => ({
|
|
138
|
+
label: `${title} (${productionUrl})`,
|
|
139
|
+
value: id
|
|
140
|
+
}))
|
|
141
|
+
];
|
|
142
|
+
const storefrontId = await renderSelectPrompt({
|
|
143
|
+
message: "Select a Hydrogen storefront to link",
|
|
144
|
+
choices
|
|
115
145
|
});
|
|
116
|
-
return {
|
|
146
|
+
return storefrontId ? storefronts.find(({ id }) => id === storefrontId) : void 0;
|
|
117
147
|
}
|
|
118
148
|
async function handleProjectLocation({
|
|
119
149
|
storefrontInfo,
|
|
@@ -380,7 +410,7 @@ async function renderProjectReady(project, {
|
|
|
380
410
|
{
|
|
381
411
|
link: {
|
|
382
412
|
label: "Guides",
|
|
383
|
-
url: "https://
|
|
413
|
+
url: "https://h2o.fyi/building"
|
|
384
414
|
}
|
|
385
415
|
},
|
|
386
416
|
{
|
|
@@ -434,7 +464,8 @@ async function renderProjectReady(project, {
|
|
|
434
464
|
function createAbortHandler(controller, project) {
|
|
435
465
|
return async function abort(error) {
|
|
436
466
|
controller.abort();
|
|
437
|
-
|
|
467
|
+
await Promise.resolve();
|
|
468
|
+
if (project?.directory) {
|
|
438
469
|
await rmdir(project.directory, { force: true }).catch(() => {
|
|
439
470
|
});
|
|
440
471
|
}
|
|
@@ -457,4 +488,4 @@ function normalizeRoutePath(routePath) {
|
|
|
457
488
|
return routePath.replace(/(^|\.)_index$/, "").replace(/((^|\.)[^\.]+)_\./g, "$1.").replace(/\.(?!\w+\])/g, "/").replace(/\$$/g, ":catchAll").replace(/\$/g, ":").replace(/[\[\]]/g, "").replace(/:\w*Handle/i, ":handle");
|
|
458
489
|
}
|
|
459
490
|
|
|
460
|
-
export { LANGUAGES, commitAll, createAbortHandler, createInitialCommit, generateProjectEntries, handleCliShortcut, handleCssStrategy, handleDependencies, handleI18n, handleLanguage, handleProjectLocation, handleRouteGeneration, handleStorefrontLink, renderProjectReady };
|
|
491
|
+
export { LANGUAGES, commitAll, createAbortHandler, createInitialCommit, generateProjectEntries, handleCliShortcut, handleCssStrategy, handleDependencies, handleI18n, handleLanguage, handleProjectLocation, handleRouteGeneration, handleStorefrontLink, handleStorefrontSelection, renderProjectReady };
|
|
@@ -37,7 +37,7 @@ async function setupLocalStarterTemplate(options, controller) {
|
|
|
37
37
|
if (templateAction === "mock")
|
|
38
38
|
project.storefrontTitle = "Mock.shop";
|
|
39
39
|
const abort = createAbortHandler(controller, project);
|
|
40
|
-
const createStorefrontPromise = storefrontInfo && createStorefront(storefrontInfo.session, storefrontInfo.title).then(async ({ storefront, jobId }) => {
|
|
40
|
+
const createStorefrontPromise = storefrontInfo && !storefrontInfo.id && createStorefront(storefrontInfo.session, storefrontInfo.title).then(async ({ storefront, jobId }) => {
|
|
41
41
|
if (jobId)
|
|
42
42
|
await waitForJob(storefrontInfo.session, jobId);
|
|
43
43
|
return storefront;
|
|
@@ -46,9 +46,9 @@ async function setupLocalStarterTemplate(options, controller) {
|
|
|
46
46
|
let backgroundWorkPromise = copy(
|
|
47
47
|
templateDir,
|
|
48
48
|
project.directory,
|
|
49
|
-
// Filter out the `app` directory, which will be generated later
|
|
49
|
+
// Filter out the `app` directory and server.ts, which will be generated later
|
|
50
50
|
{
|
|
51
|
-
filter: (filepath) => !/^(app
|
|
51
|
+
filter: (filepath) => !/^(app\/|dist\/|node_modules\/|server\.ts)/i.test(
|
|
52
52
|
relativePath(templateDir, filepath)
|
|
53
53
|
)
|
|
54
54
|
}
|
|
@@ -90,19 +90,23 @@ async function setupLocalStarterTemplate(options, controller) {
|
|
|
90
90
|
)
|
|
91
91
|
];
|
|
92
92
|
const envLeadingComment = "# The variables added in this file are only available locally in MiniOxygen.\n# Run `h2 link` to also inject environment variables from your storefront,\n# or `h2 env pull` to populate this file.";
|
|
93
|
-
|
|
93
|
+
let storefrontToLink;
|
|
94
|
+
if (storefrontInfo) {
|
|
94
95
|
promises.push(
|
|
95
96
|
// Save linked storefront in project
|
|
96
97
|
setUserAccount(project.directory, storefrontInfo),
|
|
97
|
-
createStorefrontPromise.then(
|
|
98
|
-
(storefront) => (
|
|
99
|
-
// Save linked storefront in project
|
|
100
|
-
setStorefront(project.directory, storefront)
|
|
101
|
-
)
|
|
102
|
-
),
|
|
103
98
|
// Write empty dotenv file to fallback to remote Oxygen variables
|
|
104
99
|
writeFile(joinPath(project.directory, ".env"), envLeadingComment)
|
|
105
100
|
);
|
|
101
|
+
if (storefrontInfo.id) {
|
|
102
|
+
storefrontToLink = { id: storefrontInfo.id, title: storefrontInfo.title };
|
|
103
|
+
} else if (createStorefrontPromise) {
|
|
104
|
+
promises.push(
|
|
105
|
+
createStorefrontPromise.then((createdStorefront) => {
|
|
106
|
+
storefrontToLink = createdStorefront;
|
|
107
|
+
})
|
|
108
|
+
);
|
|
109
|
+
}
|
|
106
110
|
} else if (templateAction === "mock") {
|
|
107
111
|
promises.push(
|
|
108
112
|
// Set required env vars
|
|
@@ -115,7 +119,11 @@ async function setupLocalStarterTemplate(options, controller) {
|
|
|
115
119
|
)
|
|
116
120
|
);
|
|
117
121
|
}
|
|
118
|
-
return Promise.all(promises).
|
|
122
|
+
return Promise.all(promises).then(() => {
|
|
123
|
+
if (storefrontToLink) {
|
|
124
|
+
return setStorefront(project.directory, storefrontToLink);
|
|
125
|
+
}
|
|
126
|
+
}).catch(abort);
|
|
119
127
|
});
|
|
120
128
|
const { language, transpileProject } = await handleLanguage(
|
|
121
129
|
project.directory,
|
|
@@ -1,38 +1,58 @@
|
|
|
1
1
|
import { AbortError } from '@shopify/cli-kit/node/error';
|
|
2
|
-
import { copyFile } from '@shopify/cli-kit/node/fs';
|
|
2
|
+
import { fileExists, copyFile } from '@shopify/cli-kit/node/fs';
|
|
3
|
+
import { readAndParsePackageJson } from '@shopify/cli-kit/node/node-package-manager';
|
|
3
4
|
import { joinPath } from '@shopify/cli-kit/node/path';
|
|
4
5
|
import { renderTasks, renderInfo } from '@shopify/cli-kit/node/ui';
|
|
5
6
|
import { getLatestTemplates } from '../template-downloader.js';
|
|
6
|
-
import {
|
|
7
|
+
import { applyTemplateDiff } from '../template-diff.js';
|
|
8
|
+
import { createAbortHandler, handleProjectLocation, handleLanguage, createInitialCommit, handleDependencies, commitAll, renderProjectReady } from './common.js';
|
|
7
9
|
|
|
8
10
|
async function setupRemoteTemplate(options, controller) {
|
|
9
|
-
const isOfficialTemplate = options.template === "demo-store" || options.template === "hello-world";
|
|
10
|
-
if (!isOfficialTemplate) {
|
|
11
|
-
throw new AbortError(
|
|
12
|
-
"Only `demo-store` and `hello-world` are supported in --template flag for now.",
|
|
13
|
-
"Skip the --template flag to run the setup flow."
|
|
14
|
-
);
|
|
15
|
-
}
|
|
16
11
|
const appTemplate = options.template;
|
|
12
|
+
let abort = createAbortHandler(controller);
|
|
17
13
|
const backgroundDownloadPromise = getLatestTemplates({
|
|
18
14
|
signal: controller.signal
|
|
19
|
-
}).
|
|
20
|
-
|
|
21
|
-
|
|
15
|
+
}).then(async ({ templatesDir, examplesDir }) => {
|
|
16
|
+
const templatePath = joinPath(templatesDir, appTemplate);
|
|
17
|
+
const examplePath = joinPath(examplesDir, appTemplate);
|
|
18
|
+
if (await fileExists(templatePath)) {
|
|
19
|
+
return { templatesDir, sourcePath: templatePath };
|
|
20
|
+
}
|
|
21
|
+
if (await fileExists(examplePath)) {
|
|
22
|
+
return { templatesDir, sourcePath: examplePath };
|
|
23
|
+
}
|
|
24
|
+
throw new AbortError(
|
|
25
|
+
"Unknown value in --template flag.",
|
|
26
|
+
"Skip the --template flag or provide the name of a template or example in the Hydrogen repository."
|
|
27
|
+
);
|
|
28
|
+
}).catch(abort);
|
|
22
29
|
const project = await handleProjectLocation({ ...options, controller });
|
|
23
30
|
if (!project)
|
|
24
31
|
return;
|
|
25
|
-
|
|
26
|
-
let backgroundWorkPromise = backgroundDownloadPromise.then(
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
32
|
+
abort = createAbortHandler(controller, project);
|
|
33
|
+
let backgroundWorkPromise = backgroundDownloadPromise.then(async (result) => {
|
|
34
|
+
if (controller.signal.aborted)
|
|
35
|
+
return;
|
|
36
|
+
const { sourcePath: sourcePath2, templatesDir } = result;
|
|
37
|
+
const pkgJson = await readAndParsePackageJson(
|
|
38
|
+
joinPath(sourcePath2, "package.json")
|
|
39
|
+
);
|
|
40
|
+
if (pkgJson.scripts?.dev?.includes("--diff")) {
|
|
41
|
+
return applyTemplateDiff(
|
|
42
|
+
project.directory,
|
|
43
|
+
sourcePath2,
|
|
44
|
+
joinPath(templatesDir, "skeleton")
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
return copyFile(sourcePath2, project.directory);
|
|
48
|
+
}).catch(abort);
|
|
49
|
+
if (controller.signal.aborted)
|
|
50
|
+
return;
|
|
51
|
+
const { sourcePath } = await backgroundDownloadPromise;
|
|
52
|
+
const supportsTranspilation = await fileExists(
|
|
53
|
+
joinPath(sourcePath, "tsconfig.json")
|
|
35
54
|
);
|
|
55
|
+
const { language, transpileProject } = supportsTranspilation ? await handleLanguage(project.directory, controller, options.language) : { language: "js", transpileProject: () => Promise.resolve() };
|
|
36
56
|
backgroundWorkPromise = backgroundWorkPromise.then(() => transpileProject().catch(abort)).then(
|
|
37
57
|
() => options.git ? createInitialCommit(project.directory) : void 0
|
|
38
58
|
);
|
|
@@ -74,17 +94,17 @@ async function setupRemoteTemplate(options, controller) {
|
|
|
74
94
|
}
|
|
75
95
|
});
|
|
76
96
|
}
|
|
97
|
+
if (controller.signal.aborted)
|
|
98
|
+
return;
|
|
77
99
|
await renderTasks(tasks);
|
|
78
100
|
if (options.git) {
|
|
79
101
|
await commitAll(project.directory, "Lockfile");
|
|
80
102
|
}
|
|
81
103
|
await renderProjectReady(project, setupSummary);
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
});
|
|
87
|
-
}
|
|
104
|
+
renderInfo({
|
|
105
|
+
headline: `Your project will display inventory from ${options.template === "demo-store" ? "the Hydrogen Demo Store" : "Mock.shop"}.`,
|
|
106
|
+
body: `To connect this project to your Shopify store\u2019s inventory, update \`${project.name}/.env\` with your store ID and Storefront API key.`
|
|
107
|
+
});
|
|
88
108
|
return {
|
|
89
109
|
...project,
|
|
90
110
|
...setupSummary
|