@shopify/cli-hydrogen 4.0.8 → 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.
Files changed (76) hide show
  1. package/README.md +9 -0
  2. package/dist/commands/hydrogen/build.d.ts +4 -1
  3. package/dist/commands/hydrogen/build.js +21 -17
  4. package/dist/commands/hydrogen/check.d.ts +3 -6
  5. package/dist/commands/hydrogen/check.js +10 -9
  6. package/dist/commands/hydrogen/dev.d.ts +3 -2
  7. package/dist/commands/hydrogen/dev.js +24 -22
  8. package/dist/commands/hydrogen/g.d.ts +10 -0
  9. package/dist/commands/hydrogen/g.js +17 -0
  10. package/dist/commands/hydrogen/generate/route.d.ts +7 -9
  11. package/dist/commands/hydrogen/generate/route.js +49 -47
  12. package/dist/commands/hydrogen/generate/route.test.js +48 -40
  13. package/dist/commands/hydrogen/generate/routes.d.ts +2 -2
  14. package/dist/commands/hydrogen/init.d.ts +3 -3
  15. package/dist/commands/hydrogen/init.js +76 -95
  16. package/dist/commands/hydrogen/init.test.js +126 -0
  17. package/dist/commands/hydrogen/preview.d.ts +2 -2
  18. package/dist/commands/hydrogen/preview.js +4 -4
  19. package/dist/commands/hydrogen/shortcut.d.ts +9 -0
  20. package/dist/commands/hydrogen/shortcut.js +74 -0
  21. package/dist/commands/hydrogen/shortcut.test.js +58 -0
  22. package/dist/generator-templates/routes/[robots.txt].tsx +35 -1
  23. package/dist/generator-templates/routes/[sitemap.xml].tsx +45 -10
  24. package/dist/generator-templates/routes/account/login.tsx +42 -13
  25. package/dist/generator-templates/routes/account/register.tsx +42 -13
  26. package/dist/generator-templates/routes/cart.tsx +42 -2
  27. package/dist/generator-templates/routes/collections/$collectionHandle.tsx +44 -5
  28. package/dist/generator-templates/routes/index.tsx +33 -0
  29. package/dist/generator-templates/routes/pages/$pageHandle.tsx +48 -10
  30. package/dist/generator-templates/routes/policies/$policyHandle.tsx +67 -14
  31. package/dist/generator-templates/routes/policies/index.tsx +54 -4
  32. package/dist/generator-templates/routes/products/$productHandle.tsx +44 -9
  33. package/dist/hooks/init.js +2 -2
  34. package/dist/{utils → lib}/check-lockfile.js +7 -4
  35. package/dist/{utils → lib}/check-lockfile.test.js +19 -28
  36. package/dist/{utils → lib}/check-version.test.js +3 -2
  37. package/dist/lib/colors.d.ts +8 -0
  38. package/dist/lib/colors.js +8 -0
  39. package/dist/{utils → lib}/config.js +10 -19
  40. package/dist/{utils → lib}/flags.d.ts +9 -3
  41. package/dist/{utils → lib}/flags.js +19 -4
  42. package/dist/lib/flags.test.d.ts +1 -0
  43. package/dist/{utils → lib}/mini-oxygen.js +14 -12
  44. package/dist/{utils → lib}/missing-routes.js +1 -1
  45. package/dist/lib/remix-version-interop.d.ts +11 -0
  46. package/dist/lib/remix-version-interop.js +54 -0
  47. package/dist/lib/remix-version-interop.test.d.ts +1 -0
  48. package/dist/lib/remix-version-interop.test.js +93 -0
  49. package/dist/lib/shell.d.ts +12 -0
  50. package/dist/lib/shell.js +73 -0
  51. package/dist/lib/template-downloader.d.ts +6 -0
  52. package/dist/{utils → lib}/template-downloader.js +21 -16
  53. package/dist/{utils → lib}/transpile-ts.js +5 -5
  54. package/dist/lib/virtual-routes.test.d.ts +1 -0
  55. package/dist/virtual-routes/routes/index.jsx +2 -15
  56. package/dist/virtual-routes/virtual-root.jsx +5 -6
  57. package/oclif.manifest.json +1 -1
  58. package/package.json +11 -10
  59. package/dist/utils/template-downloader.d.ts +0 -11
  60. /package/dist/{utils/check-lockfile.test.d.ts → commands/hydrogen/init.test.d.ts} +0 -0
  61. /package/dist/{utils/check-version.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/flags.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/virtual-routes.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 → lib}/flags.test.js +0 -0
  69. /package/dist/{utils → lib}/log.d.ts +0 -0
  70. /package/dist/{utils → lib}/log.js +0 -0
  71. /package/dist/{utils → lib}/mini-oxygen.d.ts +0 -0
  72. /package/dist/{utils → lib}/missing-routes.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
