@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
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { Flags } from '@oclif/core';
|
|
2
2
|
import Command from '@shopify/cli-kit/node/base-command';
|
|
3
3
|
import { outputInfo, outputWarn, outputContent, outputToken } from '@shopify/cli-kit/node/output';
|
|
4
|
-
import { rmdir, fileSize, fileExists, copyFile } from '@shopify/cli-kit/node/fs';
|
|
5
|
-
import { resolvePath, relativePath } from '@shopify/cli-kit/node/path';
|
|
4
|
+
import { rmdir, fileSize, glob, readFile, writeFile, fileExists, copyFile } from '@shopify/cli-kit/node/fs';
|
|
5
|
+
import { resolvePath, joinPath, relativePath } from '@shopify/cli-kit/node/path';
|
|
6
6
|
import { getPackageManager } from '@shopify/cli-kit/node/node-package-manager';
|
|
7
7
|
import colors from '@shopify/cli-kit/node/colors';
|
|
8
8
|
import { getProjectPaths, getRemixConfig, handleRemixImportFail, assertOxygenChecks } from '../../lib/remix-config.js';
|
|
@@ -13,6 +13,7 @@ import { muteRemixLogs, createRemixLogger } from '../../lib/log.js';
|
|
|
13
13
|
import { codegen } from '../../lib/codegen.js';
|
|
14
14
|
import { hasMetafile, buildBundleAnalysis, getBundleAnalysisSummary } from '../../lib/bundle/analyzer.js';
|
|
15
15
|
import { AbortError } from '@shopify/cli-kit/node/error';
|
|
16
|
+
import { isCI } from '../../lib/is-ci.js';
|
|
16
17
|
|
|
17
18
|
const LOG_WORKER_BUILT = "\u{1F4E6} Worker built";
|
|
18
19
|
const MAX_WORKER_BUNDLE_SIZE = 10;
|
|
@@ -26,21 +27,27 @@ class Build extends Command {
|
|
|
26
27
|
allowNo: true,
|
|
27
28
|
default: true
|
|
28
29
|
}),
|
|
29
|
-
|
|
30
|
+
"bundle-stats": Flags.boolean({
|
|
30
31
|
description: "Show a bundle size summary after building.",
|
|
31
32
|
default: true,
|
|
32
33
|
allowNo: true
|
|
33
34
|
}),
|
|
35
|
+
"lockfile-check": Flags.boolean({
|
|
36
|
+
description: "Checks that there is exactly 1 valid lockfile in the project.",
|
|
37
|
+
env: "SHOPIFY_HYDROGEN_FLAG_LOCKFILE_CHECK",
|
|
38
|
+
default: true,
|
|
39
|
+
allowNo: true
|
|
40
|
+
}),
|
|
34
41
|
"disable-route-warning": Flags.boolean({
|
|
35
42
|
description: "Disable warning about missing standard routes.",
|
|
36
43
|
env: "SHOPIFY_HYDROGEN_FLAG_DISABLE_ROUTE_WARNING"
|
|
37
44
|
}),
|
|
38
|
-
|
|
45
|
+
"codegen-unstable": Flags.boolean({
|
|
39
46
|
description: "Generate types for the Storefront API queries found in your project.",
|
|
40
47
|
required: false,
|
|
41
48
|
default: false
|
|
42
49
|
}),
|
|
43
|
-
|
|
50
|
+
"codegen-config-path": commonFlags.codegenConfigPath,
|
|
44
51
|
base: deprecated("--base")(),
|
|
45
52
|
entry: deprecated("--entry")(),
|
|
46
53
|
target: deprecated("--target")()
|
|
@@ -62,6 +69,7 @@ async function runBuild({
|
|
|
62
69
|
sourcemap = false,
|
|
63
70
|
disableRouteWarning = false,
|
|
64
71
|
bundleStats = true,
|
|
72
|
+
lockfileCheck = true,
|
|
65
73
|
assetPath
|
|
66
74
|
}) {
|
|
67
75
|
if (!process.env.NODE_ENV) {
|
|
@@ -71,7 +79,10 @@ async function runBuild({
|
|
|
71
79
|
process.env.HYDROGEN_ASSET_BASE_URL = assetPath;
|
|
72
80
|
}
|
|
73
81
|
const { root, buildPath, buildPathClient, buildPathWorkerFile, publicPath } = getProjectPaths(directory);
|
|
74
|
-
|
|
82
|
+
if (lockfileCheck) {
|
|
83
|
+
await checkLockfileStatus(root, isCI());
|
|
84
|
+
}
|
|
85
|
+
await muteRemixLogs();
|
|
75
86
|
console.time(LOG_WORKER_BUILT);
|
|
76
87
|
outputInfo(`
|
|
77
88
|
\u{1F3D7}\uFE0F Building in ${process.env.NODE_ENV} mode...`);
|
|
@@ -135,10 +146,25 @@ This build is missing ${missingRoutes.length} route${missingRoutes.length > 1 ?
|
|
|
135
146
|
);
|
|
136
147
|
}
|
|
137
148
|
}
|
|
149
|
+
if (process.env.NODE_ENV !== "development") {
|
|
150
|
+
await cleanClientSourcemaps(buildPathClient);
|
|
151
|
+
}
|
|
138
152
|
if (!process.env.SHOPIFY_UNIT_TEST && !assetPath) {
|
|
139
153
|
process.exit(0);
|
|
140
154
|
}
|
|
141
155
|
}
|
|
156
|
+
async function cleanClientSourcemaps(buildPathClient) {
|
|
157
|
+
const bundleFiles = await glob(joinPath(buildPathClient, "**/*.js"));
|
|
158
|
+
await Promise.all(
|
|
159
|
+
bundleFiles.map(async (filePath) => {
|
|
160
|
+
const file = await readFile(filePath);
|
|
161
|
+
return await writeFile(
|
|
162
|
+
filePath,
|
|
163
|
+
file.replace(/\/\/# sourceMappingURL=.+\.js\.map$/gm, "")
|
|
164
|
+
);
|
|
165
|
+
})
|
|
166
|
+
);
|
|
167
|
+
}
|
|
142
168
|
async function writeBundleAnalysis(buildPath, root, buildPathWorkerFile, sizeMB, bundleStats, remixConfig) {
|
|
143
169
|
const bundleAnalysisPath = await buildBundleAnalysis(buildPath);
|
|
144
170
|
outputInfo(
|
|
@@ -3,11 +3,13 @@ import Command from '@shopify/cli-kit/node/base-command';
|
|
|
3
3
|
import colors from '@shopify/cli-kit/node/colors';
|
|
4
4
|
import { outputWarn, outputInfo, outputContent } from '@shopify/cli-kit/node/output';
|
|
5
5
|
import { AbortError } from '@shopify/cli-kit/node/error';
|
|
6
|
+
import { getLatestGitCommit } from '@shopify/cli-kit/node/git';
|
|
6
7
|
import { resolvePath } from '@shopify/cli-kit/node/path';
|
|
7
|
-
import { renderFatalError, renderSuccess, renderTasks } from '@shopify/cli-kit/node/ui';
|
|
8
|
+
import { renderFatalError, renderSelectPrompt, renderSuccess, renderTasks } from '@shopify/cli-kit/node/ui';
|
|
9
|
+
import { ciPlatform } from '@shopify/cli-kit/node/context/local';
|
|
8
10
|
import { parseToken, createDeploy } from '@shopify/oxygen-cli/deploy';
|
|
9
|
-
import { commonFlags } from '../../lib/flags.js';
|
|
10
|
-
import {
|
|
11
|
+
import { commonFlags, flagsToCamelObject } from '../../lib/flags.js';
|
|
12
|
+
import { getOxygenDeploymentData } from '../../lib/get-oxygen-deployment-data.js';
|
|
11
13
|
import { runBuild } from './build.js';
|
|
12
14
|
|
|
13
15
|
const deploymentLogger = (message, level = "info") => {
|
|
@@ -17,25 +19,36 @@ const deploymentLogger = (message, level = "info") => {
|
|
|
17
19
|
};
|
|
18
20
|
class Deploy extends Command {
|
|
19
21
|
static flags = {
|
|
22
|
+
"env-branch": Flags.string({
|
|
23
|
+
char: "e",
|
|
24
|
+
description: "Environment branch (tag) for environment to deploy to",
|
|
25
|
+
required: false
|
|
26
|
+
}),
|
|
20
27
|
path: commonFlags.path,
|
|
21
28
|
shop: commonFlags.shop,
|
|
22
|
-
|
|
29
|
+
"public-deployment": Flags.boolean({
|
|
23
30
|
env: "SHOPIFY_HYDROGEN_FLAG_PUBLIC_DEPLOYMENT",
|
|
24
31
|
description: "Marks a preview deployment as publicly accessible.",
|
|
25
32
|
required: false,
|
|
26
33
|
default: false
|
|
27
34
|
}),
|
|
28
|
-
|
|
35
|
+
token: Flags.string({
|
|
36
|
+
char: "t",
|
|
37
|
+
description: "Oxygen deployment token",
|
|
38
|
+
env: "SHOPIFY_HYDROGEN_DEPLOYMENT_TOKEN",
|
|
39
|
+
required: false
|
|
40
|
+
}),
|
|
41
|
+
"metadata-url": Flags.string({
|
|
29
42
|
description: "URL that links to the deployment. Will be saved and displayed in the Shopify admin",
|
|
30
43
|
required: false,
|
|
31
44
|
env: "SHOPIFY_HYDROGEN_FLAG_METADATA_URL"
|
|
32
45
|
}),
|
|
33
|
-
|
|
46
|
+
"metadata-user": Flags.string({
|
|
34
47
|
description: "User that initiated the deployment. Will be saved and displayed in the Shopify admin",
|
|
35
48
|
required: false,
|
|
36
49
|
env: "SHOPIFY_HYDROGEN_FLAG_METADATA_USER"
|
|
37
50
|
}),
|
|
38
|
-
|
|
51
|
+
"metadata-version": Flags.string({
|
|
39
52
|
description: "A version identifier for the deployment. Will be saved and displayed in the Shopify admin",
|
|
40
53
|
required: false,
|
|
41
54
|
env: "SHOPIFY_HYDROGEN_FLAG_METADATA_VERSION"
|
|
@@ -44,24 +57,26 @@ class Deploy extends Command {
|
|
|
44
57
|
static hidden = true;
|
|
45
58
|
async run() {
|
|
46
59
|
const { flags } = await this.parse(Deploy);
|
|
47
|
-
const
|
|
48
|
-
await oxygenDeploy({
|
|
49
|
-
path: actualPath,
|
|
50
|
-
shop: flags.shop,
|
|
51
|
-
publicDeployment: flags.publicDeployment,
|
|
52
|
-
metadataUrl: flags.metadataUrl,
|
|
53
|
-
metadataUser: flags.metadataUser,
|
|
54
|
-
metadataVersion: flags.metadataVersion
|
|
55
|
-
}).catch((error) => {
|
|
60
|
+
const deploymentOptions = this.flagsToOxygenDeploymentOptions(flags);
|
|
61
|
+
await oxygenDeploy(deploymentOptions).catch((error) => {
|
|
56
62
|
renderFatalError(error);
|
|
57
63
|
process.exit(1);
|
|
58
64
|
}).finally(() => {
|
|
59
65
|
process.exit(0);
|
|
60
66
|
});
|
|
61
67
|
}
|
|
68
|
+
flagsToOxygenDeploymentOptions(flags) {
|
|
69
|
+
const camelFlags = flagsToCamelObject(flags);
|
|
70
|
+
return {
|
|
71
|
+
...camelFlags,
|
|
72
|
+
environmentTag: flags["env-branch"],
|
|
73
|
+
path: flags.path ? resolvePath(flags.path) : process.cwd()
|
|
74
|
+
};
|
|
75
|
+
}
|
|
62
76
|
}
|
|
63
77
|
async function oxygenDeploy(options) {
|
|
64
78
|
const {
|
|
79
|
+
environmentTag,
|
|
65
80
|
path,
|
|
66
81
|
shop,
|
|
67
82
|
publicDeployment,
|
|
@@ -69,25 +84,69 @@ async function oxygenDeploy(options) {
|
|
|
69
84
|
metadataUser,
|
|
70
85
|
metadataVersion
|
|
71
86
|
} = options;
|
|
72
|
-
const
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
87
|
+
const ci = ciPlatform();
|
|
88
|
+
let token = options.token;
|
|
89
|
+
let branch;
|
|
90
|
+
let deploymentData;
|
|
91
|
+
let deploymentEnvironmentTag = void 0;
|
|
92
|
+
let gitCommit;
|
|
93
|
+
try {
|
|
94
|
+
gitCommit = await getLatestGitCommit(path);
|
|
95
|
+
branch = (/HEAD -> ([^,]*)/.exec(gitCommit.refs) || [])[1];
|
|
96
|
+
} catch (error) {
|
|
97
|
+
outputWarn("Could not retrieve Git history.");
|
|
98
|
+
branch = void 0;
|
|
99
|
+
}
|
|
100
|
+
if (!ci.isCI) {
|
|
101
|
+
deploymentData = await getOxygenDeploymentData({
|
|
102
|
+
root: path,
|
|
103
|
+
flagShop: shop
|
|
104
|
+
});
|
|
105
|
+
if (!deploymentData) {
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
token = token || deploymentData.oxygenDeploymentToken;
|
|
109
|
+
}
|
|
76
110
|
if (!token) {
|
|
77
|
-
|
|
111
|
+
const errMessage = ci.isCI ? [
|
|
112
|
+
"No deployment token provided. Use the ",
|
|
113
|
+
{ command: "--token" },
|
|
114
|
+
" flag to provide a token."
|
|
115
|
+
] : `Could not obtain an Oxygen deployment token, please try again or contact Shopify support.`;
|
|
116
|
+
throw new AbortError(errMessage);
|
|
117
|
+
}
|
|
118
|
+
if (!ci.isCI && !environmentTag && deploymentData?.environments) {
|
|
119
|
+
if (deploymentData.environments.length > 1) {
|
|
120
|
+
const choices = [
|
|
121
|
+
...deploymentData.environments.map(({ name, branch: branch2 }) => ({
|
|
122
|
+
label: name,
|
|
123
|
+
value: branch2
|
|
124
|
+
}))
|
|
125
|
+
];
|
|
126
|
+
deploymentEnvironmentTag = await renderSelectPrompt({
|
|
127
|
+
message: "Select an environment to deploy to",
|
|
128
|
+
choices,
|
|
129
|
+
defaultValue: branch
|
|
130
|
+
});
|
|
131
|
+
} else {
|
|
132
|
+
outputInfo(
|
|
133
|
+
`Using current checked out branch ${branch} as environment tag`
|
|
134
|
+
);
|
|
135
|
+
}
|
|
78
136
|
}
|
|
79
137
|
const config = {
|
|
80
138
|
assetsDir: "dist/client",
|
|
81
139
|
deploymentUrl: "https://oxygen.shopifyapps.com",
|
|
82
140
|
deploymentToken: parseToken(token),
|
|
83
|
-
|
|
141
|
+
environmentTag: environmentTag || deploymentEnvironmentTag || branch,
|
|
142
|
+
verificationMaxDuration: 180,
|
|
84
143
|
metadata: {
|
|
85
144
|
...metadataUrl ? { url: metadataUrl } : {},
|
|
86
145
|
...metadataUser ? { user: metadataUser } : {},
|
|
87
146
|
...metadataVersion ? { version: metadataVersion } : {}
|
|
88
147
|
},
|
|
89
148
|
publicDeployment,
|
|
90
|
-
|
|
149
|
+
skipVerification: false,
|
|
91
150
|
rootPath: path,
|
|
92
151
|
skipBuild: false,
|
|
93
152
|
workerOnly: false,
|
|
@@ -120,10 +179,10 @@ async function oxygenDeploy(options) {
|
|
|
120
179
|
useCodegen: false
|
|
121
180
|
});
|
|
122
181
|
},
|
|
123
|
-
|
|
182
|
+
onVerificationComplete: () => resolveHealthCheck(),
|
|
124
183
|
onUploadFilesStart: () => uploadStart(),
|
|
125
184
|
onUploadFilesComplete: () => resolveUpload(),
|
|
126
|
-
|
|
185
|
+
onVerificationError: (error) => {
|
|
127
186
|
deployError = new AbortError(
|
|
128
187
|
error.message,
|
|
129
188
|
"Please verify the deployment status in the Shopify Admin and retry deploying if necessary."
|
|
@@ -146,7 +205,7 @@ async function oxygenDeploy(options) {
|
|
|
146
205
|
task: async () => await uploadPromise
|
|
147
206
|
},
|
|
148
207
|
{
|
|
149
|
-
title: "
|
|
208
|
+
title: "Verifying deployment",
|
|
150
209
|
task: async () => await healthCheckPromise
|
|
151
210
|
}
|
|
152
211
|
]);
|
|
@@ -1,13 +1,14 @@
|
|
|
1
|
-
import { vi, describe, beforeEach, afterEach, it
|
|
1
|
+
import { vi, describe, expect, beforeEach, afterEach, it } from 'vitest';
|
|
2
2
|
import { login } from '../../lib/auth.js';
|
|
3
3
|
import { getStorefronts } from '../../lib/graphql/admin/link-storefront.js';
|
|
4
4
|
import { AbortError } from '@shopify/cli-kit/node/error';
|
|
5
5
|
import { renderSelectPrompt, renderSuccess, renderFatalError } from '@shopify/cli-kit/node/ui';
|
|
6
|
+
import { getLatestGitCommit } from '@shopify/cli-kit/node/git';
|
|
6
7
|
import { oxygenDeploy, deploymentLogger } from './deploy.js';
|
|
7
|
-
import {
|
|
8
|
+
import { getOxygenDeploymentData } from '../../lib/get-oxygen-deployment-data.js';
|
|
8
9
|
import { createDeploy, parseToken } from '@shopify/oxygen-cli/deploy';
|
|
9
10
|
|
|
10
|
-
vi.mock("../../lib/get-oxygen-
|
|
11
|
+
vi.mock("../../lib/get-oxygen-deployment-data.js");
|
|
11
12
|
vi.mock("@shopify/oxygen-cli/deploy");
|
|
12
13
|
vi.mock("../../lib/auth.js");
|
|
13
14
|
vi.mock("../../lib/shopify-config.js");
|
|
@@ -19,6 +20,8 @@ vi.mock("@shopify/cli-kit/node/output", async () => {
|
|
|
19
20
|
return {
|
|
20
21
|
outputContent: () => ({ value: "" }),
|
|
21
22
|
outputInfo: () => {
|
|
23
|
+
},
|
|
24
|
+
outputWarn: () => {
|
|
22
25
|
}
|
|
23
26
|
};
|
|
24
27
|
});
|
|
@@ -30,6 +33,16 @@ vi.mock("@shopify/cli-kit/node/ui", async () => {
|
|
|
30
33
|
renderTasks: vi.fn()
|
|
31
34
|
};
|
|
32
35
|
});
|
|
36
|
+
vi.mock("@shopify/cli-kit/node/git", async () => {
|
|
37
|
+
return {
|
|
38
|
+
getLatestGitCommit: vi.fn()
|
|
39
|
+
};
|
|
40
|
+
});
|
|
41
|
+
vi.mock("@shopify/cli-kit/node/context/local", async () => {
|
|
42
|
+
return {
|
|
43
|
+
ciPlatform: () => ({ isCI: false })
|
|
44
|
+
};
|
|
45
|
+
});
|
|
33
46
|
describe("deploy", () => {
|
|
34
47
|
const ADMIN_SESSION = {
|
|
35
48
|
token: "abc123",
|
|
@@ -66,6 +79,31 @@ describe("deploy", () => {
|
|
|
66
79
|
namespace: "some-namespace",
|
|
67
80
|
namespaceId: "1"
|
|
68
81
|
};
|
|
82
|
+
const expectedConfig = {
|
|
83
|
+
assetsDir: "dist/client",
|
|
84
|
+
deploymentUrl: "https://oxygen.shopifyapps.com",
|
|
85
|
+
deploymentToken: mockToken,
|
|
86
|
+
verificationMaxDuration: 180,
|
|
87
|
+
metadata: {
|
|
88
|
+
url: deployParams.metadataUrl,
|
|
89
|
+
user: deployParams.metadataUser,
|
|
90
|
+
version: deployParams.metadataVersion
|
|
91
|
+
},
|
|
92
|
+
publicDeployment: deployParams.publicDeployment,
|
|
93
|
+
skipVerification: false,
|
|
94
|
+
rootPath: deployParams.path,
|
|
95
|
+
skipBuild: false,
|
|
96
|
+
workerOnly: false,
|
|
97
|
+
workerDir: "dist/worker"
|
|
98
|
+
};
|
|
99
|
+
const expectedHooks = {
|
|
100
|
+
buildFunction: expect.any(Function),
|
|
101
|
+
onVerificationComplete: expect.any(Function),
|
|
102
|
+
onUploadFilesStart: expect.any(Function),
|
|
103
|
+
onUploadFilesComplete: expect.any(Function),
|
|
104
|
+
onVerificationError: expect.any(Function),
|
|
105
|
+
onUploadFilesError: expect.any(Function)
|
|
106
|
+
};
|
|
69
107
|
beforeEach(async () => {
|
|
70
108
|
process.exit = vi.fn();
|
|
71
109
|
vi.mocked(login).mockResolvedValue({
|
|
@@ -83,54 +121,68 @@ describe("deploy", () => {
|
|
|
83
121
|
vi.mocked(createDeploy).mockResolvedValue(
|
|
84
122
|
"https://a-lovely-deployment.com"
|
|
85
123
|
);
|
|
86
|
-
vi.mocked(
|
|
124
|
+
vi.mocked(getOxygenDeploymentData).mockResolvedValue({
|
|
125
|
+
oxygenDeploymentToken: "some-encoded-token",
|
|
126
|
+
environments: []
|
|
127
|
+
});
|
|
87
128
|
vi.mocked(parseToken).mockReturnValue(mockToken);
|
|
88
129
|
});
|
|
89
130
|
afterEach(() => {
|
|
90
131
|
vi.resetAllMocks();
|
|
91
132
|
process.exit = originalExit;
|
|
92
133
|
});
|
|
93
|
-
it("calls
|
|
134
|
+
it("calls getOxygenDeploymentData with the correct parameters", async () => {
|
|
94
135
|
await oxygenDeploy(deployParams);
|
|
95
|
-
expect(
|
|
136
|
+
expect(getOxygenDeploymentData).toHaveBeenCalledWith({
|
|
96
137
|
root: "./",
|
|
97
138
|
flagShop: "snowdevil.myshopify.com"
|
|
98
139
|
});
|
|
99
|
-
expect(
|
|
140
|
+
expect(getOxygenDeploymentData).toHaveBeenCalledTimes(1);
|
|
100
141
|
});
|
|
101
142
|
it("calls createDeploy with the correct parameters", async () => {
|
|
102
143
|
await oxygenDeploy(deployParams);
|
|
103
|
-
const expectedConfig = {
|
|
104
|
-
assetsDir: "dist/client",
|
|
105
|
-
deploymentUrl: "https://oxygen.shopifyapps.com",
|
|
106
|
-
deploymentToken: mockToken,
|
|
107
|
-
healthCheckMaxDuration: 180,
|
|
108
|
-
metadata: {
|
|
109
|
-
url: deployParams.metadataUrl,
|
|
110
|
-
user: deployParams.metadataUser,
|
|
111
|
-
version: deployParams.metadataVersion
|
|
112
|
-
},
|
|
113
|
-
publicDeployment: deployParams.publicDeployment,
|
|
114
|
-
skipHealthCheck: false,
|
|
115
|
-
rootPath: deployParams.path,
|
|
116
|
-
skipBuild: false,
|
|
117
|
-
workerOnly: false,
|
|
118
|
-
workerDir: "dist/worker"
|
|
119
|
-
};
|
|
120
144
|
expect(vi.mocked(createDeploy)).toHaveBeenCalledWith({
|
|
121
145
|
config: expectedConfig,
|
|
122
|
-
hooks:
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
146
|
+
hooks: expectedHooks,
|
|
147
|
+
logger: deploymentLogger
|
|
148
|
+
});
|
|
149
|
+
expect(vi.mocked(renderSuccess)).toHaveBeenCalled;
|
|
150
|
+
});
|
|
151
|
+
it("calls createDeploy with the checked out branch name", async () => {
|
|
152
|
+
vi.mocked(getLatestGitCommit).mockResolvedValue({
|
|
153
|
+
hash: "123",
|
|
154
|
+
message: "test commit",
|
|
155
|
+
date: "2021-01-01",
|
|
156
|
+
author_name: "test author",
|
|
157
|
+
author_email: "test@author.com",
|
|
158
|
+
body: "test body",
|
|
159
|
+
refs: "HEAD -> main"
|
|
160
|
+
});
|
|
161
|
+
await oxygenDeploy(deployParams);
|
|
162
|
+
expect(vi.mocked(createDeploy)).toHaveBeenCalledWith({
|
|
163
|
+
config: { ...expectedConfig, environmentTag: "main" },
|
|
164
|
+
hooks: expectedHooks,
|
|
130
165
|
logger: deploymentLogger
|
|
131
166
|
});
|
|
132
167
|
expect(vi.mocked(renderSuccess)).toHaveBeenCalled;
|
|
133
168
|
});
|
|
169
|
+
it("calls renderSelectPrompt when there are multiple environments", async () => {
|
|
170
|
+
vi.mocked(getOxygenDeploymentData).mockResolvedValue({
|
|
171
|
+
oxygenDeploymentToken: "some-encoded-token",
|
|
172
|
+
environments: [
|
|
173
|
+
{ name: "production", branch: "main" },
|
|
174
|
+
{ name: "preview", branch: "staging" }
|
|
175
|
+
]
|
|
176
|
+
});
|
|
177
|
+
await oxygenDeploy(deployParams);
|
|
178
|
+
expect(vi.mocked(renderSelectPrompt)).toHaveBeenCalledWith({
|
|
179
|
+
message: "Select an environment to deploy to",
|
|
180
|
+
choices: [
|
|
181
|
+
{ label: "production", value: "main" },
|
|
182
|
+
{ label: "preview", value: "staging" }
|
|
183
|
+
]
|
|
184
|
+
});
|
|
185
|
+
});
|
|
134
186
|
it("handles error during uploadFiles", async () => {
|
|
135
187
|
const mockRenderFatalError = vi.fn();
|
|
136
188
|
vi.mocked(renderFatalError).mockImplementation(mockRenderFatalError);
|
|
@@ -156,14 +208,14 @@ describe("deploy", () => {
|
|
|
156
208
|
}
|
|
157
209
|
}
|
|
158
210
|
});
|
|
159
|
-
it("handles error during
|
|
211
|
+
it("handles error during deployment verification", async () => {
|
|
160
212
|
const mockRenderFatalError = vi.fn();
|
|
161
213
|
vi.mocked(renderFatalError).mockImplementation(mockRenderFatalError);
|
|
162
214
|
const error = new Error("Cloudflare is down!");
|
|
163
215
|
vi.mocked(createDeploy).mockImplementation((options) => {
|
|
164
216
|
options.hooks?.onUploadFilesStart?.();
|
|
165
217
|
options.hooks?.onUploadFilesComplete?.();
|
|
166
|
-
options.hooks?.
|
|
218
|
+
options.hooks?.onVerificationError?.(error);
|
|
167
219
|
return new Promise((_resolve, reject) => {
|
|
168
220
|
reject(error);
|
|
169
221
|
});
|
|
@@ -18,6 +18,7 @@ import { getAllEnvironmentVariables } from '../../lib/environment-variables.js';
|
|
|
18
18
|
import { getConfig } from '../../lib/shopify-config.js';
|
|
19
19
|
import { setupLiveReload } from '../../lib/live-reload.js';
|
|
20
20
|
import { checkRemixVersions } from '../../lib/remix-version-check.js';
|
|
21
|
+
import { getGraphiQLUrl } from '../../lib/graphiql-url.js';
|
|
21
22
|
|
|
22
23
|
const LOG_REBUILDING = "\u{1F9F1} Rebuilding...";
|
|
23
24
|
const LOG_REBUILT = "\u{1F680} Rebuilt";
|
|
@@ -121,16 +122,19 @@ async function runDev({
|
|
|
121
122
|
},
|
|
122
123
|
workerRuntime
|
|
123
124
|
);
|
|
124
|
-
const graphiqlUrl = `${miniOxygen.listeningAt}/graphiql`;
|
|
125
125
|
const debugNetworkUrl = `${miniOxygen.listeningAt}/debug-network`;
|
|
126
|
-
enhanceH2Logs({
|
|
126
|
+
enhanceH2Logs({ host: miniOxygen.listeningAt, ...remixConfig });
|
|
127
127
|
miniOxygen.showBanner({
|
|
128
128
|
appName: storefront ? colors.cyan(storefront?.title) : void 0,
|
|
129
129
|
headlinePrefix: initialBuildDurationMs > 0 ? `Initial build: ${initialBuildDurationMs}ms
|
|
130
130
|
` : "",
|
|
131
131
|
extraLines: [
|
|
132
|
-
colors.dim(
|
|
133
|
-
|
|
132
|
+
colors.dim(
|
|
133
|
+
`
|
|
134
|
+
View GraphiQL API browser: ${getGraphiQLUrl({
|
|
135
|
+
host: miniOxygen.listeningAt
|
|
136
|
+
})}`
|
|
137
|
+
),
|
|
134
138
|
workerRuntime ? "" : colors.dim(
|
|
135
139
|
`
|
|
136
140
|
View server-side network requests: ${debugNetworkUrl}`
|
|
@@ -450,13 +450,11 @@ function usePredictiveSearch(): UseSearchReturn {
|
|
|
450
450
|
|
|
451
451
|
/**
|
|
452
452
|
* Converts a plural search type to a singular search type
|
|
453
|
-
* @param type - The plural search type
|
|
454
|
-
* @returns The singular search type
|
|
455
453
|
*
|
|
456
454
|
* @example
|
|
457
|
-
* ```
|
|
458
|
-
* pluralToSingularSearchType('articles') // => 'ARTICLE'
|
|
459
|
-
* pluralToSingularSearchType(['articles', 'products']) // => 'ARTICLE,PRODUCT'
|
|
455
|
+
* ```js
|
|
456
|
+
* pluralToSingularSearchType('articles'); // => 'ARTICLE'
|
|
457
|
+
* pluralToSingularSearchType(['articles', 'products']); // => 'ARTICLE,PRODUCT'
|
|
460
458
|
* ```
|
|
461
459
|
*/
|
|
462
460
|
function pluralToSingularSearchType(
|
|
@@ -189,14 +189,13 @@ export function CatchBoundary() {
|
|
|
189
189
|
* @see https://shopify.dev/docs/api/storefront/latest/objects/CustomerAccessToken
|
|
190
190
|
*
|
|
191
191
|
* @example
|
|
192
|
-
* ```
|
|
193
|
-
* //
|
|
192
|
+
* ```js
|
|
194
193
|
* const {isLoggedIn, headers} = await validateCustomerAccessToken(
|
|
195
194
|
* customerAccessToken,
|
|
196
195
|
* session,
|
|
197
|
-
*
|
|
198
|
-
*
|
|
199
|
-
|
|
196
|
+
* );
|
|
197
|
+
* ```
|
|
198
|
+
*/
|
|
200
199
|
async function validateCustomerAccessToken(
|
|
201
200
|
session: HydrogenSession,
|
|
202
201
|
customerAccessToken?: CustomerAccessToken,
|
|
@@ -108,8 +108,6 @@ async function fetchPredictiveSearchResults({
|
|
|
108
108
|
|
|
109
109
|
/**
|
|
110
110
|
* Normalize results and apply tracking qurery parameters to each result url
|
|
111
|
-
* @param predictiveSearch
|
|
112
|
-
* @param locale
|
|
113
111
|
*/
|
|
114
112
|
export function normalizePredictiveSearchResults(
|
|
115
113
|
predictiveSearch: PredictiveSearchQuery['predictiveSearch'],
|
|
@@ -3,21 +3,20 @@ import {redirect, type LoaderArgs} from '@shopify/remix-oxygen';
|
|
|
3
3
|
/**
|
|
4
4
|
* Automatically creates a new cart based on the URL and redirects straight to checkout.
|
|
5
5
|
* Expected URL structure:
|
|
6
|
-
* ```
|
|
6
|
+
* ```js
|
|
7
7
|
* /cart/<variant_id>:<quantity>
|
|
8
8
|
*
|
|
9
9
|
* ```
|
|
10
|
+
*
|
|
10
11
|
* More than one `<variant_id>:<quantity>` separated by a comma, can be supplied in the URL, for
|
|
11
12
|
* carts with more than one product variant.
|
|
12
13
|
*
|
|
13
|
-
* @param `?discount` an optional discount code to apply to the cart
|
|
14
14
|
* @example
|
|
15
|
-
* Example path creating a cart with two product variants, different quantities, and a discount code:
|
|
16
|
-
* ```
|
|
15
|
+
* Example path creating a cart with two product variants, different quantities, and a discount code in the querystring:
|
|
16
|
+
* ```js
|
|
17
17
|
* /cart/41007289663544:1,41007289696312:2?discount=HYDROBOARD
|
|
18
18
|
*
|
|
19
19
|
* ```
|
|
20
|
-
* @preserve
|
|
21
20
|
*/
|
|
22
21
|
export async function loader({request, context, params}: LoaderArgs) {
|
|
23
22
|
const {cart} = context;
|
|
@@ -3,14 +3,13 @@ import {redirect, type LoaderArgs} from '@shopify/remix-oxygen';
|
|
|
3
3
|
/**
|
|
4
4
|
* Automatically applies a discount found on the url
|
|
5
5
|
* If a cart exists it's updated with the discount, otherwise a cart is created with the discount already applied
|
|
6
|
-
*
|
|
6
|
+
*
|
|
7
7
|
* @example
|
|
8
|
-
* Example path applying a discount and redirecting
|
|
9
|
-
* ```
|
|
8
|
+
* Example path applying a discount and optional redirecting (defaults to the home page)
|
|
9
|
+
* ```js
|
|
10
10
|
* /discount/FREESHIPPING?redirect=/products
|
|
11
11
|
*
|
|
12
12
|
* ```
|
|
13
|
-
* @preserve
|
|
14
13
|
*/
|
|
15
14
|
export async function loader({request, context, params}: LoaderArgs) {
|
|
16
15
|
const {cart} = context;
|
|
@@ -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.4.1",
|
|
19
|
+
"@shopify/hydrogen": "^2023.7.9",
|
|
20
|
+
"@shopify/remix-oxygen": "^1.1.5",
|
|
21
21
|
"graphql": "^16.6.0",
|
|
22
22
|
"graphql-tag": "^2.12.6",
|
|
23
23
|
"isbot": "^3.6.6",
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
"@shopify/prettier-config": "^1.1.2",
|
|
31
31
|
"@total-typescript/ts-reset": "^0.4.2",
|
|
32
32
|
"@types/eslint": "^8.4.10",
|
|
33
|
-
"@types/react": "^18.2.
|
|
33
|
+
"@types/react": "^18.2.22",
|
|
34
34
|
"@types/react-dom": "^18.2.7",
|
|
35
35
|
"eslint": "^8.20.0",
|
|
36
36
|
"eslint-plugin-hydrogen": "0.12.2",
|