@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.
- package/README.md +9 -0
- package/dist/commands/hydrogen/build.d.ts +4 -1
- package/dist/commands/hydrogen/build.js +21 -17
- 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 -2
- package/dist/commands/hydrogen/dev.js +24 -22
- 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 +76 -95
- package/dist/commands/hydrogen/init.test.js +126 -0
- 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 +45 -10
- 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 +10 -19
- package/dist/{utils → lib}/flags.d.ts +9 -3
- package/dist/{utils → lib}/flags.js +19 -4
- package/dist/lib/flags.test.d.ts +1 -0
- package/dist/{utils → lib}/mini-oxygen.js +14 -12
- package/dist/{utils → lib}/missing-routes.js +1 -1
- 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/init.test.d.ts} +0 -0
- /package/dist/{utils/check-version.test.d.ts → commands/hydrogen/shortcut.test.d.ts} +0 -0
- /package/dist/{utils → lib}/check-lockfile.d.ts +0 -0
- /package/dist/{utils/flags.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/virtual-routes.test.d.ts → lib/check-version.test.d.ts} +0 -0
- /package/dist/{utils → lib}/config.d.ts +0 -0
- /package/dist/{utils → lib}/flags.test.js +0 -0
- /package/dist/{utils → lib}/log.d.ts +0 -0
- /package/dist/{utils → lib}/log.js +0 -0
- /package/dist/{utils → lib}/mini-oxygen.d.ts +0 -0
- /package/dist/{utils → lib}/missing-routes.d.ts +0 -0
- /package/dist/{utils → lib}/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
|
@@ -1,21 +1,31 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
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
|
-
|
|
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(
|
|
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:
|
|
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(
|
|
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
|
|
40
|
-
|
|
41
|
-
|
|
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
|
|
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 {
|
|
2
|
-
|
|
3
|
-
|
|
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:
|
|
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 {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
79
|
+
const PRODUCT_QUERY = `#graphql
|
|
80
|
+
query product_query(
|
|
46
81
|
$country: CountryCode
|
|
47
82
|
$language: LanguageCode
|
|
48
83
|
$handle: String!
|
package/dist/hooks/init.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { spawnSync } from 'child_process';
|
|
2
|
-
import {
|
|
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
|
-
|
|
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 {
|
|
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
|
|
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
|
|
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 {
|
|
3
|
-
import {
|
|
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
|
|
17
|
-
checkIgnore: checkIgnoreMock
|
|
18
|
-
};
|
|
10
|
+
const outputMock = mockAndCaptureOutput();
|
|
19
11
|
beforeEach(() => {
|
|
20
|
-
vi.
|
|
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
|
-
|
|
20
|
+
outputMock.clear();
|
|
26
21
|
});
|
|
27
22
|
describe("when a lockfile is present", () => {
|
|
28
23
|
it("does not call displayLockfileWarning", async () => {
|
|
29
|
-
await
|
|
30
|
-
await
|
|
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
|
|
42
|
-
await
|
|
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
|
|
55
|
-
await
|
|
56
|
-
await
|
|
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
|
|
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 {
|
|
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(
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { renderFatalError } from '@shopify/cli-kit/node/ui';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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((
|
|
133
|
-
const { name, ext } = path.parse(
|
|
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
|
|
2
|
-
import {
|
|
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[
|
|
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 {
|
|
2
|
-
import
|
|
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 =
|
|
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
|
|
25
|
+
envPath: await fileExists(dotenvPath) ? dotenvPath : void 0,
|
|
24
26
|
log: () => {
|
|
25
27
|
},
|
|
26
|
-
buildWatchPaths: watch ? [
|
|
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
|
-
|
|
34
|
-
|
|
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 ?
|
|
61
|
-
|
|
62
|
-
|
|
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
|
-
)} ${
|
|
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
|
-
|
|
70
|
+
outputInfo(`${request.method} ${response.status} ${request.url}`);
|
|
69
71
|
}
|
|
70
72
|
}
|
|
71
73
|
}
|
|
@@ -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 };
|