@shopify/cli-hydrogen 5.3.1 → 5.4.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/build.js +32 -6
- package/dist/commands/hydrogen/deploy.js +85 -26
- package/dist/commands/hydrogen/deploy.test.js +86 -34
- package/dist/commands/hydrogen/dev.js +8 -4
- package/dist/generator-templates/starter/app/components/Aside.tsx +2 -2
- package/dist/generator-templates/starter/app/components/Search.tsx +3 -5
- package/dist/generator-templates/starter/app/root.tsx +4 -5
- package/dist/generator-templates/starter/app/routes/_index.tsx +2 -1
- package/dist/generator-templates/starter/app/routes/api.predictive-search.tsx +0 -2
- package/dist/generator-templates/starter/app/routes/cart.$lines.tsx +4 -5
- package/dist/generator-templates/starter/app/routes/collections._index.tsx +1 -1
- package/dist/generator-templates/starter/app/routes/discount.$code.tsx +3 -4
- package/dist/generator-templates/starter/package.json +4 -4
- package/dist/lib/check-lockfile.js +52 -47
- package/dist/lib/check-lockfile.test.js +16 -0
- package/dist/lib/{get-oxygen-token.js → get-oxygen-deployment-data.js} +5 -5
- package/dist/lib/{get-oxygen-token.test.js → get-oxygen-deployment-data.test.js} +31 -17
- package/dist/lib/graphiql-url.js +15 -0
- package/dist/lib/graphql/admin/{oxygen-token.js → get-oxygen-data.js} +8 -4
- package/dist/lib/is-ci.js +6 -0
- package/dist/lib/log.js +6 -5
- package/dist/lib/log.test.js +7 -7
- package/dist/lib/mini-oxygen/node.js +6 -9
- package/dist/lib/request-events.js +28 -12
- package/oclif.manifest.json +31 -9
- package/package.json +3 -3
|
@@ -2,24 +2,28 @@ import { fileExists } from '@shopify/cli-kit/node/fs';
|
|
|
2
2
|
import { resolvePath } from '@shopify/cli-kit/node/path';
|
|
3
3
|
import { checkIfIgnoredInGitRepository } from '@shopify/cli-kit/node/git';
|
|
4
4
|
import { renderWarning } from '@shopify/cli-kit/node/ui';
|
|
5
|
+
import { AbortError } from '@shopify/cli-kit/node/error';
|
|
5
6
|
import { lockfiles } from '@shopify/cli-kit/node/node-package-manager';
|
|
6
7
|
|
|
7
|
-
function missingLockfileWarning() {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
8
|
+
function missingLockfileWarning(shouldExit) {
|
|
9
|
+
const headline = "No lockfile found";
|
|
10
|
+
const body = `If you don\u2019t commit a lockfile, then your app might install the wrong package versions when deploying. To avoid versioning issues, generate a new lockfile and commit it to your repository.`;
|
|
11
|
+
const nextSteps = [
|
|
12
|
+
[
|
|
13
|
+
"Generate a lockfile. Run",
|
|
14
|
+
{
|
|
15
|
+
command: "npm|yarn|pnpm install"
|
|
16
|
+
}
|
|
17
|
+
],
|
|
18
|
+
"Commit the new file to your repository"
|
|
19
|
+
];
|
|
20
|
+
if (shouldExit) {
|
|
21
|
+
throw new AbortError(headline, body, nextSteps);
|
|
22
|
+
} else {
|
|
23
|
+
renderWarning({ headline, body, nextSteps });
|
|
24
|
+
}
|
|
21
25
|
}
|
|
22
|
-
function multipleLockfilesWarning(lockfiles2) {
|
|
26
|
+
function multipleLockfilesWarning(lockfiles2, shouldExit) {
|
|
23
27
|
const packageManagers = {
|
|
24
28
|
"yarn.lock": "yarn",
|
|
25
29
|
"package-lock.json": "npm",
|
|
@@ -28,30 +32,32 @@ function multipleLockfilesWarning(lockfiles2) {
|
|
|
28
32
|
const lockfileList = lockfiles2.map((lockfile) => {
|
|
29
33
|
return `${lockfile} (created by ${packageManagers[lockfile]})`;
|
|
30
34
|
});
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
`Your project contains more than one lockfile. This can cause version conflicts when installing and deploying your app. The following lockfiles were detected:
|
|
35
|
+
const headline = "Multiple lockfiles found";
|
|
36
|
+
const body = [
|
|
37
|
+
`Your project contains more than one lockfile. This can cause version conflicts when installing and deploying your app. The following lockfiles were detected:
|
|
35
38
|
`,
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
39
|
+
{ list: { items: lockfileList } }
|
|
40
|
+
];
|
|
41
|
+
const nextSteps = [
|
|
42
|
+
"Delete any unneeded lockfiles",
|
|
43
|
+
"Commit the change to your repository"
|
|
44
|
+
];
|
|
45
|
+
if (shouldExit) {
|
|
46
|
+
throw new AbortError(headline, body, nextSteps);
|
|
47
|
+
} else {
|
|
48
|
+
renderWarning({ headline, body, nextSteps });
|
|
49
|
+
}
|
|
43
50
|
}
|
|
44
51
|
function lockfileIgnoredWarning(lockfile) {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
});
|
|
52
|
+
const headline = "Lockfile ignored by Git";
|
|
53
|
+
const body = `Your project\u2019s lockfile isn\u2019t being tracked by Git. If you don\u2019t commit a lockfile, then your app might install the wrong package versions when deploying.`;
|
|
54
|
+
const nextSteps = [
|
|
55
|
+
`In your project\u2019s .gitignore file, delete any references to ${lockfile}`,
|
|
56
|
+
"Commit the change to your repository"
|
|
57
|
+
];
|
|
58
|
+
renderWarning({ headline, body, nextSteps });
|
|
53
59
|
}
|
|
54
|
-
async function checkLockfileStatus(directory) {
|
|
60
|
+
async function checkLockfileStatus(directory, shouldExit = false) {
|
|
55
61
|
if (process.env.LOCAL_DEV)
|
|
56
62
|
return;
|
|
57
63
|
const availableLockfiles = [];
|
|
@@ -60,21 +66,20 @@ async function checkLockfileStatus(directory) {
|
|
|
60
66
|
availableLockfiles.push(lockFileName);
|
|
61
67
|
}
|
|
62
68
|
}
|
|
63
|
-
if (
|
|
64
|
-
return missingLockfileWarning();
|
|
69
|
+
if (availableLockfiles.length === 0) {
|
|
70
|
+
return missingLockfileWarning(shouldExit);
|
|
65
71
|
}
|
|
66
72
|
if (availableLockfiles.length > 1) {
|
|
67
|
-
return multipleLockfilesWarning(availableLockfiles);
|
|
73
|
+
return multipleLockfilesWarning(availableLockfiles, shouldExit);
|
|
68
74
|
}
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
]
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
} catch {
|
|
75
|
+
const lockfile = availableLockfiles[0];
|
|
76
|
+
const ignoredLockfile = await checkIfIgnoredInGitRepository(directory, [
|
|
77
|
+
lockfile
|
|
78
|
+
]).catch(() => {
|
|
79
|
+
return [];
|
|
80
|
+
});
|
|
81
|
+
if (ignoredLockfile.length > 0) {
|
|
82
|
+
lockfileIgnoredWarning(lockfile);
|
|
78
83
|
}
|
|
79
84
|
}
|
|
80
85
|
|
|
@@ -53,6 +53,15 @@ describe("checkLockfileStatus()", () => {
|
|
|
53
53
|
);
|
|
54
54
|
});
|
|
55
55
|
});
|
|
56
|
+
it("throws when shouldExit is true", async () => {
|
|
57
|
+
await inTemporaryDirectory(async (tmpDir) => {
|
|
58
|
+
await writeFile(joinPath(tmpDir, "package-lock.json"), "");
|
|
59
|
+
await writeFile(joinPath(tmpDir, "pnpm-lock.yaml"), "");
|
|
60
|
+
await expect(checkLockfileStatus(tmpDir, true)).rejects.toThrow(
|
|
61
|
+
/Multiple lockfiles found/is
|
|
62
|
+
);
|
|
63
|
+
});
|
|
64
|
+
});
|
|
56
65
|
});
|
|
57
66
|
describe("when a lockfile is missing", () => {
|
|
58
67
|
it("renders a warning", async () => {
|
|
@@ -61,5 +70,12 @@ describe("checkLockfileStatus()", () => {
|
|
|
61
70
|
expect(outputMock.warn()).toMatch(/ warning .+ No lockfile found .+/is);
|
|
62
71
|
});
|
|
63
72
|
});
|
|
73
|
+
it("throws when shouldExit is true", async () => {
|
|
74
|
+
await inTemporaryDirectory(async (tmpDir) => {
|
|
75
|
+
await expect(checkLockfileStatus(tmpDir, true)).rejects.toThrow(
|
|
76
|
+
/No lockfile found/is
|
|
77
|
+
);
|
|
78
|
+
});
|
|
79
|
+
});
|
|
64
80
|
});
|
|
65
81
|
});
|
|
@@ -4,9 +4,9 @@ import { linkStorefront } from '../commands/hydrogen/link.js';
|
|
|
4
4
|
import { login } from './auth.js';
|
|
5
5
|
import { getCliCommand } from './shell.js';
|
|
6
6
|
import { renderMissingLink, renderMissingStorefront } from './render-errors.js';
|
|
7
|
-
import {
|
|
7
|
+
import { getOxygenData } from './graphql/admin/get-oxygen-data.js';
|
|
8
8
|
|
|
9
|
-
async function
|
|
9
|
+
async function getOxygenDeploymentData({
|
|
10
10
|
root
|
|
11
11
|
}) {
|
|
12
12
|
const [{ session, config }, cliCommand] = await Promise.all([
|
|
@@ -28,7 +28,7 @@ async function getOxygenDeploymentToken({
|
|
|
28
28
|
if (!config.storefront) {
|
|
29
29
|
return;
|
|
30
30
|
}
|
|
31
|
-
const { storefront } = await
|
|
31
|
+
const { storefront } = await getOxygenData(session, config.storefront.id);
|
|
32
32
|
if (!storefront) {
|
|
33
33
|
renderMissingStorefront({
|
|
34
34
|
session,
|
|
@@ -41,7 +41,7 @@ async function getOxygenDeploymentToken({
|
|
|
41
41
|
outputWarn(`Could not retrieve a deployment token.`);
|
|
42
42
|
return;
|
|
43
43
|
}
|
|
44
|
-
return storefront
|
|
44
|
+
return storefront;
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
export {
|
|
47
|
+
export { getOxygenDeploymentData };
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { vi, describe, beforeEach, afterEach, it, expect } from 'vitest';
|
|
2
2
|
import { renderConfirmationPrompt } from '@shopify/cli-kit/node/ui';
|
|
3
|
-
import {
|
|
3
|
+
import { getOxygenDeploymentData } from './get-oxygen-deployment-data.js';
|
|
4
4
|
import { login } from './auth.js';
|
|
5
5
|
import { getConfig } from './shopify-config.js';
|
|
6
6
|
import { renderMissingLink, renderMissingStorefront } from './render-errors.js';
|
|
7
7
|
import { linkStorefront } from '../commands/hydrogen/link.js';
|
|
8
|
-
import {
|
|
8
|
+
import { getOxygenData } from './graphql/admin/get-oxygen-data.js';
|
|
9
9
|
|
|
10
10
|
vi.mock("@shopify/cli-kit/node/ui", async () => {
|
|
11
11
|
const original = await vi.importActual("@shopify/cli-kit/node/ui");
|
|
@@ -19,9 +19,19 @@ vi.mock("./admin-session.js");
|
|
|
19
19
|
vi.mock("./shopify-config.js");
|
|
20
20
|
vi.mock("./render-errors.js");
|
|
21
21
|
vi.mock("../commands/hydrogen/link.js");
|
|
22
|
-
vi.mock("./graphql/admin/oxygen-
|
|
23
|
-
describe("
|
|
22
|
+
vi.mock("./graphql/admin/get-oxygen-data.js");
|
|
23
|
+
describe("getOxygenDeploymentData", () => {
|
|
24
24
|
const OXYGEN_DEPLOYMENT_TOKEN = "a-lovely-token";
|
|
25
|
+
const environments = [
|
|
26
|
+
{
|
|
27
|
+
name: "production",
|
|
28
|
+
branch: "main"
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
name: "preview",
|
|
32
|
+
branch: "staging"
|
|
33
|
+
}
|
|
34
|
+
];
|
|
25
35
|
beforeEach(() => {
|
|
26
36
|
vi.mocked(login).mockResolvedValue({
|
|
27
37
|
session: {
|
|
@@ -41,16 +51,20 @@ describe("getOxygenDeploymentToken", () => {
|
|
|
41
51
|
vi.mocked(getConfig).mockResolvedValue({
|
|
42
52
|
storefront: { id: "storefront-id", title: "Existing Link" }
|
|
43
53
|
});
|
|
44
|
-
vi.mocked(
|
|
45
|
-
storefront: {
|
|
54
|
+
vi.mocked(getOxygenData).mockResolvedValue({
|
|
55
|
+
storefront: {
|
|
56
|
+
oxygenDeploymentToken: OXYGEN_DEPLOYMENT_TOKEN,
|
|
57
|
+
environments
|
|
58
|
+
}
|
|
46
59
|
});
|
|
47
60
|
});
|
|
48
61
|
afterEach(() => {
|
|
49
62
|
vi.resetAllMocks();
|
|
50
63
|
});
|
|
51
|
-
it("returns the oxygen deployment token", async () => {
|
|
52
|
-
const
|
|
53
|
-
expect(
|
|
64
|
+
it("returns the oxygen deployment token and environments", async () => {
|
|
65
|
+
const data = await getOxygenDeploymentData({ root: "test-root" });
|
|
66
|
+
expect(data?.oxygenDeploymentToken).toBe(OXYGEN_DEPLOYMENT_TOKEN);
|
|
67
|
+
expect(data?.environments).toEqual(environments);
|
|
54
68
|
});
|
|
55
69
|
describe("when there is no linked storefront", () => {
|
|
56
70
|
beforeEach(() => {
|
|
@@ -69,36 +83,36 @@ describe("getOxygenDeploymentToken", () => {
|
|
|
69
83
|
});
|
|
70
84
|
it("calls renderMissingLink and prompts the user to create a link", async () => {
|
|
71
85
|
vi.mocked(renderConfirmationPrompt).mockResolvedValue(true);
|
|
72
|
-
await
|
|
86
|
+
await getOxygenDeploymentData({ root: "test-root" });
|
|
73
87
|
expect(renderMissingLink).toHaveBeenCalled();
|
|
74
88
|
expect(renderConfirmationPrompt).toHaveBeenCalled();
|
|
75
89
|
expect(linkStorefront).toHaveBeenCalled();
|
|
76
90
|
});
|
|
77
91
|
it("returns nothing if the user does not create a new link", async () => {
|
|
78
92
|
vi.mocked(renderConfirmationPrompt).mockResolvedValue(false);
|
|
79
|
-
const token = await
|
|
93
|
+
const token = await getOxygenDeploymentData({ root: "test-root" });
|
|
80
94
|
expect(token).toEqual(void 0);
|
|
81
95
|
});
|
|
82
96
|
});
|
|
83
97
|
describe("when there is no matching storefront in the shop", () => {
|
|
84
98
|
beforeEach(() => {
|
|
85
|
-
vi.mocked(
|
|
99
|
+
vi.mocked(getOxygenData).mockResolvedValue({ storefront: null });
|
|
86
100
|
});
|
|
87
101
|
it("calls renderMissingStorefront and returns nothing", async () => {
|
|
88
|
-
const token = await
|
|
102
|
+
const token = await getOxygenDeploymentData({ root: "test-root" });
|
|
89
103
|
expect(renderMissingStorefront).toHaveBeenCalled();
|
|
90
104
|
expect(token).toEqual(void 0);
|
|
91
105
|
});
|
|
92
106
|
});
|
|
93
107
|
describe("when the storefront does not have an oxygen deployment token", () => {
|
|
94
108
|
beforeEach(() => {
|
|
95
|
-
vi.mocked(
|
|
96
|
-
storefront: { oxygenDeploymentToken: "" }
|
|
109
|
+
vi.mocked(getOxygenData).mockResolvedValue({
|
|
110
|
+
storefront: { oxygenDeploymentToken: "", environments: [] }
|
|
97
111
|
});
|
|
98
112
|
});
|
|
99
113
|
it("returns nothing", async () => {
|
|
100
|
-
const
|
|
101
|
-
expect(
|
|
114
|
+
const data = await getOxygenDeploymentData({ root: "test-root" });
|
|
115
|
+
expect(data).toEqual(void 0);
|
|
102
116
|
});
|
|
103
117
|
});
|
|
104
118
|
});
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
function getGraphiQLUrl({
|
|
2
|
+
host = "",
|
|
3
|
+
graphql
|
|
4
|
+
}) {
|
|
5
|
+
let url = `${host.endsWith("/") ? host.slice(0, -1) : host}/graphiql`;
|
|
6
|
+
if (graphql) {
|
|
7
|
+
let { query, variables } = graphql;
|
|
8
|
+
if (typeof variables !== "string")
|
|
9
|
+
variables = JSON.stringify(variables);
|
|
10
|
+
url += `?query=${encodeURIComponent(query)}${variables ? `&variables=${encodeURIComponent(variables)}` : ""}`;
|
|
11
|
+
}
|
|
12
|
+
return url;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export { getGraphiQLUrl };
|
|
@@ -1,15 +1,19 @@
|
|
|
1
1
|
import { adminRequest } from './client.js';
|
|
2
2
|
|
|
3
|
-
const
|
|
3
|
+
const GetDeploymentDataQuery = `#graphql
|
|
4
4
|
query GetDeploymentToken($id: ID!) {
|
|
5
5
|
hydrogenStorefront(id: $id) {
|
|
6
6
|
oxygenDeploymentToken
|
|
7
|
+
environments {
|
|
8
|
+
name
|
|
9
|
+
branch
|
|
10
|
+
}
|
|
7
11
|
}
|
|
8
12
|
}
|
|
9
13
|
`;
|
|
10
|
-
async function
|
|
14
|
+
async function getOxygenData(adminSession, storefrontId) {
|
|
11
15
|
const { hydrogenStorefront } = await adminRequest(
|
|
12
|
-
|
|
16
|
+
GetDeploymentDataQuery,
|
|
13
17
|
adminSession,
|
|
14
18
|
{
|
|
15
19
|
id: storefrontId
|
|
@@ -18,4 +22,4 @@ async function getOxygenToken(adminSession, storefrontId) {
|
|
|
18
22
|
return { storefront: hydrogenStorefront };
|
|
19
23
|
}
|
|
20
24
|
|
|
21
|
-
export {
|
|
25
|
+
export { GetDeploymentDataQuery, getOxygenData };
|
package/dist/lib/log.js
CHANGED
|
@@ -2,6 +2,7 @@ import { renderFatalError, renderWarning, renderInfo } from '@shopify/cli-kit/no
|
|
|
2
2
|
import { BugError } from '@shopify/cli-kit/node/error';
|
|
3
3
|
import { outputContent, outputToken } from '@shopify/cli-kit/node/output';
|
|
4
4
|
import colors from '@shopify/cli-kit/node/colors';
|
|
5
|
+
import { getGraphiQLUrl } from './graphiql-url.js';
|
|
5
6
|
|
|
6
7
|
const originalConsole = { ...console };
|
|
7
8
|
const methodsReplaced = /* @__PURE__ */ new Set();
|
|
@@ -194,11 +195,11 @@ function enhanceH2Logs(options) {
|
|
|
194
195
|
}
|
|
195
196
|
}
|
|
196
197
|
if (typeof cause !== "string" && !!cause?.graphql?.query) {
|
|
197
|
-
const
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
const [, queryType, queryName] = query.match(/(query|mutation)\s+(\w+)/) || [];
|
|
198
|
+
const link = getGraphiQLUrl({
|
|
199
|
+
host: options.host,
|
|
200
|
+
graphql: cause.graphql
|
|
201
|
+
});
|
|
202
|
+
const [, queryType, queryName] = cause.graphql.query.match(/(query|mutation)\s+(\w+)/) || [];
|
|
202
203
|
tryMessage = (tryMessage ? `${tryMessage}
|
|
203
204
|
|
|
204
205
|
` : "") + outputContent`To debug the ${queryType || "query"}${queryName ? ` \`${colors.whiteBright(queryName)}\`` : ""}, try it in ${outputToken.link(colors.bold("GraphiQL"), link)}.`.value;
|
package/dist/lib/log.test.js
CHANGED
|
@@ -5,7 +5,7 @@ import { resetAllLogs, enhanceH2Logs } from './log.js';
|
|
|
5
5
|
|
|
6
6
|
describe("log replacer", () => {
|
|
7
7
|
describe("enhanceH2Logs", () => {
|
|
8
|
-
const
|
|
8
|
+
const host = "http://localhost:3000";
|
|
9
9
|
const rootDirectory = fileURLToPath(import.meta.url);
|
|
10
10
|
const outputMock = mockAndCaptureOutput();
|
|
11
11
|
beforeEach(() => {
|
|
@@ -18,7 +18,7 @@ describe("log replacer", () => {
|
|
|
18
18
|
});
|
|
19
19
|
describe("enhances h2:info pattern", () => {
|
|
20
20
|
it("renders in an info banner", () => {
|
|
21
|
-
enhanceH2Logs({
|
|
21
|
+
enhanceH2Logs({ host, rootDirectory });
|
|
22
22
|
console.warn("[h2:info:storefront.query] Tip");
|
|
23
23
|
const message = outputMock.info();
|
|
24
24
|
expect(message).not.toMatch("h2");
|
|
@@ -29,7 +29,7 @@ describe("log replacer", () => {
|
|
|
29
29
|
});
|
|
30
30
|
describe("enhances h2:warn pattern", () => {
|
|
31
31
|
it("renders in a warning banner", () => {
|
|
32
|
-
enhanceH2Logs({
|
|
32
|
+
enhanceH2Logs({ host, rootDirectory });
|
|
33
33
|
console.warn("[h2:warn:storefront.query] Wrong query 1");
|
|
34
34
|
const warning = outputMock.warn();
|
|
35
35
|
expect(warning).not.toMatch("h2");
|
|
@@ -38,7 +38,7 @@ describe("log replacer", () => {
|
|
|
38
38
|
expect(warning).toMatch("Wrong query");
|
|
39
39
|
});
|
|
40
40
|
it("shows links from the last line as a list", () => {
|
|
41
|
-
enhanceH2Logs({
|
|
41
|
+
enhanceH2Logs({ host, rootDirectory });
|
|
42
42
|
console.warn(
|
|
43
43
|
"[h2:warn:storefront.query] Wrong query.\nhttps://docs.com/something"
|
|
44
44
|
);
|
|
@@ -50,7 +50,7 @@ describe("log replacer", () => {
|
|
|
50
50
|
});
|
|
51
51
|
describe("enhances h2:error pattern", () => {
|
|
52
52
|
it("renders in an error banner", () => {
|
|
53
|
-
enhanceH2Logs({
|
|
53
|
+
enhanceH2Logs({ host, rootDirectory });
|
|
54
54
|
console.error(new Error("[h2:error:storefront.query] Wrong query 2"));
|
|
55
55
|
const error = outputMock.error();
|
|
56
56
|
expect(error.split("stack trace:")[0]).not.toMatch("h2");
|
|
@@ -59,7 +59,7 @@ describe("log replacer", () => {
|
|
|
59
59
|
expect(error).toMatch("Wrong query");
|
|
60
60
|
});
|
|
61
61
|
it("shows a GraphiQL link when the error is related to a GraphQL query", () => {
|
|
62
|
-
enhanceH2Logs({
|
|
62
|
+
enhanceH2Logs({ host, rootDirectory });
|
|
63
63
|
console.error(
|
|
64
64
|
new Error("[h2:error:storefront.query] Wrong query 3", {
|
|
65
65
|
cause: {
|
|
@@ -75,7 +75,7 @@ describe("log replacer", () => {
|
|
|
75
75
|
`);
|
|
76
76
|
});
|
|
77
77
|
it("trims stack traces when the error is related to a GraphQL query", () => {
|
|
78
|
-
enhanceH2Logs({
|
|
78
|
+
enhanceH2Logs({ host, rootDirectory });
|
|
79
79
|
console.error(
|
|
80
80
|
new Error("[h2:error:storefront.query] Wrong query 4", {
|
|
81
81
|
cause: { graphql: { query: "query test {}" } }
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { randomUUID } from 'node:crypto';
|
|
2
2
|
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
3
|
-
import { resolvePath } 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';
|
|
6
5
|
import { startServer, Request } from '@shopify/mini-oxygen';
|
|
@@ -9,14 +8,12 @@ import { OXYGEN_HEADERS_MAP, logRequestLine } from './common.js';
|
|
|
9
8
|
import { clearHistory, streamRequestEvents, logRequestEvent } from '../request-events.js';
|
|
10
9
|
|
|
11
10
|
async function startNodeServer({
|
|
12
|
-
root,
|
|
13
11
|
port = DEFAULT_PORT,
|
|
14
12
|
watch = false,
|
|
15
13
|
buildPathWorkerFile,
|
|
16
14
|
buildPathClient,
|
|
17
15
|
env
|
|
18
16
|
}) {
|
|
19
|
-
resolvePath(root, ".env");
|
|
20
17
|
const oxygenHeaders = Object.fromEntries(
|
|
21
18
|
Object.entries(OXYGEN_HEADERS_MAP).map(([key, value]) => {
|
|
22
19
|
return [key, value.defaultValue];
|
|
@@ -25,13 +22,13 @@ async function startNodeServer({
|
|
|
25
22
|
const asyncLocalStorage = new AsyncLocalStorage();
|
|
26
23
|
const serviceBindings = {
|
|
27
24
|
H2O_LOG_EVENT: {
|
|
28
|
-
fetch: (request) => logRequestEvent(
|
|
25
|
+
fetch: async (request) => logRequestEvent(
|
|
29
26
|
new Request(request.url, {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
27
|
+
method: "POST",
|
|
28
|
+
body: JSON.stringify({
|
|
29
|
+
...await request.json(),
|
|
33
30
|
...asyncLocalStorage.getStore()
|
|
34
|
-
}
|
|
31
|
+
})
|
|
35
32
|
})
|
|
36
33
|
)
|
|
37
34
|
}
|
|
@@ -65,7 +62,7 @@ async function startNodeServer({
|
|
|
65
62
|
}
|
|
66
63
|
const startTimeMs = Date.now();
|
|
67
64
|
const response = await asyncLocalStorage.run(
|
|
68
|
-
{
|
|
65
|
+
{ requestId, purpose: request.headers.get("purpose") },
|
|
69
66
|
() => defaultDispatcher(request)
|
|
70
67
|
);
|
|
71
68
|
logRequestLine(request, {
|
|
@@ -1,20 +1,24 @@
|
|
|
1
1
|
import { EventEmitter } from 'node:events';
|
|
2
2
|
import { ReadableStream } from 'node:stream/web';
|
|
3
3
|
import { Response } from '@shopify/mini-oxygen';
|
|
4
|
+
import { getGraphiQLUrl } from './graphiql-url.js';
|
|
4
5
|
|
|
5
6
|
const DEV_ROUTES = /* @__PURE__ */ new Set(["/graphiql", "/debug-network"]);
|
|
6
7
|
const EVENT_MAP = {
|
|
7
8
|
request: "Request",
|
|
8
9
|
subrequest: "Sub request"
|
|
9
10
|
};
|
|
10
|
-
function getRequestInfo(request) {
|
|
11
|
+
async function getRequestInfo(request) {
|
|
12
|
+
const data = await request.json();
|
|
11
13
|
return {
|
|
12
|
-
id:
|
|
13
|
-
eventType:
|
|
14
|
-
startTime:
|
|
15
|
-
endTime:
|
|
16
|
-
purpose:
|
|
17
|
-
cacheStatus:
|
|
14
|
+
id: data.requestId ?? "",
|
|
15
|
+
eventType: data.eventType || "unknown",
|
|
16
|
+
startTime: data.startTime,
|
|
17
|
+
endTime: data.endTime || Date.now(),
|
|
18
|
+
purpose: data.purpose === "prefetch" ? "(prefetch)" : "",
|
|
19
|
+
cacheStatus: data.cacheStatus ?? "",
|
|
20
|
+
stackLine: data.stackLine ?? "",
|
|
21
|
+
graphql: data.graphql ? JSON.parse(data.graphql) : null
|
|
18
22
|
};
|
|
19
23
|
}
|
|
20
24
|
const eventEmitter = new EventEmitter();
|
|
@@ -24,24 +28,36 @@ async function clearHistory() {
|
|
|
24
28
|
return new Response("ok");
|
|
25
29
|
}
|
|
26
30
|
async function logRequestEvent(request) {
|
|
27
|
-
|
|
31
|
+
const url = new URL(request.url);
|
|
32
|
+
if (DEV_ROUTES.has(url.pathname)) {
|
|
28
33
|
return new Response("ok");
|
|
29
34
|
}
|
|
30
|
-
const { eventType, purpose, ...data } = getRequestInfo(request);
|
|
35
|
+
const { eventType, purpose, stackLine, graphql, ...data } = await getRequestInfo(request);
|
|
36
|
+
let originFile = "";
|
|
37
|
+
let graphiqlLink = "";
|
|
31
38
|
let description = request.url;
|
|
32
39
|
if (eventType === "subrequest") {
|
|
33
|
-
description =
|
|
40
|
+
description = graphql?.query.match(/(query|mutation)\s+(\w+)/)?.[0]?.replace(/\s+/, " ") || decodeURIComponent(url.search.slice(1));
|
|
41
|
+
const [, fnName, filePath] = stackLine?.match(/\s+at ([^\s]+) \(.*?\/(app\/[^\n]*)\)/) || [];
|
|
42
|
+
if (fnName && filePath) {
|
|
43
|
+
originFile = `${fnName}:${filePath}`;
|
|
44
|
+
}
|
|
45
|
+
if (graphql) {
|
|
46
|
+
graphiqlLink = getGraphiQLUrl({ graphql });
|
|
47
|
+
}
|
|
34
48
|
}
|
|
35
49
|
const event = {
|
|
36
50
|
event: EVENT_MAP[eventType] || eventType,
|
|
37
51
|
data: JSON.stringify({
|
|
38
52
|
...data,
|
|
39
|
-
url: `${purpose} ${description}`.trim()
|
|
53
|
+
url: `${purpose} ${description}`.trim(),
|
|
54
|
+
graphiqlLink,
|
|
55
|
+
originFile
|
|
40
56
|
})
|
|
41
57
|
};
|
|
58
|
+
eventHistory.push(event);
|
|
42
59
|
if (eventHistory.length > 100)
|
|
43
60
|
eventHistory.shift();
|
|
44
|
-
eventHistory.push(event);
|
|
45
61
|
eventEmitter.emit("request", event);
|
|
46
62
|
return new Response("ok");
|
|
47
63
|
}
|
package/oclif.manifest.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "5.
|
|
2
|
+
"version": "5.4.1",
|
|
3
3
|
"commands": {
|
|
4
4
|
"hydrogen:build": {
|
|
5
5
|
"id": "hydrogen:build",
|
|
@@ -28,6 +28,12 @@
|
|
|
28
28
|
"description": "Show a bundle size summary after building.",
|
|
29
29
|
"allowNo": true
|
|
30
30
|
},
|
|
31
|
+
"lockfile-check": {
|
|
32
|
+
"name": "lockfile-check",
|
|
33
|
+
"type": "boolean",
|
|
34
|
+
"description": "Checks that there is exactly 1 valid lockfile in the project.",
|
|
35
|
+
"allowNo": true
|
|
36
|
+
},
|
|
31
37
|
"disable-route-warning": {
|
|
32
38
|
"name": "disable-route-warning",
|
|
33
39
|
"type": "boolean",
|
|
@@ -147,6 +153,14 @@
|
|
|
147
153
|
"hidden": true,
|
|
148
154
|
"aliases": [],
|
|
149
155
|
"flags": {
|
|
156
|
+
"env-branch": {
|
|
157
|
+
"name": "env-branch",
|
|
158
|
+
"type": "option",
|
|
159
|
+
"char": "e",
|
|
160
|
+
"description": "Environment branch (tag) for environment to deploy to",
|
|
161
|
+
"required": false,
|
|
162
|
+
"multiple": false
|
|
163
|
+
},
|
|
150
164
|
"path": {
|
|
151
165
|
"name": "path",
|
|
152
166
|
"type": "option",
|
|
@@ -160,29 +174,37 @@
|
|
|
160
174
|
"description": "Shop URL. It can be the shop prefix (janes-apparel) or the full myshopify.com URL (janes-apparel.myshopify.com, https://janes-apparel.myshopify.com).",
|
|
161
175
|
"multiple": false
|
|
162
176
|
},
|
|
163
|
-
"
|
|
164
|
-
"name": "
|
|
177
|
+
"public-deployment": {
|
|
178
|
+
"name": "public-deployment",
|
|
165
179
|
"type": "boolean",
|
|
166
180
|
"description": "Marks a preview deployment as publicly accessible.",
|
|
167
181
|
"required": false,
|
|
168
182
|
"allowNo": false
|
|
169
183
|
},
|
|
170
|
-
"
|
|
171
|
-
"name": "
|
|
184
|
+
"token": {
|
|
185
|
+
"name": "token",
|
|
186
|
+
"type": "option",
|
|
187
|
+
"char": "t",
|
|
188
|
+
"description": "Oxygen deployment token",
|
|
189
|
+
"required": false,
|
|
190
|
+
"multiple": false
|
|
191
|
+
},
|
|
192
|
+
"metadata-url": {
|
|
193
|
+
"name": "metadata-url",
|
|
172
194
|
"type": "option",
|
|
173
195
|
"description": "URL that links to the deployment. Will be saved and displayed in the Shopify admin",
|
|
174
196
|
"required": false,
|
|
175
197
|
"multiple": false
|
|
176
198
|
},
|
|
177
|
-
"
|
|
178
|
-
"name": "
|
|
199
|
+
"metadata-user": {
|
|
200
|
+
"name": "metadata-user",
|
|
179
201
|
"type": "option",
|
|
180
202
|
"description": "User that initiated the deployment. Will be saved and displayed in the Shopify admin",
|
|
181
203
|
"required": false,
|
|
182
204
|
"multiple": false
|
|
183
205
|
},
|
|
184
|
-
"
|
|
185
|
-
"name": "
|
|
206
|
+
"metadata-version": {
|
|
207
|
+
"name": "metadata-version",
|
|
186
208
|
"type": "option",
|
|
187
209
|
"description": "A version identifier for the deployment. Will be saved and displayed in the Shopify admin",
|
|
188
210
|
"required": false,
|