@shopify/cli-hydrogen 7.0.0 → 7.1.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 +3 -8
- package/dist/commands/hydrogen/deploy.js +79 -32
- package/dist/commands/hydrogen/deploy.test.js +162 -5
- package/dist/commands/hydrogen/generate/route.js +6 -1
- package/dist/commands/hydrogen/init.test.js +2 -1
- package/dist/commands/hydrogen/setup.js +17 -19
- package/dist/generator-templates/starter/CHANGELOG.md +45 -0
- package/dist/generator-templates/starter/README.md +25 -2
- package/dist/generator-templates/starter/app/components/Cart.tsx +2 -2
- package/dist/generator-templates/starter/app/components/Layout.tsx +9 -1
- package/dist/generator-templates/starter/app/components/Search.tsx +44 -15
- package/dist/generator-templates/starter/app/lib/search.ts +29 -0
- package/dist/generator-templates/starter/app/root.tsx +0 -2
- package/dist/generator-templates/starter/app/routes/account.orders._index.tsx +2 -2
- package/dist/generator-templates/starter/app/routes/api.predictive-search.tsx +1 -21
- package/dist/generator-templates/starter/app/routes/cart.tsx +1 -5
- package/dist/generator-templates/starter/app/routes/search.tsx +8 -2
- package/dist/generator-templates/starter/app/styles/app.css +10 -15
- package/dist/generator-templates/starter/package.json +7 -7
- package/dist/generator-templates/starter/remix.config.js +1 -0
- package/dist/generator-templates/starter/remix.env.d.ts +6 -2
- package/dist/generator-templates/starter/server.ts +1 -0
- package/dist/hooks/init.js +3 -3
- package/dist/lib/codegen.js +1 -8
- package/dist/lib/file.js +4 -1
- package/dist/lib/flags.js +6 -0
- package/dist/lib/graphiql-url.js +3 -0
- package/dist/lib/mini-oxygen/assets.js +17 -1
- package/dist/lib/onboarding/common.js +4 -3
- package/dist/lib/onboarding/local.js +7 -7
- package/dist/lib/onboarding/remote.js +2 -1
- package/dist/lib/setups/i18n/replacers.test.js +3 -2
- package/dist/lib/setups/routes/generate.js +58 -10
- package/dist/lib/setups/routes/templates/locale-check.js +9 -0
- package/dist/lib/setups/routes/templates/locale-check.ts +16 -0
- package/dist/lib/shell.js +1 -1
- package/dist/lib/template-diff.js +13 -3
- package/dist/virtual-routes/components/RequestDetails.jsx +2 -2
- package/oclif.manifest.json +47 -8
- package/package.json +5 -5
|
@@ -22,22 +22,17 @@ class Build extends Command {
|
|
|
22
22
|
static flags = {
|
|
23
23
|
path: commonFlags.path,
|
|
24
24
|
sourcemap: Flags.boolean({
|
|
25
|
-
description: "
|
|
25
|
+
description: "Controls whether sourcemaps are generated. Default to true, use `--no-sourcemaps` to disable.",
|
|
26
26
|
env: "SHOPIFY_HYDROGEN_FLAG_SOURCEMAP",
|
|
27
27
|
allowNo: true,
|
|
28
28
|
default: true
|
|
29
29
|
}),
|
|
30
30
|
"bundle-stats": Flags.boolean({
|
|
31
|
-
description: "Show a bundle size summary after building.",
|
|
32
|
-
default: true,
|
|
33
|
-
allowNo: true
|
|
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",
|
|
31
|
+
description: "Show a bundle size summary after building. Defaults to true, use `--no-bundle-stats` to disable.",
|
|
38
32
|
default: true,
|
|
39
33
|
allowNo: true
|
|
40
34
|
}),
|
|
35
|
+
"lockfile-check": commonFlags.lockfileCheck,
|
|
41
36
|
"disable-route-warning": Flags.boolean({
|
|
42
37
|
description: "Disable warning about missing standard routes.",
|
|
43
38
|
env: "SHOPIFY_HYDROGEN_FLAG_DISABLE_ROUTE_WARNING"
|
|
@@ -9,10 +9,12 @@ import { resolvePath } from '@shopify/cli-kit/node/path';
|
|
|
9
9
|
import { renderFatalError, renderWarning, renderSelectPrompt, renderSuccess, renderTasks } from '@shopify/cli-kit/node/ui';
|
|
10
10
|
import { ciPlatform } from '@shopify/cli-kit/node/context/local';
|
|
11
11
|
import { parseToken, createDeploy } from '@shopify/oxygen-cli/deploy';
|
|
12
|
+
import { loadEnvironmentVariableFile } from '@shopify/oxygen-cli/utils';
|
|
12
13
|
import { commonFlags, flagsToCamelObject } from '../../lib/flags.js';
|
|
13
14
|
import { getOxygenDeploymentData } from '../../lib/get-oxygen-deployment-data.js';
|
|
14
15
|
import { runBuild } from './build.js';
|
|
15
16
|
|
|
17
|
+
const DEPLOY_OUTPUT_FILE_HANDLE = "h2_deploy_log.json";
|
|
16
18
|
const deploymentLogger = (message, level = "info") => {
|
|
17
19
|
if (level === "error" || level === "warn") {
|
|
18
20
|
outputWarn(message);
|
|
@@ -25,6 +27,10 @@ class Deploy extends Command {
|
|
|
25
27
|
description: "Environment branch (tag) for environment to deploy to.",
|
|
26
28
|
required: false
|
|
27
29
|
}),
|
|
30
|
+
"env-file": Flags.string({
|
|
31
|
+
description: "Path to an environment file to override existing environment variables for the deployment.",
|
|
32
|
+
required: false
|
|
33
|
+
}),
|
|
28
34
|
preview: Flags.boolean({
|
|
29
35
|
description: "Deploys to the Preview environment. Overrides --env-branch and Git metadata.",
|
|
30
36
|
required: false,
|
|
@@ -37,17 +43,28 @@ class Deploy extends Command {
|
|
|
37
43
|
env: "SHOPIFY_HYDROGEN_FLAG_FORCE",
|
|
38
44
|
required: false
|
|
39
45
|
}),
|
|
46
|
+
"no-verify": Flags.boolean({
|
|
47
|
+
description: "Skip the routability verification step after deployment.",
|
|
48
|
+
default: false,
|
|
49
|
+
required: false
|
|
50
|
+
}),
|
|
40
51
|
"auth-bypass-token": Flags.boolean({
|
|
41
52
|
description: "Generate an authentication bypass token, which can be used to perform end-to-end tests against the deployment.",
|
|
42
53
|
required: false,
|
|
43
54
|
default: false
|
|
44
55
|
}),
|
|
56
|
+
"build-command": Flags.string({
|
|
57
|
+
description: "Specify a build command to run before deploying. If not specified, `shopify hydrogen build` will be used.",
|
|
58
|
+
required: false
|
|
59
|
+
}),
|
|
60
|
+
"lockfile-check": commonFlags.lockfileCheck,
|
|
45
61
|
path: commonFlags.path,
|
|
46
62
|
shop: commonFlags.shop,
|
|
47
|
-
"
|
|
48
|
-
|
|
63
|
+
"json-output": Flags.boolean({
|
|
64
|
+
allowNo: true,
|
|
65
|
+
description: "Create a JSON file containing the deployment details in CI environments. Defaults to true, use `--no-json-output` to disable.",
|
|
49
66
|
required: false,
|
|
50
|
-
default:
|
|
67
|
+
default: true
|
|
51
68
|
}),
|
|
52
69
|
token: Flags.string({
|
|
53
70
|
char: "t",
|
|
@@ -92,6 +109,7 @@ class Deploy extends Command {
|
|
|
92
109
|
...camelFlags,
|
|
93
110
|
defaultEnvironment: flags.preview,
|
|
94
111
|
environmentTag: flags["env-branch"],
|
|
112
|
+
environmentFile: flags["env-file"],
|
|
95
113
|
path: flags.path ? resolvePath(flags.path) : process.cwd()
|
|
96
114
|
};
|
|
97
115
|
}
|
|
@@ -99,10 +117,10 @@ class Deploy extends Command {
|
|
|
99
117
|
function createUnexpectedAbortError(message) {
|
|
100
118
|
return new AbortError(
|
|
101
119
|
message || "The deployment failed due to an unexpected error.",
|
|
102
|
-
"Retrying the
|
|
120
|
+
"Retrying the deployment may succeed.",
|
|
103
121
|
[
|
|
104
122
|
[
|
|
105
|
-
"If the issue
|
|
123
|
+
"If the issue persists, please check the",
|
|
106
124
|
{
|
|
107
125
|
link: {
|
|
108
126
|
label: "Shopify status page",
|
|
@@ -117,10 +135,14 @@ function createUnexpectedAbortError(message) {
|
|
|
117
135
|
async function oxygenDeploy(options) {
|
|
118
136
|
const {
|
|
119
137
|
authBypassToken: generateAuthBypassToken,
|
|
138
|
+
buildCommand,
|
|
120
139
|
defaultEnvironment,
|
|
121
140
|
environmentTag,
|
|
141
|
+
environmentFile,
|
|
122
142
|
force: forceOnUncommitedChanges,
|
|
123
|
-
|
|
143
|
+
noVerify,
|
|
144
|
+
lockfileCheck,
|
|
145
|
+
jsonOutput,
|
|
124
146
|
path,
|
|
125
147
|
shop,
|
|
126
148
|
metadataUrl,
|
|
@@ -173,6 +195,16 @@ async function oxygenDeploy(options) {
|
|
|
173
195
|
});
|
|
174
196
|
metadataDescription = `${commitHash} with additional changes`;
|
|
175
197
|
}
|
|
198
|
+
let overriddenEnvironmentVariables;
|
|
199
|
+
if (environmentFile) {
|
|
200
|
+
try {
|
|
201
|
+
overriddenEnvironmentVariables = loadEnvironmentVariableFile(environmentFile);
|
|
202
|
+
} catch (error) {
|
|
203
|
+
throw new AbortError(
|
|
204
|
+
`Could not load environment file at ${environmentFile}`
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
176
208
|
if (!isCI) {
|
|
177
209
|
deploymentData = await getOxygenDeploymentData({
|
|
178
210
|
root: path,
|
|
@@ -244,11 +276,12 @@ async function oxygenDeploy(options) {
|
|
|
244
276
|
...metadataUser ? { user: metadataUser } : {},
|
|
245
277
|
...metadataVersion ? { version: metadataVersion } : {}
|
|
246
278
|
},
|
|
247
|
-
skipVerification:
|
|
279
|
+
skipVerification: noVerify,
|
|
248
280
|
rootPath: path,
|
|
249
281
|
skipBuild: false,
|
|
250
282
|
workerOnly: false,
|
|
251
|
-
workerDir: "dist/worker"
|
|
283
|
+
workerDir: "dist/worker",
|
|
284
|
+
overriddenEnvironmentVariables
|
|
252
285
|
};
|
|
253
286
|
let resolveUpload;
|
|
254
287
|
const uploadPromise = new Promise((resolve) => {
|
|
@@ -272,17 +305,6 @@ async function oxygenDeploy(options) {
|
|
|
272
305
|
rejectDeploy = reject;
|
|
273
306
|
});
|
|
274
307
|
const hooks = {
|
|
275
|
-
buildFunction: async (assetPath) => {
|
|
276
|
-
outputInfo(
|
|
277
|
-
outputContent`${colors.whiteBright("Building project...")}`.value
|
|
278
|
-
);
|
|
279
|
-
await runBuild({
|
|
280
|
-
directory: path,
|
|
281
|
-
assetPath,
|
|
282
|
-
sourcemap: true,
|
|
283
|
-
useCodegen: false
|
|
284
|
-
});
|
|
285
|
-
},
|
|
286
308
|
onDeploymentCompleted: () => resolveDeploymentCompletedVerification(),
|
|
287
309
|
onVerificationComplete: () => resolveRoutableCheck(),
|
|
288
310
|
onDeploymentCompletedVerificationError() {
|
|
@@ -309,6 +331,22 @@ async function oxygenDeploy(options) {
|
|
|
309
331
|
);
|
|
310
332
|
}
|
|
311
333
|
};
|
|
334
|
+
if (buildCommand) {
|
|
335
|
+
config.buildCommand = buildCommand;
|
|
336
|
+
} else {
|
|
337
|
+
hooks.buildFunction = async (assetPath) => {
|
|
338
|
+
outputInfo(
|
|
339
|
+
outputContent`${colors.whiteBright("Building project...")}`.value
|
|
340
|
+
);
|
|
341
|
+
await runBuild({
|
|
342
|
+
directory: path,
|
|
343
|
+
assetPath,
|
|
344
|
+
lockfileCheck,
|
|
345
|
+
sourcemap: true,
|
|
346
|
+
useCodegen: false
|
|
347
|
+
});
|
|
348
|
+
};
|
|
349
|
+
}
|
|
312
350
|
const uploadStart = async () => {
|
|
313
351
|
outputInfo(
|
|
314
352
|
outputContent`${colors.whiteBright("Deploying to Oxygen..\n")}`.value
|
|
@@ -324,7 +362,8 @@ async function oxygenDeploy(options) {
|
|
|
324
362
|
},
|
|
325
363
|
{
|
|
326
364
|
title: "Verifying deployment is routable",
|
|
327
|
-
task: async () => await routableCheckPromise
|
|
365
|
+
task: async () => await routableCheckPromise,
|
|
366
|
+
skip: () => noVerify
|
|
328
367
|
}
|
|
329
368
|
]);
|
|
330
369
|
};
|
|
@@ -333,27 +372,35 @@ async function oxygenDeploy(options) {
|
|
|
333
372
|
rejectDeploy(createUnexpectedAbortError());
|
|
334
373
|
return;
|
|
335
374
|
}
|
|
336
|
-
const nextSteps = [
|
|
337
|
-
|
|
375
|
+
const nextSteps = [];
|
|
376
|
+
if (isCI) {
|
|
377
|
+
if (jsonOutput) {
|
|
378
|
+
nextSteps.push([
|
|
379
|
+
"View the deployment information in",
|
|
380
|
+
{ subdued: DEPLOY_OUTPUT_FILE_HANDLE }
|
|
381
|
+
]);
|
|
382
|
+
}
|
|
383
|
+
} else {
|
|
384
|
+
nextSteps.push([
|
|
338
385
|
"Open",
|
|
339
386
|
{ link: { url: completedDeployment.url } },
|
|
340
|
-
|
|
341
|
-
]
|
|
342
|
-
];
|
|
343
|
-
if (completedDeployment?.authBypassToken) {
|
|
344
|
-
nextSteps.push([
|
|
345
|
-
"Use the",
|
|
346
|
-
{ subdued: completedDeployment.authBypassToken },
|
|
347
|
-
"token to perform end-to-end tests against the deployment."
|
|
387
|
+
"in your browser to view your deployment."
|
|
348
388
|
]);
|
|
389
|
+
if (completedDeployment?.authBypassToken) {
|
|
390
|
+
nextSteps.push([
|
|
391
|
+
"Use the",
|
|
392
|
+
{ subdued: completedDeployment.authBypassToken },
|
|
393
|
+
"token to perform end-to-end tests against the deployment."
|
|
394
|
+
]);
|
|
395
|
+
}
|
|
349
396
|
}
|
|
350
397
|
renderSuccess({
|
|
351
398
|
body: ["Successfully deployed to Oxygen"],
|
|
352
399
|
nextSteps
|
|
353
400
|
});
|
|
354
|
-
if (isCI &&
|
|
401
|
+
if (isCI && jsonOutput) {
|
|
355
402
|
await writeFile(
|
|
356
|
-
|
|
403
|
+
DEPLOY_OUTPUT_FILE_HANDLE,
|
|
357
404
|
JSON.stringify(completedDeployment)
|
|
358
405
|
);
|
|
359
406
|
}
|
|
@@ -8,12 +8,16 @@ import { ensureIsClean, GitDirectoryNotCleanError, getLatestGitCommit } from '@s
|
|
|
8
8
|
import { oxygenDeploy, deploymentLogger } from './deploy.js';
|
|
9
9
|
import { getOxygenDeploymentData } from '../../lib/get-oxygen-deployment-data.js';
|
|
10
10
|
import { createDeploy, parseToken } from '@shopify/oxygen-cli/deploy';
|
|
11
|
+
import { loadEnvironmentVariableFile } from '@shopify/oxygen-cli/utils';
|
|
11
12
|
import { ciPlatform } from '@shopify/cli-kit/node/context/local';
|
|
13
|
+
import { runBuild } from './build.js';
|
|
12
14
|
|
|
13
|
-
vi.mock("../../lib/get-oxygen-deployment-data.js");
|
|
14
15
|
vi.mock("@shopify/oxygen-cli/deploy");
|
|
16
|
+
vi.mock("@shopify/oxygen-cli/utils");
|
|
15
17
|
vi.mock("@shopify/cli-kit/node/fs");
|
|
16
18
|
vi.mock("@shopify/cli-kit/node/context/local");
|
|
19
|
+
vi.mock("../../lib/get-oxygen-deployment-data.js");
|
|
20
|
+
vi.mock("./build.js");
|
|
17
21
|
vi.mock("../../lib/auth.js");
|
|
18
22
|
vi.mock("../../lib/shopify-config.js");
|
|
19
23
|
vi.mock("../../lib/graphql/admin/link-storefront.js");
|
|
@@ -69,7 +73,9 @@ describe("deploy", () => {
|
|
|
69
73
|
authBypassToken: true,
|
|
70
74
|
defaultEnvironment: false,
|
|
71
75
|
force: false,
|
|
72
|
-
|
|
76
|
+
noVerify: true,
|
|
77
|
+
lockfileCheck: false,
|
|
78
|
+
jsonOutput: true,
|
|
73
79
|
path: "./",
|
|
74
80
|
shop: "snowdevil.myshopify.com",
|
|
75
81
|
metadataUrl: "https://example.com",
|
|
@@ -98,7 +104,7 @@ describe("deploy", () => {
|
|
|
98
104
|
user: deployParams.metadataUser,
|
|
99
105
|
version: deployParams.metadataVersion
|
|
100
106
|
},
|
|
101
|
-
skipVerification:
|
|
107
|
+
skipVerification: true,
|
|
102
108
|
rootPath: deployParams.path,
|
|
103
109
|
skipBuild: false,
|
|
104
110
|
workerOnly: false,
|
|
@@ -161,6 +167,46 @@ describe("deploy", () => {
|
|
|
161
167
|
});
|
|
162
168
|
expect(vi.mocked(renderSuccess)).toHaveBeenCalled;
|
|
163
169
|
});
|
|
170
|
+
it("calls createDeploy with overridden variables in environment file", async () => {
|
|
171
|
+
const overriddenEnvironmentVariables = [
|
|
172
|
+
{
|
|
173
|
+
key: "fake-key",
|
|
174
|
+
value: "fake-value",
|
|
175
|
+
isSecret: true
|
|
176
|
+
}
|
|
177
|
+
];
|
|
178
|
+
vi.mocked(loadEnvironmentVariableFile).mockReturnValue(
|
|
179
|
+
overriddenEnvironmentVariables
|
|
180
|
+
);
|
|
181
|
+
await oxygenDeploy({
|
|
182
|
+
...deployParams,
|
|
183
|
+
environmentFile: "fake-env-file"
|
|
184
|
+
});
|
|
185
|
+
expect(vi.mocked(createDeploy)).toHaveBeenCalledWith({
|
|
186
|
+
config: {
|
|
187
|
+
...expectedConfig,
|
|
188
|
+
overriddenEnvironmentVariables
|
|
189
|
+
},
|
|
190
|
+
hooks: expectedHooks,
|
|
191
|
+
logger: deploymentLogger
|
|
192
|
+
});
|
|
193
|
+
});
|
|
194
|
+
it("errors when supplied environment file does not exist", async () => {
|
|
195
|
+
vi.mocked(loadEnvironmentVariableFile).mockImplementation(
|
|
196
|
+
(_path) => {
|
|
197
|
+
throw new AbortError("File not found");
|
|
198
|
+
}
|
|
199
|
+
);
|
|
200
|
+
try {
|
|
201
|
+
await oxygenDeploy({
|
|
202
|
+
...deployParams,
|
|
203
|
+
environmentFile: "fake-env-file"
|
|
204
|
+
});
|
|
205
|
+
expect(true).toBe(false);
|
|
206
|
+
} catch (err) {
|
|
207
|
+
expect(err).toBeInstanceOf(AbortError);
|
|
208
|
+
}
|
|
209
|
+
});
|
|
164
210
|
it("errors when there are uncommited changes", async () => {
|
|
165
211
|
vi.mocked(ensureIsClean).mockRejectedValue(
|
|
166
212
|
new GitDirectoryNotCleanError("Uncommitted changes")
|
|
@@ -304,6 +350,42 @@ describe("deploy", () => {
|
|
|
304
350
|
});
|
|
305
351
|
});
|
|
306
352
|
});
|
|
353
|
+
it("passes the lockfileCheck to the build function when the flag is set", async () => {
|
|
354
|
+
const params = {
|
|
355
|
+
...deployParams,
|
|
356
|
+
lockfileCheck: false
|
|
357
|
+
};
|
|
358
|
+
vi.mocked(createDeploy).mockImplementationOnce((options) => {
|
|
359
|
+
options.hooks?.buildFunction?.("some-cool-asset-path");
|
|
360
|
+
return new Promise((resolve, _reject) => {
|
|
361
|
+
resolve({ url: "https://a-lovely-deployment.com" });
|
|
362
|
+
});
|
|
363
|
+
});
|
|
364
|
+
await oxygenDeploy(params);
|
|
365
|
+
expect(vi.mocked(runBuild)).toHaveBeenCalledWith({
|
|
366
|
+
assetPath: "some-cool-asset-path",
|
|
367
|
+
directory: params.path,
|
|
368
|
+
lockfileCheck: false,
|
|
369
|
+
sourcemap: true,
|
|
370
|
+
useCodegen: false
|
|
371
|
+
});
|
|
372
|
+
});
|
|
373
|
+
it("passes a build command to createDeploy when the build-command flag is used", async () => {
|
|
374
|
+
const params = {
|
|
375
|
+
...deployParams,
|
|
376
|
+
buildCommand: "hocus pocus"
|
|
377
|
+
};
|
|
378
|
+
const { buildFunction: _, ...hooks } = expectedHooks;
|
|
379
|
+
await oxygenDeploy(params);
|
|
380
|
+
expect(vi.mocked(createDeploy)).toHaveBeenCalledWith({
|
|
381
|
+
config: {
|
|
382
|
+
...expectedConfig,
|
|
383
|
+
buildCommand: "hocus pocus"
|
|
384
|
+
},
|
|
385
|
+
hooks,
|
|
386
|
+
logger: deploymentLogger
|
|
387
|
+
});
|
|
388
|
+
});
|
|
307
389
|
it("writes a file with JSON content in CI environments", async () => {
|
|
308
390
|
vi.mocked(ciPlatform).mockReturnValue({
|
|
309
391
|
isCI: true,
|
|
@@ -325,7 +407,7 @@ describe("deploy", () => {
|
|
|
325
407
|
})
|
|
326
408
|
);
|
|
327
409
|
vi.mocked(writeFile).mockClear();
|
|
328
|
-
ciDeployParams.
|
|
410
|
+
ciDeployParams.jsonOutput = false;
|
|
329
411
|
await oxygenDeploy(ciDeployParams);
|
|
330
412
|
expect(vi.mocked(writeFile)).not.toHaveBeenCalled();
|
|
331
413
|
});
|
|
@@ -401,10 +483,85 @@ describe("deploy", () => {
|
|
|
401
483
|
} catch (err) {
|
|
402
484
|
if (err instanceof AbortError) {
|
|
403
485
|
expect(err.message).toBe("oh shit");
|
|
404
|
-
expect(err.tryMessage).toBe("Retrying the
|
|
486
|
+
expect(err.tryMessage).toBe("Retrying the deployment may succeed.");
|
|
405
487
|
} else {
|
|
406
488
|
expect(true).toBe(false);
|
|
407
489
|
}
|
|
408
490
|
}
|
|
409
491
|
});
|
|
492
|
+
describe("next steps", () => {
|
|
493
|
+
it("renders a link to the deployment", async () => {
|
|
494
|
+
vi.mocked(createDeploy).mockResolvedValue({
|
|
495
|
+
url: "https://a-lovely-deployment.com"
|
|
496
|
+
});
|
|
497
|
+
await oxygenDeploy(deployParams);
|
|
498
|
+
expect(vi.mocked(renderSuccess)).toHaveBeenCalledWith({
|
|
499
|
+
body: ["Successfully deployed to Oxygen"],
|
|
500
|
+
nextSteps: [
|
|
501
|
+
[
|
|
502
|
+
"Open",
|
|
503
|
+
{ link: { url: "https://a-lovely-deployment.com" } },
|
|
504
|
+
"in your browser to view your deployment."
|
|
505
|
+
]
|
|
506
|
+
]
|
|
507
|
+
});
|
|
508
|
+
});
|
|
509
|
+
it("renders a link to the deployment and shows auth bypass token when one is created", async () => {
|
|
510
|
+
vi.mocked(createDeploy).mockResolvedValue({
|
|
511
|
+
url: "https://a-lovely-deployment.com",
|
|
512
|
+
authBypassToken: "some-token"
|
|
513
|
+
});
|
|
514
|
+
await oxygenDeploy(deployParams);
|
|
515
|
+
expect(vi.mocked(renderSuccess)).toHaveBeenCalledWith({
|
|
516
|
+
body: ["Successfully deployed to Oxygen"],
|
|
517
|
+
nextSteps: [
|
|
518
|
+
[
|
|
519
|
+
"Open",
|
|
520
|
+
{ link: { url: "https://a-lovely-deployment.com" } },
|
|
521
|
+
"in your browser to view your deployment."
|
|
522
|
+
],
|
|
523
|
+
[
|
|
524
|
+
"Use the",
|
|
525
|
+
{ subdued: "some-token" },
|
|
526
|
+
"token to perform end-to-end tests against the deployment."
|
|
527
|
+
]
|
|
528
|
+
]
|
|
529
|
+
});
|
|
530
|
+
});
|
|
531
|
+
describe("when in a CI environment", () => {
|
|
532
|
+
it("renders information about h2_deploy_log.json", async () => {
|
|
533
|
+
vi.mocked(ciPlatform).mockReturnValue({
|
|
534
|
+
isCI: true,
|
|
535
|
+
name: "github",
|
|
536
|
+
metadata: {}
|
|
537
|
+
});
|
|
538
|
+
await oxygenDeploy({ ...deployParams, token: "fake-token" });
|
|
539
|
+
expect(vi.mocked(renderSuccess)).toHaveBeenCalledWith({
|
|
540
|
+
body: ["Successfully deployed to Oxygen"],
|
|
541
|
+
nextSteps: [
|
|
542
|
+
[
|
|
543
|
+
"View the deployment information in",
|
|
544
|
+
{ subdued: "h2_deploy_log.json" }
|
|
545
|
+
]
|
|
546
|
+
]
|
|
547
|
+
});
|
|
548
|
+
});
|
|
549
|
+
it("renders no next steps if jsonOutput is set to false", async () => {
|
|
550
|
+
vi.mocked(ciPlatform).mockReturnValue({
|
|
551
|
+
isCI: true,
|
|
552
|
+
name: "github",
|
|
553
|
+
metadata: {}
|
|
554
|
+
});
|
|
555
|
+
await oxygenDeploy({
|
|
556
|
+
...deployParams,
|
|
557
|
+
token: "fake-token",
|
|
558
|
+
jsonOutput: false
|
|
559
|
+
});
|
|
560
|
+
expect(vi.mocked(renderSuccess)).toHaveBeenCalledWith({
|
|
561
|
+
body: ["Successfully deployed to Oxygen"],
|
|
562
|
+
nextSteps: []
|
|
563
|
+
});
|
|
564
|
+
});
|
|
565
|
+
});
|
|
566
|
+
});
|
|
410
567
|
});
|
|
@@ -18,6 +18,10 @@ class GenerateRoute extends Command {
|
|
|
18
18
|
description: "Generate TypeScript files",
|
|
19
19
|
env: "SHOPIFY_HYDROGEN_FLAG_TYPESCRIPT"
|
|
20
20
|
}),
|
|
21
|
+
"locale-param": Flags.string({
|
|
22
|
+
description: "The param name in Remix routes for the i18n locale, if any. Example: `locale` becomes ($locale).",
|
|
23
|
+
env: "SHOPIFY_HYDROGEN_FLAG_ADAPTER"
|
|
24
|
+
}),
|
|
21
25
|
force: commonFlags.force,
|
|
22
26
|
path: commonFlags.path
|
|
23
27
|
};
|
|
@@ -40,7 +44,8 @@ class GenerateRoute extends Command {
|
|
|
40
44
|
await runGenerate({
|
|
41
45
|
...flags,
|
|
42
46
|
directory,
|
|
43
|
-
routeName
|
|
47
|
+
routeName,
|
|
48
|
+
localePrefix: flags["locale-param"]
|
|
44
49
|
});
|
|
45
50
|
}
|
|
46
51
|
}
|
|
@@ -171,8 +171,8 @@ describe("init", () => {
|
|
|
171
171
|
const examplePath = templatePath.replace("templates", "examples").replace("skeleton", exampleName);
|
|
172
172
|
const ignore = ["**/node_modules/**", "**/dist/**"];
|
|
173
173
|
const resultFiles = await glob("**/*", { ignore, cwd: tmpDir });
|
|
174
|
-
const templateFiles = await glob("**/*", { ignore, cwd: templatePath });
|
|
175
174
|
const exampleFiles = await glob("**/*", { ignore, cwd: examplePath });
|
|
175
|
+
const templateFiles = (await glob("**/*", { ignore, cwd: templatePath })).filter((item) => !item.endsWith("CHANGELOG.md"));
|
|
176
176
|
expect(resultFiles).toEqual(
|
|
177
177
|
expect.arrayContaining([
|
|
178
178
|
.../* @__PURE__ */ new Set([...templateFiles, ...exampleFiles])
|
|
@@ -462,6 +462,7 @@ describe("init", () => {
|
|
|
462
462
|
});
|
|
463
463
|
const resultFiles = await glob("**/*", { cwd: tmpDir });
|
|
464
464
|
expect(resultFiles).toContain("app/routes/($locale)._index.tsx");
|
|
465
|
+
expect(resultFiles).toContain("app/routes/($locale).tsx");
|
|
465
466
|
const serverFile = await readFile(`${tmpDir}/server.ts`);
|
|
466
467
|
expect(serverFile).toMatch(/i18n: getLocaleFromRequest\(request\),/);
|
|
467
468
|
expect(serverFile).toMatch(/url.pathname/);
|
|
@@ -7,7 +7,7 @@ import { commonFlags, overrideFlag, flagsToCamelObject } from '../../lib/flags.j
|
|
|
7
7
|
import { renderI18nPrompt, setupI18nStrategy } from '../../lib/setups/i18n/index.js';
|
|
8
8
|
import { getRemixConfig } from '../../lib/remix-config.js';
|
|
9
9
|
import { handleRouteGeneration, generateProjectEntries, handleCliShortcut, renderProjectReady } from '../../lib/onboarding/common.js';
|
|
10
|
-
import { getCliCommand } from '../../lib/shell.js';
|
|
10
|
+
import { getCliCommand, ALIAS_NAME } from '../../lib/shell.js';
|
|
11
11
|
import { getTemplateAppFile } from '../../lib/build.js';
|
|
12
12
|
|
|
13
13
|
class Setup extends Command {
|
|
@@ -89,24 +89,22 @@ async function runSetup(options) {
|
|
|
89
89
|
() => setupI18nStrategy(i18n, remixConfig)
|
|
90
90
|
);
|
|
91
91
|
}
|
|
92
|
-
let
|
|
93
|
-
const
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
options.shortcut
|
|
100
|
-
);
|
|
101
|
-
if (createShortcut) {
|
|
102
|
-
backgroundWorkPromise = backgroundWorkPromise.then(async () => {
|
|
103
|
-
hasCreatedShortcut = await createShortcut();
|
|
104
|
-
});
|
|
105
|
-
showShortcutBanner();
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
if (!i18n && !needsRouteGeneration && !needsAlias)
|
|
92
|
+
let cliCommand = await Promise.resolve(cliCommandPromise);
|
|
93
|
+
const { createShortcut, showShortcutBanner } = await handleCliShortcut(
|
|
94
|
+
controller,
|
|
95
|
+
cliCommand,
|
|
96
|
+
options.shortcut
|
|
97
|
+
);
|
|
98
|
+
if (!i18n && !needsRouteGeneration && !createShortcut)
|
|
109
99
|
return;
|
|
100
|
+
if (createShortcut) {
|
|
101
|
+
backgroundWorkPromise = backgroundWorkPromise.then(async () => {
|
|
102
|
+
if (await createShortcut()) {
|
|
103
|
+
cliCommand = ALIAS_NAME;
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
showShortcutBanner();
|
|
107
|
+
}
|
|
110
108
|
await renderTasks(tasks);
|
|
111
109
|
await renderProjectReady(
|
|
112
110
|
{
|
|
@@ -115,7 +113,7 @@ async function runSetup(options) {
|
|
|
115
113
|
directory: remixConfig.rootDirectory
|
|
116
114
|
},
|
|
117
115
|
{
|
|
118
|
-
|
|
116
|
+
cliCommand,
|
|
119
117
|
depsInstalled: true,
|
|
120
118
|
packageManager: "npm",
|
|
121
119
|
i18n,
|
|
@@ -1,5 +1,50 @@
|
|
|
1
1
|
# skeleton
|
|
2
2
|
|
|
3
|
+
## 1.0.4
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- This is an important fix to a bug with 404 routes and path-based i18n projects where some unknown routes would not properly render a 404. This fixes all new projects, but to fix existing projects, add a `($locale).tsx` route with the following contents: ([#1732](https://github.com/Shopify/hydrogen/pull/1732)) by [@blittle](https://github.com/blittle)
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
import {type LoaderFunctionArgs} from '@remix-run/server-runtime';
|
|
11
|
+
|
|
12
|
+
export async function loader({params, context}: LoaderFunctionArgs) {
|
|
13
|
+
const {language, country} = context.storefront.i18n;
|
|
14
|
+
|
|
15
|
+
if (
|
|
16
|
+
params.locale &&
|
|
17
|
+
params.locale.toLowerCase() !== `${language}-${country}`.toLowerCase()
|
|
18
|
+
) {
|
|
19
|
+
// If the locale URL param is defined, yet we still are still at the default locale
|
|
20
|
+
// then the the locale param must be invalid, send to the 404 page
|
|
21
|
+
throw new Response(null, {status: 404});
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
- Add defensive null checks to the default cart implementation in the starter template ([#1746](https://github.com/Shopify/hydrogen/pull/1746)) by [@blittle](https://github.com/blittle)
|
|
29
|
+
|
|
30
|
+
- 🐛 Fix issue where customer login does not persist to checkout ([#1719](https://github.com/Shopify/hydrogen/pull/1719)) by [@michenly](https://github.com/michenly)
|
|
31
|
+
|
|
32
|
+
✨ Add `customerAccount` option to `createCartHandler`. Where a `?logged_in=true` will be added to the checkoutUrl for cart query if a customer is logged in.
|
|
33
|
+
|
|
34
|
+
- Updated dependencies [[`faeba9f8`](https://github.com/Shopify/hydrogen/commit/faeba9f8947d6b9420b33274a0f39b62418ff2e5), [`6d585026`](https://github.com/Shopify/hydrogen/commit/6d585026623204e99d54a5f2efa3d1c74f690bb6), [`fcecfb23`](https://github.com/Shopify/hydrogen/commit/fcecfb2307210b9d73a7cc90ba865508937217ba), [`28864d6f`](https://github.com/Shopify/hydrogen/commit/28864d6ffbb19b62a5fb8f4c9bbe27568de62411), [`c0ec7714`](https://github.com/Shopify/hydrogen/commit/c0ec77141fb1d7a713d91219b8777bc541780ae8), [`226cf478`](https://github.com/Shopify/hydrogen/commit/226cf478a5bdef1cca33fe8f69832ae0e557d9d9), [`06d9fd91`](https://github.com/Shopify/hydrogen/commit/06d9fd91140bd52a8ee41a20bc114ce2e7fb67dc)]:
|
|
35
|
+
- @shopify/cli-hydrogen@7.1.0
|
|
36
|
+
- @shopify/hydrogen@2024.1.2
|
|
37
|
+
|
|
38
|
+
## 1.0.3
|
|
39
|
+
|
|
40
|
+
### Patch Changes
|
|
41
|
+
|
|
42
|
+
- ♻️ `CustomerClient` type is deprecated and replaced by `CustomerAccount` ([#1692](https://github.com/Shopify/hydrogen/pull/1692)) by [@michenly](https://github.com/michenly)
|
|
43
|
+
|
|
44
|
+
- Updated dependencies [[`02798786`](https://github.com/Shopify/hydrogen/commit/02798786bf8ae5c53f6430723a86d62b8e94d120), [`52b15df4`](https://github.com/Shopify/hydrogen/commit/52b15df457ce723bbc83ad594ded73a7b06447d6), [`a2664362`](https://github.com/Shopify/hydrogen/commit/a2664362a7d89b34835553a9b0eb7af55ca70ae4), [`eee5d927`](https://github.com/Shopify/hydrogen/commit/eee5d9274b72404dfb0ffef30d5503fd553be5fe), [`c7b2017f`](https://github.com/Shopify/hydrogen/commit/c7b2017f11a2cb4d280dfd8f170e65a908b9ea02), [`06320ee4`](https://github.com/Shopify/hydrogen/commit/06320ee48b94dbfece945461031a252f454fd0a3)]:
|
|
45
|
+
- @shopify/hydrogen@2024.1.1
|
|
46
|
+
- @shopify/cli-hydrogen@7.0.1
|
|
47
|
+
|
|
3
48
|
## 1.0.2
|
|
4
49
|
|
|
5
50
|
### Patch Changes
|
|
@@ -41,12 +41,18 @@ npm run dev
|
|
|
41
41
|
|
|
42
42
|
## Setup for using Customer Account API (`/account` section)
|
|
43
43
|
|
|
44
|
+
### Enabled new Customer Account Experience
|
|
45
|
+
|
|
46
|
+
1. Go to your Shopify admin => Settings => Customer accounts => New customer account
|
|
47
|
+
|
|
44
48
|
### Setup public domain using ngrok
|
|
45
49
|
|
|
46
50
|
1. Setup a [ngrok](https://ngrok.com/) account and add a permanent domain (ie. `https://<your-ngrok-domain>.app`).
|
|
47
51
|
1. Install the [ngrok CLI](https://ngrok.com/download) to use in terminal
|
|
48
52
|
1. Start ngrok using `ngrok http --domain=<your-ngrok-domain>.app 3000`
|
|
49
53
|
|
|
54
|
+
> [!IMPORTANT]
|
|
55
|
+
> To successfully interact with the Customer Account API routes you will need to use the ngrok domain during development instead of localhost
|
|
50
56
|
### Include public domain in Customer Account API settings
|
|
51
57
|
|
|
52
58
|
1. Go to your Shopify admin => `Hydrogen` or `Headless` app/channel => Customer Account API => Application setup
|
|
@@ -54,10 +60,27 @@ npm run dev
|
|
|
54
60
|
1. Edit `Javascript origin(s)` to include your public domain `https://<your-ngrok-domain>.app` or keep it blank
|
|
55
61
|
1. Edit `Logout URI` to include your public domain `https://<your-ngrok-domain>.app` or keep it blank
|
|
56
62
|
|
|
63
|
+
### Add the ngrok domain to the CSP policy
|
|
64
|
+
|
|
65
|
+
Modify your `/app/entry.server.tsx` to allow the ngrok domain as a connect-src
|
|
66
|
+
|
|
67
|
+
```diff
|
|
68
|
+
- const {nonce, header, NonceProvider} = createContentSecurityPolicy()
|
|
69
|
+
+ const {nonce, header, NonceProvider} = createContentSecurityPolicy({
|
|
70
|
+
+ connectSrc: [
|
|
71
|
+
+ 'wss://<your-ngrok-domain>.app:*', // Your ngrok websocket domain
|
|
72
|
+
+ ],
|
|
73
|
+
+ });
|
|
74
|
+
```
|
|
75
|
+
|
|
57
76
|
### Prepare Environment variables
|
|
58
77
|
|
|
59
78
|
Run [`npx shopify hydrogen link`](https://shopify.dev/docs/custom-storefronts/hydrogen/cli#link) or [`npx shopify hydrogen env pull`](https://shopify.dev/docs/custom-storefronts/hydrogen/cli#env-pull) to link this app to your own test shop.
|
|
60
79
|
|
|
61
|
-
|
|
80
|
+
Alternately, the values of the required environment variables "PUBLIC_CUSTOMER_ACCOUNT_API_CLIENT_ID" and "PUBLIC_CUSTOMER_ACCOUNT_API_URL" can be found in customer account api settings in the Hydrogen admin channel.
|
|
81
|
+
|
|
82
|
+
> [!IMPORTANT]
|
|
83
|
+
> Note that `mock.shop` doesn't supply these variables automatically and your own test shop is required for using Customer Account API
|
|
62
84
|
|
|
63
|
-
|
|
85
|
+
> [!NOTE]
|
|
86
|
+
> B2B features such as contextual pricing is not available in SF API with Customer Account API login. If you require this feature, we suggest using the [legacy-customer-account-flow](https://github.com/Shopify/hydrogen/tree/main/examples/legacy-customer-account-flow). This feature should be available in the Customer Account API in the 2024-04 release.
|