@@ -1,21 +1,31 @@
1
- import {json, type MetaFunction, type LoaderArgs} from '@shopify/remix-oxygen';
2
- import {useLoaderData} from '@remix-run/react';
1
+ import {
2
+ json,
3
+ type MetaFunction,
4
+ type LoaderArgs,
5
+ type ErrorBoundaryComponent,
6
+ } from '@shopify/remix-oxygen';
7
+ import {
8
+ useLoaderData,
9
+ type V2_MetaFunction,
10
+ useCatch,
11
+ useRouteError,
12
+ isRouteErrorResponse,
13
+ } from '@remix-run/react';
14
+ import {Shop} from '@shopify/hydrogen/storefront-api-types';
3
15
 
4
- import {ShopPolicy} from '@shopify/hydrogen/storefront-api-types';
5
-
6
- export async function loader({request, params, context}: LoaderArgs) {
16
+ export async function loader({params, context}: LoaderArgs) {
7
17
  const handle = params.policyHandle;
8
18
 
9
19
  if (!handle) {
10
- throw new Response(null, {status: 404});
20
+ throw new Response('No handle was passed in', {status: 404});
11
21
  }
12
22
 
13
23
  const policyName = handle.replace(/-([a-z])/g, (_: unknown, m1: string) =>
14
24
  m1.toUpperCase(),
15
- );
25
+ ) as SelectedPolicies;
16
26
 
17
27
  const data = await context.storefront.query<{
18
- shop: Record<string, ShopPolicy>;
28
+ shop: Pick<Shop, SelectedPolicies>;
19
29
  }>(POLICY_CONTENT_QUERY, {
20
30
  variables: {
21
31
  privacyPolicy: false,
@@ -30,16 +40,20 @@ export async function loader({request, params, context}: LoaderArgs) {
30
40
  const policy = data.shop?.[policyName];
31
41
 
32
42
  if (!policy) {
33
- throw new Response(null, {status: 404});
43
+ throw new Response('Could not find the policy', {status: 404});
34
44
  }
35
45
 
36
46
  return json({policy});
37
47
  }
38
48
 
39
- export const meta: MetaFunction<typeof loader> = ({data}) => {
40
- return {
41
- title: data?.policy?.title ?? 'Policies',
42
- };
49
+ export const metaV1: MetaFunction<typeof loader> = ({data}) => {
50
+ const title = data?.policy?.title ?? 'Policies';
51
+ return {title};
52
+ };
53
+
54
+ export const meta: V2_MetaFunction<typeof loader> = ({data}) => {
55
+ const title = data?.policy?.title ?? 'Policies';
56
+ return [{title}];
43
57
  };
44
58
 
45
59
  export default function Policies() {
@@ -53,6 +67,36 @@ export default function Policies() {
53
67
  );
54
68
  }
55
69
 
70
+ export const ErrorBoundaryV1: ErrorBoundaryComponent = ({error}) => {
71
+ console.error(error);
72
+
73
+ return <div>There was an error.</div>;
74
+ };
75
+
76
+ export function CatchBoundary() {
77
+ const caught = useCatch();
78
+ console.error(caught);
79
+
80
+ return (
81
+ <div>
82
+ There was an error. Status: {caught.status}. Message:{' '}
83
+ {caught.data?.message}
84
+ </div>
85
+ );
86
+ }
87
+
88
+ export function ErrorBoundary() {
89
+ const error = useRouteError();
90
+
91
+ if (isRouteErrorResponse(error)) {
92
+ console.error(error.status, error.statusText, error.data);
93
+ return <div>Route Error</div>;
94
+ } else {
95
+ console.error((error as Error).message);
96
+ return <div>Thrown Error</div>;
97
+ }
98
+ }
99
+
56
100
  const POLICY_CONTENT_QUERY = `#graphql
57
101
  fragment Policy on ShopPolicy {
58
102
  body
@@ -62,7 +106,7 @@ const POLICY_CONTENT_QUERY = `#graphql
62
106
  url
63
107
  }
64
108
 
65
- query PoliciesQuery(
109
+ query policy_query(
66
110
  $language: LanguageCode
67
111
  $privacyPolicy: Boolean!
68
112
  $shippingPolicy: Boolean!
@@ -85,3 +129,12 @@ const POLICY_CONTENT_QUERY = `#graphql
85
129
  }
86
130
  }
87
131
  `;
132
+
133
+ const policies = [
134
+ 'privacyPolicy',
135
+ 'shippingPolicy',
136
+ 'refundPolicy',
137
+ 'termsOfService',
138
+ ] as const;
139
+
140
+ type SelectedPolicies = (typeof policies)[number];
@@ -1,10 +1,20 @@
1
- import {json, type LoaderArgs} from '@shopify/remix-oxygen';
2
- import {useLoaderData, Link} from '@remix-run/react';
3
- import type {ShopPolicy} from '@shopify/hydrogen/storefront-api-types';
1
+ import {
2
+ json,
3
+ type LoaderArgs,
4
+ type ErrorBoundaryComponent,
5
+ } from '@shopify/remix-oxygen';
6
+ import {
7
+ useLoaderData,
8
+ Link,
9
+ useCatch,
10
+ useRouteError,
11
+ isRouteErrorResponse,
12
+ } from '@remix-run/react';
13
+ import type {Shop} from '@shopify/hydrogen/storefront-api-types';
4
14
 
5
15
  export async function loader({context: {storefront}}: LoaderArgs) {
6
16
  const data = await storefront.query<{
7
- shop: Record<string, ShopPolicy>;
17
+ shop: Pick<Shop, SelectedPolicies>;
8
18
  }>(POLICIES_QUERY);
9
19
 
10
20
  const policies = Object.values(data.shop || {});
@@ -36,6 +46,36 @@ export default function Policies() {
36
46
  );
37
47
  }
38
48
 
49
+ export const ErrorBoundaryV1: ErrorBoundaryComponent = ({error}) => {
50
+ console.error(error);
51
+
52
+ return <div>There was an error.</div>;
53
+ };
54
+
55
+ export function CatchBoundary() {
56
+ const caught = useCatch();
57
+ console.error(caught);
58
+
59
+ return (
60
+ <div>
61
+ There was an error. Status: {caught.status}. Message:{' '}
62
+ {caught.data?.message}
63
+ </div>
64
+ );
65
+ }
66
+
67
+ export function ErrorBoundary() {
68
+ const error = useRouteError();
69
+
70
+ if (isRouteErrorResponse(error)) {
71
+ console.error(error.status, error.statusText, error.data);
72
+ return <div>Route Error</div>;
73
+ } else {
74
+ console.error((error as Error).message);
75
+ return <div>Thrown Error</div>;
76
+ }
77
+ }
78
+
39
79
  const POLICIES_QUERY = `#graphql
40
80
  fragment Policy on ShopPolicy {
41
81
  id
@@ -65,3 +105,13 @@ const POLICIES_QUERY = `#graphql
65
105
  }
66
106
  }
67
107
  `;
108
+
109
+ const policies = [
110
+ 'privacyPolicy',
111
+ 'shippingPolicy',
112
+ 'refundPolicy',
113
+ 'termsOfService',
114
+ 'subscriptionPolicy',
115
+ ] as const;
116
+
117
+ type SelectedPolicies = (typeof policies)[number];
@@ -1,15 +1,21 @@
1
- import {defer, type LoaderArgs} from '@shopify/remix-oxygen';
2
- import {useLoaderData} from '@remix-run/react';
3
- import type {
4
- ProductVariant,
5
- Product as ProductType,
6
- } from '@shopify/hydrogen/storefront-api-types';
1
+ import {
2
+ defer,
3
+ type LoaderArgs,
4
+ type ErrorBoundaryComponent,
5
+ } from '@shopify/remix-oxygen';
6
+ import {
7
+ useLoaderData,
8
+ useCatch,
9
+ useRouteError,
10
+ isRouteErrorResponse,
11
+ } from '@remix-run/react';
12
+ import type {Product as ProductType} from '@shopify/hydrogen/storefront-api-types';
7
13
 
8
14
  export async function loader({params, context}: LoaderArgs) {
9
15
  const {productHandle} = params;
10
16
 
11
17
  const {product} = await context.storefront.query<{
12
- product: ProductType & {selectedVariant?: ProductVariant};
18
+ product: Pick<ProductType, 'id' | 'title' | 'descriptionHtml' | 'vendor'>;
13
19
  }>(PRODUCT_QUERY, {
14
20
  variables: {
15
21
  handle: productHandle,
@@ -40,9 +46,38 @@ export default function Product() {
40
46
  );
41
47
  }
42
48
 
43
- const PRODUCT_QUERY = `#graphql
49
+ export const ErrorBoundaryV1: ErrorBoundaryComponent = ({error}) => {
50
+ console.error(error);
51
+
52
+ return <div>There was an error.</div>;
53
+ };
54
+
55
+ export function CatchBoundary() {
56
+ const caught = useCatch();
57
+ console.error(caught);
58
+
59
+ return (
60
+ <div>
61
+ There was an error. Status: {caught.status}. Message:{' '}
62
+ {caught.data?.message}
63
+ </div>
64
+ );
65
+ }
66
+
67
+ export function ErrorBoundary() {
68
+ const error = useRouteError();
69
+
70
+ if (isRouteErrorResponse(error)) {
71
+ console.error(error.status, error.statusText, error.data);
72
+ return <div>Route Error</div>;
73
+ } else {
74
+ console.error((error as Error).message);
75
+ return <div>Thrown Error</div>;
76
+ }
77
+ }
44
78
 
45
- query Product(
79
+ const PRODUCT_QUERY = `#graphql
80
+ query product_query(
46
81
  $country: CountryCode
47
82
  $language: LanguageCode
48
83
  $handle: String!
@@ -1,10 +1,10 @@
1
1
  import { spawnSync } from 'child_process';
2
- import { output } from '@shopify/cli-kit';
2
+ import { outputDebug } from '@shopify/cli-kit/node/output';
3
3
 
4
4
  const EXPERIMENTAL_VM_MODULES_FLAG = "--experimental-vm-modules";
5
5
  const hook = async function(options) {
6
6
  if (options.id && ["hydrogen:dev", "hydrogen:preview"].includes(options.id) && !process.execArgv.includes(EXPERIMENTAL_VM_MODULES_FLAG) && !(process.env.NODE_OPTIONS ?? "").includes(EXPERIMENTAL_VM_MODULES_FLAG)) {
7
- output.debug(
7
+ outputDebug(
8
8
  `Restarting CLI process with ${EXPERIMENTAL_VM_MODULES_FLAG} flag.`
9
9
  );
10
10
  const [command, ...args] = process.argv;
@@ -1,4 +1,6 @@
1
- import { file, path, git } from '@shopify/cli-kit';
1
+ import { fileExists } from '@shopify/cli-kit/node/fs';
2
+ import { resolvePath } from '@shopify/cli-kit/node/path';
3
+ import { checkIfIgnoredInGitRepository } from '@shopify/cli-kit/node/git';
2
4
  import { renderWarning } from '@shopify/cli-kit/node/ui';
3
5
  import { lockfiles } from '@shopify/cli-kit/node/node-package-manager';
4
6
 
@@ -54,7 +56,7 @@ async function checkLockfileStatus(directory) {
54
56
  return;
55
57
  const availableLockfiles = [];
56
58
  for (const lockFileName of lockfiles) {
57
- if (await file.exists(path.resolve(directory, lockFileName))) {
59
+ if (await fileExists(resolvePath(directory, lockFileName))) {
58
60
  availableLockfiles.push(lockFileName);
59
61
  }
60
62
  }
@@ -65,9 +67,10 @@ async function checkLockfileStatus(directory) {
65
67
  return multipleLockfilesWarning(availableLockfiles);
66
68
  }
67
69
  try {
68
- const repo = git.factory(directory);
69
70
  const lockfile = availableLockfiles[0];
70
- const ignoredLockfile = await repo.checkIgnore([lockfile]);
71
+ const ignoredLockfile = await checkIfIgnoredInGitRepository(directory, [
72
+ lockfile
73
+ ]);
71
74
  if (ignoredLockfile.length) {
72
75
  lockfileIgnoredWarning(lockfile);
73
76
  }
@@ -1,34 +1,28 @@
1
1
  import { checkLockfileStatus } from './check-lockfile.js';
2
- import { vi, describe, beforeEach, afterEach, it, expect } from 'vitest';
3
- import { git, outputMocker, file, path } from '@shopify/cli-kit';
2
+ import { describe, vi, beforeEach, afterEach, it, expect } from 'vitest';
3
+ import { inTemporaryDirectory, writeFile } from '@shopify/cli-kit/node/fs';
4
+ import { joinPath } from '@shopify/cli-kit/node/path';
5
+ import { checkIfIgnoredInGitRepository } from '@shopify/cli-kit/node/git';
6
+ import { mockAndCaptureOutput } from '@shopify/cli-kit/node/testing/output';
4
7
 
5
- vi.mock("@shopify/cli-kit", async () => {
6
- const cliKit = await vi.importActual("@shopify/cli-kit");
7
- return {
8
- ...cliKit,
9
- git: {
10
- factory: vi.fn()
11
- }
12
- };
13
- });
14
8
  describe("checkLockfileStatus()", () => {
15
9
  const checkIgnoreMock = vi.fn();
16
- const gitFactoryMock = {
17
- checkIgnore: checkIgnoreMock
18
- };
10
+ const outputMock = mockAndCaptureOutput();
19
11
  beforeEach(() => {
20
- vi.mocked(git.factory).mockReturnValue(gitFactoryMock);
12
+ vi.mock("@shopify/cli-kit/node/git");
13
+ vi.mocked(checkIfIgnoredInGitRepository).mockImplementation(
14
+ checkIgnoreMock
15
+ );
21
16
  vi.mocked(checkIgnoreMock).mockResolvedValue([]);
22
17
  });
23
18
  afterEach(() => {
24
19
  vi.restoreAllMocks();
25
- outputMocker.mockAndCaptureOutput().clear();
20
+ outputMock.clear();
26
21
  });
27
22
  describe("when a lockfile is present", () => {
28
23
  it("does not call displayLockfileWarning", async () => {
29
- await file.inTemporaryDirectory(async (tmpDir) => {
30
- await file.write(path.join(tmpDir, "package-lock.json"), "");
31
- const outputMock = outputMocker.mockAndCaptureOutput();
24
+ await inTemporaryDirectory(async (tmpDir) => {
25
+ await writeFile(joinPath(tmpDir, "package-lock.json"), "");
32
26
  await checkLockfileStatus(tmpDir);
33
27
  expect(outputMock.warn()).toBe("");
34
28
  });
@@ -38,9 +32,8 @@ describe("checkLockfileStatus()", () => {
38
32
  vi.mocked(checkIgnoreMock).mockResolvedValue(["package-lock.json"]);
39
33
  });
40
34
  it("renders a warning", async () => {
41
- await file.inTemporaryDirectory(async (tmpDir) => {
42
- await file.write(path.join(tmpDir, "package-lock.json"), "");
43
- const outputMock = outputMocker.mockAndCaptureOutput();
35
+ await inTemporaryDirectory(async (tmpDir) => {
36
+ await writeFile(joinPath(tmpDir, "package-lock.json"), "");
44
37
  await checkLockfileStatus(tmpDir);
45
38
  expect(outputMock.warn()).toMatch(
46
39
  / warning .+ Lockfile ignored by Git .+/is
@@ -51,10 +44,9 @@ describe("checkLockfileStatus()", () => {
51
44
  });
52
45
  describe("when there are multiple lockfiles", () => {
53
46
  it("renders a warning", async () => {
54
- await file.inTemporaryDirectory(async (tmpDir) => {
55
- await file.write(path.join(tmpDir, "package-lock.json"), "");
56
- await file.write(path.join(tmpDir, "pnpm-lock.yaml"), "");
57
- const outputMock = outputMocker.mockAndCaptureOutput();
47
+ await inTemporaryDirectory(async (tmpDir) => {
48
+ await writeFile(joinPath(tmpDir, "package-lock.json"), "");
49
+ await writeFile(joinPath(tmpDir, "pnpm-lock.yaml"), "");
58
50
  await checkLockfileStatus(tmpDir);
59
51
  expect(outputMock.warn()).toMatch(
60
52
  / warning .+ Multiple lockfiles found .+/is
@@ -64,8 +56,7 @@ describe("checkLockfileStatus()", () => {
64
56
  });
65
57
  describe("when a lockfile is missing", () => {
66
58
  it("renders a warning", async () => {
67
- await file.inTemporaryDirectory(async (tmpDir) => {
68
- const outputMock = outputMocker.mockAndCaptureOutput();
59
+ await inTemporaryDirectory(async (tmpDir) => {
69
60
  await checkLockfileStatus(tmpDir);
70
61
  expect(outputMock.warn()).toMatch(/ warning .+ No lockfile found .+/is);
71
62
  });
@@ -1,6 +1,6 @@
1
1
  import { checkHydrogenVersion } from './check-version.js';
2
2
  import { vi, describe, afterEach, it, expect, beforeEach } from 'vitest';
3
- import { outputMocker } from '@shopify/cli-kit';
3
+ import { mockAndCaptureOutput } from '@shopify/cli-kit/node/testing/output';
4
4
  import { checkForNewVersion } from '@shopify/cli-kit/node/node-package-manager';
5
5
 
6
6
  vi.mock("@shopify/cli-kit/node/node-package-manager", () => {
@@ -9,8 +9,10 @@ vi.mock("@shopify/cli-kit/node/node-package-manager", () => {
9
9
  };
10
10
  });
11
11
  describe("checkHydrogenVersion()", () => {
12
+ const outputMock = mockAndCaptureOutput();
12
13
  afterEach(() => {
13
14
  vi.restoreAllMocks();
15
+ outputMock.clear();
14
16
  });
15
17
  describe("when a current version is available", () => {
16
18
  it("calls checkForNewVersion", async () => {
@@ -36,7 +38,6 @@ describe("checkHydrogenVersion()", () => {
36
38
  expect(await checkHydrogenVersion("dir")).toBeInstanceOf(Function);
37
39
  });
38
40
  it("outputs a message to the user with the new version", async () => {
39
- const outputMock = outputMocker.mockAndCaptureOutput();
40
41
  const showUpgrade = await checkHydrogenVersion("dir");
41
42
  const { currentVersion, newVersion } = showUpgrade();
42
43
  expect(outputMock.info()).toMatch(
@@ -0,0 +1,8 @@
1
+ import ansiColors from 'ansi-colors';
2
+
3
+ declare const colors: {
4
+ dim: ansiColors.StyleFunction;
5
+ bold: ansiColors.StyleFunction;
6
+ };
7
+
8
+ export { colors };
@@ -0,0 +1,8 @@
1
+ import ansiColors from 'ansi-colors';
2
+
3
+ const colors = {
4
+ dim: ansiColors.dim,
5
+ bold: ansiColors.bold
6
+ };
7
+
8
+ export { colors };
@@ -1,6 +1,6 @@
1
1
  import { renderFatalError } from '@shopify/cli-kit/node/ui';
2
- import { output, file } from '@shopify/cli-kit';
3
- import { createRequire } from 'module';
2
+ import { outputWarn } from '@shopify/cli-kit/node/output';
3
+ import { fileExists } from '@shopify/cli-kit/node/fs';
4
4
  import { fileURLToPath } from 'url';
5
5
  import path from 'path';
6
6
  import fs from 'fs/promises';
@@ -8,6 +8,7 @@ import fs from 'fs/promises';
8
8
  const BUILD_DIR = "dist";
9
9
  const CLIENT_SUBDIR = "client";
10
10
  const WORKER_SUBDIR = "worker";
11
+ const oxygenServerMainFields = ["browser", "module", "main"];
11
12
  function getProjectPaths(appPath, entry) {
12
13
  const root = appPath ?? process.cwd();
13
14
  const publicPath = path.join(root, "public");
@@ -25,13 +26,6 @@ function getProjectPaths(appPath, entry) {
25
26
  async function getRemixConfig(root, mode = process.env.NODE_ENV) {
26
27
  const { readConfig } = await import('@remix-run/dev/dist/config.js');
27
28
  const config = await readConfig(root, mode);
28
- if (!config.serverConditions) {
29
- const require2 = createRequire(import.meta.url);
30
- const actualConfigFile = require2(path.join(root, "remix.config"));
31
- config.serverBuildTarget = "cloudflare-workers";
32
- config.serverConditions = actualConfigFile.serverConditions;
33
- config.serverMainFields = actualConfigFile.serverMainFields;
34
- }
35
29
  if (!config.serverEntryPoint) {
36
30
  throwConfigError(
37
31
  "Could not find a server entry point.",
@@ -61,17 +55,14 @@ async function getRemixConfig(root, mode = process.env.NODE_ENV) {
61
55
  );
62
56
  }
63
57
  if (process.env.NODE_ENV === "development" && !config.serverConditions?.includes("development")) {
64
- output.warn(
58
+ outputWarn(
65
59
  "Add `process.env.NODE_ENV` value to serverConditions in remix.config.js to enable debugging features in development."
66
60
  );
67
61
  }
68
- const expectedServerMainFields = ["browser", "module", "main"];
69
- if (!config.serverMainFields || !expectedServerMainFields.every(
70
- (v, i) => config.serverMainFields?.[i] === v
71
- )) {
62
+ if (!config.serverMainFields || !oxygenServerMainFields.every((v, i) => config.serverMainFields?.[i] === v)) {
72
63
  throwConfigError(
73
64
  `The serverMainFields in remix.config.js must be ${JSON.stringify(
74
- expectedServerMainFields
65
+ oxygenServerMainFields
75
66
  )}.`
76
67
  );
77
68
  }
@@ -104,7 +95,7 @@ async function getRemixConfig(root, mode = process.env.NODE_ENV) {
104
95
  config.watchPaths ??= [];
105
96
  config.watchPaths.push(
106
97
  ...(await fs.readdir(packagesPath)).map(
107
- (pkg) => path.resolve(packagesPath, pkg, "dist", "development", "index.js")
98
+ (pkg) => pkg === "hydrogen-react" ? path.resolve(packagesPath, pkg, "dist", "browser-dev", "index.mjs") : path.resolve(packagesPath, pkg, "dist", "development", "index.js")
108
99
  )
109
100
  );
110
101
  config.watchPaths.push(
@@ -124,13 +115,13 @@ function throwConfigError(message, tryMessage = null) {
124
115
  }
125
116
  async function assertEntryFileExists(root, fileRelative) {
126
117
  const fileAbsolute = path.resolve(root, fileRelative);
127
- const exists = await file.exists(fileAbsolute);
118
+ const exists = await fileExists(fileAbsolute);
128
119
  if (!exists) {
129
120
  if (!path.extname(fileAbsolute)) {
130
121
  const { readdir } = await import('fs/promises');
131
122
  const files = await readdir(path.dirname(fileAbsolute));
132
- const exists2 = files.some((file2) => {
133
- const { name, ext } = path.parse(file2);
123
+ const exists2 = files.some((file) => {
124
+ const { name, ext } = path.parse(file);
134
125
  return name === path.basename(fileAbsolute) && /^\.[jt]s$/.test(ext);
135
126
  });
136
127
  if (exists2)
@@ -1,8 +1,8 @@
1
1
  import * as _oclif_core_lib_interfaces_parser_js from '@oclif/core/lib/interfaces/parser.js';
2
2
 
3
3
  declare const commonFlags: {
4
- path: _oclif_core_lib_interfaces_parser_js.OptionFlag<string | undefined>;
5
- port: _oclif_core_lib_interfaces_parser_js.OptionFlag<number>;
4
+ path: _oclif_core_lib_interfaces_parser_js.OptionFlag<string | undefined, _oclif_core_lib_interfaces_parser_js.CustomOptions>;
5
+ port: _oclif_core_lib_interfaces_parser_js.OptionFlag<number, _oclif_core_lib_interfaces_parser_js.CustomOptions>;
6
6
  force: _oclif_core_lib_interfaces_parser_js.BooleanFlag<boolean>;
7
7
  };
8
8
  declare function flagsToCamelObject(obj: Record<string, any>): any;
@@ -14,5 +14,11 @@ declare function flagsToCamelObject(obj: Record<string, any>): any;
14
14
  * output: { force: true, installDeps: false, language: 'js' }
15
15
  */
16
16
  declare function parseProcessFlags(processArgv: string[], flagMap?: Record<string, string>): any;
17
+ /**
18
+ * Create a deprecated flag to prevent the CLI from crashing when a deprecated flag is used.
19
+ * Displays an info message when the flag is used.
20
+ * @param name The name of the flag.
21
+ */
22
+ declare function deprecated(name: string): _oclif_core_lib_interfaces_parser_js.FlagDefinition<unknown, Record<string, unknown>>;
17
23
 
18
- export { commonFlags, flagsToCamelObject, parseProcessFlags };
24
+ export { commonFlags, deprecated, flagsToCamelObject, parseProcessFlags };
@@ -1,5 +1,7 @@
1
- import Flags from '@oclif/core/lib/flags.js';
2
- import { string } from '@shopify/cli-kit';
1
+ import { Flags } from '@oclif/core';
2
+ import { camelize } from '@shopify/cli-kit/common/string';
3
+ import { renderInfo } from '@shopify/cli-kit/node/ui';
4
+ import { colors } from './colors.js';
3
5
 
4
6
  const commonFlags = {
5
7
  path: Flags.string({
@@ -19,7 +21,7 @@ const commonFlags = {
19
21
  };
20
22
  function flagsToCamelObject(obj) {
21
23
  return Object.entries(obj).reduce((acc, [key, value]) => {
22
- acc[string.camelize(key)] = value;
24
+ acc[camelize(key)] = value;
23
25
  return acc;
24
26
  }, {});
25
27
  }
@@ -41,5 +43,18 @@ function parseProcessFlags(processArgv, flagMap = {}) {
41
43
  }
42
44
  return flagsToCamelObject(options);
43
45
  }
46
+ function deprecated(name) {
47
+ return Flags.custom({
48
+ parse: () => {
49
+ renderInfo({
50
+ headline: `The ${colors.bold(
51
+ name
52
+ )} flag is deprecated and will be removed in a future version of Shopify CLI.`
53
+ });
54
+ return Promise.resolve(" ");
55
+ },
56
+ hidden: true
57
+ });
58
+ }
44
59
 
45
- export { commonFlags, flagsToCamelObject, parseProcessFlags };
60
+ export { commonFlags, deprecated, flagsToCamelObject, parseProcessFlags };
@@ -0,0 +1 @@
1
+
@@ -1,5 +1,7 @@
1
- import { path, file, output } from '@shopify/cli-kit';
2
- import colors from '@shopify/cli-kit/node/colors';
1
+ import { outputInfo, outputContent, outputToken } from '@shopify/cli-kit/node/output';
2
+ import { resolvePath } from '@shopify/cli-kit/node/path';
3
+ import { fileExists } from '@shopify/cli-kit/node/fs';
4
+ import { colors } from './colors.js';
3
5
 
4
6
  async function startMiniOxygen({
5
7
  root,
@@ -10,7 +12,7 @@ async function startMiniOxygen({
10
12
  }) {
11
13
  const { default: miniOxygen } = await import('@shopify/mini-oxygen');
12
14
  const miniOxygenPreview = miniOxygen.default ?? miniOxygen;
13
- const dotenvPath = path.resolve(root, ".env");
15
+ const dotenvPath = resolvePath(root, ".env");
14
16
  const { port: actualPort } = await miniOxygenPreview({
15
17
  workerFile: buildPathWorkerFile,
16
18
  assetsDir: buildPathClient,
@@ -20,18 +22,18 @@ async function startMiniOxygen({
20
22
  autoReload: watch,
21
23
  modules: true,
22
24
  env: process.env,
23
- envPath: await file.exists(dotenvPath) ? dotenvPath : void 0,
25
+ envPath: await fileExists(dotenvPath) ? dotenvPath : void 0,
24
26
  log: () => {
25
27
  },
26
- buildWatchPaths: watch ? [path.resolve(root, buildPathWorkerFile)] : void 0,
28
+ buildWatchPaths: watch ? [resolvePath(root, buildPathWorkerFile)] : void 0,
27
29
  onResponse: (request, response) => logResponse(
28
30
  request,
29
31
  response
30
32
  )
31
33
  });
32
34
  const listeningAt = `http://localhost:${actualPort}`;
33
- output.info(
34
- output.content`🚥 MiniOxygen server started at ${output.token.link(
35
+ outputInfo(
36
+ outputContent`🚥 MiniOxygen server started at ${outputToken.link(
35
37
  listeningAt,
36
38
  listeningAt
37
39
  )}\n`
@@ -57,15 +59,15 @@ function logResponse(request, response) {
57
59
  route = url.pathname;
58
60
  info = `[${dataParam}]`;
59
61
  }
60
- const colorizeStatus = response.status < 300 ? output.token.green : response.status < 400 ? output.token.cyan : output.token.errorText;
61
- output.info(
62
- output.content`${request.method.padStart(6)} ${colorizeStatus(
62
+ const colorizeStatus = response.status < 300 ? outputToken.green : response.status < 400 ? outputToken.cyan : outputToken.errorText;
63
+ outputInfo(
64
+ outputContent`${request.method.padStart(6)} ${colorizeStatus(
63
65
  String(response.status)
64
- )} ${output.token.italic(type.padEnd(7, " "))} ${route}${info ? " " + colors.dim(info) : ""} ${request.headers.get("purpose") === "prefetch" ? output.token.italic("(prefetch)") : ""}`
66
+ )} ${outputToken.italic(type.padEnd(7, " "))} ${route}${info ? " " + colors.dim(info) : ""} ${request.headers.get("purpose") === "prefetch" ? outputToken.italic("(prefetch)") : ""}`
65
67
  );
66
68
  } catch {
67
69
  if (request && response) {
68
- output.info(`${request.method} ${response.status} ${request.url}`);
70
+ outputInfo(`${request.method} ${response.status} ${request.url}`);
69
71
  }
70
72
  }
71
73
  }
@@ -12,7 +12,7 @@ const REQUIRED_ROUTES = [
12
12
  "pages/:pageHandle",
13
13
  "policies/:policyHandle",
14
14
  "search",
15
- "discounts/:discountCode",
15
+ "discount/:discountCode",
16
16
  "account",
17
17
  "account/login",
18
18
  "account/register",
@@ -0,0 +1,11 @@
1
+ declare function isRemixV2(): boolean;
2
+ declare function getV2Flags(root: string): Promise<{
3
+ isV2Meta: boolean;
4
+ isV2ErrorBoundary: boolean;
5
+ isV2RouteConvention: boolean;
6
+ }>;
7
+ type RemixV2Flags = Partial<Awaited<ReturnType<typeof getV2Flags>>>;
8
+ declare function convertRouteToV2(route: string): string;
9
+ declare function convertTemplateToRemixVersion(template: string, { isV2Meta, isV2ErrorBoundary }: RemixV2Flags): string;
10
+
11
+ export { RemixV2Flags, convertRouteToV2, convertTemplateToRemixVersion, getV2Flags, isRemixV2 };