@shopify/cli-hydrogen 5.4.3 → 5.5.1
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/debug/cpu.js +98 -0
- package/dist/commands/hydrogen/dev.js +3 -4
- package/dist/commands/hydrogen/init.test.js +2 -2
- package/dist/generator-templates/starter/app/root.tsx +3 -1
- package/dist/generator-templates/starter/app/routes/account.orders._index.tsx +1 -1
- package/dist/generator-templates/starter/app/routes/account_.recover.tsx +7 -2
- package/dist/generator-templates/starter/app/routes/account_.register.tsx +3 -3
- package/dist/generator-templates/starter/app/routes/blogs.$blogHandle._index.tsx +1 -0
- package/dist/generator-templates/starter/package.json +4 -3
- package/dist/generator-templates/starter/storefrontapi.generated.d.ts +4 -4
- package/dist/hooks/init.js +4 -1
- package/dist/lib/cpu-profiler.js +92 -0
- package/dist/lib/mini-oxygen/node.js +5 -5
- package/dist/lib/mini-oxygen/workerd.js +21 -14
- package/dist/lib/onboarding/common.js +2 -2
- package/dist/lib/request-events.js +18 -7
- package/dist/lib/setups/i18n/domains.test.js +14 -0
- package/dist/lib/setups/i18n/index.js +3 -3
- package/dist/lib/setups/i18n/replacers.js +84 -64
- package/dist/lib/setups/i18n/replacers.test.js +242 -0
- package/dist/lib/setups/i18n/subdomains.test.js +14 -0
- package/dist/lib/setups/i18n/subfolders.test.js +14 -0
- package/dist/lib/setups/i18n/templates/domains.js +1 -1
- package/dist/lib/setups/i18n/templates/domains.ts +5 -2
- package/dist/lib/setups/i18n/templates/subdomains.ts +4 -1
- package/dist/lib/setups/i18n/templates/subfolders.js +1 -2
- package/dist/lib/setups/i18n/templates/subfolders.ts +7 -6
- package/dist/lib/setups/routes/generate.js +5 -2
- package/dist/lib/transpile/file.js +6 -0
- package/dist/lib/transpile/index.js +2 -0
- package/dist/lib/transpile/morph/classes.js +48 -0
- package/dist/lib/transpile/morph/functions.js +76 -0
- package/dist/lib/transpile/morph/index.js +67 -0
- package/dist/lib/transpile/morph/typedefs.js +133 -0
- package/dist/lib/transpile/morph/utils.js +15 -0
- package/dist/lib/{transpile-ts.js → transpile/project.js} +38 -59
- package/oclif.manifest.json +27 -1
- package/package.json +6 -6
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { Flags } from '@oclif/core';
|
|
2
|
+
import { resolvePath, joinPath } from '@shopify/cli-kit/node/path';
|
|
3
|
+
import Command from '@shopify/cli-kit/node/base-command';
|
|
4
|
+
import { outputInfo, outputWarn } from '@shopify/cli-kit/node/output';
|
|
5
|
+
import colors from '@shopify/cli-kit/node/colors';
|
|
6
|
+
import { writeFile } from '@shopify/cli-kit/node/fs';
|
|
7
|
+
import ansiEscapes from 'ansi-escapes';
|
|
8
|
+
import { getProjectPaths, handleRemixImportFail, getRemixConfig } from '../../../lib/remix-config.js';
|
|
9
|
+
import { muteDevLogs, createRemixLogger } from '../../../lib/log.js';
|
|
10
|
+
import { commonFlags, flagsToCamelObject } from '../../../lib/flags.js';
|
|
11
|
+
import { createCpuStartupProfiler } from '../../../lib/cpu-profiler.js';
|
|
12
|
+
|
|
13
|
+
const DEFAULT_OUTPUT_PATH = "startup.cpuprofile";
|
|
14
|
+
class DebugCpu extends Command {
|
|
15
|
+
static description = "Builds and profiles the server startup time the app.";
|
|
16
|
+
static flags = {
|
|
17
|
+
path: commonFlags.path,
|
|
18
|
+
output: Flags.string({
|
|
19
|
+
description: `Specify a path to generate the profile file. Defaults to "${DEFAULT_OUTPUT_PATH}".`,
|
|
20
|
+
default: DEFAULT_OUTPUT_PATH,
|
|
21
|
+
required: false
|
|
22
|
+
})
|
|
23
|
+
};
|
|
24
|
+
async run() {
|
|
25
|
+
const { flags } = await this.parse(DebugCpu);
|
|
26
|
+
const directory = flags.path ? resolvePath(flags.path) : process.cwd();
|
|
27
|
+
const output = flags.output ? resolvePath(flags.output) : joinPath(process.cwd(), flags.output);
|
|
28
|
+
await runDebugCpu({
|
|
29
|
+
...flagsToCamelObject(flags),
|
|
30
|
+
path: directory,
|
|
31
|
+
output
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
async function runDebugCpu({
|
|
36
|
+
path: appPath,
|
|
37
|
+
output = DEFAULT_OUTPUT_PATH
|
|
38
|
+
}) {
|
|
39
|
+
if (!process.env.NODE_ENV)
|
|
40
|
+
process.env.NODE_ENV = "production";
|
|
41
|
+
muteDevLogs({ workerReload: false });
|
|
42
|
+
const { root, buildPathWorkerFile } = getProjectPaths(appPath);
|
|
43
|
+
outputInfo(
|
|
44
|
+
"\u23F3\uFE0F Starting profiler for CPU startup... Profile will be written to:\n" + colors.dim(output)
|
|
45
|
+
);
|
|
46
|
+
const runProfiler = await createCpuStartupProfiler();
|
|
47
|
+
const [{ watch }, { createFileWatchCache }] = await Promise.all([
|
|
48
|
+
import('@remix-run/dev/dist/compiler/watch.js'),
|
|
49
|
+
import('@remix-run/dev/dist/compiler/fileWatchCache.js')
|
|
50
|
+
]).catch(handleRemixImportFail);
|
|
51
|
+
let times = 0;
|
|
52
|
+
const fileWatchCache = createFileWatchCache();
|
|
53
|
+
await watch(
|
|
54
|
+
{
|
|
55
|
+
config: await getRemixConfig(root),
|
|
56
|
+
options: {
|
|
57
|
+
mode: process.env.NODE_ENV,
|
|
58
|
+
sourcemap: true
|
|
59
|
+
},
|
|
60
|
+
fileWatchCache,
|
|
61
|
+
logger: createRemixLogger()
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
onBuildStart() {
|
|
65
|
+
if (times > 0) {
|
|
66
|
+
process.stdout.write(ansiEscapes.eraseLines(4));
|
|
67
|
+
}
|
|
68
|
+
outputInfo(`
|
|
69
|
+
#${++times} Building and profiling...`);
|
|
70
|
+
},
|
|
71
|
+
async onBuildFinish(context, duration, succeeded) {
|
|
72
|
+
if (succeeded) {
|
|
73
|
+
const { profile, totalScriptTimeMs } = await runProfiler(
|
|
74
|
+
buildPathWorkerFile
|
|
75
|
+
);
|
|
76
|
+
process.stdout.write(ansiEscapes.eraseLines(2));
|
|
77
|
+
outputInfo(
|
|
78
|
+
`#${times} Total time: ${totalScriptTimeMs.toLocaleString()} ms
|
|
79
|
+
${colors.dim(output)}`
|
|
80
|
+
);
|
|
81
|
+
await writeFile(output, JSON.stringify(profile, null, 2));
|
|
82
|
+
outputInfo(`
|
|
83
|
+
Waiting for changes...`);
|
|
84
|
+
} else {
|
|
85
|
+
outputWarn("\nBuild failed, waiting for changes to restart...");
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
async onFileChanged(file) {
|
|
89
|
+
fileWatchCache.invalidateFile(file);
|
|
90
|
+
},
|
|
91
|
+
async onFileDeleted(file) {
|
|
92
|
+
fileWatchCache.invalidateFile(file);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export { DebugCpu as default };
|
|
@@ -122,7 +122,6 @@ async function runDev({
|
|
|
122
122
|
},
|
|
123
123
|
workerRuntime
|
|
124
124
|
);
|
|
125
|
-
const debugNetworkUrl = `${miniOxygen.listeningAt}/debug-network`;
|
|
126
125
|
enhanceH2Logs({ host: miniOxygen.listeningAt, ...remixConfig });
|
|
127
126
|
miniOxygen.showBanner({
|
|
128
127
|
appName: storefront ? colors.cyan(storefront?.title) : void 0,
|
|
@@ -135,11 +134,11 @@ View GraphiQL API browser: ${getGraphiQLUrl({
|
|
|
135
134
|
host: miniOxygen.listeningAt
|
|
136
135
|
})}`
|
|
137
136
|
),
|
|
138
|
-
|
|
137
|
+
colors.dim(
|
|
139
138
|
`
|
|
140
|
-
View server-side network requests: ${
|
|
139
|
+
View server-side network requests: ${miniOxygen.listeningAt}/debug-network`
|
|
141
140
|
)
|
|
142
|
-
]
|
|
141
|
+
]
|
|
143
142
|
});
|
|
144
143
|
if (useCodegen) {
|
|
145
144
|
spawnCodegenProcess({ ...remixConfig, configFilePath: codegenConfigPath });
|
|
@@ -171,7 +171,7 @@ describe("init", () => {
|
|
|
171
171
|
)
|
|
172
172
|
);
|
|
173
173
|
await expect(readFile(`${tmpDir}/server.js`)).resolves.toMatch(
|
|
174
|
-
/export default {\n\s+async fetch\(\s*request,\s*env,\s*executionContext,?\s*\)/
|
|
174
|
+
/export default {\n\s+\/\*\*.*?\*\/\n\s+async fetch\(\s*request,\s*env,\s*executionContext,?\s*\)/s
|
|
175
175
|
);
|
|
176
176
|
const output = outputMock.info();
|
|
177
177
|
expect(output).toMatch("success");
|
|
@@ -266,7 +266,7 @@ describe("init", () => {
|
|
|
266
266
|
);
|
|
267
267
|
expect(projectFiles).toContain("app/routes/_index.jsx");
|
|
268
268
|
await expect(readFile(`${tmpDir}/server.js`)).resolves.toMatch(
|
|
269
|
-
/export default {\n\s+async fetch\(\s*request,\s*env,\s*executionContext,?\s*\)/
|
|
269
|
+
/export default {\n\s+\/\*\*.*?\*\/\n\s+async fetch\(\s*request,\s*env,\s*executionContext,?\s*\)/s
|
|
270
270
|
);
|
|
271
271
|
const output = outputMock.info();
|
|
272
272
|
expect(output).toMatch("success");
|
|
@@ -21,7 +21,9 @@ import resetStyles from './styles/reset.css';
|
|
|
21
21
|
import appStyles from './styles/app.css';
|
|
22
22
|
import {Layout} from '~/components/Layout';
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
/**
|
|
25
|
+
* This is important to avoid re-fetching root queries on sub-navigations
|
|
26
|
+
*/
|
|
25
27
|
export const shouldRevalidate: ShouldRevalidateFunction = ({
|
|
26
28
|
formMethod,
|
|
27
29
|
currentUrl,
|
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
json,
|
|
3
|
+
redirect,
|
|
4
|
+
type LoaderArgs,
|
|
5
|
+
type ActionArgs,
|
|
6
|
+
} from '@shopify/remix-oxygen';
|
|
2
7
|
import {Form, Link, useActionData} from '@remix-run/react';
|
|
3
8
|
|
|
4
9
|
type ActionResponse = {
|
|
@@ -15,7 +20,7 @@ export async function loader({context}: LoaderArgs) {
|
|
|
15
20
|
return json({});
|
|
16
21
|
}
|
|
17
22
|
|
|
18
|
-
export async function action({request, context}:
|
|
23
|
+
export async function action({request, context}: ActionArgs) {
|
|
19
24
|
const {storefront} = context;
|
|
20
25
|
const form = await request.formData();
|
|
21
26
|
const email = form.has('email') ? String(form.get('email')) : null;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
2
|
json,
|
|
3
3
|
redirect,
|
|
4
|
-
type ActionFunction,
|
|
5
4
|
type LoaderArgs,
|
|
5
|
+
type ActionArgs,
|
|
6
6
|
} from '@shopify/remix-oxygen';
|
|
7
7
|
import {Form, Link, useActionData} from '@remix-run/react';
|
|
8
8
|
import type {CustomerCreateMutation} from 'storefrontapi.generated';
|
|
@@ -23,7 +23,7 @@ export async function loader({context}: LoaderArgs) {
|
|
|
23
23
|
return json({});
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
export
|
|
26
|
+
export async function action({request, context}: ActionArgs) {
|
|
27
27
|
if (request.method !== 'POST') {
|
|
28
28
|
return json({error: 'Method not allowed'}, {status: 405});
|
|
29
29
|
}
|
|
@@ -101,7 +101,7 @@ export const action: ActionFunction = async ({request, context}) => {
|
|
|
101
101
|
}
|
|
102
102
|
return json({error}, {status: 400});
|
|
103
103
|
}
|
|
104
|
-
}
|
|
104
|
+
}
|
|
105
105
|
|
|
106
106
|
export default function Register() {
|
|
107
107
|
const data = useActionData<ActionResponse>();
|
|
@@ -15,9 +15,9 @@
|
|
|
15
15
|
"dependencies": {
|
|
16
16
|
"@remix-run/react": "1.19.1",
|
|
17
17
|
"@shopify/cli": "3.49.2",
|
|
18
|
-
"@shopify/cli-hydrogen": "^5.
|
|
19
|
-
"@shopify/hydrogen": "^2023.7.
|
|
20
|
-
"@shopify/remix-oxygen": "^1.1.
|
|
18
|
+
"@shopify/cli-hydrogen": "^5.5.1",
|
|
19
|
+
"@shopify/hydrogen": "^2023.7.13",
|
|
20
|
+
"@shopify/remix-oxygen": "^1.1.8",
|
|
21
21
|
"graphql": "^16.6.0",
|
|
22
22
|
"graphql-tag": "^2.12.6",
|
|
23
23
|
"isbot": "^3.6.6",
|
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
},
|
|
27
27
|
"devDependencies": {
|
|
28
28
|
"@remix-run/dev": "1.19.1",
|
|
29
|
+
"@remix-run/eslint-config": "1.19.1",
|
|
29
30
|
"@shopify/oxygen-workers-types": "^3.17.3",
|
|
30
31
|
"@shopify/prettier-config": "^1.1.2",
|
|
31
32
|
"@total-typescript/ts-reset": "^0.4.2",
|
|
@@ -637,7 +637,7 @@ export type CustomerOrdersFragment = Pick<
|
|
|
637
637
|
>;
|
|
638
638
|
pageInfo: Pick<
|
|
639
639
|
StorefrontAPI.PageInfo,
|
|
640
|
-
'hasPreviousPage' | 'hasNextPage' | 'endCursor'
|
|
640
|
+
'hasPreviousPage' | 'hasNextPage' | 'endCursor' | 'startCursor'
|
|
641
641
|
>;
|
|
642
642
|
};
|
|
643
643
|
};
|
|
@@ -689,7 +689,7 @@ export type CustomerOrdersQuery = {
|
|
|
689
689
|
>;
|
|
690
690
|
pageInfo: Pick<
|
|
691
691
|
StorefrontAPI.PageInfo,
|
|
692
|
-
'hasPreviousPage' | 'hasNextPage' | 'endCursor'
|
|
692
|
+
'hasPreviousPage' | 'hasNextPage' | 'endCursor' | 'startCursor'
|
|
693
693
|
>;
|
|
694
694
|
};
|
|
695
695
|
}
|
|
@@ -1113,7 +1113,7 @@ export type BlogQuery = {
|
|
|
1113
1113
|
>;
|
|
1114
1114
|
pageInfo: Pick<
|
|
1115
1115
|
StorefrontAPI.PageInfo,
|
|
1116
|
-
'hasPreviousPage' | 'hasNextPage' | 'endCursor'
|
|
1116
|
+
'hasPreviousPage' | 'hasNextPage' | 'endCursor' | 'startCursor'
|
|
1117
1117
|
>;
|
|
1118
1118
|
};
|
|
1119
1119
|
}
|
|
@@ -1811,7 +1811,7 @@ interface GeneratedQueryTypes {
|
|
|
1811
1811
|
return: ArticleQuery;
|
|
1812
1812
|
variables: ArticleQueryVariables;
|
|
1813
1813
|
};
|
|
1814
|
-
'#graphql\n query Blog(\n $language: LanguageCode\n $blogHandle: String!\n $first: Int\n $last: Int\n $startCursor: String\n $endCursor: String\n ) @inContext(language: $language) {\n blog(handle: $blogHandle) {\n title\n seo {\n title\n description\n }\n articles(\n first: $first,\n last: $last,\n before: $startCursor,\n after: $endCursor\n ) {\n nodes {\n ...ArticleItem\n }\n pageInfo {\n hasPreviousPage\n hasNextPage\n hasNextPage\n endCursor\n }\n\n }\n }\n }\n fragment ArticleItem on Article {\n author: authorV2 {\n name\n }\n contentHtml\n handle\n id\n image {\n id\n altText\n url\n width\n height\n }\n publishedAt\n title\n blog {\n handle\n }\n }\n': {
|
|
1814
|
+
'#graphql\n query Blog(\n $language: LanguageCode\n $blogHandle: String!\n $first: Int\n $last: Int\n $startCursor: String\n $endCursor: String\n ) @inContext(language: $language) {\n blog(handle: $blogHandle) {\n title\n seo {\n title\n description\n }\n articles(\n first: $first,\n last: $last,\n before: $startCursor,\n after: $endCursor\n ) {\n nodes {\n ...ArticleItem\n }\n pageInfo {\n hasPreviousPage\n hasNextPage\n hasNextPage\n endCursor\n startCursor\n }\n\n }\n }\n }\n fragment ArticleItem on Article {\n author: authorV2 {\n name\n }\n contentHtml\n handle\n id\n image {\n id\n altText\n url\n width\n height\n }\n publishedAt\n title\n blog {\n handle\n }\n }\n': {
|
|
1815
1815
|
return: BlogQuery;
|
|
1816
1816
|
variables: BlogQueryVariables;
|
|
1817
1817
|
};
|
package/dist/hooks/init.js
CHANGED
|
@@ -3,7 +3,10 @@ import { outputDebug } from '@shopify/cli-kit/node/output';
|
|
|
3
3
|
|
|
4
4
|
const EXPERIMENTAL_VM_MODULES_FLAG = "--experimental-vm-modules";
|
|
5
5
|
const hook = async function(options) {
|
|
6
|
-
if (options.id &&
|
|
6
|
+
if (options.id && // All the commands that rely on MiniOxygen:
|
|
7
|
+
["hydrogen:dev", "hydrogen:preview", "hydrogen:debug:cpu"].includes(
|
|
8
|
+
options.id
|
|
9
|
+
) && !process.execArgv.includes(EXPERIMENTAL_VM_MODULES_FLAG) && !(process.env.NODE_OPTIONS ?? "").includes(EXPERIMENTAL_VM_MODULES_FLAG)) {
|
|
7
10
|
outputDebug(
|
|
8
11
|
`Restarting CLI process with ${EXPERIMENTAL_VM_MODULES_FLAG} flag.`
|
|
9
12
|
);
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { readFile } from '@shopify/cli-kit/node/fs';
|
|
2
|
+
import { Session } from 'node:inspector';
|
|
3
|
+
|
|
4
|
+
async function createCpuStartupProfiler() {
|
|
5
|
+
const { createMiniOxygen } = await import('@shopify/mini-oxygen');
|
|
6
|
+
const miniOxygen = createMiniOxygen({
|
|
7
|
+
script: "export default {}",
|
|
8
|
+
modules: true,
|
|
9
|
+
log: () => {
|
|
10
|
+
}
|
|
11
|
+
});
|
|
12
|
+
await miniOxygen.ready();
|
|
13
|
+
return async (scriptPath) => {
|
|
14
|
+
const script = await readFile(scriptPath);
|
|
15
|
+
const stopProfiler = await startProfiler();
|
|
16
|
+
await miniOxygen.reload({ script });
|
|
17
|
+
const rawProfile = await stopProfiler();
|
|
18
|
+
return enhanceProfileNodes(rawProfile, scriptPath + ".map");
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
function startProfiler() {
|
|
22
|
+
const session = new Session();
|
|
23
|
+
session.connect();
|
|
24
|
+
return new Promise((resolveStart) => {
|
|
25
|
+
session.post("Profiler.enable", () => {
|
|
26
|
+
session.post("Profiler.start", () => {
|
|
27
|
+
resolveStart(() => {
|
|
28
|
+
return new Promise((resolveStop, rejectStop) => {
|
|
29
|
+
session.post("Profiler.stop", (err, { profile }) => {
|
|
30
|
+
session.disconnect();
|
|
31
|
+
if (err) {
|
|
32
|
+
return rejectStop(err);
|
|
33
|
+
}
|
|
34
|
+
resolveStop(profile);
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
async function enhanceProfileNodes(profile, sourceMapPath) {
|
|
43
|
+
const { SourceMapConsumer } = await import('source-map');
|
|
44
|
+
const sourceMap = JSON.parse(await readFile(sourceMapPath));
|
|
45
|
+
const smc = await new SourceMapConsumer(sourceMap, "file://" + sourceMapPath);
|
|
46
|
+
const scriptDescendants = /* @__PURE__ */ new Set();
|
|
47
|
+
let totalScriptTimeMicrosec = 0;
|
|
48
|
+
const totalTimeMicrosec = profile.endTime - profile.startTime;
|
|
49
|
+
const timePerSample = profile.samples?.length ? totalTimeMicrosec / profile.samples.length : 0;
|
|
50
|
+
for (const node of profile.nodes) {
|
|
51
|
+
if (node.callFrame.url === "<script>" || scriptDescendants.has(node.id)) {
|
|
52
|
+
scriptDescendants.add(node.id);
|
|
53
|
+
node.children?.forEach((id) => scriptDescendants.add(id));
|
|
54
|
+
}
|
|
55
|
+
if (scriptDescendants.has(node.id)) {
|
|
56
|
+
augmentNode(node, smc);
|
|
57
|
+
totalScriptTimeMicrosec += Math.round((node.hitCount ?? 0) * timePerSample * 1e3) / 1e3;
|
|
58
|
+
} else {
|
|
59
|
+
silenceNode(node);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
smc.destroy();
|
|
63
|
+
return {
|
|
64
|
+
profile,
|
|
65
|
+
totalTimeMs: totalTimeMicrosec / 1e3,
|
|
66
|
+
totalScriptTimeMs: totalScriptTimeMicrosec / 1e3
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
function augmentNode(node, smc) {
|
|
70
|
+
const originalPosition = smc.originalPositionFor({
|
|
71
|
+
line: node.callFrame.lineNumber + 1,
|
|
72
|
+
column: node.callFrame.columnNumber + 1
|
|
73
|
+
});
|
|
74
|
+
node.callFrame.url = originalPosition.source || node.callFrame.url;
|
|
75
|
+
node.callFrame.functionName = originalPosition.name || node.callFrame.functionName;
|
|
76
|
+
node.callFrame.lineNumber = originalPosition.line ? originalPosition.line - 1 : node.callFrame.lineNumber;
|
|
77
|
+
node.callFrame.columnNumber = originalPosition.column ?? node.callFrame.columnNumber;
|
|
78
|
+
}
|
|
79
|
+
function silenceNode(node) {
|
|
80
|
+
Object.assign(node, {
|
|
81
|
+
children: [],
|
|
82
|
+
callFrame: {
|
|
83
|
+
functionName: "(profiler)",
|
|
84
|
+
scriptId: "0",
|
|
85
|
+
url: "",
|
|
86
|
+
lineNumber: -1,
|
|
87
|
+
columnNumber: -1
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export { createCpuStartupProfiler };
|
|
@@ -5,7 +5,7 @@ import { renderSuccess } from '@shopify/cli-kit/node/ui';
|
|
|
5
5
|
import { 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 {
|
|
8
|
+
import { handleDebugNetworkRequest, H2O_BINDING_NAME, logRequestEvent } from '../request-events.js';
|
|
9
9
|
|
|
10
10
|
async function startNodeServer({
|
|
11
11
|
port = DEFAULT_PORT,
|
|
@@ -21,13 +21,13 @@ async function startNodeServer({
|
|
|
21
21
|
);
|
|
22
22
|
const asyncLocalStorage = new AsyncLocalStorage();
|
|
23
23
|
const serviceBindings = {
|
|
24
|
-
|
|
24
|
+
[H2O_BINDING_NAME]: {
|
|
25
25
|
fetch: async (request) => logRequestEvent(
|
|
26
26
|
new Request(request.url, {
|
|
27
27
|
method: "POST",
|
|
28
28
|
body: JSON.stringify({
|
|
29
|
-
...
|
|
30
|
-
...
|
|
29
|
+
...asyncLocalStorage.getStore(),
|
|
30
|
+
...await request.json()
|
|
31
31
|
})
|
|
32
32
|
})
|
|
33
33
|
)
|
|
@@ -53,7 +53,7 @@ async function startNodeServer({
|
|
|
53
53
|
async onRequest(request, defaultDispatcher) {
|
|
54
54
|
const url = new URL(request.url);
|
|
55
55
|
if (url.pathname === "/debug-network-server") {
|
|
56
|
-
return
|
|
56
|
+
return handleDebugNetworkRequest(request);
|
|
57
57
|
}
|
|
58
58
|
let requestId = request.headers.get("request-id");
|
|
59
59
|
if (!requestId) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Miniflare, NoOpLog,
|
|
1
|
+
import { Response, Miniflare, NoOpLog, Request } from 'miniflare';
|
|
2
2
|
import { resolvePath } from '@shopify/cli-kit/node/path';
|
|
3
3
|
import { glob, readFile, createFileReadStream, fileSize } from '@shopify/cli-kit/node/fs';
|
|
4
4
|
import { renderSuccess } from '@shopify/cli-kit/node/ui';
|
|
@@ -7,7 +7,9 @@ import { findInspectorUrl, connectToInspector } from './workerd-inspector.js';
|
|
|
7
7
|
import { DEFAULT_PORT } from '../flags.js';
|
|
8
8
|
import { findPort } from '../find-port.js';
|
|
9
9
|
import { OXYGEN_HEADERS_MAP, logRequestLine } from './common.js';
|
|
10
|
+
import { setConstructors, handleDebugNetworkRequest, H2O_BINDING_NAME, logRequestEvent } from '../request-events.js';
|
|
10
11
|
|
|
12
|
+
const DEFAULT_INSPECTOR_PORT = 8787;
|
|
11
13
|
async function startWorkerdServer({
|
|
12
14
|
root,
|
|
13
15
|
port = DEFAULT_PORT,
|
|
@@ -16,7 +18,8 @@ async function startWorkerdServer({
|
|
|
16
18
|
buildPathClient,
|
|
17
19
|
env
|
|
18
20
|
}) {
|
|
19
|
-
const
|
|
21
|
+
const appPort = await findPort(port);
|
|
22
|
+
const inspectorPort = await findPort(DEFAULT_INSPECTOR_PORT);
|
|
20
23
|
const oxygenHeadersMap = Object.values(OXYGEN_HEADERS_MAP).reduce(
|
|
21
24
|
(acc, item) => {
|
|
22
25
|
acc[item.name] = item.defaultValue;
|
|
@@ -24,13 +27,14 @@ async function startWorkerdServer({
|
|
|
24
27
|
},
|
|
25
28
|
{}
|
|
26
29
|
);
|
|
30
|
+
setConstructors({ Response });
|
|
27
31
|
const buildMiniOxygenOptions = async () => ({
|
|
28
32
|
cf: false,
|
|
29
33
|
verbose: false,
|
|
30
|
-
port,
|
|
34
|
+
port: appPort,
|
|
35
|
+
inspectorPort,
|
|
31
36
|
log: new NoOpLog(),
|
|
32
37
|
liveReload: watch,
|
|
33
|
-
inspectorPort,
|
|
34
38
|
host: "localhost",
|
|
35
39
|
workers: [
|
|
36
40
|
{
|
|
@@ -44,6 +48,7 @@ async function startWorkerdServer({
|
|
|
44
48
|
serviceBindings: {
|
|
45
49
|
hydrogen: "hydrogen",
|
|
46
50
|
assets: createAssetHandler(buildPathClient),
|
|
51
|
+
debugNetwork: handleDebugNetworkRequest,
|
|
47
52
|
logRequest
|
|
48
53
|
}
|
|
49
54
|
},
|
|
@@ -56,9 +61,12 @@ async function startWorkerdServer({
|
|
|
56
61
|
contents: await readFile(resolvePath(root, buildPathWorkerFile))
|
|
57
62
|
}
|
|
58
63
|
],
|
|
59
|
-
bindings: { ...env },
|
|
60
64
|
compatibilityFlags: ["streams_enable_constructors"],
|
|
61
|
-
compatibilityDate: "2022-10-31"
|
|
65
|
+
compatibilityDate: "2022-10-31",
|
|
66
|
+
bindings: { ...env },
|
|
67
|
+
serviceBindings: {
|
|
68
|
+
[H2O_BINDING_NAME]: logRequestEvent
|
|
69
|
+
}
|
|
62
70
|
}
|
|
63
71
|
]
|
|
64
72
|
});
|
|
@@ -69,7 +77,7 @@ async function startWorkerdServer({
|
|
|
69
77
|
let inspectorUrl = await findInspectorUrl(inspectorPort);
|
|
70
78
|
let cleanupInspector = inspectorUrl ? connectToInspector({ inspectorUrl, sourceMapPath }) : void 0;
|
|
71
79
|
return {
|
|
72
|
-
port,
|
|
80
|
+
port: appPort,
|
|
73
81
|
listeningAt,
|
|
74
82
|
async reload(nextOptions) {
|
|
75
83
|
miniOxygenOptions = await buildMiniOxygenOptions();
|
|
@@ -105,13 +113,11 @@ async function startWorkerdServer({
|
|
|
105
113
|
};
|
|
106
114
|
}
|
|
107
115
|
async function miniOxygenHandler(request, env, context) {
|
|
116
|
+
const { pathname } = new URL(request.url);
|
|
117
|
+
if (pathname === "/debug-network-server") {
|
|
118
|
+
return env.debugNetwork.fetch(request);
|
|
119
|
+
}
|
|
108
120
|
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
121
|
if (new Set(env.initialAssets).has(pathname.slice(1))) {
|
|
116
122
|
const response2 = await env.assets.fetch(
|
|
117
123
|
new Request(request.url, {
|
|
@@ -125,6 +131,7 @@ async function miniOxygenHandler(request, env, context) {
|
|
|
125
131
|
}
|
|
126
132
|
const requestInit = {
|
|
127
133
|
headers: {
|
|
134
|
+
"request-id": crypto.randomUUID(),
|
|
128
135
|
...env.oxygenHeadersMap,
|
|
129
136
|
...Object.fromEntries(request.headers.entries())
|
|
130
137
|
}
|
|
@@ -138,7 +145,7 @@ async function miniOxygenHandler(request, env, context) {
|
|
|
138
145
|
method: request.method,
|
|
139
146
|
signal: request.signal,
|
|
140
147
|
headers: {
|
|
141
|
-
...
|
|
148
|
+
...requestInit.headers,
|
|
142
149
|
"h2-duration-ms": String(durationMs),
|
|
143
150
|
"h2-response-status": String(response.status)
|
|
144
151
|
}
|
|
@@ -12,7 +12,7 @@ import { login, renderLoginSuccess } from '../auth.js';
|
|
|
12
12
|
import { renderI18nPrompt, setupI18nStrategy, I18N_STRATEGY_NAME_MAP } from '../setups/i18n/index.js';
|
|
13
13
|
import { titleize } from '../string.js';
|
|
14
14
|
import { ALIAS_NAME, createPlatformShortcut, getCliCommand } from '../shell.js';
|
|
15
|
-
import { transpileProject } from '../transpile
|
|
15
|
+
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';
|
|
@@ -193,7 +193,7 @@ async function handleLanguage(projectDir, controller, flagLanguage) {
|
|
|
193
193
|
return {
|
|
194
194
|
language,
|
|
195
195
|
async transpileProject() {
|
|
196
|
-
if (language
|
|
196
|
+
if (language !== "ts") {
|
|
197
197
|
await transpileProject(projectDir);
|
|
198
198
|
}
|
|
199
199
|
}
|
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
import { EventEmitter } from 'node:events';
|
|
2
2
|
import { ReadableStream } from 'node:stream/web';
|
|
3
|
-
import { Response } from '@shopify/mini-oxygen';
|
|
4
3
|
import { getGraphiQLUrl } from './graphiql-url.js';
|
|
4
|
+
import { Response } from '@shopify/mini-oxygen';
|
|
5
5
|
|
|
6
|
+
const H2O_BINDING_NAME = "H2O_LOG_EVENT";
|
|
7
|
+
let ResponseConstructor = Response;
|
|
8
|
+
function setConstructors(constructors) {
|
|
9
|
+
ResponseConstructor = constructors.Response;
|
|
10
|
+
}
|
|
6
11
|
const DEV_ROUTES = /* @__PURE__ */ new Set(["/graphiql", "/debug-network"]);
|
|
7
12
|
const EVENT_MAP = {
|
|
8
13
|
request: "Request",
|
|
@@ -22,14 +27,17 @@ async function getRequestInfo(request) {
|
|
|
22
27
|
}
|
|
23
28
|
const eventEmitter = new EventEmitter();
|
|
24
29
|
const eventHistory = [];
|
|
25
|
-
|
|
30
|
+
function createResponse(main = "ok", init) {
|
|
31
|
+
return new ResponseConstructor(main, init);
|
|
32
|
+
}
|
|
33
|
+
async function clearHistory(request) {
|
|
26
34
|
eventHistory.length = 0;
|
|
27
|
-
return
|
|
35
|
+
return createResponse();
|
|
28
36
|
}
|
|
29
37
|
async function logRequestEvent(request) {
|
|
30
38
|
const url = new URL(request.url);
|
|
31
39
|
if (DEV_ROUTES.has(url.pathname)) {
|
|
32
|
-
return
|
|
40
|
+
return createResponse();
|
|
33
41
|
}
|
|
34
42
|
const { eventType, purpose, graphql, ...data } = await getRequestInfo(request);
|
|
35
43
|
let graphiqlLink = "";
|
|
@@ -52,7 +60,7 @@ async function logRequestEvent(request) {
|
|
|
52
60
|
if (eventHistory.length > 100)
|
|
53
61
|
eventHistory.shift();
|
|
54
62
|
eventEmitter.emit("request", event);
|
|
55
|
-
return
|
|
63
|
+
return createResponse();
|
|
56
64
|
}
|
|
57
65
|
function streamRequestEvents(request) {
|
|
58
66
|
const stream = new ReadableStream({
|
|
@@ -81,7 +89,7 @@ function streamRequestEvents(request) {
|
|
|
81
89
|
return close();
|
|
82
90
|
}
|
|
83
91
|
});
|
|
84
|
-
return
|
|
92
|
+
return createResponse(stream, {
|
|
85
93
|
headers: {
|
|
86
94
|
"Content-Type": "text/event-stream",
|
|
87
95
|
"Cache-Control": "no-store",
|
|
@@ -89,5 +97,8 @@ function streamRequestEvents(request) {
|
|
|
89
97
|
}
|
|
90
98
|
});
|
|
91
99
|
}
|
|
100
|
+
function handleDebugNetworkRequest(request) {
|
|
101
|
+
return request.method === "DELETE" ? clearHistory() : streamRequestEvents(request);
|
|
102
|
+
}
|
|
92
103
|
|
|
93
|
-
export { DEV_ROUTES,
|
|
104
|
+
export { DEV_ROUTES, H2O_BINDING_NAME, handleDebugNetworkRequest, logRequestEvent, setConstructors };
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import { fileURLToPath } from 'node:url';
|
|
1
2
|
import { describe, it, expect } from 'vitest';
|
|
2
3
|
import { getLocaleFromRequest } from './templates/domains.js';
|
|
4
|
+
import { readFile } from '@shopify/cli-kit/node/fs';
|
|
3
5
|
|
|
4
6
|
describe("Setup i18n with domains", () => {
|
|
5
7
|
it("extracts the locale from the domain", () => {
|
|
@@ -22,4 +24,16 @@ describe("Setup i18n with domains", () => {
|
|
|
22
24
|
country: "ES"
|
|
23
25
|
});
|
|
24
26
|
});
|
|
27
|
+
it("does not access imported types directly", async () => {
|
|
28
|
+
const template = await readFile(
|
|
29
|
+
fileURLToPath(new URL("./templates/domains.ts", import.meta.url))
|
|
30
|
+
);
|
|
31
|
+
const typeImports = (template.match(/import\s+type\s+{([^}]+)}/)?.[1] || "").trim().split(/\s*,\s*/);
|
|
32
|
+
expect(typeImports).not.toHaveLength(0);
|
|
33
|
+
const fnCode = template.match(/function .*\n}$/ms)?.[0] || "";
|
|
34
|
+
expect(fnCode).toBeTruthy();
|
|
35
|
+
typeImports.forEach(
|
|
36
|
+
(typeImport) => expect(fnCode).not.toContain(typeImport)
|
|
37
|
+
);
|
|
38
|
+
});
|
|
25
39
|
});
|
|
@@ -16,16 +16,16 @@ const I18N_STRATEGY_NAME_MAP = {
|
|
|
16
16
|
};
|
|
17
17
|
const I18N_CHOICES = [...SETUP_I18N_STRATEGIES, "none"];
|
|
18
18
|
async function setupI18nStrategy(strategy, options) {
|
|
19
|
-
const
|
|
19
|
+
const isJs = options.serverEntryPoint?.endsWith(".js") ?? false;
|
|
20
20
|
const templatePath = fileURLToPath(
|
|
21
|
-
new URL(`./templates/${strategy}
|
|
21
|
+
new URL(`./templates/${strategy}.ts`, import.meta.url)
|
|
22
22
|
);
|
|
23
23
|
if (!await fileExists(templatePath)) {
|
|
24
24
|
throw new Error("Unknown strategy");
|
|
25
25
|
}
|
|
26
26
|
const template = await readFile(templatePath);
|
|
27
27
|
const formatConfig = await getCodeFormatOptions(options.rootDirectory);
|
|
28
|
-
await replaceServerI18n(options, formatConfig, template);
|
|
28
|
+
await replaceServerI18n(options, formatConfig, template, isJs);
|
|
29
29
|
await replaceRemixEnv(options, formatConfig, template);
|
|
30
30
|
}
|
|
31
31
|
async function renderI18nPrompt(options) {
|