@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
package/dist/lib/flags.js
CHANGED
|
@@ -17,6 +17,10 @@ const commonFlags = {
|
|
|
17
17
|
env: "SHOPIFY_HYDROGEN_FLAG_PORT",
|
|
18
18
|
default: DEFAULT_PORT
|
|
19
19
|
}),
|
|
20
|
+
workerRuntime: Flags.boolean({
|
|
21
|
+
description: "Run the app in a worker environment closer to Oxygen production instead of a Node.js sandbox. This flag is unstable and may change without notice.",
|
|
22
|
+
env: "SHOPIFY_HYDROGEN_FLAG_WORKER_UNSTABLE"
|
|
23
|
+
}),
|
|
20
24
|
force: Flags.boolean({
|
|
21
25
|
description: "Overwrite the destination directory and files if they already exist.",
|
|
22
26
|
env: "SHOPIFY_HYDROGEN_FLAG_FORCE",
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { renderConfirmationPrompt } from '@shopify/cli-kit/node/ui';
|
|
2
|
+
import { outputWarn } from '@shopify/cli-kit/node/output';
|
|
3
|
+
import { linkStorefront } from '../commands/hydrogen/link.js';
|
|
4
|
+
import { login } from './auth.js';
|
|
5
|
+
import { getCliCommand } from './shell.js';
|
|
6
|
+
import { renderMissingLink, renderMissingStorefront } from './render-errors.js';
|
|
7
|
+
import { getOxygenToken } from './graphql/admin/oxygen-token.js';
|
|
8
|
+
|
|
9
|
+
async function getOxygenDeploymentToken({
|
|
10
|
+
root
|
|
11
|
+
}) {
|
|
12
|
+
const [{ session, config }, cliCommand] = await Promise.all([
|
|
13
|
+
login(root),
|
|
14
|
+
getCliCommand()
|
|
15
|
+
]);
|
|
16
|
+
if (!config.storefront?.id) {
|
|
17
|
+
renderMissingLink({ session, cliCommand });
|
|
18
|
+
const runLink = await renderConfirmationPrompt({
|
|
19
|
+
message: ["Run", { command: `${cliCommand} link` }]
|
|
20
|
+
});
|
|
21
|
+
if (!runLink) {
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
config.storefront = await linkStorefront(root, session, config, {
|
|
25
|
+
cliCommand
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
if (!config.storefront) {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
const { storefront } = await getOxygenToken(session, config.storefront.id);
|
|
32
|
+
if (!storefront) {
|
|
33
|
+
renderMissingStorefront({
|
|
34
|
+
session,
|
|
35
|
+
storefront: config.storefront,
|
|
36
|
+
cliCommand
|
|
37
|
+
});
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
if (!storefront.oxygenDeploymentToken) {
|
|
41
|
+
outputWarn(`Could not retrieve a deployment token.`);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
return storefront.oxygenDeploymentToken;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export { getOxygenDeploymentToken };
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { vi, describe, beforeEach, afterEach, it, expect } from 'vitest';
|
|
2
|
+
import { renderConfirmationPrompt } from '@shopify/cli-kit/node/ui';
|
|
3
|
+
import { getOxygenDeploymentToken } from './get-oxygen-token.js';
|
|
4
|
+
import { login } from './auth.js';
|
|
5
|
+
import { getConfig } from './shopify-config.js';
|
|
6
|
+
import { renderMissingLink, renderMissingStorefront } from './render-errors.js';
|
|
7
|
+
import { linkStorefront } from '../commands/hydrogen/link.js';
|
|
8
|
+
import { getOxygenToken } from './graphql/admin/oxygen-token.js';
|
|
9
|
+
|
|
10
|
+
vi.mock("@shopify/cli-kit/node/ui", async () => {
|
|
11
|
+
const original = await vi.importActual("@shopify/cli-kit/node/ui");
|
|
12
|
+
return {
|
|
13
|
+
...original,
|
|
14
|
+
renderConfirmationPrompt: vi.fn()
|
|
15
|
+
};
|
|
16
|
+
});
|
|
17
|
+
vi.mock("./auth.js");
|
|
18
|
+
vi.mock("./admin-session.js");
|
|
19
|
+
vi.mock("./shopify-config.js");
|
|
20
|
+
vi.mock("./render-errors.js");
|
|
21
|
+
vi.mock("../commands/hydrogen/link.js");
|
|
22
|
+
vi.mock("./graphql/admin/oxygen-token.js");
|
|
23
|
+
describe("getOxygenDeploymentToken", () => {
|
|
24
|
+
const OXYGEN_DEPLOYMENT_TOKEN = "a-lovely-token";
|
|
25
|
+
beforeEach(() => {
|
|
26
|
+
vi.mocked(login).mockResolvedValue({
|
|
27
|
+
session: {
|
|
28
|
+
token: "123",
|
|
29
|
+
storeFqdn: "www.snowdevil.com"
|
|
30
|
+
},
|
|
31
|
+
config: {
|
|
32
|
+
shop: "snowdevil.myshopify.com",
|
|
33
|
+
shopName: "Snowdevil",
|
|
34
|
+
email: "merchant@shop.com",
|
|
35
|
+
storefront: {
|
|
36
|
+
id: "1",
|
|
37
|
+
title: "Snowboards"
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
vi.mocked(getConfig).mockResolvedValue({
|
|
42
|
+
storefront: { id: "storefront-id", title: "Existing Link" }
|
|
43
|
+
});
|
|
44
|
+
vi.mocked(getOxygenToken).mockResolvedValue({
|
|
45
|
+
storefront: { oxygenDeploymentToken: OXYGEN_DEPLOYMENT_TOKEN }
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
afterEach(() => {
|
|
49
|
+
vi.resetAllMocks();
|
|
50
|
+
});
|
|
51
|
+
it("returns the oxygen deployment token", async () => {
|
|
52
|
+
const token = await getOxygenDeploymentToken({ root: "test-root" });
|
|
53
|
+
expect(token).toBe(OXYGEN_DEPLOYMENT_TOKEN);
|
|
54
|
+
});
|
|
55
|
+
describe("when there is no linked storefront", () => {
|
|
56
|
+
beforeEach(() => {
|
|
57
|
+
vi.mocked(login).mockResolvedValue({
|
|
58
|
+
session: {
|
|
59
|
+
token: "123",
|
|
60
|
+
storeFqdn: "www.snowdevil.com"
|
|
61
|
+
},
|
|
62
|
+
config: {
|
|
63
|
+
shop: "snowdevil.myshopify.com",
|
|
64
|
+
shopName: "Snowdevil",
|
|
65
|
+
email: "merchant@shop.com",
|
|
66
|
+
storefront: void 0
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
it("calls renderMissingLink and prompts the user to create a link", async () => {
|
|
71
|
+
vi.mocked(renderConfirmationPrompt).mockResolvedValue(true);
|
|
72
|
+
await getOxygenDeploymentToken({ root: "test-root" });
|
|
73
|
+
expect(renderMissingLink).toHaveBeenCalled();
|
|
74
|
+
expect(renderConfirmationPrompt).toHaveBeenCalled();
|
|
75
|
+
expect(linkStorefront).toHaveBeenCalled();
|
|
76
|
+
});
|
|
77
|
+
it("returns nothing if the user does not create a new link", async () => {
|
|
78
|
+
vi.mocked(renderConfirmationPrompt).mockResolvedValue(false);
|
|
79
|
+
const token = await getOxygenDeploymentToken({ root: "test-root" });
|
|
80
|
+
expect(token).toEqual(void 0);
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
describe("when there is no matching storefront in the shop", () => {
|
|
84
|
+
beforeEach(() => {
|
|
85
|
+
vi.mocked(getOxygenToken).mockResolvedValue({ storefront: null });
|
|
86
|
+
});
|
|
87
|
+
it("calls renderMissingStorefront and returns nothing", async () => {
|
|
88
|
+
const token = await getOxygenDeploymentToken({ root: "test-root" });
|
|
89
|
+
expect(renderMissingStorefront).toHaveBeenCalled();
|
|
90
|
+
expect(token).toEqual(void 0);
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
describe("when the storefront does not have an oxygen deployment token", () => {
|
|
94
|
+
beforeEach(() => {
|
|
95
|
+
vi.mocked(getOxygenToken).mockResolvedValue({
|
|
96
|
+
storefront: { oxygenDeploymentToken: "" }
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
it("returns nothing", async () => {
|
|
100
|
+
const token = await getOxygenDeploymentToken({ root: "test-root" });
|
|
101
|
+
expect(token).toEqual(void 0);
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
});
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { adminRequest } from './client.js';
|
|
2
|
+
|
|
3
|
+
const GetDeploymentTokenQuery = `#graphql
|
|
4
|
+
query GetDeploymentToken($id: ID!) {
|
|
5
|
+
hydrogenStorefront(id: $id) {
|
|
6
|
+
oxygenDeploymentToken
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
`;
|
|
10
|
+
async function getOxygenToken(adminSession, storefrontId) {
|
|
11
|
+
const { hydrogenStorefront } = await adminRequest(
|
|
12
|
+
GetDeploymentTokenQuery,
|
|
13
|
+
adminSession,
|
|
14
|
+
{
|
|
15
|
+
id: storefrontId
|
|
16
|
+
}
|
|
17
|
+
);
|
|
18
|
+
return { storefront: hydrogenStorefront };
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export { GetDeploymentTokenQuery, getOxygenToken };
|
package/dist/lib/live-reload.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import http from 'node:http';
|
|
2
|
+
import { handleRemixImportFail } from './remix-config.js';
|
|
2
3
|
|
|
3
4
|
async function setupLiveReload(devServerPort) {
|
|
4
5
|
try {
|
|
@@ -7,7 +8,7 @@ async function setupLiveReload(devServerPort) {
|
|
|
7
8
|
import('@remix-run/dev/dist/devServer_unstable/socket.js'),
|
|
8
9
|
import('@remix-run/dev/dist/devServer_unstable/hdr.js'),
|
|
9
10
|
import('@remix-run/dev/dist/result.js')
|
|
10
|
-
]);
|
|
11
|
+
]).catch(handleRemixImportFail);
|
|
11
12
|
const state = {};
|
|
12
13
|
const server = http.createServer(function(req, res) {
|
|
13
14
|
res.writeHead(200);
|
package/dist/lib/log.js
CHANGED
|
@@ -37,10 +37,17 @@ function injectLogReplacer(method, debouncer) {
|
|
|
37
37
|
console[method] = (...args) => {
|
|
38
38
|
if (debounceMessage(args, debouncer?.(args)))
|
|
39
39
|
return;
|
|
40
|
-
const
|
|
41
|
-
|
|
40
|
+
const replacers = messageReplacers.reduce((acc, [matcher, replacer]) => {
|
|
41
|
+
if (matcher(args))
|
|
42
|
+
acc.push(replacer);
|
|
43
|
+
return acc;
|
|
44
|
+
}, []);
|
|
45
|
+
if (replacers.length === 0)
|
|
42
46
|
return originalConsole[method](...args);
|
|
43
|
-
const result =
|
|
47
|
+
const result = replacers.reduce(
|
|
48
|
+
(resultArgs, replacer) => resultArgs && replacer(resultArgs),
|
|
49
|
+
args
|
|
50
|
+
);
|
|
44
51
|
if (result)
|
|
45
52
|
return originalConsole[method](...result);
|
|
46
53
|
};
|
|
@@ -48,8 +55,10 @@ function injectLogReplacer(method, debouncer) {
|
|
|
48
55
|
}
|
|
49
56
|
function muteDevLogs({ workerReload } = {}) {
|
|
50
57
|
injectLogReplacer("log");
|
|
58
|
+
injectLogReplacer("error");
|
|
59
|
+
injectLogReplacer("warn");
|
|
51
60
|
let isFirstWorkerReload = true;
|
|
52
|
-
addMessageReplacers("dev", [
|
|
61
|
+
addMessageReplacers("dev-node", [
|
|
53
62
|
([first]) => typeof first === "string" && first.includes("[mf:"),
|
|
54
63
|
(args) => {
|
|
55
64
|
const first = args[0];
|
|
@@ -65,6 +74,29 @@ function muteDevLogs({ workerReload } = {}) {
|
|
|
65
74
|
}
|
|
66
75
|
}
|
|
67
76
|
]);
|
|
77
|
+
addMessageReplacers(
|
|
78
|
+
"dev-workerd",
|
|
79
|
+
[
|
|
80
|
+
// Workerd logs
|
|
81
|
+
([first]) => typeof first === "string" && /^\x1B\[31m(workerd\/|stack:)/.test(first),
|
|
82
|
+
() => {
|
|
83
|
+
}
|
|
84
|
+
],
|
|
85
|
+
// Non-actionable warnings/errors:
|
|
86
|
+
[
|
|
87
|
+
([first]) => typeof first === "string" && /^A promise rejection/i.test(first),
|
|
88
|
+
() => {
|
|
89
|
+
}
|
|
90
|
+
],
|
|
91
|
+
[
|
|
92
|
+
([first]) => {
|
|
93
|
+
const message = first?.message ?? first;
|
|
94
|
+
return typeof message === "string" && /^Network connection lost/i.test(message);
|
|
95
|
+
},
|
|
96
|
+
() => {
|
|
97
|
+
}
|
|
98
|
+
]
|
|
99
|
+
);
|
|
68
100
|
}
|
|
69
101
|
const originalWrite = process.stdout.write;
|
|
70
102
|
function muteAuthLogs({
|
|
@@ -76,12 +108,17 @@ function muteAuthLogs({
|
|
|
76
108
|
process.stdout.write = (item, cb) => {
|
|
77
109
|
if (typeof item !== "string")
|
|
78
110
|
return write(item, cb);
|
|
79
|
-
const
|
|
80
|
-
(
|
|
81
|
-
|
|
82
|
-
|
|
111
|
+
const replacers = messageReplacers.reduce((acc, [matcher, replacer]) => {
|
|
112
|
+
if (matcher([item]))
|
|
113
|
+
acc.push(replacer);
|
|
114
|
+
return acc;
|
|
115
|
+
}, []);
|
|
116
|
+
if (replacers.length === 0)
|
|
83
117
|
return write(item, cb);
|
|
84
|
-
const result =
|
|
118
|
+
const result = replacers.reduce(
|
|
119
|
+
(resultArgs, replacer) => resultArgs && replacer(resultArgs),
|
|
120
|
+
[item]
|
|
121
|
+
);
|
|
85
122
|
if (result)
|
|
86
123
|
return write(result[0], cb);
|
|
87
124
|
};
|
|
@@ -129,13 +166,13 @@ function enhanceH2Logs(options) {
|
|
|
129
166
|
addMessageReplacers("h2-warn", [
|
|
130
167
|
([first]) => {
|
|
131
168
|
const message = first?.message ?? first;
|
|
132
|
-
return typeof message === "string" && message.
|
|
169
|
+
return typeof message === "string" && message.includes("[h2:");
|
|
133
170
|
},
|
|
134
171
|
(args) => {
|
|
135
172
|
const firstArg = args[0];
|
|
136
173
|
const errorObject = typeof firstArg === "object" && !!firstArg.stack ? firstArg : void 0;
|
|
137
174
|
const stringArg = errorObject?.message ?? firstArg;
|
|
138
|
-
const [, type, scope, message] = stringArg.match(
|
|
175
|
+
const [, type, scope, message] = stringArg.match(/\[h2:([^:]+):([^\]]+)\]\s+(.*)$/ims) || [];
|
|
139
176
|
if (!type || !scope || !message)
|
|
140
177
|
return args;
|
|
141
178
|
const headline = `In Hydrogen's \`${scope.trim()}\`:
|
|
@@ -149,8 +186,14 @@ function enhanceH2Logs(options) {
|
|
|
149
186
|
if (type === "error" || errorObject) {
|
|
150
187
|
let tryMessage = hasLinks ? lastLine : void 0;
|
|
151
188
|
let stack = errorObject?.stack;
|
|
152
|
-
|
|
153
|
-
if (
|
|
189
|
+
let cause = errorObject?.cause;
|
|
190
|
+
if (typeof cause === "string") {
|
|
191
|
+
try {
|
|
192
|
+
cause = JSON.parse(cause);
|
|
193
|
+
} catch {
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
if (typeof cause !== "string" && !!cause?.graphql?.query) {
|
|
154
197
|
const { query, variables } = cause.graphql;
|
|
155
198
|
const link = `${options.graphiqlUrl}?query=${encodeURIComponent(
|
|
156
199
|
query
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { outputToken, outputInfo, outputContent } from '@shopify/cli-kit/node/output';
|
|
2
|
+
import colors from '@shopify/cli-kit/node/colors';
|
|
3
|
+
import { DEV_ROUTES } from '../request-events.js';
|
|
4
|
+
|
|
5
|
+
function logRequestLine(request, {
|
|
6
|
+
responseStatus = 200,
|
|
7
|
+
durationMs = 0
|
|
8
|
+
} = {}) {
|
|
9
|
+
try {
|
|
10
|
+
const url = new URL(request.url);
|
|
11
|
+
if (DEV_ROUTES.has(url.pathname))
|
|
12
|
+
return;
|
|
13
|
+
const isDataRequest = url.searchParams.has("_data");
|
|
14
|
+
let route = request.url.replace(url.origin, "");
|
|
15
|
+
let info = "";
|
|
16
|
+
let type = "render";
|
|
17
|
+
if (isDataRequest) {
|
|
18
|
+
type = request.method === "GET" ? "loader" : "action";
|
|
19
|
+
const dataParam = url.searchParams.get("_data")?.replace("routes/", "");
|
|
20
|
+
route = url.pathname;
|
|
21
|
+
info = `[${dataParam}]`;
|
|
22
|
+
}
|
|
23
|
+
const colorizeStatus = responseStatus < 300 ? outputToken.green : responseStatus < 400 ? outputToken.cyan : outputToken.errorText;
|
|
24
|
+
outputInfo(
|
|
25
|
+
outputContent`${request.method.padStart(6)} ${colorizeStatus(
|
|
26
|
+
String(responseStatus)
|
|
27
|
+
)} ${outputToken.italic(type.padEnd(7, " "))} ${route} ${durationMs > 0 ? colors.dim(` ${durationMs}ms`) : ""}${info ? " " + colors.dim(info) : ""}${request.headers.get("purpose") === "prefetch" ? outputToken.italic(colors.dim(" prefetch")) : ""}`
|
|
28
|
+
);
|
|
29
|
+
} catch {
|
|
30
|
+
if (request && responseStatus) {
|
|
31
|
+
outputInfo(`${request.method} ${responseStatus} ${request.url}`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
const OXYGEN_HEADERS_MAP = {
|
|
36
|
+
ip: { name: "oxygen-buyer-ip", defaultValue: "127.0.0.1" },
|
|
37
|
+
longitude: { name: "oxygen-buyer-longitude", defaultValue: "-122.40140" },
|
|
38
|
+
latitude: { name: "oxygen-buyer-latitude", defaultValue: "37.78855" },
|
|
39
|
+
continent: { name: "oxygen-buyer-continent", defaultValue: "NA" },
|
|
40
|
+
country: { name: "oxygen-buyer-country", defaultValue: "US" },
|
|
41
|
+
region: { name: "oxygen-buyer-region", defaultValue: "California" },
|
|
42
|
+
regionCode: { name: "oxygen-buyer-region-code", defaultValue: "CA" },
|
|
43
|
+
city: { name: "oxygen-buyer-city", defaultValue: "San Francisco" },
|
|
44
|
+
isEuCountry: { name: "oxygen-buyer-is-eu-country", defaultValue: "" },
|
|
45
|
+
timezone: {
|
|
46
|
+
name: "oxygen-buyer-timezone",
|
|
47
|
+
defaultValue: "America/Los_Angeles"
|
|
48
|
+
},
|
|
49
|
+
// Not documented but available in Oxygen:
|
|
50
|
+
deploymentId: { name: "oxygen-buyer-deployment-id", defaultValue: "local" },
|
|
51
|
+
shopId: { name: "oxygen-buyer-shop-id", defaultValue: "development" },
|
|
52
|
+
storefrontId: {
|
|
53
|
+
name: "oxygen-buyer-storefront-id",
|
|
54
|
+
defaultValue: "development"
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
export { OXYGEN_HEADERS_MAP, logRequestLine };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
async function startMiniOxygen(options, useWorkerd = false) {
|
|
2
|
+
if (useWorkerd) {
|
|
3
|
+
const { startWorkerdServer } = await import('./workerd.js');
|
|
4
|
+
return startWorkerdServer(options);
|
|
5
|
+
} else {
|
|
6
|
+
process.env.MINIFLARE_SUBREQUEST_LIMIT = 100;
|
|
7
|
+
const { startNodeServer } = await import('./node.js');
|
|
8
|
+
return startNodeServer(options);
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export { startMiniOxygen };
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
3
|
+
import { resolvePath } from '@shopify/cli-kit/node/path';
|
|
4
|
+
import { readFile } from '@shopify/cli-kit/node/fs';
|
|
5
|
+
import { renderSuccess } from '@shopify/cli-kit/node/ui';
|
|
6
|
+
import { startServer, Request } from '@shopify/mini-oxygen';
|
|
7
|
+
import { DEFAULT_PORT } from '../flags.js';
|
|
8
|
+
import { OXYGEN_HEADERS_MAP, logRequestLine } from './common.js';
|
|
9
|
+
import { clearHistory, streamRequestEvents, logRequestEvent } from '../request-events.js';
|
|
10
|
+
|
|
11
|
+
async function startNodeServer({
|
|
12
|
+
root,
|
|
13
|
+
port = DEFAULT_PORT,
|
|
14
|
+
watch = false,
|
|
15
|
+
buildPathWorkerFile,
|
|
16
|
+
buildPathClient,
|
|
17
|
+
env
|
|
18
|
+
}) {
|
|
19
|
+
resolvePath(root, ".env");
|
|
20
|
+
const oxygenHeaders = Object.fromEntries(
|
|
21
|
+
Object.entries(OXYGEN_HEADERS_MAP).map(([key, value]) => {
|
|
22
|
+
return [key, value.defaultValue];
|
|
23
|
+
})
|
|
24
|
+
);
|
|
25
|
+
const asyncLocalStorage = new AsyncLocalStorage();
|
|
26
|
+
const serviceBindings = {
|
|
27
|
+
H2O_LOG_EVENT: {
|
|
28
|
+
fetch: (request) => logRequestEvent(
|
|
29
|
+
new Request(request.url, {
|
|
30
|
+
headers: {
|
|
31
|
+
...Object.fromEntries(request.headers.entries()),
|
|
32
|
+
// Merge some headers from the parent request
|
|
33
|
+
...asyncLocalStorage.getStore()
|
|
34
|
+
}
|
|
35
|
+
})
|
|
36
|
+
)
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
const miniOxygen = await startServer({
|
|
40
|
+
script: await readFile(buildPathWorkerFile),
|
|
41
|
+
workerFile: buildPathWorkerFile,
|
|
42
|
+
assetsDir: buildPathClient,
|
|
43
|
+
publicPath: "",
|
|
44
|
+
port,
|
|
45
|
+
watch,
|
|
46
|
+
autoReload: watch,
|
|
47
|
+
modules: true,
|
|
48
|
+
env: {
|
|
49
|
+
...env,
|
|
50
|
+
...process.env,
|
|
51
|
+
...serviceBindings
|
|
52
|
+
},
|
|
53
|
+
log: () => {
|
|
54
|
+
},
|
|
55
|
+
oxygenHeaders,
|
|
56
|
+
async onRequest(request, defaultDispatcher) {
|
|
57
|
+
const url = new URL(request.url);
|
|
58
|
+
if (url.pathname === "/debug-network-server") {
|
|
59
|
+
return request.method === "DELETE" ? clearHistory() : streamRequestEvents(request);
|
|
60
|
+
}
|
|
61
|
+
let requestId = request.headers.get("request-id");
|
|
62
|
+
if (!requestId) {
|
|
63
|
+
requestId = randomUUID();
|
|
64
|
+
request.headers.set("request-id", requestId);
|
|
65
|
+
}
|
|
66
|
+
const startTimeMs = Date.now();
|
|
67
|
+
const response = await asyncLocalStorage.run(
|
|
68
|
+
{ "request-id": requestId, purpose: request.headers.get("purpose") },
|
|
69
|
+
() => defaultDispatcher(request)
|
|
70
|
+
);
|
|
71
|
+
logRequestLine(request, {
|
|
72
|
+
responseStatus: response.status,
|
|
73
|
+
durationMs: startTimeMs > 0 ? Date.now() - startTimeMs : 0
|
|
74
|
+
});
|
|
75
|
+
return response;
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
const listeningAt = `http://localhost:${miniOxygen.port}`;
|
|
79
|
+
return {
|
|
80
|
+
listeningAt,
|
|
81
|
+
port: miniOxygen.port,
|
|
82
|
+
async reload(options) {
|
|
83
|
+
const nextOptions = {};
|
|
84
|
+
if (options?.env) {
|
|
85
|
+
nextOptions.env = {
|
|
86
|
+
...options.env,
|
|
87
|
+
...process.env
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
nextOptions.script = await readFile(buildPathWorkerFile);
|
|
91
|
+
await miniOxygen.reload(nextOptions);
|
|
92
|
+
},
|
|
93
|
+
showBanner(options) {
|
|
94
|
+
console.log("");
|
|
95
|
+
renderSuccess({
|
|
96
|
+
headline: `${options?.headlinePrefix ?? ""}MiniOxygen ${options?.mode ?? "development"} server running.`,
|
|
97
|
+
body: [
|
|
98
|
+
`View ${options?.appName ?? "Hydrogen"} app: ${listeningAt}`,
|
|
99
|
+
...options?.extraLines ?? []
|
|
100
|
+
]
|
|
101
|
+
});
|
|
102
|
+
console.log("");
|
|
103
|
+
},
|
|
104
|
+
async close() {
|
|
105
|
+
await miniOxygen.close();
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export { startNodeServer };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|