@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.
Files changed (76) hide show
  1. package/dist/commands/hydrogen/build.d.ts +4 -4
  2. package/dist/commands/hydrogen/build.js +17 -16
  3. package/dist/commands/hydrogen/check.d.ts +3 -6
  4. package/dist/commands/hydrogen/check.js +10 -9
  5. package/dist/commands/hydrogen/dev.d.ts +3 -3
  6. package/dist/commands/hydrogen/dev.js +22 -21
  7. package/dist/commands/hydrogen/g.d.ts +10 -0
  8. package/dist/commands/hydrogen/g.js +17 -0
  9. package/dist/commands/hydrogen/generate/route.d.ts +7 -9
  10. package/dist/commands/hydrogen/generate/route.js +49 -47
  11. package/dist/commands/hydrogen/generate/route.test.js +48 -40
  12. package/dist/commands/hydrogen/generate/routes.d.ts +2 -2
  13. package/dist/commands/hydrogen/init.d.ts +3 -3
  14. package/dist/commands/hydrogen/init.js +74 -93
  15. package/dist/commands/hydrogen/init.test.js +71 -24
  16. package/dist/commands/hydrogen/preview.d.ts +2 -2
  17. package/dist/commands/hydrogen/preview.js +4 -4
  18. package/dist/commands/hydrogen/shortcut.d.ts +9 -0
  19. package/dist/commands/hydrogen/shortcut.js +74 -0
  20. package/dist/commands/hydrogen/shortcut.test.js +58 -0
  21. package/dist/generator-templates/routes/[robots.txt].tsx +35 -1
  22. package/dist/generator-templates/routes/[sitemap.xml].tsx +33 -2
  23. package/dist/generator-templates/routes/account/login.tsx +42 -13
  24. package/dist/generator-templates/routes/account/register.tsx +42 -13
  25. package/dist/generator-templates/routes/cart.tsx +42 -2
  26. package/dist/generator-templates/routes/collections/$collectionHandle.tsx +44 -5
  27. package/dist/generator-templates/routes/index.tsx +33 -0
  28. package/dist/generator-templates/routes/pages/$pageHandle.tsx +48 -10
  29. package/dist/generator-templates/routes/policies/$policyHandle.tsx +67 -14
  30. package/dist/generator-templates/routes/policies/index.tsx +54 -4
  31. package/dist/generator-templates/routes/products/$productHandle.tsx +44 -9
  32. package/dist/hooks/init.js +2 -2
  33. package/dist/{utils → lib}/check-lockfile.js +7 -4
  34. package/dist/{utils → lib}/check-lockfile.test.js +19 -28
  35. package/dist/{utils → lib}/check-version.test.js +3 -2
  36. package/dist/lib/colors.d.ts +8 -0
  37. package/dist/lib/colors.js +8 -0
  38. package/dist/{utils → lib}/config.js +9 -18
  39. package/dist/{utils → lib}/flags.d.ts +3 -3
  40. package/dist/{utils → lib}/flags.js +4 -4
  41. package/dist/{utils → lib}/mini-oxygen.js +14 -12
  42. package/dist/{utils → lib}/missing-routes.d.ts +3 -1
  43. package/dist/{utils → lib}/missing-routes.js +7 -6
  44. package/dist/lib/missing-routes.test.d.ts +1 -0
  45. package/dist/lib/missing-routes.test.js +45 -0
  46. package/dist/lib/remix-version-interop.d.ts +11 -0
  47. package/dist/lib/remix-version-interop.js +54 -0
  48. package/dist/lib/remix-version-interop.test.d.ts +1 -0
  49. package/dist/lib/remix-version-interop.test.js +93 -0
  50. package/dist/lib/shell.d.ts +12 -0
  51. package/dist/lib/shell.js +73 -0
  52. package/dist/lib/template-downloader.d.ts +6 -0
  53. package/dist/{utils → lib}/template-downloader.js +21 -16
  54. package/dist/{utils → lib}/transpile-ts.js +5 -5
  55. package/dist/lib/virtual-routes.test.d.ts +1 -0
  56. package/dist/virtual-routes/routes/index.jsx +2 -15
  57. package/dist/virtual-routes/virtual-root.jsx +5 -6
  58. package/oclif.manifest.json +1 -1
  59. package/package.json +11 -10
  60. package/dist/utils/template-downloader.d.ts +0 -11
  61. /package/dist/{utils/check-lockfile.test.d.ts → commands/hydrogen/shortcut.test.d.ts} +0 -0
  62. /package/dist/{utils → lib}/check-lockfile.d.ts +0 -0
  63. /package/dist/{utils/check-version.test.d.ts → lib/check-lockfile.test.d.ts} +0 -0
  64. /package/dist/{utils → lib}/check-version.d.ts +0 -0
  65. /package/dist/{utils → lib}/check-version.js +0 -0
  66. /package/dist/{utils/flags.test.d.ts → lib/check-version.test.d.ts} +0 -0
  67. /package/dist/{utils → lib}/config.d.ts +0 -0
  68. /package/dist/{utils/virtual-routes.test.d.ts → lib/flags.test.d.ts} +0 -0
  69. /package/dist/{utils → lib}/flags.test.js +0 -0
  70. /package/dist/{utils → lib}/log.d.ts +0 -0
  71. /package/dist/{utils → lib}/log.js +0 -0
  72. /package/dist/{utils → lib}/mini-oxygen.d.ts +0 -0
  73. /package/dist/{utils → lib}/transpile-ts.d.ts +0 -0
  74. /package/dist/{utils → lib}/virtual-routes.d.ts +0 -0
  75. /package/dist/{utils → lib}/virtual-routes.js +0 -0
  76. /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 { file, output } from '@shopify/cli-kit';
