@shopify/cli-hydrogen 5.2.2 → 5.3.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 +49 -25
- package/dist/commands/hydrogen/deploy.js +171 -0
- package/dist/commands/hydrogen/deploy.test.js +185 -0
- package/dist/commands/hydrogen/dev.js +27 -14
- package/dist/commands/hydrogen/init.js +10 -6
- package/dist/commands/hydrogen/init.test.js +16 -1
- package/dist/commands/hydrogen/preview.js +27 -11
- package/dist/generator-templates/starter/app/root.tsx +6 -4
- package/dist/generator-templates/starter/app/routes/account.tsx +1 -1
- package/dist/generator-templates/starter/app/routes/cart.$lines.tsx +70 -0
- package/dist/generator-templates/starter/app/routes/cart.tsx +1 -1
- package/dist/generator-templates/starter/app/routes/discount.$code.tsx +43 -0
- package/dist/generator-templates/starter/app/routes/products.$handle.tsx +3 -1
- package/dist/generator-templates/starter/package.json +4 -4
- package/dist/generator-templates/starter/remix.env.d.ts +12 -3
- package/dist/generator-templates/starter/server.ts +22 -19
- package/dist/generator-templates/starter/tsconfig.json +1 -1
- package/dist/lib/bundle/analyzer.js +56 -0
- package/dist/lib/bundle/bundle-analyzer.html +2045 -0
- package/dist/lib/flags.js +4 -0
- package/dist/lib/get-oxygen-token.js +47 -0
- package/dist/lib/get-oxygen-token.test.js +104 -0
- package/dist/lib/graphql/admin/oxygen-token.js +21 -0
- package/dist/lib/live-reload.js +2 -1
- package/dist/lib/log.js +56 -13
- package/dist/lib/mini-oxygen/common.js +58 -0
- package/dist/lib/mini-oxygen/index.js +12 -0
- package/dist/lib/mini-oxygen/node.js +110 -0
- package/dist/lib/mini-oxygen/types.js +1 -0
- package/dist/lib/mini-oxygen/workerd-inspector.js +392 -0
- package/dist/lib/mini-oxygen/workerd.js +182 -0
- package/dist/lib/onboarding/common.js +24 -13
- package/dist/lib/onboarding/local.js +1 -1
- package/dist/lib/remix-config.js +12 -2
- package/dist/lib/remix-version-check.js +7 -4
- package/dist/lib/remix-version-check.test.js +1 -1
- package/dist/lib/render-errors.js +1 -1
- package/dist/lib/request-events.js +84 -0
- package/dist/lib/setups/routes/generate.js +3 -3
- package/dist/lib/transpile-ts.js +21 -23
- package/dist/lib/virtual-routes.js +11 -9
- package/dist/virtual-routes/components/FlameChartWrapper.jsx +125 -0
- package/dist/virtual-routes/routes/debug-network.jsx +289 -0
- package/dist/virtual-routes/routes/index.jsx +4 -4
- package/dist/virtual-routes/virtual-root.jsx +7 -4
- package/oclif.manifest.json +81 -3
- package/package.json +35 -12
- package/dist/lib/mini-oxygen.js +0 -108
|
@@ -0,0 +1,392 @@
|
|
|
1
|
+
import { dirname } from 'node:path';
|
|
2
|
+
import { readFile } from 'node:fs/promises';
|
|
3
|
+
import { SourceMapConsumer } from 'source-map';
|
|
4
|
+
import { parse } from 'stack-trace';
|
|
5
|
+
import WebSocket from 'ws';
|
|
6
|
+
|
|
7
|
+
async function findInspectorUrl(inspectorPort) {
|
|
8
|
+
try {
|
|
9
|
+
const jsonUrl = `http://127.0.0.1:${inspectorPort}/json`;
|
|
10
|
+
const body = await (await fetch(jsonUrl)).json();
|
|
11
|
+
return body?.find(({ id }) => id === "core:user:hydrogen")?.webSocketDebuggerUrl;
|
|
12
|
+
} catch (error) {
|
|
13
|
+
console.error("Error attempting to retrieve debugger URL:", error);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
function connectToInspector({
|
|
17
|
+
inspectorUrl,
|
|
18
|
+
sourceMapPath
|
|
19
|
+
}) {
|
|
20
|
+
const messageCounterRef = { value: -1 };
|
|
21
|
+
const getMessageId = () => messageCounterRef.value--;
|
|
22
|
+
const pendingMessages = /* @__PURE__ */ new Map();
|
|
23
|
+
const ws = new WebSocket(inspectorUrl);
|
|
24
|
+
let keepAliveInterval;
|
|
25
|
+
const isClosed = () => ws.readyState === WebSocket.CLOSED || ws.readyState === WebSocket.CLOSING;
|
|
26
|
+
const send = (method, params) => {
|
|
27
|
+
if (!isClosed()) {
|
|
28
|
+
const id = getMessageId();
|
|
29
|
+
let promiseResolve = void 0;
|
|
30
|
+
const promise = new Promise(
|
|
31
|
+
(resolve) => promiseResolve = resolve
|
|
32
|
+
);
|
|
33
|
+
pendingMessages.set(id, promiseResolve);
|
|
34
|
+
ws.send(JSON.stringify({ id, method, params }));
|
|
35
|
+
return promise;
|
|
36
|
+
}
|
|
37
|
+
return Promise.resolve(void 0);
|
|
38
|
+
};
|
|
39
|
+
const cleanupMessageQueue = (data) => {
|
|
40
|
+
try {
|
|
41
|
+
if (data?.id < 0) {
|
|
42
|
+
const resolve = pendingMessages.get(data.id);
|
|
43
|
+
if (resolve !== void 0) {
|
|
44
|
+
pendingMessages.delete(data.id);
|
|
45
|
+
resolve(data.result);
|
|
46
|
+
}
|
|
47
|
+
return true;
|
|
48
|
+
}
|
|
49
|
+
} catch (error) {
|
|
50
|
+
console.error(error);
|
|
51
|
+
}
|
|
52
|
+
return false;
|
|
53
|
+
};
|
|
54
|
+
function getPropertyValue(name, response) {
|
|
55
|
+
return response?.result.find((prop) => prop.name === name)?.value;
|
|
56
|
+
}
|
|
57
|
+
async function reconstructError(initialProperties, ro) {
|
|
58
|
+
let errorProperties = { ...initialProperties };
|
|
59
|
+
const objectId = ro?.objectId;
|
|
60
|
+
if (objectId) {
|
|
61
|
+
const [sourceMapConsumer, getPropertiesResponse] = await Promise.all([
|
|
62
|
+
getSourceMapConsumer(),
|
|
63
|
+
send("Runtime.getProperties", {
|
|
64
|
+
objectId,
|
|
65
|
+
ownProperties: false,
|
|
66
|
+
accessorPropertiesOnly: false,
|
|
67
|
+
generatePreview: false,
|
|
68
|
+
nonIndexedPropertiesOnly: false
|
|
69
|
+
})
|
|
70
|
+
]);
|
|
71
|
+
const message = getPropertyValue("message", getPropertiesResponse);
|
|
72
|
+
if (message?.value) {
|
|
73
|
+
errorProperties.message = message.value;
|
|
74
|
+
}
|
|
75
|
+
const stack = getPropertyValue("stack", getPropertiesResponse);
|
|
76
|
+
if (stack?.value) {
|
|
77
|
+
errorProperties.stack = sourceMapConsumer ? formatStack(sourceMapConsumer, stack.value) : stack.value;
|
|
78
|
+
}
|
|
79
|
+
const cause = getPropertyValue("cause", getPropertiesResponse);
|
|
80
|
+
if (cause) {
|
|
81
|
+
errorProperties.cause = cause.description ?? cause.value;
|
|
82
|
+
if (cause.subtype === "error" && sourceMapConsumer && cause.description !== void 0) {
|
|
83
|
+
errorProperties.stack = formatStack(
|
|
84
|
+
sourceMapConsumer,
|
|
85
|
+
cause.description
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
const isDomException = ro?.className === "DOMException";
|
|
90
|
+
if (isDomException) {
|
|
91
|
+
const stackDescriptor = getPropertiesResponse?.result.find(
|
|
92
|
+
(prop) => prop.name === "stack"
|
|
93
|
+
);
|
|
94
|
+
const getObjectId = stackDescriptor?.get?.objectId;
|
|
95
|
+
if (getObjectId !== void 0) {
|
|
96
|
+
const callFunctionResponse = await send("Runtime.callFunctionOn", {
|
|
97
|
+
objectId,
|
|
98
|
+
functionDeclaration: "function invokeGetter(getter) { return Reflect.apply(getter, this, []); }",
|
|
99
|
+
arguments: [{ objectId: getObjectId }],
|
|
100
|
+
silent: true
|
|
101
|
+
});
|
|
102
|
+
if (callFunctionResponse !== void 0) {
|
|
103
|
+
const stack2 = callFunctionResponse.result.value;
|
|
104
|
+
if (typeof stack2 === "string" && sourceMapConsumer !== void 0) {
|
|
105
|
+
errorProperties.stack = formatStack(sourceMapConsumer, stack2);
|
|
106
|
+
} else {
|
|
107
|
+
try {
|
|
108
|
+
errorProperties.stack = JSON.stringify(stack2);
|
|
109
|
+
} catch {
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
const error = new Error(errorProperties.message);
|
|
117
|
+
error.stack = errorProperties.stack;
|
|
118
|
+
if (errorProperties.cause) {
|
|
119
|
+
error.cause = errorProperties.cause;
|
|
120
|
+
}
|
|
121
|
+
return error;
|
|
122
|
+
}
|
|
123
|
+
const sourceMapAbortController = new AbortController();
|
|
124
|
+
let sourceMapConsumerPromise;
|
|
125
|
+
const getSourceMapConsumer = () => {
|
|
126
|
+
return sourceMapConsumerPromise ??= (async () => {
|
|
127
|
+
if (!sourceMapPath || sourceMapAbortController.signal.aborted) {
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
try {
|
|
131
|
+
const mapContent = await readFile(sourceMapPath, "utf-8");
|
|
132
|
+
if (sourceMapAbortController.signal.aborted)
|
|
133
|
+
return;
|
|
134
|
+
const map = JSON.parse(mapContent);
|
|
135
|
+
map.sourceRoot = dirname(sourceMapPath);
|
|
136
|
+
const sourceMapConsumer = await new SourceMapConsumer(map);
|
|
137
|
+
if (sourceMapAbortController.signal.aborted) {
|
|
138
|
+
sourceMapConsumer.destroy();
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
sourceMapAbortController.signal.addEventListener("abort", () => {
|
|
142
|
+
sourceMapConsumerPromise = Promise.resolve(void 0);
|
|
143
|
+
sourceMapConsumer.destroy();
|
|
144
|
+
});
|
|
145
|
+
return sourceMapConsumer;
|
|
146
|
+
} catch {
|
|
147
|
+
}
|
|
148
|
+
})();
|
|
149
|
+
};
|
|
150
|
+
ws.addEventListener("message", async (event) => {
|
|
151
|
+
if (typeof event.data === "string") {
|
|
152
|
+
const evt = JSON.parse(event.data);
|
|
153
|
+
cleanupMessageQueue(evt);
|
|
154
|
+
if (evt.method === "Runtime.exceptionThrown") {
|
|
155
|
+
const params = evt.params;
|
|
156
|
+
const errorProperties = {};
|
|
157
|
+
const sourceMapConsumer = await getSourceMapConsumer();
|
|
158
|
+
if (sourceMapConsumer !== void 0) {
|
|
159
|
+
const message = params.exceptionDetails.exception?.description?.split("\n")[0];
|
|
160
|
+
const stack = params.exceptionDetails.stackTrace?.callFrames;
|
|
161
|
+
const formatted = formatStructuredError(
|
|
162
|
+
sourceMapConsumer,
|
|
163
|
+
message,
|
|
164
|
+
stack
|
|
165
|
+
);
|
|
166
|
+
errorProperties.message = params.exceptionDetails.text;
|
|
167
|
+
errorProperties.stack = formatted;
|
|
168
|
+
} else {
|
|
169
|
+
errorProperties.message = params.exceptionDetails.text + " " + (params.exceptionDetails.exception?.description ?? "");
|
|
170
|
+
}
|
|
171
|
+
console.error(
|
|
172
|
+
await reconstructError(
|
|
173
|
+
errorProperties,
|
|
174
|
+
params.exceptionDetails.exception
|
|
175
|
+
)
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
if (evt.method === "Runtime.consoleAPICalled") {
|
|
179
|
+
const params = evt.params;
|
|
180
|
+
await logConsoleMessage(params, reconstructError);
|
|
181
|
+
}
|
|
182
|
+
} else {
|
|
183
|
+
console.error("Unrecognised devtools event:", event);
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
ws.once("open", () => {
|
|
187
|
+
send("Runtime.enable");
|
|
188
|
+
keepAliveInterval = setInterval(() => send("Runtime.getIsolateId"), 1e4);
|
|
189
|
+
});
|
|
190
|
+
ws.on("unexpected-response", () => {
|
|
191
|
+
console.log("Waiting for connection...");
|
|
192
|
+
});
|
|
193
|
+
ws.once("close", () => {
|
|
194
|
+
clearInterval(keepAliveInterval);
|
|
195
|
+
sourceMapAbortController.abort();
|
|
196
|
+
});
|
|
197
|
+
return () => {
|
|
198
|
+
clearInterval(keepAliveInterval);
|
|
199
|
+
if (!isClosed()) {
|
|
200
|
+
try {
|
|
201
|
+
ws.close();
|
|
202
|
+
} catch (err) {
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
sourceMapAbortController.abort();
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
const mapConsoleAPIMessageTypeToConsoleMethod = {
|
|
209
|
+
log: "log",
|
|
210
|
+
debug: "debug",
|
|
211
|
+
info: "info",
|
|
212
|
+
warning: "warn",
|
|
213
|
+
error: "error",
|
|
214
|
+
dir: "dir",
|
|
215
|
+
dirxml: "dirxml",
|
|
216
|
+
table: "table",
|
|
217
|
+
trace: "trace",
|
|
218
|
+
clear: "clear",
|
|
219
|
+
count: "count",
|
|
220
|
+
assert: "assert",
|
|
221
|
+
profile: "profile",
|
|
222
|
+
profileEnd: "profileEnd",
|
|
223
|
+
timeEnd: "timeEnd",
|
|
224
|
+
startGroup: "group",
|
|
225
|
+
startGroupCollapsed: "groupCollapsed",
|
|
226
|
+
endGroup: "groupEnd"
|
|
227
|
+
};
|
|
228
|
+
async function logConsoleMessage(evt, reconstructError) {
|
|
229
|
+
const args = [];
|
|
230
|
+
for (const ro of evt.args) {
|
|
231
|
+
switch (ro.type) {
|
|
232
|
+
case "string":
|
|
233
|
+
case "number":
|
|
234
|
+
case "boolean":
|
|
235
|
+
case "undefined":
|
|
236
|
+
case "symbol":
|
|
237
|
+
case "bigint":
|
|
238
|
+
args.push(ro.value);
|
|
239
|
+
break;
|
|
240
|
+
case "function":
|
|
241
|
+
args.push(`[Function: ${ro.description ?? "<no-description>"}]`);
|
|
242
|
+
break;
|
|
243
|
+
case "object":
|
|
244
|
+
if (!ro.preview) {
|
|
245
|
+
args.push(
|
|
246
|
+
ro.subtype === "null" ? "null" : ro.description ?? "<no-description>"
|
|
247
|
+
);
|
|
248
|
+
} else {
|
|
249
|
+
if (ro.preview.description)
|
|
250
|
+
args.push(ro.preview.description);
|
|
251
|
+
switch (ro.preview.subtype) {
|
|
252
|
+
case "array":
|
|
253
|
+
args.push(
|
|
254
|
+
"[ " + ro.preview.properties.map(({ value }) => {
|
|
255
|
+
return value;
|
|
256
|
+
}).join(", ") + (ro.preview.overflow ? "..." : "") + " ]"
|
|
257
|
+
);
|
|
258
|
+
break;
|
|
259
|
+
case "weakmap":
|
|
260
|
+
case "map":
|
|
261
|
+
ro.preview.entries === void 0 ? args.push("{}") : args.push(
|
|
262
|
+
"{\n" + ro.preview.entries.map(({ key, value }) => {
|
|
263
|
+
return ` ${key?.description ?? "<unknown>"} => ${value.description}`;
|
|
264
|
+
}).join(",\n") + (ro.preview.overflow ? "\n ..." : "") + "\n}"
|
|
265
|
+
);
|
|
266
|
+
break;
|
|
267
|
+
case "weakset":
|
|
268
|
+
case "set":
|
|
269
|
+
ro.preview.entries === void 0 ? args.push("{}") : args.push(
|
|
270
|
+
"{ " + ro.preview.entries.map(({ value }) => {
|
|
271
|
+
return `${value.description}`;
|
|
272
|
+
}).join(", ") + (ro.preview.overflow ? ", ..." : "") + " }"
|
|
273
|
+
);
|
|
274
|
+
break;
|
|
275
|
+
case "regexp":
|
|
276
|
+
break;
|
|
277
|
+
case "date":
|
|
278
|
+
break;
|
|
279
|
+
case "generator":
|
|
280
|
+
args.push(ro.preview?.properties[0]?.value || "");
|
|
281
|
+
break;
|
|
282
|
+
case "promise":
|
|
283
|
+
if (ro.preview?.properties[0]?.value === "pending") {
|
|
284
|
+
args.push(`{<${ro.preview.properties[0].value}>}`);
|
|
285
|
+
} else {
|
|
286
|
+
args.push(
|
|
287
|
+
`{<${ro.preview?.properties[0]?.value}>: ${ro.preview?.properties[1]?.value}}`
|
|
288
|
+
);
|
|
289
|
+
}
|
|
290
|
+
break;
|
|
291
|
+
case "node":
|
|
292
|
+
case "iterator":
|
|
293
|
+
case "proxy":
|
|
294
|
+
case "typedarray":
|
|
295
|
+
case "arraybuffer":
|
|
296
|
+
case "dataview":
|
|
297
|
+
case "webassemblymemory":
|
|
298
|
+
case "wasmvalue":
|
|
299
|
+
break;
|
|
300
|
+
case "error":
|
|
301
|
+
const errorProperties = {
|
|
302
|
+
message: ro.preview.description?.split("\n").filter((line) => !/^\s+at\s/.test(line)).join("\n") ?? ro.preview.properties.find(({ name }) => name === "message")?.value ?? "",
|
|
303
|
+
stack: ro.preview.description ?? ro.description ?? ro.preview.properties.find(({ name }) => name === "stack")?.value,
|
|
304
|
+
cause: ro.preview.properties.find(({ name }) => name === "cause")?.value
|
|
305
|
+
};
|
|
306
|
+
const error = await reconstructError(errorProperties, ro);
|
|
307
|
+
args.splice(-1, 1, error);
|
|
308
|
+
break;
|
|
309
|
+
default:
|
|
310
|
+
args.push(
|
|
311
|
+
"{\n" + ro.preview.properties.map(({ name, value }) => {
|
|
312
|
+
return ` ${name}: ${value}`;
|
|
313
|
+
}).join(",\n") + (ro.preview.overflow ? "\n ..." : "") + "\n}"
|
|
314
|
+
);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
break;
|
|
318
|
+
default:
|
|
319
|
+
args.push(ro.description || ro.unserializableValue || "\u{1F98B}");
|
|
320
|
+
break;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
const method = mapConsoleAPIMessageTypeToConsoleMethod[evt.type];
|
|
324
|
+
if (method in console) {
|
|
325
|
+
switch (method) {
|
|
326
|
+
case "dir":
|
|
327
|
+
console.dir(args);
|
|
328
|
+
break;
|
|
329
|
+
case "table":
|
|
330
|
+
console.table(args);
|
|
331
|
+
break;
|
|
332
|
+
default:
|
|
333
|
+
console[method].apply(console, args);
|
|
334
|
+
break;
|
|
335
|
+
}
|
|
336
|
+
} else {
|
|
337
|
+
console.warn(`Unsupported console method: ${method}`);
|
|
338
|
+
console.warn("console event:", evt);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
function formatStructuredError(sourceMapConsumer, message, frames) {
|
|
342
|
+
const lines = [];
|
|
343
|
+
if (message !== void 0)
|
|
344
|
+
lines.push(message);
|
|
345
|
+
frames?.forEach(({ functionName, lineNumber, columnNumber }, i) => {
|
|
346
|
+
try {
|
|
347
|
+
if (lineNumber) {
|
|
348
|
+
const pos = sourceMapConsumer.originalPositionFor({
|
|
349
|
+
line: lineNumber + 1,
|
|
350
|
+
column: columnNumber
|
|
351
|
+
});
|
|
352
|
+
if (i === 0 && pos.source && pos.line) {
|
|
353
|
+
const fileSource = sourceMapConsumer.sourceContentFor(pos.source);
|
|
354
|
+
const fileSourceLine = fileSource?.split("\n")[pos.line - 1] || "";
|
|
355
|
+
lines.push(fileSourceLine.trim());
|
|
356
|
+
if (pos.column) {
|
|
357
|
+
lines.push(
|
|
358
|
+
`${" ".repeat(pos.column - fileSourceLine.search(/\S/))}^`
|
|
359
|
+
);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
if (pos && pos.line !== null && pos.column !== null) {
|
|
363
|
+
const convertedFnName = pos.name || functionName || "";
|
|
364
|
+
let convertedLocation = `${pos.source}:${pos.line}:${pos.column + 1}`;
|
|
365
|
+
if (convertedFnName === "") {
|
|
366
|
+
lines.push(` at ${convertedLocation}`);
|
|
367
|
+
} else {
|
|
368
|
+
lines.push(` at ${convertedFnName} (${convertedLocation})`);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
} catch {
|
|
373
|
+
}
|
|
374
|
+
});
|
|
375
|
+
return lines.join("\n");
|
|
376
|
+
}
|
|
377
|
+
function formatStack(sourceMapConsumer, stack) {
|
|
378
|
+
const message = stack.split("\n")[0];
|
|
379
|
+
const callSites = parse({ stack });
|
|
380
|
+
const frames = callSites.map((site) => ({
|
|
381
|
+
functionName: site.getFunctionName() ?? "",
|
|
382
|
+
// `Protocol.Runtime.CallFrame`s line numbers are 0-indexed, hence `- 1`
|
|
383
|
+
lineNumber: (site.getLineNumber() ?? 1) - 1,
|
|
384
|
+
columnNumber: site.getColumnNumber() ?? 1,
|
|
385
|
+
// Unused by `formattedError`
|
|
386
|
+
scriptId: "",
|
|
387
|
+
url: ""
|
|
388
|
+
}));
|
|
389
|
+
return formatStructuredError(sourceMapConsumer, message, frames);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
export { connectToInspector, findInspectorUrl, mapConsoleAPIMessageTypeToConsoleMethod };
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import { Miniflare, NoOpLog, Response, Request } from 'miniflare';
|
|
2
|
+
import { resolvePath } from '@shopify/cli-kit/node/path';
|
|
3
|
+
import { glob, readFile, createFileReadStream, fileSize } from '@shopify/cli-kit/node/fs';
|
|
4
|
+
import { renderSuccess } from '@shopify/cli-kit/node/ui';
|
|
5
|
+
import { lookupMimeType } from '@shopify/cli-kit/node/mimes';
|
|
6
|
+
import { findInspectorUrl, connectToInspector } from './workerd-inspector.js';
|
|
7
|
+
import { DEFAULT_PORT } from '../flags.js';
|
|
8
|
+
import { findPort } from '../find-port.js';
|
|
9
|
+
import { OXYGEN_HEADERS_MAP, logRequestLine } from './common.js';
|
|
10
|
+
|
|
11
|
+
async function startWorkerdServer({
|
|
12
|
+
root,
|
|
13
|
+
port = DEFAULT_PORT,
|
|
14
|
+
watch = false,
|
|
15
|
+
buildPathWorkerFile,
|
|
16
|
+
buildPathClient,
|
|
17
|
+
env
|
|
18
|
+
}) {
|
|
19
|
+
const inspectorPort = await findPort(8787);
|
|
20
|
+
const oxygenHeadersMap = Object.values(OXYGEN_HEADERS_MAP).reduce(
|
|
21
|
+
(acc, item) => {
|
|
22
|
+
acc[item.name] = item.defaultValue;
|
|
23
|
+
return acc;
|
|
24
|
+
},
|
|
25
|
+
{}
|
|
26
|
+
);
|
|
27
|
+
const buildMiniOxygenOptions = async () => ({
|
|
28
|
+
cf: false,
|
|
29
|
+
verbose: false,
|
|
30
|
+
port,
|
|
31
|
+
log: new NoOpLog(),
|
|
32
|
+
liveReload: watch,
|
|
33
|
+
inspectorPort,
|
|
34
|
+
host: "localhost",
|
|
35
|
+
workers: [
|
|
36
|
+
{
|
|
37
|
+
name: "mini-oxygen",
|
|
38
|
+
modules: true,
|
|
39
|
+
script: `export default { fetch: ${miniOxygenHandler.toString()} }`,
|
|
40
|
+
bindings: {
|
|
41
|
+
initialAssets: await glob("**/*", { cwd: buildPathClient }),
|
|
42
|
+
oxygenHeadersMap
|
|
43
|
+
},
|
|
44
|
+
serviceBindings: {
|
|
45
|
+
hydrogen: "hydrogen",
|
|
46
|
+
assets: createAssetHandler(buildPathClient),
|
|
47
|
+
logRequest
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
name: "hydrogen",
|
|
52
|
+
modules: [
|
|
53
|
+
{
|
|
54
|
+
type: "ESModule",
|
|
55
|
+
path: resolvePath(root, buildPathWorkerFile),
|
|
56
|
+
contents: await readFile(resolvePath(root, buildPathWorkerFile))
|
|
57
|
+
}
|
|
58
|
+
],
|
|
59
|
+
bindings: { ...env },
|
|
60
|
+
compatibilityFlags: ["streams_enable_constructors"],
|
|
61
|
+
compatibilityDate: "2022-10-31"
|
|
62
|
+
}
|
|
63
|
+
]
|
|
64
|
+
});
|
|
65
|
+
let miniOxygenOptions = await buildMiniOxygenOptions();
|
|
66
|
+
const miniOxygen = new Miniflare(miniOxygenOptions);
|
|
67
|
+
const listeningAt = (await miniOxygen.ready).origin;
|
|
68
|
+
const sourceMapPath = buildPathWorkerFile + ".map";
|
|
69
|
+
let inspectorUrl = await findInspectorUrl(inspectorPort);
|
|
70
|
+
let cleanupInspector = inspectorUrl ? connectToInspector({ inspectorUrl, sourceMapPath }) : void 0;
|
|
71
|
+
return {
|
|
72
|
+
port,
|
|
73
|
+
listeningAt,
|
|
74
|
+
async reload(nextOptions) {
|
|
75
|
+
miniOxygenOptions = await buildMiniOxygenOptions();
|
|
76
|
+
if (nextOptions) {
|
|
77
|
+
const hydrogen = miniOxygenOptions.workers.find(
|
|
78
|
+
(worker) => worker.name === "hydrogen"
|
|
79
|
+
);
|
|
80
|
+
if (hydrogen) {
|
|
81
|
+
hydrogen.bindings = { ...nextOptions?.env ?? env };
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
cleanupInspector?.();
|
|
85
|
+
await miniOxygen.setOptions(miniOxygenOptions);
|
|
86
|
+
inspectorUrl ??= await findInspectorUrl(inspectorPort);
|
|
87
|
+
if (inspectorUrl) {
|
|
88
|
+
cleanupInspector = connectToInspector({ inspectorUrl, sourceMapPath });
|
|
89
|
+
}
|
|
90
|
+
},
|
|
91
|
+
showBanner(options) {
|
|
92
|
+
console.log("");
|
|
93
|
+
renderSuccess({
|
|
94
|
+
headline: `${options?.headlinePrefix ?? ""}MiniOxygen (Unstable Worker Runtime) ${options?.mode ?? "development"} server running.`,
|
|
95
|
+
body: [
|
|
96
|
+
`View ${options?.appName ?? "Hydrogen"} app: ${listeningAt}`,
|
|
97
|
+
...options?.extraLines ?? []
|
|
98
|
+
]
|
|
99
|
+
});
|
|
100
|
+
console.log("");
|
|
101
|
+
},
|
|
102
|
+
async close() {
|
|
103
|
+
await miniOxygen.dispose();
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
async function miniOxygenHandler(request, env, context) {
|
|
108
|
+
if (request.method === "GET") {
|
|
109
|
+
const pathname = new URL(request.url).pathname;
|
|
110
|
+
if (pathname.startsWith("/debug-network")) {
|
|
111
|
+
return new Response(
|
|
112
|
+
"The Network Debugger is currently not supported in the Worker Runtime."
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
if (new Set(env.initialAssets).has(pathname.slice(1))) {
|
|
116
|
+
const response2 = await env.assets.fetch(
|
|
117
|
+
new Request(request.url, {
|
|
118
|
+
signal: request.signal,
|
|
119
|
+
headers: request.headers
|
|
120
|
+
})
|
|
121
|
+
);
|
|
122
|
+
if (response2.status !== 404)
|
|
123
|
+
return response2;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
const requestInit = {
|
|
127
|
+
headers: {
|
|
128
|
+
...env.oxygenHeadersMap,
|
|
129
|
+
...Object.fromEntries(request.headers.entries())
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
const startTimeMs = Date.now();
|
|
133
|
+
const response = await env.hydrogen.fetch(request, requestInit);
|
|
134
|
+
const durationMs = Date.now() - startTimeMs;
|
|
135
|
+
context.waitUntil(
|
|
136
|
+
env.logRequest.fetch(
|
|
137
|
+
new Request(request.url, {
|
|
138
|
+
method: request.method,
|
|
139
|
+
signal: request.signal,
|
|
140
|
+
headers: {
|
|
141
|
+
...Object.fromEntries(request.headers.entries()),
|
|
142
|
+
"h2-duration-ms": String(durationMs),
|
|
143
|
+
"h2-response-status": String(response.status)
|
|
144
|
+
}
|
|
145
|
+
})
|
|
146
|
+
)
|
|
147
|
+
);
|
|
148
|
+
return response;
|
|
149
|
+
}
|
|
150
|
+
function createAssetHandler(buildPathClient) {
|
|
151
|
+
return async (request) => {
|
|
152
|
+
const relativeAssetPath = new URL(request.url).pathname.replace("/", "");
|
|
153
|
+
if (relativeAssetPath) {
|
|
154
|
+
try {
|
|
155
|
+
const absoluteAssetPath = resolvePath(
|
|
156
|
+
buildPathClient,
|
|
157
|
+
relativeAssetPath
|
|
158
|
+
);
|
|
159
|
+
return new Response(createFileReadStream(absoluteAssetPath), {
|
|
160
|
+
headers: {
|
|
161
|
+
"Content-Type": lookupMimeType(relativeAssetPath) || "text/plain",
|
|
162
|
+
"Content-Length": String(await fileSize(absoluteAssetPath))
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
} catch (error) {
|
|
166
|
+
if (error.code !== "ENOENT") {
|
|
167
|
+
throw error;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
return new Response("Not Found", { status: 404 });
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
async function logRequest(request) {
|
|
175
|
+
logRequestLine(request, {
|
|
176
|
+
responseStatus: Number(request.headers.get("h2-response-status") || 200),
|
|
177
|
+
durationMs: Number(request.headers.get("h2-duration-ms") || 0)
|
|
178
|
+
});
|
|
179
|
+
return new Response("ok");
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export { startWorkerdServer };
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { readdir } from 'node:fs/promises';
|
|
2
|
-
import {
|
|
2
|
+
import { packageManagerFromUserAgent, installNodeModules } from '@shopify/cli-kit/node/node-package-manager';
|
|
3
3
|
import { renderConfirmationPrompt, renderInfo, renderTextPrompt, renderSelectPrompt, renderFatalError, renderWarning, renderSuccess } from '@shopify/cli-kit/node/ui';
|
|
4
4
|
import { hyphenate, capitalize } from '@shopify/cli-kit/common/string';
|
|
5
|
-
import { resolvePath, basename
|
|
5
|
+
import { joinPath, resolvePath, basename } from '@shopify/cli-kit/node/path';
|
|
6
6
|
import { initializeGitRepository, addAllToGitFromDirectory, createGitCommit } from '@shopify/cli-kit/node/git';
|
|
7
7
|
import { AbortError } from '@shopify/cli-kit/node/error';
|
|
8
8
|
import { rmdir, writeFile, fileExists, isDirectory } from '@shopify/cli-kit/node/fs';
|
|
@@ -39,7 +39,7 @@ async function handleI18n(controller, cliCommand, flagI18n) {
|
|
|
39
39
|
};
|
|
40
40
|
}
|
|
41
41
|
async function handleRouteGeneration(controller, flagRoutes) {
|
|
42
|
-
const routesToScaffold = flagRoutes ? "all" : await renderRoutePrompt({
|
|
42
|
+
const routesToScaffold = flagRoutes === true ? "all" : flagRoutes === false ? [] : await renderRoutePrompt({
|
|
43
43
|
abortSignal: controller.signal
|
|
44
44
|
});
|
|
45
45
|
const needsRouteGeneration = routesToScaffold === "all" || routesToScaffold.length > 0;
|
|
@@ -47,14 +47,25 @@ async function handleRouteGeneration(controller, flagRoutes) {
|
|
|
47
47
|
needsRouteGeneration,
|
|
48
48
|
setupRoutes: async (directory, language, i18nStrategy) => {
|
|
49
49
|
if (needsRouteGeneration) {
|
|
50
|
-
const result = await generateRoutes(
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
50
|
+
const result = await generateRoutes(
|
|
51
|
+
{
|
|
52
|
+
routeName: routesToScaffold,
|
|
53
|
+
directory,
|
|
54
|
+
force: true,
|
|
55
|
+
typescript: language === "ts",
|
|
56
|
+
localePrefix: i18nStrategy === "subfolders" ? "locale" : false,
|
|
57
|
+
signal: controller.signal
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
rootDirectory: directory,
|
|
61
|
+
appDirectory: joinPath(directory, "app"),
|
|
62
|
+
future: {
|
|
63
|
+
v2_errorBoundary: true,
|
|
64
|
+
v2_meta: true,
|
|
65
|
+
v2_routeConvention: true
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
);
|
|
58
69
|
return result.routeGroups;
|
|
59
70
|
}
|
|
60
71
|
}
|
|
@@ -189,7 +200,7 @@ async function handleLanguage(projectDir, controller, flagLanguage) {
|
|
|
189
200
|
};
|
|
190
201
|
}
|
|
191
202
|
async function handleCssStrategy(projectDir, controller, flagStyling) {
|
|
192
|
-
const selection = flagStyling
|
|
203
|
+
const selection = flagStyling ?? await renderCssPrompt({
|
|
193
204
|
abortSignal: controller.signal,
|
|
194
205
|
extraChoices: { none: "Skip and set up later" }
|
|
195
206
|
});
|
|
@@ -215,7 +226,7 @@ async function handleCssStrategy(projectDir, controller, flagStyling) {
|
|
|
215
226
|
};
|
|
216
227
|
}
|
|
217
228
|
async function handleDependencies(projectDir, controller, shouldInstallDeps) {
|
|
218
|
-
const detectedPackageManager =
|
|
229
|
+
const detectedPackageManager = packageManagerFromUserAgent();
|
|
219
230
|
let actualPackageManager = "npm";
|
|
220
231
|
if (shouldInstallDeps !== false) {
|
|
221
232
|
if (detectedPackageManager === "unknown") {
|
|
@@ -199,7 +199,7 @@ async function setupLocalStarterTemplate(options, controller) {
|
|
|
199
199
|
);
|
|
200
200
|
const { setupRoutes } = await handleRouteGeneration(
|
|
201
201
|
controller,
|
|
202
|
-
options.routes
|
|
202
|
+
options.routes ?? true
|
|
203
203
|
// TODO: Remove default value when multi-select UI component is available
|
|
204
204
|
);
|
|
205
205
|
setupSummary.i18n = i18nStrategy;
|
package/dist/lib/remix-config.js
CHANGED
|
@@ -6,6 +6,7 @@ import { AbortError } from '@shopify/cli-kit/node/error';
|
|
|
6
6
|
import { outputWarn } from '@shopify/cli-kit/node/output';
|
|
7
7
|
import { fileExists } from '@shopify/cli-kit/node/fs';
|
|
8
8
|
import { muteRemixLogs } from './log.js';
|
|
9
|
+
import { getRequiredRemixVersion } from './remix-version-check.js';
|
|
9
10
|
|
|
10
11
|
const BUILD_DIR = "dist";
|
|
11
12
|
const CLIENT_SUBDIR = "client";
|
|
@@ -25,9 +26,18 @@ function getProjectPaths(appPath, entry) {
|
|
|
25
26
|
publicPath
|
|
26
27
|
};
|
|
27
28
|
}
|
|
29
|
+
function handleRemixImportFail() {
|
|
30
|
+
const remixVersion = getRequiredRemixVersion();
|
|
31
|
+
throw new AbortError(
|
|
32
|
+
"Could not load Remix packages.",
|
|
33
|
+
`Please make sure you have \`@remix-run/dev@${remixVersion}\` installed and all the other Remix packages have the same version.`
|
|
34
|
+
);
|
|
35
|
+
}
|
|
28
36
|
async function getRemixConfig(root, mode = process.env.NODE_ENV) {
|
|
29
37
|
await muteRemixLogs();
|
|
30
|
-
const { readConfig } = await import('@remix-run/dev/dist/config.js')
|
|
38
|
+
const { readConfig } = await import('@remix-run/dev/dist/config.js').catch(
|
|
39
|
+
handleRemixImportFail
|
|
40
|
+
);
|
|
31
41
|
const config = await readConfig(root, mode);
|
|
32
42
|
if (process.env.LOCAL_DEV) {
|
|
33
43
|
const packagesPath = fileURLToPath(new URL("../../..", import.meta.url));
|
|
@@ -134,4 +144,4 @@ async function assertEntryFileExists(root, fileRelative) {
|
|
|
134
144
|
}
|
|
135
145
|
}
|
|
136
146
|
|
|
137
|
-
export { assertOxygenChecks, getProjectPaths, getRemixConfig };
|
|
147
|
+
export { assertOxygenChecks, getProjectPaths, getRemixConfig, handleRemixImportFail };
|