@shopify/cli-hydrogen 4.0.9 → 4.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.d.ts +4 -4
- package/dist/commands/hydrogen/build.js +17 -16
- package/dist/commands/hydrogen/check.d.ts +3 -6
- package/dist/commands/hydrogen/check.js +10 -9
- package/dist/commands/hydrogen/dev.d.ts +3 -3
- package/dist/commands/hydrogen/dev.js +22 -21
- package/dist/commands/hydrogen/g.d.ts +10 -0
- package/dist/commands/hydrogen/g.js +17 -0
- package/dist/commands/hydrogen/generate/route.d.ts +7 -9
- package/dist/commands/hydrogen/generate/route.js +49 -47
- package/dist/commands/hydrogen/generate/route.test.js +48 -40
- package/dist/commands/hydrogen/generate/routes.d.ts +2 -2
- package/dist/commands/hydrogen/init.d.ts +3 -3
- package/dist/commands/hydrogen/init.js +74 -93
- package/dist/commands/hydrogen/init.test.js +71 -24
- package/dist/commands/hydrogen/preview.d.ts +2 -2
- package/dist/commands/hydrogen/preview.js +4 -4
- package/dist/commands/hydrogen/shortcut.d.ts +9 -0
- package/dist/commands/hydrogen/shortcut.js +74 -0
- package/dist/commands/hydrogen/shortcut.test.js +58 -0
- package/dist/generator-templates/routes/[robots.txt].tsx +35 -1
- package/dist/generator-templates/routes/[sitemap.xml].tsx +33 -2
- package/dist/generator-templates/routes/account/login.tsx +42 -13
- package/dist/generator-templates/routes/account/register.tsx +42 -13
- package/dist/generator-templates/routes/cart.tsx +42 -2
- package/dist/generator-templates/routes/collections/$collectionHandle.tsx +44 -5
- package/dist/generator-templates/routes/index.tsx +33 -0
- package/dist/generator-templates/routes/pages/$pageHandle.tsx +48 -10
- package/dist/generator-templates/routes/policies/$policyHandle.tsx +67 -14
- package/dist/generator-templates/routes/policies/index.tsx +54 -4
- package/dist/generator-templates/routes/products/$productHandle.tsx +44 -9
- package/dist/hooks/init.js +2 -2
- package/dist/{utils → lib}/check-lockfile.js +7 -4
- package/dist/{utils → lib}/check-lockfile.test.js +19 -28
- package/dist/{utils → lib}/check-version.test.js +3 -2
- package/dist/lib/colors.d.ts +8 -0
- package/dist/lib/colors.js +8 -0
- package/dist/{utils → lib}/config.js +9 -18
- package/dist/{utils → lib}/flags.d.ts +3 -3
- package/dist/{utils → lib}/flags.js +4 -4
- package/dist/{utils → lib}/mini-oxygen.js +14 -12
- package/dist/lib/remix-version-interop.d.ts +11 -0
- package/dist/lib/remix-version-interop.js +54 -0
- package/dist/lib/remix-version-interop.test.d.ts +1 -0
- package/dist/lib/remix-version-interop.test.js +93 -0
- package/dist/lib/shell.d.ts +12 -0
- package/dist/lib/shell.js +73 -0
- package/dist/lib/template-downloader.d.ts +6 -0
- package/dist/{utils → lib}/template-downloader.js +21 -16
- package/dist/{utils → lib}/transpile-ts.js +5 -5
- package/dist/lib/virtual-routes.test.d.ts +1 -0
- package/dist/virtual-routes/routes/index.jsx +2 -15
- package/dist/virtual-routes/virtual-root.jsx +5 -6
- package/oclif.manifest.json +1 -1
- package/package.json +11 -10
- package/dist/utils/template-downloader.d.ts +0 -11
- /package/dist/{utils/check-lockfile.test.d.ts → commands/hydrogen/shortcut.test.d.ts} +0 -0
- /package/dist/{utils → lib}/check-lockfile.d.ts +0 -0
- /package/dist/{utils/check-version.test.d.ts → lib/check-lockfile.test.d.ts} +0 -0
- /package/dist/{utils → lib}/check-version.d.ts +0 -0
- /package/dist/{utils → lib}/check-version.js +0 -0
- /package/dist/{utils/flags.test.d.ts → lib/check-version.test.d.ts} +0 -0
- /package/dist/{utils → lib}/config.d.ts +0 -0
- /package/dist/{utils/virtual-routes.test.d.ts → lib/flags.test.d.ts} +0 -0
- /package/dist/{utils → lib}/flags.test.js +0 -0
- /package/dist/{utils → lib}/log.d.ts +0 -0
- /package/dist/{utils → lib}/log.js +0 -0
- /package/dist/{utils → lib}/mini-oxygen.d.ts +0 -0
- /package/dist/{utils → lib}/missing-routes.d.ts +0 -0
- /package/dist/{utils → lib}/missing-routes.js +0 -0
- /package/dist/{utils → lib}/transpile-ts.d.ts +0 -0
- /package/dist/{utils → lib}/virtual-routes.d.ts +0 -0
- /package/dist/{utils → lib}/virtual-routes.js +0 -0
- /package/dist/{utils → lib}/virtual-routes.test.js +0 -0
|
@@ -5,10 +5,10 @@ declare class GenerateRoutes extends Command {
|
|
|
5
5
|
static description: string;
|
|
6
6
|
static hidden: true;
|
|
7
7
|
static flags: {
|
|
8
|
-
adapter: _oclif_core_lib_interfaces_parser_js.OptionFlag<string | undefined>;
|
|
8
|
+
adapter: _oclif_core_lib_interfaces_parser_js.OptionFlag<string | undefined, _oclif_core_lib_interfaces_parser_js.CustomOptions>;
|
|
9
9
|
typescript: _oclif_core_lib_interfaces_parser_js.BooleanFlag<boolean>;
|
|
10
10
|
force: _oclif_core_lib_interfaces_parser_js.BooleanFlag<boolean>;
|
|
11
|
-
path: _oclif_core_lib_interfaces_parser_js.OptionFlag<string | undefined>;
|
|
11
|
+
path: _oclif_core_lib_interfaces_parser_js.OptionFlag<string | undefined, _oclif_core_lib_interfaces_parser_js.CustomOptions>;
|
|
12
12
|
};
|
|
13
13
|
run(): Promise<void>;
|
|
14
14
|
}
|
|
@@ -5,9 +5,9 @@ declare class Init extends Command {
|
|
|
5
5
|
static description: string;
|
|
6
6
|
static flags: {
|
|
7
7
|
force: _oclif_core_lib_interfaces_parser_js.BooleanFlag<boolean>;
|
|
8
|
-
path: _oclif_core_lib_interfaces_parser_js.OptionFlag<string | undefined>;
|
|
9
|
-
language: _oclif_core_lib_interfaces_parser_js.OptionFlag<string | undefined>;
|
|
10
|
-
template: _oclif_core_lib_interfaces_parser_js.OptionFlag<string | undefined>;
|
|
8
|
+
path: _oclif_core_lib_interfaces_parser_js.OptionFlag<string | undefined, _oclif_core_lib_interfaces_parser_js.CustomOptions>;
|
|
9
|
+
language: _oclif_core_lib_interfaces_parser_js.OptionFlag<string | undefined, _oclif_core_lib_interfaces_parser_js.CustomOptions>;
|
|
10
|
+
template: _oclif_core_lib_interfaces_parser_js.OptionFlag<string | undefined, _oclif_core_lib_interfaces_parser_js.CustomOptions>;
|
|
11
11
|
'install-deps': _oclif_core_lib_interfaces_parser_js.BooleanFlag<boolean>;
|
|
12
12
|
};
|
|
13
13
|
run(): Promise<void>;
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import Command from '@shopify/cli-kit/node/base-command';
|
|
2
2
|
import { packageManagerUsedForCreating, installNodeModules } from '@shopify/cli-kit/node/node-package-manager';
|
|
3
|
-
import { renderFatalError } from '@shopify/cli-kit/node/ui';
|
|
4
|
-
import Flags from '@oclif/core
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
3
|
+
import { renderFatalError, renderSelectPrompt, renderTextPrompt, renderConfirmationPrompt, renderInfo, renderTasks, renderSuccess } from '@shopify/cli-kit/node/ui';
|
|
4
|
+
import { Flags } from '@oclif/core';
|
|
5
|
+
import { basename, resolvePath, joinPath } from '@shopify/cli-kit/node/path';
|
|
6
|
+
import { rmdir, copyFile, fileExists, isDirectory } from '@shopify/cli-kit/node/fs';
|
|
7
|
+
import { outputContent, outputToken } from '@shopify/cli-kit/node/output';
|
|
8
|
+
import { commonFlags, flagsToCamelObject, parseProcessFlags } from '../../lib/flags.js';
|
|
9
|
+
import { transpileProject } from '../../lib/transpile-ts.js';
|
|
10
|
+
import { getLatestTemplates } from '../../lib/template-downloader.js';
|
|
11
|
+
import { checkHydrogenVersion } from '../../lib/check-version.js';
|
|
10
12
|
import { readdir } from 'fs/promises';
|
|
11
13
|
import { fileURLToPath } from 'url';
|
|
12
14
|
|
|
@@ -53,106 +55,77 @@ async function runInit(options = parseProcessFlags(process.argv, FLAG_MAP)) {
|
|
|
53
55
|
packageManager2 === "unknown" ? "" : `Please use the latest version with \`${packageManager2} create @shopify/hydrogen@latest\``
|
|
54
56
|
);
|
|
55
57
|
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
+
let templatesDownloaded = false;
|
|
59
|
+
const templatesPromise = getLatestTemplates().then((result) => {
|
|
60
|
+
templatesDownloaded = true;
|
|
61
|
+
return result;
|
|
62
|
+
}).catch((error) => {
|
|
58
63
|
renderFatalError(error);
|
|
59
64
|
process.exit(1);
|
|
60
65
|
});
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
],
|
|
84
|
-
default: "js"
|
|
85
|
-
});
|
|
86
|
-
}
|
|
87
|
-
if (!options.path) {
|
|
88
|
-
prompts.push({
|
|
89
|
-
type: "input",
|
|
90
|
-
name: "path",
|
|
91
|
-
message: "Where would you like to create your app?",
|
|
92
|
-
default: "hydrogen-storefront"
|
|
93
|
-
});
|
|
94
|
-
}
|
|
95
|
-
const {
|
|
96
|
-
path: location = options.path,
|
|
97
|
-
template: appTemplate = options.template,
|
|
98
|
-
language = options.language ?? "js"
|
|
99
|
-
} = prompts.length > 0 ? await ui.prompt(prompts) : options;
|
|
100
|
-
const projectName = path.basename(location);
|
|
101
|
-
const projectDir = path.resolve(process.cwd(), location);
|
|
66
|
+
const appTemplate = options.template ?? await renderSelectPrompt({
|
|
67
|
+
message: "Choose a template",
|
|
68
|
+
defaultValue: "hello-world",
|
|
69
|
+
choices: STARTER_TEMPLATES.map((value) => ({
|
|
70
|
+
label: value.replace(/-/g, " ").replace(/\b\w/g, (l) => l.toUpperCase()),
|
|
71
|
+
value
|
|
72
|
+
}))
|
|
73
|
+
});
|
|
74
|
+
const language = options.language ?? await renderSelectPrompt({
|
|
75
|
+
message: "Choose a language",
|
|
76
|
+
choices: [
|
|
77
|
+
{ label: "JavaScript", value: "js" },
|
|
78
|
+
{ label: "TypeScript", value: "ts" }
|
|
79
|
+
],
|
|
80
|
+
defaultValue: "js"
|
|
81
|
+
});
|
|
82
|
+
const location = options.path ?? await renderTextPrompt({
|
|
83
|
+
message: "Where would you like to create your app?",
|
|
84
|
+
defaultValue: "hydrogen-storefront"
|
|
85
|
+
});
|
|
86
|
+
const projectName = basename(location);
|
|
87
|
+
const projectDir = resolvePath(process.cwd(), location);
|
|
102
88
|
if (await projectExists(projectDir)) {
|
|
103
89
|
if (!options.force) {
|
|
104
|
-
const
|
|
105
|
-
{
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
choices: [
|
|
110
|
-
{ name: "Yes, delete the files", value: "true" },
|
|
111
|
-
{ name: "No, do not delete the files", value: "false" }
|
|
112
|
-
],
|
|
113
|
-
default: "false"
|
|
114
|
-
}
|
|
115
|
-
]);
|
|
116
|
-
if (deleteFiles === "false") {
|
|
90
|
+
const deleteFiles = await renderConfirmationPrompt({
|
|
91
|
+
message: `${location} is not an empty directory. Do you want to delete the existing files and continue?`,
|
|
92
|
+
defaultValue: false
|
|
93
|
+
});
|
|
94
|
+
if (!deleteFiles) {
|
|
117
95
|
renderInfo({
|
|
118
96
|
headline: `Destination path ${location} already exists and is not an empty directory. You may use \`--force\` or \`-f\` to override it.`
|
|
119
97
|
});
|
|
120
98
|
return;
|
|
121
99
|
}
|
|
122
100
|
}
|
|
123
|
-
await
|
|
101
|
+
await rmdir(projectDir, { force: true });
|
|
102
|
+
}
|
|
103
|
+
if (!templatesDownloaded) {
|
|
104
|
+
await renderTasks([
|
|
105
|
+
{
|
|
106
|
+
title: "Downloading templates",
|
|
107
|
+
task: async () => {
|
|
108
|
+
await templatesPromise;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
]);
|
|
124
112
|
}
|
|
125
|
-
let downloaded = false;
|
|
126
|
-
setTimeout(
|
|
127
|
-
() => !downloaded && output.info("\n\u{1F4E5} Downloading templates..."),
|
|
128
|
-
150
|
|
129
|
-
);
|
|
130
113
|
const { templatesDir } = await templatesPromise;
|
|
131
|
-
|
|
132
|
-
await file.copy(path.join(templatesDir, appTemplate), projectDir);
|
|
114
|
+
await copyFile(joinPath(templatesDir, appTemplate), projectDir);
|
|
133
115
|
if (language === "js") {
|
|
134
116
|
try {
|
|
135
117
|
await transpileProject(projectDir);
|
|
136
118
|
} catch (error) {
|
|
137
|
-
await
|
|
119
|
+
await rmdir(projectDir, { force: true });
|
|
138
120
|
throw error;
|
|
139
121
|
}
|
|
140
122
|
}
|
|
141
123
|
let depsInstalled = false;
|
|
142
124
|
let packageManager = await packageManagerUsedForCreating();
|
|
143
125
|
if (packageManager !== "unknown") {
|
|
144
|
-
const installDeps = options.installDeps ??
|
|
145
|
-
{
|
|
146
|
-
|
|
147
|
-
name: "installDeps",
|
|
148
|
-
message: `Install dependencies with ${packageManager}?`,
|
|
149
|
-
choices: [
|
|
150
|
-
{ name: "Yes", value: "true" },
|
|
151
|
-
{ name: "No", value: "false" }
|
|
152
|
-
],
|
|
153
|
-
default: "true"
|
|
154
|
-
}
|
|
155
|
-
])).installDeps === "true";
|
|
126
|
+
const installDeps = options.installDeps ?? await renderConfirmationPrompt({
|
|
127
|
+
message: `Install dependencies with ${packageManager}?`
|
|
128
|
+
});
|
|
156
129
|
if (installDeps) {
|
|
157
130
|
await installNodeModules({
|
|
158
131
|
directory: projectDir,
|
|
@@ -169,20 +142,28 @@ async function runInit(options = parseProcessFlags(process.argv, FLAG_MAP)) {
|
|
|
169
142
|
renderSuccess({
|
|
170
143
|
headline: `${projectName} is ready to build.`,
|
|
171
144
|
nextSteps: [
|
|
172
|
-
`Run
|
|
173
|
-
|
|
145
|
+
outputContent`Run ${outputToken.genericShellCommand(`cd ${location}`)}`.value,
|
|
146
|
+
depsInstalled ? void 0 : outputContent`Run ${outputToken.genericShellCommand(
|
|
147
|
+
`${packageManager} install`
|
|
148
|
+
)} to install the dependencies`.value,
|
|
149
|
+
outputContent`Run ${outputToken.packagejsonScript(
|
|
150
|
+
packageManager,
|
|
151
|
+
"dev"
|
|
152
|
+
)} to start your local development server and start building`.value
|
|
153
|
+
].filter((step) => Boolean(step)),
|
|
174
154
|
reference: [
|
|
175
|
-
"Building with Hydrogen: https://shopify.dev/custom-storefronts/hydrogen"
|
|
155
|
+
"Building with Hydrogen: https://shopify.dev/docs/custom-storefronts/hydrogen/building/begin-development"
|
|
176
156
|
]
|
|
177
157
|
});
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
158
|
+
if (appTemplate === "demo-store") {
|
|
159
|
+
renderInfo({
|
|
160
|
+
headline: `Your project will display inventory from the Hydrogen Demo Store.`,
|
|
161
|
+
body: `To connect this project to your Shopify store\u2019s inventory, update \`${projectName}/.env\` with your store ID and Storefront API key.`
|
|
162
|
+
});
|
|
163
|
+
}
|
|
182
164
|
}
|
|
183
165
|
async function projectExists(projectDir) {
|
|
184
|
-
|
|
185
|
-
return await file.exists(projectDir) && await file.isDirectory(projectDir) && (await readdir(projectDir)).length > 0;
|
|
166
|
+
return await fileExists(projectDir) && await isDirectory(projectDir) && (await readdir(projectDir)).length > 0;
|
|
186
167
|
}
|
|
187
168
|
function supressNodeExperimentalWarnings() {
|
|
188
169
|
const warningListener = process.listeners("warning")[0];
|
|
@@ -1,21 +1,24 @@
|
|
|
1
1
|
import { describe, beforeEach, vi, it, expect } from 'vitest';
|
|
2
2
|
import { temporaryDirectoryTask } from 'tempy';
|
|
3
3
|
import { runInit } from './init.js';
|
|
4
|
-
import {
|
|
4
|
+
import { renderSelectPrompt, renderConfirmationPrompt, renderTextPrompt, renderInfo } from '@shopify/cli-kit/node/ui';
|
|
5
|
+
import { outputContent } from '@shopify/cli-kit/node/output';
|
|
5
6
|
import { installNodeModules } from '@shopify/cli-kit/node/node-package-manager';
|
|
6
7
|
|
|
7
8
|
describe("init", () => {
|
|
8
9
|
beforeEach(() => {
|
|
9
10
|
vi.resetAllMocks();
|
|
10
|
-
vi.mock("@shopify/cli-kit");
|
|
11
|
-
vi.mock("../../
|
|
12
|
-
vi.mock("../../
|
|
11
|
+
vi.mock("@shopify/cli-kit/node/output");
|
|
12
|
+
vi.mock("../../lib/transpile-ts.js");
|
|
13
|
+
vi.mock("../../lib/template-downloader.js", async () => ({
|
|
13
14
|
getLatestTemplates: () => Promise.resolve({})
|
|
14
15
|
}));
|
|
15
16
|
vi.mock("@shopify/cli-kit/node/node-package-manager");
|
|
16
|
-
vi.mocked(
|
|
17
|
-
|
|
18
|
-
);
|
|
17
|
+
vi.mocked(outputContent).mockImplementation(() => ({
|
|
18
|
+
value: ""
|
|
19
|
+
}));
|
|
20
|
+
vi.mock("@shopify/cli-kit/node/ui");
|
|
21
|
+
vi.mock("@shopify/cli-kit/node/fs");
|
|
19
22
|
});
|
|
20
23
|
const defaultOptions = (stubs) => ({
|
|
21
24
|
template: "hello-world",
|
|
@@ -24,11 +27,27 @@ describe("init", () => {
|
|
|
24
27
|
...stubs
|
|
25
28
|
});
|
|
26
29
|
describe.each([
|
|
27
|
-
{
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
30
|
+
{
|
|
31
|
+
flag: "template",
|
|
32
|
+
value: "hello-world",
|
|
33
|
+
condition: { fn: renderSelectPrompt, match: /template/i }
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
flag: "installDeps",
|
|
37
|
+
value: true,
|
|
38
|
+
condition: { fn: renderConfirmationPrompt, match: /install dependencies/i }
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
flag: "language",
|
|
42
|
+
value: "ts",
|
|
43
|
+
condition: { fn: renderSelectPrompt, match: /language/i }
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
flag: "path",
|
|
47
|
+
value: "./my-app",
|
|
48
|
+
condition: { fn: renderTextPrompt, match: /where/i }
|
|
49
|
+
}
|
|
50
|
+
])("flag $flag", ({ flag, value, condition }) => {
|
|
32
51
|
it(`does not prompt the user for ${flag} when a value is passed in options`, async () => {
|
|
33
52
|
await temporaryDirectoryTask(async (tmpDir) => {
|
|
34
53
|
const options = defaultOptions({
|
|
@@ -36,12 +55,10 @@ describe("init", () => {
|
|
|
36
55
|
[flag]: value
|
|
37
56
|
});
|
|
38
57
|
await runInit(options);
|
|
39
|
-
expect(
|
|
40
|
-
expect.
|
|
41
|
-
expect.
|
|
42
|
-
|
|
43
|
-
})
|
|
44
|
-
])
|
|
58
|
+
expect(condition.fn).not.toHaveBeenCalledWith(
|
|
59
|
+
expect.objectContaining({
|
|
60
|
+
message: expect.stringMatching(condition.match)
|
|
61
|
+
})
|
|
45
62
|
);
|
|
46
63
|
});
|
|
47
64
|
});
|
|
@@ -52,12 +69,10 @@ describe("init", () => {
|
|
|
52
69
|
[flag]: void 0
|
|
53
70
|
});
|
|
54
71
|
await runInit(options);
|
|
55
|
-
expect(
|
|
56
|
-
expect.
|
|
57
|
-
expect.
|
|
58
|
-
|
|
59
|
-
})
|
|
60
|
-
])
|
|
72
|
+
expect(condition.fn).toHaveBeenCalledWith(
|
|
73
|
+
expect.objectContaining({
|
|
74
|
+
message: expect.stringMatching(condition.match)
|
|
75
|
+
})
|
|
61
76
|
);
|
|
62
77
|
});
|
|
63
78
|
});
|
|
@@ -76,4 +91,36 @@ describe("init", () => {
|
|
|
76
91
|
expect(installNodeModules).not.toHaveBeenCalled();
|
|
77
92
|
});
|
|
78
93
|
});
|
|
94
|
+
it("displays inventory information when using the demo-store template", async () => {
|
|
95
|
+
await temporaryDirectoryTask(async (tmpDir) => {
|
|
96
|
+
const options = defaultOptions({
|
|
97
|
+
installDeps: false,
|
|
98
|
+
path: tmpDir,
|
|
99
|
+
template: "demo-store"
|
|
100
|
+
});
|
|
101
|
+
await runInit(options);
|
|
102
|
+
expect(renderInfo).toHaveBeenCalledTimes(1);
|
|
103
|
+
expect(renderInfo).toHaveBeenCalledWith(
|
|
104
|
+
expect.objectContaining({
|
|
105
|
+
body: expect.stringContaining(
|
|
106
|
+
"To connect this project to your Shopify store\u2019s inventory"
|
|
107
|
+
),
|
|
108
|
+
headline: expect.stringContaining(
|
|
109
|
+
"Your project will display inventory from the Hydrogen Demo Store"
|
|
110
|
+
)
|
|
111
|
+
})
|
|
112
|
+
);
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
it("does not display inventory information when using non-demo-store templates", async () => {
|
|
116
|
+
await temporaryDirectoryTask(async (tmpDir) => {
|
|
117
|
+
const options = defaultOptions({
|
|
118
|
+
installDeps: false,
|
|
119
|
+
path: tmpDir,
|
|
120
|
+
template: "pizza-store"
|
|
121
|
+
});
|
|
122
|
+
await runInit(options);
|
|
123
|
+
expect(renderInfo).toHaveBeenCalledTimes(0);
|
|
124
|
+
});
|
|
125
|
+
});
|
|
79
126
|
});
|
|
@@ -4,8 +4,8 @@ import Command from '@shopify/cli-kit/node/base-command';
|
|
|
4
4
|
declare class Preview extends Command {
|
|
5
5
|
static description: string;
|
|
6
6
|
static flags: {
|
|
7
|
-
path: _oclif_core_lib_interfaces_parser_js.OptionFlag<string | undefined>;
|
|
8
|
-
port: _oclif_core_lib_interfaces_parser_js.OptionFlag<number>;
|
|
7
|
+
path: _oclif_core_lib_interfaces_parser_js.OptionFlag<string | undefined, _oclif_core_lib_interfaces_parser_js.CustomOptions>;
|
|
8
|
+
port: _oclif_core_lib_interfaces_parser_js.OptionFlag<number, _oclif_core_lib_interfaces_parser_js.CustomOptions>;
|
|
9
9
|
};
|
|
10
10
|
run(): Promise<void>;
|
|
11
11
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import Command from '@shopify/cli-kit/node/base-command';
|
|
2
|
-
import { muteDevLogs } from '../../
|
|
3
|
-
import { getProjectPaths } from '../../
|
|
4
|
-
import { commonFlags } from '../../
|
|
5
|
-
import { startMiniOxygen } from '../../
|
|
2
|
+
import { muteDevLogs } from '../../lib/log.js';
|
|
3
|
+
import { getProjectPaths } from '../../lib/config.js';
|
|
4
|
+
import { commonFlags } from '../../lib/flags.js';
|
|
5
|
+
import { startMiniOxygen } from '../../lib/mini-oxygen.js';
|
|
6
6
|
|
|
7
7
|
class Preview extends Command {
|
|
8
8
|
static description = "Runs a Hydrogen storefront in an Oxygen worker for production.";
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import Command from '@shopify/cli-kit/node/base-command';
|
|
2
|
+
|
|
3
|
+
declare class Shortcut extends Command {
|
|
4
|
+
static description: string;
|
|
5
|
+
run(): Promise<void>;
|
|
6
|
+
}
|
|
7
|
+
declare function runCreateShortcut(): Promise<void>;
|
|
8
|
+
|
|
9
|
+
export { Shortcut as default, runCreateShortcut };
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import Command from '@shopify/cli-kit/node/base-command';
|
|
2
|
+
import { renderSuccess, renderFatalError } from '@shopify/cli-kit/node/ui';
|
|
3
|
+
import { isWindows, isGitBash, supportsShell, hasAlias, shellWriteFile, homeFileExists, shellRunScript } from '../../lib/shell.js';
|
|
4
|
+
|
|
5
|
+
const ALIAS_NAME = "h2";
|
|
6
|
+
class Shortcut extends Command {
|
|
7
|
+
static description = `Creates a global \`${ALIAS_NAME}\` shortcut for the Hydrogen CLI`;
|
|
8
|
+
async run() {
|
|
9
|
+
await runCreateShortcut();
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
async function runCreateShortcut() {
|
|
13
|
+
const shortcuts = isWindows() && !isGitBash() ? await createShortcutsForWindows() : await createShortcutsForUnix();
|
|
14
|
+
if (shortcuts.length > 0) {
|
|
15
|
+
renderSuccess({
|
|
16
|
+
headline: `Shortcut ready for the following shells: ${shortcuts.join(
|
|
17
|
+
", "
|
|
18
|
+
)}.
|
|
19
|
+
Restart your terminal session and run \`${ALIAS_NAME}\` from your local project.`
|
|
20
|
+
});
|
|
21
|
+
} else {
|
|
22
|
+
renderFatalError({
|
|
23
|
+
name: "error",
|
|
24
|
+
type: 0,
|
|
25
|
+
message: "No supported shell found.",
|
|
26
|
+
tryMessage: "Please create a shortcut manually."
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
const BASH_ZSH_COMMAND = `
|
|
31
|
+
# Shopify Hydrogen alias to local projects
|
|
32
|
+
alias ${ALIAS_NAME}='$(npm prefix -s)/node_modules/.bin/shopify hydrogen'`;
|
|
33
|
+
const FISH_FUNCTION = `
|
|
34
|
+
function ${ALIAS_NAME} --wraps='shopify hydrogen' --description 'Shortcut for the Hydrogen CLI'
|
|
35
|
+
set npmPrefix (npm prefix -s)
|
|
36
|
+
$npmPrefix/node_modules/.bin/shopify hydrogen $argv
|
|
37
|
+
end
|
|
38
|
+
`;
|
|
39
|
+
async function createShortcutsForUnix() {
|
|
40
|
+
const shells = [];
|
|
41
|
+
if (supportsShell("zsh") && (hasAlias(ALIAS_NAME, "~/.zshrc") || shellWriteFile("~/.zshrc", BASH_ZSH_COMMAND, true))) {
|
|
42
|
+
shells.push("zsh");
|
|
43
|
+
}
|
|
44
|
+
if (supportsShell("bash") && (hasAlias(ALIAS_NAME, "~/.bashrc") || shellWriteFile("~/.bashrc", BASH_ZSH_COMMAND, true))) {
|
|
45
|
+
shells.push("bash");
|
|
46
|
+
}
|
|
47
|
+
if (supportsShell("fish") && await homeFileExists("~/.config/fish/functions") && shellWriteFile(`~/.config/fish/functions/${ALIAS_NAME}.fish`, FISH_FUNCTION)) {
|
|
48
|
+
shells.push("fish");
|
|
49
|
+
}
|
|
50
|
+
return shells;
|
|
51
|
+
}
|
|
52
|
+
const PS_FUNCTION = `function Invoke-Local-H2 {$npmPrefix = npm prefix -s; Invoke-Expression "$npmPrefix\\node_modules\\.bin\\shopify.ps1 hydrogen $Args"}; Set-Alias -Name ${ALIAS_NAME} -Value Invoke-Local-H2`;
|
|
53
|
+
const PS_APPEND_PROFILE_COMMAND = `
|
|
54
|
+
if (!(Test-Path -Path $PROFILE)) {
|
|
55
|
+
New-Item -ItemType File -Path $PROFILE -Force
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
$profileContent = Get-Content -Path $PROFILE
|
|
59
|
+
if (!$profileContent -or $profileContent -NotLike '*Invoke-Local-H2*') {
|
|
60
|
+
Add-Content -Path $PROFILE -Value '${PS_FUNCTION}'
|
|
61
|
+
}
|
|
62
|
+
`;
|
|
63
|
+
async function createShortcutsForWindows() {
|
|
64
|
+
const shells = [];
|
|
65
|
+
if (shellRunScript(PS_APPEND_PROFILE_COMMAND, "powershell.exe")) {
|
|
66
|
+
shells.push("PowerShell");
|
|
67
|
+
}
|
|
68
|
+
if (shellRunScript(PS_APPEND_PROFILE_COMMAND, "pwsh.exe")) {
|
|
69
|
+
shells.push("PowerShell 7+");
|
|
70
|
+
}
|
|
71
|
+
return shells;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export { Shortcut as default, runCreateShortcut };
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { describe, beforeEach, vi, afterEach, expect, it } from 'vitest';
|
|
2
|
+
import { runCreateShortcut } from './shortcut.js';
|
|
3
|
+
import { mockAndCaptureOutput } from '@shopify/cli-kit/node/testing/output';
|
|
4
|
+
import { supportsShell, isWindows, isGitBash } from '../../lib/shell.js';
|
|
5
|
+
import { execSync, exec } from 'child_process';
|
|
6
|
+
|
|
7
|
+
describe("shortcut", () => {
|
|
8
|
+
const outputMock = mockAndCaptureOutput();
|
|
9
|
+
beforeEach(() => {
|
|
10
|
+
vi.resetAllMocks();
|
|
11
|
+
vi.mock("child_process");
|
|
12
|
+
vi.mock("../../lib/shell.js", async () => {
|
|
13
|
+
return {
|
|
14
|
+
isWindows: vi.fn(),
|
|
15
|
+
isGitBash: vi.fn(),
|
|
16
|
+
supportsShell: vi.fn(),
|
|
17
|
+
shellWriteFile: () => true,
|
|
18
|
+
shellRunScript: () => true,
|
|
19
|
+
hasAlias: () => false,
|
|
20
|
+
homeFileExists: () => Promise.resolve(true)
|
|
21
|
+
};
|
|
22
|
+
});
|
|
23
|
+
vi.mocked(supportsShell).mockImplementation(
|
|
24
|
+
(shell) => !isWindows() || shell === "bash"
|
|
25
|
+
);
|
|
26
|
+
});
|
|
27
|
+
afterEach(() => {
|
|
28
|
+
outputMock.clear();
|
|
29
|
+
expect(execSync).toHaveBeenCalledTimes(0);
|
|
30
|
+
expect(exec).toHaveBeenCalledTimes(0);
|
|
31
|
+
});
|
|
32
|
+
it("creates aliases for Unix", async () => {
|
|
33
|
+
vi.mocked(isWindows).mockReturnValue(false);
|
|
34
|
+
await runCreateShortcut();
|
|
35
|
+
expect(outputMock.info()).toMatch(`zsh, bash, fish`);
|
|
36
|
+
expect(outputMock.error()).toBeFalsy();
|
|
37
|
+
});
|
|
38
|
+
it("creates aliases for Windows", async () => {
|
|
39
|
+
vi.mocked(isWindows).mockReturnValue(true);
|
|
40
|
+
await runCreateShortcut();
|
|
41
|
+
expect(outputMock.info()).toMatch(`PowerShell, PowerShell 7+`);
|
|
42
|
+
expect(outputMock.error()).toBeFalsy();
|
|
43
|
+
});
|
|
44
|
+
it("creates aliases for Windows in Git Bash", async () => {
|
|
45
|
+
vi.mocked(isWindows).mockReturnValue(true);
|
|
46
|
+
vi.mocked(isGitBash).mockReturnValueOnce(true);
|
|
47
|
+
await runCreateShortcut();
|
|
48
|
+
expect(outputMock.info()).toMatch("bash");
|
|
49
|
+
expect(outputMock.error()).toBeFalsy();
|
|
50
|
+
});
|
|
51
|
+
it("warns when not finding shells", async () => {
|
|
52
|
+
vi.mocked(isWindows).mockReturnValue(false);
|
|
53
|
+
vi.mocked(supportsShell).mockReturnValue(false);
|
|
54
|
+
await runCreateShortcut();
|
|
55
|
+
expect(outputMock.info()).toBeFalsy();
|
|
56
|
+
expect(outputMock.error()).toBeTruthy();
|
|
57
|
+
});
|
|
58
|
+
});
|
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
type LoaderArgs,
|
|
3
|
+
type ErrorBoundaryComponent,
|
|
4
|
+
} from '@shopify/remix-oxygen';
|
|
5
|
+
import {useCatch, useRouteError, isRouteErrorResponse} from '@remix-run/react';
|
|
2
6
|
|
|
3
7
|
export const loader = ({request}: LoaderArgs) => {
|
|
4
8
|
const url = new URL(request.url);
|
|
@@ -14,6 +18,36 @@ export const loader = ({request}: LoaderArgs) => {
|
|
|
14
18
|
});
|
|
15
19
|
};
|
|
16
20
|
|
|
21
|
+
export const ErrorBoundaryV1: ErrorBoundaryComponent = ({error}) => {
|
|
22
|
+
console.error(error);
|
|
23
|
+
|
|
24
|
+
return <div>There was an error.</div>;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export function CatchBoundary() {
|
|
28
|
+
const caught = useCatch();
|
|
29
|
+
console.error(caught);
|
|
30
|
+
|
|
31
|
+
return (
|
|
32
|
+
<div>
|
|
33
|
+
There was an error. Status: {caught.status}. Message:{' '}
|
|
34
|
+
{caught.data?.message}
|
|
35
|
+
</div>
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function ErrorBoundary() {
|
|
40
|
+
const error = useRouteError();
|
|
41
|
+
|
|
42
|
+
if (isRouteErrorResponse(error)) {
|
|
43
|
+
console.error(error.status, error.statusText, error.data);
|
|
44
|
+
return <div>Route Error</div>;
|
|
45
|
+
} else {
|
|
46
|
+
console.error((error as Error).message);
|
|
47
|
+
return <div>Thrown Error</div>;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
17
51
|
function robotsTxtData({url}: {url: string}) {
|
|
18
52
|
const sitemapUrl = url ? `${url}/sitemap.xml` : undefined;
|
|
19
53
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import {flattenConnection} from '@shopify/hydrogen';
|
|
2
|
-
import type {LoaderArgs} from '@shopify/remix-oxygen';
|
|
2
|
+
import type {LoaderArgs, ErrorBoundaryComponent} from '@shopify/remix-oxygen';
|
|
3
|
+
import {useCatch, useRouteError, isRouteErrorResponse} from '@remix-run/react';
|
|
3
4
|
import {
|
|
4
5
|
CollectionConnection,
|
|
5
6
|
PageConnection,
|
|
@@ -34,7 +35,7 @@ export async function loader({request, context: {storefront}}: LoaderArgs) {
|
|
|
34
35
|
});
|
|
35
36
|
|
|
36
37
|
if (!data) {
|
|
37
|
-
throw new Response(
|
|
38
|
+
throw new Response('No data found', {status: 404});
|
|
38
39
|
}
|
|
39
40
|
|
|
40
41
|
return new Response(
|
|
@@ -50,6 +51,36 @@ export async function loader({request, context: {storefront}}: LoaderArgs) {
|
|
|
50
51
|
);
|
|
51
52
|
}
|
|
52
53
|
|
|
54
|
+
export const ErrorBoundaryV1: ErrorBoundaryComponent = ({error}) => {
|
|
55
|
+
console.error(error);
|
|
56
|
+
|
|
57
|
+
return <div>There was an error.</div>;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
export function CatchBoundary() {
|
|
61
|
+
const caught = useCatch();
|
|
62
|
+
console.error(caught);
|
|
63
|
+
|
|
64
|
+
return (
|
|
65
|
+
<div>
|
|
66
|
+
There was an error. Status: {caught.status}. Message:{' '}
|
|
67
|
+
{caught.data?.message}
|
|
68
|
+
</div>
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function ErrorBoundary() {
|
|
73
|
+
const error = useRouteError();
|
|
74
|
+
|
|
75
|
+
if (isRouteErrorResponse(error)) {
|
|
76
|
+
console.error(error.status, error.statusText, error.data);
|
|
77
|
+
return <div>Route Error</div>;
|
|
78
|
+
} else {
|
|
79
|
+
console.error((error as Error).message);
|
|
80
|
+
return <div>Thrown Error</div>;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
53
84
|
function xmlEncode(string: string) {
|
|
54
85
|
return string.replace(/[&<>'"]/g, (char) => `&#${char.charCodeAt(0)};`);
|
|
55
86
|
}
|