3
- import colors from '@shopify/cli-kit/node/colors';
4
- import { getProjectPaths, getRemixConfig } from '../../utils/config.js';
5
- import { commonFlags, deprecated, flagsToCamelObject } from '../../utils/flags.js';
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/lib/flags.js';
8
- import { checkLockfileStatus } from '../../utils/check-lockfile.js';
9
- import { findMissingRoutes } from '../../utils/missing-routes.js';
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
- file.rmdir(buildPath, { force: true })
50
+ rmdir(buildPath, { force: true })
50
51
  ]);
51
- output.info(`
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 file.size(buildPathWorkerFile) / (1024 * 1024);
69
- output.info(
70
- output.content` ${colors.dim(
69
+ const sizeMB = await fileSize(buildPathWorkerFile) / (1024 * 1024);
70
+ outputInfo(
71
+ outputContent` ${colors.dim(
71
72
  path.relative(root, buildPathWorkerFile)
72
- )} ${output.token.yellow(sizeMB.toFixed(2))} MB\n`
73
+ )} ${outputToken.yellow(sizeMB.toFixed(2))} MB\n`
73
74
  );
74
75
  if (sizeMB >= 1) {
75
- output.warn(
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
- output.warn(
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 file.copy(publicPath, buildPathClient);
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
- name: string;
11
- description: string;
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 { path } from '@shopify/cli-kit';
3
- import { commonFlags } from '../../utils/flags.js';
4
- import { getRemixConfig } from '../../utils/config.js';
5
- import { logMissingRoutes, findMissingRoutes } from '../../utils/missing-routes.js';
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 ? path.resolve(flags.path) : process.cwd();
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 { output, file } from '@shopify/cli-kit';
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 '../../utils/config.js';
6
- import { muteDevLogs } from '../../utils/log.js';
7
- import { commonFlags, deprecated, flagsToCamelObject } from '../../utils/flags.js';
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/lib/flags.js';
10
- import { startMiniOxygen } from '../../utils/mini-oxygen.js';
11
- import { checkHydrogenVersion } from '../../utils/check-version.js';
12
- import { addVirtualRoutes } from '../../utils/virtual-routes.js';
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 = (file2) => {
52
- const fileRelative = path.relative(root, file2);
52
+ const getFilePaths = (file) => {
53
+ const fileRelative = path.relative(root, file);
53
54
  return [fileRelative, path.resolve(root, fileRelative)];
54
55
  };
55
- const serverBundleExists = () => file.exists(buildPathWorkerFile);
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(file2) {
91
- const [relative, absolute] = getFilePaths(file2);
92
- output.info(`
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(file2) {
102
- const [relative, absolute] = getFilePaths(file2);
103
- output.info(`
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(file2) {
113
- const [relative, absolute] = getFilePaths(file2);
114
- output.info(`
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
- output.info(LOG_REBUILDING);
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
- name: string;
19
- description: string;
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(route: string, { directory, typescript, force, adapter, templatesRoot, }: {
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 { path, file, ui } from '@shopify/cli-kit';
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 '../../../utils/flags.js';
7
- import Flags from '@oclif/core/lib/flags.js';
8
- import { transpileFile, format, resolveFormatConfig } from '../../../utils/transpile-ts.js';
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 { flags, args } = await this.parse(GenerateRoute);
50
- const directory = flags.path ? path.resolve(flags.path) : process.cwd();
51
- const { route } = args;
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 isTypescript = flags.typescript || await file.exists(path.join(directory, "tsconfig.json"));
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
- item,
64
- await runGenerate(item, {
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
- ([path2, { operation }]) => `[${operation}] app/routes${path2}${extension}`
92
+ ([path, { operation }]) => `[${operation}] app/routes${path}${extension}`
85
93
  )
86
94
  }
87
95
  }
88
96
  });
89
97
  }
90
98
  }
91
- async function runGenerate(route, {
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 = path.join(
109
+ const templatePath = joinPath(
101
110
  templatesRoot,
102
111
  GENERATOR_TEMPLATES_DIR,
103
112
  "routes",
104
- `${route}.tsx`
113
+ `${routeFrom}.tsx`
105
114
  );
106
- const destinationPath = path.join(
115
+ const destinationPath = joinPath(
107
116
  directory,
108
117
  "app",
109
118
  "routes",
110
- `${route}${extension}`
119
+ `${routeTo}${extension}`
111
120
  );
112
- const relativeDestinationPath = path.relative(directory, destinationPath);
113
- if (!force && await file.exists(destinationPath)) {
114
- const options = [
115
- { name: "No", value: "skip" },
116
- { name: `Yes`, value: "overwrite" }
117
- ];
118
- const choice = await ui.prompt([
119
- {
120
- type: "select",
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 file.read(templatePath);
136
+ let templateContent = await readFile(templatePath);
137
+ templateContent = convertTemplateToRemixVersion(templateContent, v2Flags);
136
138
  if (!typescript) {
137
- const jsConfigPath = path.join(directory, "jsconfig.json");
138
- const config = await file.exists(jsConfigPath) ? JSON.parse(
139
- (await file.read(jsConfigPath, { encoding: "utf8" })).replace(
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 file.exists(path.dirname(destinationPath))) {
158
- await file.mkdir(path.dirname(destinationPath));
159
+ if (!await fileExists(dirname(destinationPath))) {
160
+ await mkdir(dirname(destinationPath));
159
161
  }
160
- await file.write(destinationPath, templateContent);
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 { file, path, ui } from '@shopify/cli-kit';
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", async () => {
10
- const cliKit = await vi.importActual("@shopify/cli-kit");
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 file.read(path.join(appRoot, "app/routes", `${route}.jsx`))
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 file.read(path.join(appRoot, "app/routes", `${route}.tsx`))
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(ui.prompt).mockImplementationOnce(async () => {
59
- return { value: "overwrite" };
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(ui.prompt).toHaveBeenCalledWith(
71
- expect.arrayContaining([
72
- expect.objectContaining({
73
- message: expect.stringContaining("already exists")
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(ui.prompt).mockImplementationOnce(async () => {
82
- return { value: "overwrite" };
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(ui.prompt).not.toHaveBeenCalled();
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 = path.join(directory, "app", filePath);
108
- await file.mkdir(path.dirname(fullFilePath));
109
- await file.write(fullFilePath, fileContent);
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 = path.join(
121
+ const fullFilePath = joinPath(
114
122
  directory,
115
123
  GENERATOR_TEMPLATES_DIR,
116
124
  "routes",
117
125
  `${filePath}.tsx`
118
126
  );
119
- await file.mkdir(path.dirname(fullFilePath));
120
- await file.write(fullFilePath, fileContent);
127
+ await mkdir(dirname(fullFilePath));
128
+ await writeFile(fullFilePath, fileContent);
121
129
  }
122
130
  return {
123
- appRoot: path.join(directory, "app"),
131
+ appRoot: joinPath(directory, "app"),
124
132
  templatesRoot: directory
125
133
  };
126
134
  }