@shopify/cli-hydrogen 4.0.9 → 4.1.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.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/{utils → lib}/missing-routes.d.ts +3 -1
- package/dist/{utils → lib}/missing-routes.js +7 -6
- package/dist/lib/missing-routes.test.d.ts +1 -0
- package/dist/lib/missing-routes.test.js +45 -0
- 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}/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
|
@@ -4,12 +4,12 @@ import Command from '@shopify/cli-kit/node/base-command';
|
|
|
4
4
|
declare class Build extends Command {
|
|
5
5
|
static description: string;
|
|
6
6
|
static flags: {
|
|
7
|
-
path: _oclif_core_lib_interfaces_parser_js.OptionFlag<string | undefined>;
|
|
7
|
+
path: _oclif_core_lib_interfaces_parser_js.OptionFlag<string | undefined, _oclif_core_lib_interfaces_parser_js.CustomOptions>;
|
|
8
8
|
sourcemap: _oclif_core_lib_interfaces_parser_js.BooleanFlag<boolean>;
|
|
9
9
|
"disable-route-warning": _oclif_core_lib_interfaces_parser_js.BooleanFlag<boolean>;
|
|
10
|
-
base: _oclif_core_lib_interfaces_parser_js.OptionFlag<unknown>;
|
|
11
|
-
entry: _oclif_core_lib_interfaces_parser_js.OptionFlag<unknown>;
|
|
12
|
-
target: _oclif_core_lib_interfaces_parser_js.OptionFlag<unknown>;
|
|
10
|
+
base: _oclif_core_lib_interfaces_parser_js.OptionFlag<unknown, _oclif_core_lib_interfaces_parser_js.CustomOptions>;
|
|
11
|
+
entry: _oclif_core_lib_interfaces_parser_js.OptionFlag<unknown, _oclif_core_lib_interfaces_parser_js.CustomOptions>;
|
|
12
|
+
target: _oclif_core_lib_interfaces_parser_js.OptionFlag<unknown, _oclif_core_lib_interfaces_parser_js.CustomOptions>;
|
|
13
13
|
};
|
|
14
14
|
run(): Promise<void>;
|
|
15
15
|
}
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import path from 'path';
|
|
2
|
-
import {
|
|
3
|
-
import
|
|
4
|
-
import { getProjectPaths, getRemixConfig } from '../../
|
|
5
|
-
import { commonFlags, deprecated, flagsToCamelObject } from '../../
|
|
2
|
+
import { outputInfo, outputContent, outputToken, outputWarn } from '@shopify/cli-kit/node/output';
|
|
3
|
+
import { rmdir, fileSize, copyFile } from '@shopify/cli-kit/node/fs';
|
|
4
|
+
import { getProjectPaths, getRemixConfig } from '../../lib/config.js';
|
|
5
|
+
import { commonFlags, deprecated, flagsToCamelObject } from '../../lib/flags.js';
|
|
6
6
|
import Command from '@shopify/cli-kit/node/base-command';
|
|
7
|
-
import Flags from '@oclif/core
|
|
8
|
-
import { checkLockfileStatus } from '../../
|
|
9
|
-
import { findMissingRoutes } from '../../
|
|
7
|
+
import { Flags } from '@oclif/core';
|
|
8
|
+
import { checkLockfileStatus } from '../../lib/check-lockfile.js';
|
|
9
|
+
import { findMissingRoutes } from '../../lib/missing-routes.js';
|
|
10
10
|
import { getPackageManager } from '@shopify/cli-kit/node/node-package-manager';
|
|
11
|
+
import { colors } from '../../lib/colors.js';
|
|
11
12
|
|
|
12
13
|
const LOG_WORKER_BUILT = "\u{1F4E6} Worker built";
|
|
13
14
|
class Build extends Command {
|
|
@@ -46,9 +47,9 @@ async function runBuild({
|
|
|
46
47
|
console.time(LOG_WORKER_BUILT);
|
|
47
48
|
const [remixConfig] = await Promise.all([
|
|
48
49
|
getRemixConfig(root),
|
|
49
|
-
|
|
50
|
+
rmdir(buildPath, { force: true })
|
|
50
51
|
]);
|
|
51
|
-
|
|
52
|
+
outputInfo(`
|
|
52
53
|
\u{1F3D7}\uFE0F Building in ${process.env.NODE_ENV} mode...`);
|
|
53
54
|
const { build } = await import('@remix-run/dev/dist/compiler/build.js');
|
|
54
55
|
const { logCompileFailure } = await import('@remix-run/dev/dist/compiler/onCompileFailure.js');
|
|
@@ -65,14 +66,14 @@ async function runBuild({
|
|
|
65
66
|
]);
|
|
66
67
|
if (process.env.NODE_ENV !== "development") {
|
|
67
68
|
console.timeEnd(LOG_WORKER_BUILT);
|
|
68
|
-
const sizeMB = await
|
|
69
|
-
|
|
70
|
-
|
|
69
|
+
const sizeMB = await fileSize(buildPathWorkerFile) / (1024 * 1024);
|
|
70
|
+
outputInfo(
|
|
71
|
+
outputContent` ${colors.dim(
|
|
71
72
|
path.relative(root, buildPathWorkerFile)
|
|
72
|
-
)} ${
|
|
73
|
+
)} ${outputToken.yellow(sizeMB.toFixed(2))} MB\n`
|
|
73
74
|
);
|
|
74
75
|
if (sizeMB >= 1) {
|
|
75
|
-
|
|
76
|
+
outputWarn(
|
|
76
77
|
`\u{1F6A8} Worker bundle exceeds 1 MB! This can delay your worker response.${remixConfig.serverMinify ? "" : " Minify your bundle by adding `serverMinify: true` to remix.config.js."}
|
|
77
78
|
`
|
|
78
79
|
);
|
|
@@ -83,7 +84,7 @@ async function runBuild({
|
|
|
83
84
|
if (missingRoutes.length) {
|
|
84
85
|
const packageManager = await getPackageManager(root);
|
|
85
86
|
const exec = packageManager === "npm" ? "npx" : packageManager;
|
|
86
|
-
|
|
87
|
+
outputWarn(
|
|
87
88
|
`Heads up: Shopify stores have a number of standard routes that aren\u2019t set up yet.
|
|
88
89
|
Some functionality and backlinks might not work as expected until these are created or redirects are set up.
|
|
89
90
|
This build is missing ${missingRoutes.length} route${missingRoutes.length > 1 ? "s" : ""}. For more details, run \`${exec} shopify hydrogen check routes\`.
|
|
@@ -94,7 +95,7 @@ This build is missing ${missingRoutes.length} route${missingRoutes.length > 1 ?
|
|
|
94
95
|
process.exit(0);
|
|
95
96
|
}
|
|
96
97
|
async function copyPublicFiles(publicPath, buildPathClient) {
|
|
97
|
-
return
|
|
98
|
+
return copyFile(publicPath, buildPathClient);
|
|
98
99
|
}
|
|
99
100
|
|
|
100
101
|
export { copyPublicFiles, Build as default, runBuild };
|
|
@@ -4,14 +4,11 @@ import Command from '@shopify/cli-kit/node/base-command';
|
|
|
4
4
|
declare class GenerateRoute extends Command {
|
|
5
5
|
static description: string;
|
|
6
6
|
static flags: {
|
|
7
|
-
path: _oclif_core_lib_interfaces_parser_js.OptionFlag<string | undefined>;
|
|
7
|
+
path: _oclif_core_lib_interfaces_parser_js.OptionFlag<string | undefined, _oclif_core_lib_interfaces_parser_js.CustomOptions>;
|
|
8
8
|
};
|
|
9
9
|
static args: {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
required: boolean;
|
|
13
|
-
options: string[];
|
|
14
|
-
}[];
|
|
10
|
+
resource: _oclif_core_lib_interfaces_parser_js.Arg<string, Record<string, unknown>>;
|
|
11
|
+
};
|
|
15
12
|
run(): Promise<void>;
|
|
16
13
|
}
|
|
17
14
|
|
|
@@ -1,25 +1,26 @@
|
|
|
1
1
|
import Command from '@shopify/cli-kit/node/base-command';
|
|
2
|
-
import {
|
|
3
|
-
import { commonFlags } from '../../
|
|
4
|
-
import { getRemixConfig } from '../../
|
|
5
|
-
import { logMissingRoutes, findMissingRoutes } from '../../
|
|
2
|
+
import { resolvePath } from '@shopify/cli-kit/node/path';
|
|
3
|
+
import { commonFlags } from '../../lib/flags.js';
|
|
4
|
+
import { getRemixConfig } from '../../lib/config.js';
|
|
5
|
+
import { logMissingRoutes, findMissingRoutes } from '../../lib/missing-routes.js';
|
|
6
|
+
import { Args } from '@oclif/core';
|
|
6
7
|
|
|
7
8
|
class GenerateRoute extends Command {
|
|
8
9
|
static description = "Returns diagnostic information about a Hydrogen storefront.";
|
|
9
10
|
static flags = {
|
|
10
11
|
path: commonFlags.path
|
|
11
12
|
};
|
|
12
|
-
static args =
|
|
13
|
-
{
|
|
13
|
+
static args = {
|
|
14
|
+
resource: Args.string({
|
|
14
15
|
name: "resource",
|
|
15
16
|
description: `The resource to check. Currently only 'routes' is supported.`,
|
|
16
17
|
required: true,
|
|
17
18
|
options: ["routes"]
|
|
18
|
-
}
|
|
19
|
-
|
|
19
|
+
})
|
|
20
|
+
};
|
|
20
21
|
async run() {
|
|
21
22
|
const { flags, args } = await this.parse(GenerateRoute);
|
|
22
|
-
const directory = flags.path ?
|
|
23
|
+
const directory = flags.path ? resolvePath(flags.path) : process.cwd();
|
|
23
24
|
if (args.resource === "routes") {
|
|
24
25
|
await runCheckRoutes({ directory });
|
|
25
26
|
} else {
|
|
@@ -4,10 +4,10 @@ import Command from '@shopify/cli-kit/node/base-command';
|
|
|
4
4
|
declare class Dev 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
|
"disable-virtual-routes": _oclif_core_lib_interfaces_parser_js.BooleanFlag<boolean>;
|
|
10
|
-
host: _oclif_core_lib_interfaces_parser_js.OptionFlag<unknown>;
|
|
10
|
+
host: _oclif_core_lib_interfaces_parser_js.OptionFlag<unknown, _oclif_core_lib_interfaces_parser_js.CustomOptions>;
|
|
11
11
|
};
|
|
12
12
|
run(): Promise<void>;
|
|
13
13
|
}
|
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
import path from 'path';
|
|
2
2
|
import fs from 'fs/promises';
|
|
3
|
-
import {
|
|
3
|
+
import { outputInfo } from '@shopify/cli-kit/node/output';
|
|
4
|
+
import { fileExists } from '@shopify/cli-kit/node/fs';
|
|
4
5
|
import { copyPublicFiles } from './build.js';
|
|
5
|
-
import { getProjectPaths, getRemixConfig } from '../../
|
|
6
|
-
import { muteDevLogs } from '../../
|
|
7
|
-
import { commonFlags, deprecated, flagsToCamelObject } from '../../
|
|
6
|
+
import { getProjectPaths, getRemixConfig } from '../../lib/config.js';
|
|
7
|
+
import { muteDevLogs } from '../../lib/log.js';
|
|
8
|
+
import { commonFlags, deprecated, flagsToCamelObject } from '../../lib/flags.js';
|
|
8
9
|
import Command from '@shopify/cli-kit/node/base-command';
|
|
9
|
-
import Flags from '@oclif/core
|
|
10
|
-
import { startMiniOxygen } from '../../
|
|
11
|
-
import { checkHydrogenVersion } from '../../
|
|
12
|
-
import { addVirtualRoutes } from '../../
|
|
10
|
+
import { Flags } from '@oclif/core';
|
|
11
|
+
import { startMiniOxygen } from '../../lib/mini-oxygen.js';
|
|
12
|
+
import { checkHydrogenVersion } from '../../lib/check-version.js';
|
|
13
|
+
import { addVirtualRoutes } from '../../lib/virtual-routes.js';
|
|
13
14
|
|
|
14
15
|
const LOG_INITIAL_BUILD = "\n\u{1F3C1} Initial build";
|
|
15
16
|
const LOG_REBUILDING = "\u{1F9F1} Rebuilding...";
|
|
@@ -48,11 +49,11 @@ async function runDev({
|
|
|
48
49
|
const config = await getRemixConfig(root);
|
|
49
50
|
return disableVirtualRoutes ? config : addVirtualRoutes(config);
|
|
50
51
|
};
|
|
51
|
-
const getFilePaths = (
|
|
52
|
-
const fileRelative = path.relative(root,
|
|
52
|
+
const getFilePaths = (file) => {
|
|
53
|
+
const fileRelative = path.relative(root, file);
|
|
53
54
|
return [fileRelative, path.resolve(root, fileRelative)];
|
|
54
55
|
};
|
|
55
|
-
const serverBundleExists = () =>
|
|
56
|
+
const serverBundleExists = () => fileExists(buildPathWorkerFile);
|
|
56
57
|
let miniOxygenStarted = false;
|
|
57
58
|
async function safeStartMiniOxygen() {
|
|
58
59
|
if (miniOxygenStarted)
|
|
@@ -87,9 +88,9 @@ async function runDev({
|
|
|
87
88
|
console.timeEnd(LOG_INITIAL_BUILD);
|
|
88
89
|
await safeStartMiniOxygen();
|
|
89
90
|
},
|
|
90
|
-
async onFileCreated(
|
|
91
|
-
const [relative, absolute] = getFilePaths(
|
|
92
|
-
|
|
91
|
+
async onFileCreated(file) {
|
|
92
|
+
const [relative, absolute] = getFilePaths(file);
|
|
93
|
+
outputInfo(`
|
|
93
94
|
\u{1F4C4} File created: ${relative}`);
|
|
94
95
|
if (absolute.startsWith(publicPath)) {
|
|
95
96
|
await copyPublicFiles(
|
|
@@ -98,9 +99,9 @@ async function runDev({
|
|
|
98
99
|
);
|
|
99
100
|
}
|
|
100
101
|
},
|
|
101
|
-
async onFileChanged(
|
|
102
|
-
const [relative, absolute] = getFilePaths(
|
|
103
|
-
|
|
102
|
+
async onFileChanged(file) {
|
|
103
|
+
const [relative, absolute] = getFilePaths(file);
|
|
104
|
+
outputInfo(`
|
|
104
105
|
\u{1F4C4} File changed: ${relative}`);
|
|
105
106
|
if (absolute.startsWith(publicPath)) {
|
|
106
107
|
await copyPublicFiles(
|
|
@@ -109,16 +110,16 @@ async function runDev({
|
|
|
109
110
|
);
|
|
110
111
|
}
|
|
111
112
|
},
|
|
112
|
-
async onFileDeleted(
|
|
113
|
-
const [relative, absolute] = getFilePaths(
|
|
114
|
-
|
|
113
|
+
async onFileDeleted(file) {
|
|
114
|
+
const [relative, absolute] = getFilePaths(file);
|
|
115
|
+
outputInfo(`
|
|
115
116
|
\u{1F4C4} File deleted: ${relative}`);
|
|
116
117
|
if (absolute.startsWith(publicPath)) {
|
|
117
118
|
await fs.unlink(absolute.replace(publicPath, buildPathClient));
|
|
118
119
|
}
|
|
119
120
|
},
|
|
120
121
|
onRebuildStart() {
|
|
121
|
-
|
|
122
|
+
outputInfo(LOG_REBUILDING);
|
|
122
123
|
console.time(LOG_REBUILT);
|
|
123
124
|
},
|
|
124
125
|
async onRebuildFinish() {
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import Command from '@shopify/cli-kit/node/base-command';
|
|
2
|
+
|
|
3
|
+
declare class GenerateRouteShortcut extends Command {
|
|
4
|
+
static description: string;
|
|
5
|
+
static strict: boolean;
|
|
6
|
+
static hidden: boolean;
|
|
7
|
+
run(): Promise<void>;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export { GenerateRouteShortcut as default };
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import Command from '@shopify/cli-kit/node/base-command';
|
|
2
|
+
import GenerateRoute from './generate/route.js';
|
|
3
|
+
|
|
4
|
+
class GenerateRouteShortcut extends Command {
|
|
5
|
+
static description = "Shortcut for `hydrogen generate`. See `hydrogen generate --help` for more information.";
|
|
6
|
+
static strict = false;
|
|
7
|
+
static hidden = true;
|
|
8
|
+
async run() {
|
|
9
|
+
const [command, ...args] = this.argv;
|
|
10
|
+
if (command === "r" || command === "route") {
|
|
11
|
+
return new GenerateRoute(args, this.config).run();
|
|
12
|
+
}
|
|
13
|
+
throw new Error(`Invalid command argument "${command}".`);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export { GenerateRouteShortcut as default };
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as _oclif_core_lib_interfaces_parser_js from '@oclif/core/lib/interfaces/parser.js';
|
|
2
2
|
import Command from '@shopify/cli-kit/node/base-command';
|
|
3
|
+
import { RemixV2Flags } from '../../../lib/remix-version-interop.js';
|
|
3
4
|
|
|
4
5
|
declare const GENERATOR_TEMPLATES_DIR = "generator-templates";
|
|
5
6
|
interface Result {
|
|
@@ -8,27 +9,24 @@ interface Result {
|
|
|
8
9
|
declare class GenerateRoute extends Command {
|
|
9
10
|
static description: string;
|
|
10
11
|
static flags: {
|
|
11
|
-
adapter: _oclif_core_lib_interfaces_parser_js.OptionFlag<string | undefined>;
|
|
12
|
+
adapter: _oclif_core_lib_interfaces_parser_js.OptionFlag<string | undefined, _oclif_core_lib_interfaces_parser_js.CustomOptions>;
|
|
12
13
|
typescript: _oclif_core_lib_interfaces_parser_js.BooleanFlag<boolean>;
|
|
13
14
|
force: _oclif_core_lib_interfaces_parser_js.BooleanFlag<boolean>;
|
|
14
|
-
path: _oclif_core_lib_interfaces_parser_js.OptionFlag<string | undefined>;
|
|
15
|
+
path: _oclif_core_lib_interfaces_parser_js.OptionFlag<string | undefined, _oclif_core_lib_interfaces_parser_js.CustomOptions>;
|
|
15
16
|
};
|
|
16
17
|
static hidden: true;
|
|
17
18
|
static args: {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
required: boolean;
|
|
21
|
-
options: string[];
|
|
22
|
-
env: string;
|
|
23
|
-
}[];
|
|
19
|
+
route: _oclif_core_lib_interfaces_parser_js.Arg<string, Record<string, unknown>>;
|
|
20
|
+
};
|
|
24
21
|
run(): Promise<void>;
|
|
25
22
|
}
|
|
26
|
-
declare function runGenerate(
|
|
23
|
+
declare function runGenerate(routeFrom: string, routeTo: string, { directory, typescript, force, adapter, templatesRoot, v2Flags, }: {
|
|
27
24
|
directory: string;
|
|
28
25
|
typescript?: boolean;
|
|
29
26
|
force?: boolean;
|
|
30
27
|
adapter?: string;
|
|
31
28
|
templatesRoot?: string;
|
|
29
|
+
v2Flags?: RemixV2Flags;
|
|
32
30
|
}): Promise<Result>;
|
|
33
31
|
|
|
34
32
|
export { GENERATOR_TEMPLATES_DIR, GenerateRoute as default, runGenerate };
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { fileURLToPath } from 'url';
|
|
2
2
|
import Command from '@shopify/cli-kit/node/base-command';
|
|
3
|
-
import {
|
|
3
|
+
import { fileExists, readFile, mkdir, writeFile } from '@shopify/cli-kit/node/fs';
|
|
4
|
+
import { resolvePath, joinPath, relativePath, relativizePath, dirname } from '@shopify/cli-kit/node/path';
|
|
4
5
|
import { AbortError } from '@shopify/cli-kit/node/error';
|
|
5
|
-
import { renderSuccess } from '@shopify/cli-kit/node/ui';
|
|
6
|
-
import { commonFlags } from '../../../
|
|
7
|
-
import Flags from '@oclif/core
|
|
8
|
-
import { transpileFile, format, resolveFormatConfig } from '../../../
|
|
6
|
+
import { renderSuccess, renderConfirmationPrompt } from '@shopify/cli-kit/node/ui';
|
|
7
|
+
import { commonFlags } from '../../../lib/flags.js';
|
|
8
|
+
import { Flags, Args } from '@oclif/core';
|
|
9
|
+
import { transpileFile, format, resolveFormatConfig } from '../../../lib/transpile-ts.js';
|
|
10
|
+
import { getV2Flags, convertRouteToV2, convertTemplateToRemixVersion } from '../../../lib/remix-version-interop.js';
|
|
9
11
|
|
|
10
12
|
const GENERATOR_TEMPLATES_DIR = "generator-templates";
|
|
11
13
|
const ROUTE_MAP = {
|
|
@@ -35,37 +37,43 @@ class GenerateRoute extends Command {
|
|
|
35
37
|
path: commonFlags.path
|
|
36
38
|
};
|
|
37
39
|
static hidden;
|
|
38
|
-
static args =
|
|
39
|
-
{
|
|
40
|
+
static args = {
|
|
41
|
+
route: Args.string({
|
|
40
42
|
name: "route",
|
|
41
43
|
description: `The route to generate. One of ${ROUTES.join()}.`,
|
|
42
44
|
required: true,
|
|
43
45
|
options: ROUTES,
|
|
44
46
|
env: "SHOPIFY_HYDROGEN_ARG_ROUTE"
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
+
})
|
|
48
|
+
};
|
|
47
49
|
async run() {
|
|
48
50
|
const result = /* @__PURE__ */ new Map();
|
|
49
|
-
const {
|
|
50
|
-
|
|
51
|
-
|
|
51
|
+
const {
|
|
52
|
+
flags,
|
|
53
|
+
args: { route }
|
|
54
|
+
} = await this.parse(GenerateRoute);
|
|
52
55
|
const routePath = route === "all" ? Object.values(ROUTE_MAP).flat() : ROUTE_MAP[route];
|
|
53
56
|
if (!routePath) {
|
|
54
57
|
throw new AbortError(
|
|
55
58
|
`No route found for ${route}. Try one of ${ROUTES.join()}.`
|
|
56
59
|
);
|
|
57
60
|
}
|
|
58
|
-
const
|
|
61
|
+
const directory = flags.path ? resolvePath(flags.path) : process.cwd();
|
|
62
|
+
const isTypescript = flags.typescript || await fileExists(joinPath(directory, "tsconfig.json"));
|
|
59
63
|
const routesArray = Array.isArray(routePath) ? routePath : [routePath];
|
|
60
64
|
try {
|
|
65
|
+
const { isV2RouteConvention, ...v2Flags } = await getV2Flags(directory);
|
|
61
66
|
for (const item of routesArray) {
|
|
67
|
+
const routeFrom = item;
|
|
68
|
+
const routeTo = isV2RouteConvention ? convertRouteToV2(item) : item;
|
|
62
69
|
result.set(
|
|
63
|
-
|
|
64
|
-
await runGenerate(
|
|
70
|
+
routeTo,
|
|
71
|
+
await runGenerate(routeFrom, routeTo, {
|
|
65
72
|
directory,
|
|
66
73
|
typescript: isTypescript,
|
|
67
74
|
force: flags.force,
|
|
68
|
-
adapter: flags.adapter
|
|
75
|
+
adapter: flags.adapter,
|
|
76
|
+
v2Flags
|
|
69
77
|
})
|
|
70
78
|
);
|
|
71
79
|
}
|
|
@@ -81,62 +89,56 @@ class GenerateRoute extends Command {
|
|
|
81
89
|
body: {
|
|
82
90
|
list: {
|
|
83
91
|
items: Array.from(result.entries()).map(
|
|
84
|
-
([
|
|
92
|
+
([path, { operation }]) => `[${operation}] app/routes${path}${extension}`
|
|
85
93
|
)
|
|
86
94
|
}
|
|
87
95
|
}
|
|
88
96
|
});
|
|
89
97
|
}
|
|
90
98
|
}
|
|
91
|
-
async function runGenerate(
|
|
99
|
+
async function runGenerate(routeFrom, routeTo, {
|
|
92
100
|
directory,
|
|
93
101
|
typescript,
|
|
94
102
|
force,
|
|
95
103
|
adapter,
|
|
96
|
-
templatesRoot = fileURLToPath(new URL("../../../", import.meta.url))
|
|
104
|
+
templatesRoot = fileURLToPath(new URL("../../../", import.meta.url)),
|
|
105
|
+
v2Flags = {}
|
|
97
106
|
}) {
|
|
98
107
|
let operation;
|
|
99
108
|
const extension = typescript ? ".tsx" : ".jsx";
|
|
100
|
-
const templatePath =
|
|
109
|
+
const templatePath = joinPath(
|
|
101
110
|
templatesRoot,
|
|
102
111
|
GENERATOR_TEMPLATES_DIR,
|
|
103
112
|
"routes",
|
|
104
|
-
`${
|
|
113
|
+
`${routeFrom}.tsx`
|
|
105
114
|
);
|
|
106
|
-
const destinationPath =
|
|
115
|
+
const destinationPath = joinPath(
|
|
107
116
|
directory,
|
|
108
117
|
"app",
|
|
109
118
|
"routes",
|
|
110
|
-
`${
|
|
119
|
+
`${routeTo}${extension}`
|
|
111
120
|
);
|
|
112
|
-
const relativeDestinationPath =
|
|
113
|
-
if (!force && await
|
|
114
|
-
const
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
name: "value",
|
|
122
|
-
message: `The file ${path.relativize(
|
|
123
|
-
relativeDestinationPath
|
|
124
|
-
)} already exists. Do you want to overwrite it?`,
|
|
125
|
-
choices: options
|
|
126
|
-
}
|
|
127
|
-
]);
|
|
128
|
-
operation = choice.value === "skip" ? "skipped" : "overwritten";
|
|
121
|
+
const relativeDestinationPath = relativePath(directory, destinationPath);
|
|
122
|
+
if (!force && await fileExists(destinationPath)) {
|
|
123
|
+
const shouldOverwrite = await renderConfirmationPrompt({
|
|
124
|
+
message: `The file ${relativizePath(
|
|
125
|
+
relativeDestinationPath
|
|
126
|
+
)} already exists. Do you want to overwrite it?`,
|
|
127
|
+
defaultValue: false
|
|
128
|
+
});
|
|
129
|
+
operation = shouldOverwrite ? "overwritten" : "skipped";
|
|
129
130
|
if (operation === "skipped") {
|
|
130
131
|
return { operation };
|
|
131
132
|
}
|
|
132
133
|
} else {
|
|
133
134
|
operation = "generated";
|
|
134
135
|
}
|
|
135
|
-
let templateContent = await
|
|
136
|
+
let templateContent = await readFile(templatePath);
|
|
137
|
+
templateContent = convertTemplateToRemixVersion(templateContent, v2Flags);
|
|
136
138
|
if (!typescript) {
|
|
137
|
-
const jsConfigPath =
|
|
138
|
-
const config = await
|
|
139
|
-
(await
|
|
139
|
+
const jsConfigPath = joinPath(directory, "jsconfig.json");
|
|
140
|
+
const config = await fileExists(jsConfigPath) ? JSON.parse(
|
|
141
|
+
(await readFile(jsConfigPath, { encoding: "utf8" })).replace(
|
|
140
142
|
/^\s*\/\/.*$/gm,
|
|
141
143
|
""
|
|
142
144
|
)
|
|
@@ -154,10 +156,10 @@ async function runGenerate(route, {
|
|
|
154
156
|
await resolveFormatConfig(destinationPath),
|
|
155
157
|
destinationPath
|
|
156
158
|
);
|
|
157
|
-
if (!await
|
|
158
|
-
await
|
|
159
|
+
if (!await fileExists(dirname(destinationPath))) {
|
|
160
|
+
await mkdir(dirname(destinationPath));
|
|
159
161
|
}
|
|
160
|
-
await
|
|
162
|
+
await writeFile(destinationPath, templateContent);
|
|
161
163
|
return {
|
|
162
164
|
operation
|
|
163
165
|
};
|
|
@@ -1,24 +1,16 @@
|
|
|
1
1
|
import { describe, beforeEach, vi, it, expect } from 'vitest';
|
|
2
2
|
import { temporaryDirectoryTask } from 'tempy';
|
|
3
3
|
import { runGenerate, GENERATOR_TEMPLATES_DIR } from './route.js';
|
|
4
|
-
import {
|
|
4
|
+
import { renderConfirmationPrompt } from '@shopify/cli-kit/node/ui';
|
|
5
|
+
import { readFile, mkdir, writeFile } from '@shopify/cli-kit/node/fs';
|
|
6
|
+
import { joinPath, dirname } from '@shopify/cli-kit/node/path';
|
|
7
|
+
import { convertRouteToV2 } from '../../../lib/remix-version-interop.js';
|
|
5
8
|
|
|
6
9
|
describe("generate/route", () => {
|
|
7
10
|
beforeEach(() => {
|
|
8
11
|
vi.resetAllMocks();
|
|
9
|
-
vi.mock("@shopify/cli-kit"
|
|
10
|
-
|
|
11
|
-
return {
|
|
12
|
-
...cliKit,
|
|
13
|
-
output: {
|
|
14
|
-
...cliKit.output,
|
|
15
|
-
success: vi.fn()
|
|
16
|
-
},
|
|
17
|
-
ui: {
|
|
18
|
-
prompt: vi.fn()
|
|
19
|
-
}
|
|
20
|
-
};
|
|
21
|
-
});
|
|
12
|
+
vi.mock("@shopify/cli-kit/node/output");
|
|
13
|
+
vi.mock("@shopify/cli-kit/node/ui");
|
|
22
14
|
});
|
|
23
15
|
it("generates a route file", async () => {
|
|
24
16
|
await temporaryDirectoryTask(async (tmpDir) => {
|
|
@@ -27,12 +19,30 @@ describe("generate/route", () => {
|
|
|
27
19
|
files: [],
|
|
28
20
|
templates: [[route, `const str = "hello world"`]]
|
|
29
21
|
});
|
|
30
|
-
await runGenerate(route, {
|
|
22
|
+
await runGenerate(route, route, {
|
|
31
23
|
directory: appRoot,
|
|
32
24
|
templatesRoot
|
|
33
25
|
});
|
|
34
26
|
expect(
|
|
35
|
-
await
|
|
27
|
+
await readFile(joinPath(appRoot, "app/routes", `${route}.jsx`))
|
|
28
|
+
).toContain(`const str = 'hello world'`);
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
it("generates a route file for Remix v2", async () => {
|
|
32
|
+
await temporaryDirectoryTask(async (tmpDir) => {
|
|
33
|
+
const route = "custom/path/$handle/index";
|
|
34
|
+
const { appRoot, templatesRoot } = await createHydrogen(tmpDir, {
|
|
35
|
+
files: [],
|
|
36
|
+
templates: [[route, `const str = "hello world"`]]
|
|
37
|
+
});
|
|
38
|
+
await runGenerate(route, convertRouteToV2(route), {
|
|
39
|
+
directory: appRoot,
|
|
40
|
+
templatesRoot
|
|
41
|
+
});
|
|
42
|
+
expect(
|
|
43
|
+
await readFile(
|
|
44
|
+
joinPath(appRoot, "app/routes", `custom.path.$handle._index.jsx`)
|
|
45
|
+
)
|
|
36
46
|
).toContain(`const str = 'hello world'`);
|
|
37
47
|
});
|
|
38
48
|
});
|
|
@@ -43,55 +53,53 @@ describe("generate/route", () => {
|
|
|
43
53
|
files: [],
|
|
44
54
|
templates: [[route, 'const str = "hello typescript"']]
|
|
45
55
|
});
|
|
46
|
-
await runGenerate(route, {
|
|
56
|
+
await runGenerate(route, route, {
|
|
47
57
|
directory: appRoot,
|
|
48
58
|
templatesRoot,
|
|
49
59
|
typescript: true
|
|
50
60
|
});
|
|
51
61
|
expect(
|
|
52
|
-
await
|
|
62
|
+
await readFile(joinPath(appRoot, "app/routes", `${route}.tsx`))
|
|
53
63
|
).toContain(`const str = 'hello typescript'`);
|
|
54
64
|
});
|
|
55
65
|
});
|
|
56
66
|
it("prompts the user if there the file already exists", async () => {
|
|
57
67
|
await temporaryDirectoryTask(async (tmpDir) => {
|
|
58
|
-
vi.mocked(
|
|
59
|
-
|
|
60
|
-
|
|
68
|
+
vi.mocked(renderConfirmationPrompt).mockImplementationOnce(
|
|
69
|
+
async () => true
|
|
70
|
+
);
|
|
61
71
|
const route = "page/$pageHandle";
|
|
62
72
|
const { appRoot, templatesRoot } = await createHydrogen(tmpDir, {
|
|
63
73
|
files: [[`app/routes/${route}.jsx`, 'const str = "I exist"']],
|
|
64
74
|
templates: [[route, 'const str = "hello world"']]
|
|
65
75
|
});
|
|
66
|
-
await runGenerate(route, {
|
|
76
|
+
await runGenerate(route, route, {
|
|
67
77
|
directory: appRoot,
|
|
68
78
|
templatesRoot
|
|
69
79
|
});
|
|
70
|
-
expect(
|
|
71
|
-
expect.
|
|
72
|
-
expect.
|
|
73
|
-
|
|
74
|
-
})
|
|
75
|
-
])
|
|
80
|
+
expect(renderConfirmationPrompt).toHaveBeenCalledWith(
|
|
81
|
+
expect.objectContaining({
|
|
82
|
+
message: expect.stringContaining("already exists")
|
|
83
|
+
})
|
|
76
84
|
);
|
|
77
85
|
});
|
|
78
86
|
});
|
|
79
87
|
it("does not prompt the user if the force property is true", async () => {
|
|
80
88
|
await temporaryDirectoryTask(async (tmpDir) => {
|
|
81
|
-
vi.mocked(
|
|
82
|
-
|
|
83
|
-
|
|
89
|
+
vi.mocked(renderConfirmationPrompt).mockImplementationOnce(
|
|
90
|
+
async () => true
|
|
91
|
+
);
|
|
84
92
|
const route = "page/$pageHandle";
|
|
85
93
|
const { appRoot, templatesRoot } = await createHydrogen(tmpDir, {
|
|
86
94
|
files: [[`app/routes/${route}.jsx`, 'const str = "I exist"']],
|
|
87
95
|
templates: [[route, 'const str = "hello world"']]
|
|
88
96
|
});
|
|
89
|
-
await runGenerate(route, {
|
|
97
|
+
await runGenerate(route, route, {
|
|
90
98
|
directory: appRoot,
|
|
91
99
|
templatesRoot,
|
|
92
100
|
force: true
|
|
93
101
|
});
|
|
94
|
-
expect(
|
|
102
|
+
expect(renderConfirmationPrompt).not.toHaveBeenCalled();
|
|
95
103
|
});
|
|
96
104
|
});
|
|
97
105
|
});
|
|
@@ -104,23 +112,23 @@ async function createHydrogen(directory, {
|
|
|
104
112
|
}) {
|
|
105
113
|
for (const item of files) {
|
|
106
114
|
const [filePath, fileContent] = item;
|
|
107
|
-
const fullFilePath =
|
|
108
|
-
await
|
|
109
|
-
await
|
|
115
|
+
const fullFilePath = joinPath(directory, "app", filePath);
|
|
116
|
+
await mkdir(dirname(fullFilePath));
|
|
117
|
+
await writeFile(fullFilePath, fileContent);
|
|
110
118
|
}
|
|
111
119
|
for (const item of templates) {
|
|
112
120
|
const [filePath, fileContent] = item;
|
|
113
|
-
const fullFilePath =
|
|
121
|
+
const fullFilePath = joinPath(
|
|
114
122
|
directory,
|
|
115
123
|
GENERATOR_TEMPLATES_DIR,
|
|
116
124
|
"routes",
|
|
117
125
|
`${filePath}.tsx`
|
|
118
126
|
);
|
|
119
|
-
await
|
|
120
|
-
await
|
|
127
|
+
await mkdir(dirname(fullFilePath));
|
|
128
|
+
await writeFile(fullFilePath, fileContent);
|
|
121
129
|
}
|
|
122
130
|
return {
|
|
123
|
-
appRoot:
|
|
131
|
+
appRoot: joinPath(directory, "app"),
|
|
124
132
|
templatesRoot: directory
|
|
125
133
|
};
|
|
126
134
|
}
|