@shopify/cli-hydrogen 0.0.0-next-744c490-20230315110711
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/LICENSE.md +7 -0
- package/README.md +14 -0
- package/dist/commands/hydrogen/build.d.ts +23 -0
- package/dist/commands/hydrogen/build.js +100 -0
- package/dist/commands/hydrogen/check.d.ts +18 -0
- package/dist/commands/hydrogen/check.js +35 -0
- package/dist/commands/hydrogen/dev.d.ts +15 -0
- package/dist/commands/hydrogen/dev.js +134 -0
- package/dist/commands/hydrogen/generate/route.d.ts +34 -0
- package/dist/commands/hydrogen/generate/route.js +166 -0
- package/dist/commands/hydrogen/generate/route.test.d.ts +1 -0
- package/dist/commands/hydrogen/generate/route.test.js +126 -0
- package/dist/commands/hydrogen/generate/routes.d.ts +16 -0
- package/dist/commands/hydrogen/generate/routes.js +22 -0
- package/dist/commands/hydrogen/init.d.ts +24 -0
- package/dist/commands/hydrogen/init.js +199 -0
- package/dist/commands/hydrogen/init.test.d.ts +1 -0
- package/dist/commands/hydrogen/init.test.js +79 -0
- package/dist/commands/hydrogen/preview.d.ts +17 -0
- package/dist/commands/hydrogen/preview.js +34 -0
- package/dist/create-app.d.ts +1 -0
- package/dist/create-app.js +4 -0
- package/dist/generator-templates/routes/[robots.txt].tsx +41 -0
- package/dist/generator-templates/routes/[sitemap.xml].tsx +204 -0
- package/dist/generator-templates/routes/account/login.tsx +74 -0
- package/dist/generator-templates/routes/account/register.tsx +74 -0
- package/dist/generator-templates/routes/cart.tsx +41 -0
- package/dist/generator-templates/routes/collections/$collectionHandle.tsx +65 -0
- package/dist/generator-templates/routes/graphiql.tsx +10 -0
- package/dist/generator-templates/routes/index.tsx +7 -0
- package/dist/generator-templates/routes/pages/$pageHandle.tsx +74 -0
- package/dist/generator-templates/routes/policies/$policyHandle.tsx +87 -0
- package/dist/generator-templates/routes/policies/index.tsx +67 -0
- package/dist/generator-templates/routes/products/$productHandle.tsx +57 -0
- package/dist/hooks/init.d.ts +5 -0
- package/dist/hooks/init.js +18 -0
- package/dist/utils/check-lockfile.d.ts +3 -0
- package/dist/utils/check-lockfile.js +78 -0
- package/dist/utils/check-lockfile.test.d.ts +1 -0
- package/dist/utils/check-lockfile.test.js +74 -0
- package/dist/utils/check-version.d.ts +16 -0
- package/dist/utils/check-version.js +55 -0
- package/dist/utils/check-version.test.d.ts +1 -0
- package/dist/utils/check-version.test.js +60 -0
- package/dist/utils/config.d.ts +18 -0
- package/dist/utils/config.js +146 -0
- package/dist/utils/flags.d.ts +24 -0
- package/dist/utils/flags.js +60 -0
- package/dist/utils/flags.test.d.ts +1 -0
- package/dist/utils/flags.test.js +43 -0
- package/dist/utils/log.d.ts +5 -0
- package/dist/utils/log.js +20 -0
- package/dist/utils/mini-oxygen.d.ts +11 -0
- package/dist/utils/mini-oxygen.js +73 -0
- package/dist/utils/missing-routes.d.ts +6 -0
- package/dist/utils/missing-routes.js +71 -0
- package/dist/utils/template-downloader.d.ts +11 -0
- package/dist/utils/template-downloader.js +70 -0
- package/dist/utils/transpile-ts.d.ts +16 -0
- package/dist/utils/transpile-ts.js +169 -0
- package/dist/utils/virtual-routes.d.ts +7 -0
- package/dist/utils/virtual-routes.js +44 -0
- package/dist/utils/virtual-routes.test.d.ts +1 -0
- package/dist/utils/virtual-routes.test.js +49 -0
- package/dist/virtual-routes/assets/favicon.svg +28 -0
- package/dist/virtual-routes/assets/inter-variable-font.woff2 +0 -0
- package/dist/virtual-routes/assets/jetbrainsmono-variable-font.woff2 +0 -0
- package/dist/virtual-routes/assets/styles.css +238 -0
- package/dist/virtual-routes/components/HydrogenLogoBaseBW.jsx +7 -0
- package/dist/virtual-routes/components/HydrogenLogoBaseColor.jsx +13 -0
- package/dist/virtual-routes/components/IconBanner.jsx +47 -0
- package/dist/virtual-routes/components/IconDiscord.jsx +4 -0
- package/dist/virtual-routes/components/IconError.jsx +20 -0
- package/dist/virtual-routes/components/IconGithub.jsx +4 -0
- package/dist/virtual-routes/components/IconTwitter.jsx +4 -0
- package/dist/virtual-routes/components/Layout.jsx +6 -0
- package/dist/virtual-routes/routes/graphiql.jsx +5 -0
- package/dist/virtual-routes/routes/index.jsx +163 -0
- package/dist/virtual-routes/virtual-root.jsx +38 -0
- package/oclif.manifest.json +1 -0
- package/package.json +72 -0
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type MetaFunction,
|
|
3
|
+
type ActionFunction,
|
|
4
|
+
type LoaderArgs,
|
|
5
|
+
redirect,
|
|
6
|
+
json,
|
|
7
|
+
} from '@shopify/remix-oxygen';
|
|
8
|
+
import {Form, Link, useActionData, useLoaderData} from '@remix-run/react';
|
|
9
|
+
import {useState} from 'react';
|
|
10
|
+
|
|
11
|
+
export async function loader({context, params}: LoaderArgs) {
|
|
12
|
+
const customerAccessToken = await context.session.get('customerAccessToken');
|
|
13
|
+
|
|
14
|
+
if (customerAccessToken) {
|
|
15
|
+
return redirect(params.lang ? `${params.lang}/account` : '/account');
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
type ActionData = {
|
|
20
|
+
formError?: string;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const badRequest = (data: ActionData) => json(data, {status: 400});
|
|
24
|
+
|
|
25
|
+
export const action: ActionFunction = async ({request, context, params}) => {
|
|
26
|
+
const formData = await request.formData();
|
|
27
|
+
|
|
28
|
+
const email = formData.get('email');
|
|
29
|
+
const password = formData.get('password');
|
|
30
|
+
|
|
31
|
+
if (
|
|
32
|
+
!email ||
|
|
33
|
+
!password ||
|
|
34
|
+
typeof email !== 'string' ||
|
|
35
|
+
typeof password !== 'string'
|
|
36
|
+
) {
|
|
37
|
+
return badRequest({
|
|
38
|
+
formError: 'Please provide both an email and a password.',
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// TODO Add login logic
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export default function Login() {
|
|
46
|
+
return (
|
|
47
|
+
<Form method="post">
|
|
48
|
+
<input
|
|
49
|
+
id="email"
|
|
50
|
+
name="email"
|
|
51
|
+
type="email"
|
|
52
|
+
autoComplete="email"
|
|
53
|
+
required
|
|
54
|
+
placeholder="Email address"
|
|
55
|
+
aria-label="Email address"
|
|
56
|
+
// eslint-disable-next-line jsx-a11y/no-autofocus
|
|
57
|
+
autoFocus
|
|
58
|
+
/>
|
|
59
|
+
<input
|
|
60
|
+
id="password"
|
|
61
|
+
name="password"
|
|
62
|
+
type="password"
|
|
63
|
+
autoComplete="current-password"
|
|
64
|
+
placeholder="Password"
|
|
65
|
+
aria-label="Password"
|
|
66
|
+
minLength={8}
|
|
67
|
+
required
|
|
68
|
+
// eslint-disable-next-line jsx-a11y/no-autofocus
|
|
69
|
+
autoFocus
|
|
70
|
+
/>
|
|
71
|
+
<button type="submit">Sign in</button>
|
|
72
|
+
</Form>
|
|
73
|
+
);
|
|
74
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type MetaFunction,
|
|
3
|
+
type ActionFunction,
|
|
4
|
+
type LoaderArgs,
|
|
5
|
+
redirect,
|
|
6
|
+
json,
|
|
7
|
+
} from '@shopify/remix-oxygen';
|
|
8
|
+
import {Form, Link, useActionData, useLoaderData} from '@remix-run/react';
|
|
9
|
+
import {useState} from 'react';
|
|
10
|
+
|
|
11
|
+
export async function loader({context, params}: LoaderArgs) {
|
|
12
|
+
const customerAccessToken = await context.session.get('customerAccessToken');
|
|
13
|
+
|
|
14
|
+
if (customerAccessToken) {
|
|
15
|
+
return redirect(params.lang ? `${params.lang}/account` : '/account');
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
type ActionData = {
|
|
20
|
+
formError?: string;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const badRequest = (data: ActionData) => json(data, {status: 400});
|
|
24
|
+
|
|
25
|
+
export const action: ActionFunction = async ({request, context, params}) => {
|
|
26
|
+
const formData = await request.formData();
|
|
27
|
+
|
|
28
|
+
const email = formData.get('email');
|
|
29
|
+
const password = formData.get('password');
|
|
30
|
+
|
|
31
|
+
if (
|
|
32
|
+
!email ||
|
|
33
|
+
!password ||
|
|
34
|
+
typeof email !== 'string' ||
|
|
35
|
+
typeof password !== 'string'
|
|
36
|
+
) {
|
|
37
|
+
return badRequest({
|
|
38
|
+
formError: 'Please provide both an email and a password.',
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// TODO Add register logic
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export default function Register() {
|
|
46
|
+
return (
|
|
47
|
+
<Form method="post">
|
|
48
|
+
<input
|
|
49
|
+
id="email"
|
|
50
|
+
name="email"
|
|
51
|
+
type="email"
|
|
52
|
+
autoComplete="email"
|
|
53
|
+
required
|
|
54
|
+
placeholder="Email address"
|
|
55
|
+
aria-label="Email address"
|
|
56
|
+
// eslint-disable-next-line jsx-a11y/no-autofocus
|
|
57
|
+
autoFocus
|
|
58
|
+
/>
|
|
59
|
+
<input
|
|
60
|
+
id="password"
|
|
61
|
+
name="password"
|
|
62
|
+
type="password"
|
|
63
|
+
autoComplete="current-password"
|
|
64
|
+
placeholder="Password"
|
|
65
|
+
aria-label="Password"
|
|
66
|
+
minLength={8}
|
|
67
|
+
required
|
|
68
|
+
// eslint-disable-next-line jsx-a11y/no-autofocus
|
|
69
|
+
autoFocus
|
|
70
|
+
/>
|
|
71
|
+
<button type="submit">Sign up</button>
|
|
72
|
+
</Form>
|
|
73
|
+
);
|
|
74
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import {Await, useMatches} from '@remix-run/react';
|
|
2
|
+
import {Suspense} from 'react';
|
|
3
|
+
import {flattenConnection} from '@shopify/hydrogen';
|
|
4
|
+
import type {Cart as CartType} from '@shopify/hydrogen/storefront-api-types';
|
|
5
|
+
|
|
6
|
+
export async function action() {
|
|
7
|
+
// @TODO implement cart action
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export default function CartRoute() {
|
|
11
|
+
const [root] = useMatches();
|
|
12
|
+
return (
|
|
13
|
+
<Suspense fallback="loading">
|
|
14
|
+
<Await resolve={root.data?.cart as CartType}>
|
|
15
|
+
{(cart) => {
|
|
16
|
+
const linesCount = Boolean(cart?.lines?.edges?.length || 0);
|
|
17
|
+
if (!linesCount) {
|
|
18
|
+
return (
|
|
19
|
+
<p>Looks like you haven’t added anything to your cart.</p>
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const cartLines = cart?.lines ? flattenConnection(cart?.lines) : [];
|
|
24
|
+
|
|
25
|
+
return (
|
|
26
|
+
<>
|
|
27
|
+
<h1>Cart</h1>
|
|
28
|
+
<ul>
|
|
29
|
+
{cartLines.map((line) => (
|
|
30
|
+
<div key={line.id}>
|
|
31
|
+
<h2>{line?.merchandise?.title}</h2>
|
|
32
|
+
</div>
|
|
33
|
+
))}
|
|
34
|
+
</ul>
|
|
35
|
+
</>
|
|
36
|
+
);
|
|
37
|
+
}}
|
|
38
|
+
</Await>
|
|
39
|
+
</Suspense>
|
|
40
|
+
);
|
|
41
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import {json, type LoaderArgs} from '@shopify/remix-oxygen';
|
|
2
|
+
import {useLoaderData} from '@remix-run/react';
|
|
3
|
+
import type {Collection as CollectionType} from '@shopify/hydrogen/storefront-api-types';
|
|
4
|
+
import {Link} from '@remix-run/react';
|
|
5
|
+
|
|
6
|
+
export async function loader({params, request, context}: LoaderArgs) {
|
|
7
|
+
const {collectionHandle} = params;
|
|
8
|
+
|
|
9
|
+
const {collection} = await context.storefront.query<{
|
|
10
|
+
collection: CollectionType;
|
|
11
|
+
}>(COLLECTION_QUERY, {
|
|
12
|
+
variables: {
|
|
13
|
+
handle: collectionHandle,
|
|
14
|
+
country: context.storefront.i18n?.country,
|
|
15
|
+
language: context.storefront.i18n?.language,
|
|
16
|
+
},
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
if (!collection) {
|
|
20
|
+
throw new Response(null, {status: 404});
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return json({collection});
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export default function Collection() {
|
|
27
|
+
const {collection} = useLoaderData<typeof loader>();
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
<>
|
|
31
|
+
<h1>{collection.title}</h1>
|
|
32
|
+
{collection.products.nodes.map((product) => (
|
|
33
|
+
<div key={product.id}>
|
|
34
|
+
<Link to={`/products/${product.handle}`}>{product.title}</Link>
|
|
35
|
+
</div>
|
|
36
|
+
))}
|
|
37
|
+
</>
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const COLLECTION_QUERY = `#graphql
|
|
42
|
+
query CollectionDetails(
|
|
43
|
+
$handle: String!
|
|
44
|
+
$country: CountryCode
|
|
45
|
+
$language: LanguageCode
|
|
46
|
+
|
|
47
|
+
) @inContext(country: $country, language: $language) {
|
|
48
|
+
collection(handle: $handle) {
|
|
49
|
+
id
|
|
50
|
+
handle
|
|
51
|
+
title
|
|
52
|
+
description
|
|
53
|
+
products(
|
|
54
|
+
first: 100,
|
|
55
|
+
) {
|
|
56
|
+
nodes {
|
|
57
|
+
title
|
|
58
|
+
id
|
|
59
|
+
handle
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
}
|
|
65
|
+
`;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import {graphiqlLoader} from '@shopify/hydrogen';
|
|
2
|
+
import {redirect, type LoaderArgs} from '@shopify/remix-oxygen';
|
|
3
|
+
|
|
4
|
+
export async function loader(args: LoaderArgs) {
|
|
5
|
+
if (process.env.NODE_ENV === 'development') {
|
|
6
|
+
return graphiqlLoader(args);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
return redirect('/');
|
|
10
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import {
|
|
2
|
+
json,
|
|
3
|
+
type MetaFunction,
|
|
4
|
+
type LoaderArgs,
|
|
5
|
+
SerializeFrom,
|
|
6
|
+
} from '@shopify/remix-oxygen';
|
|
7
|
+
import {useLoaderData} from '@remix-run/react';
|
|
8
|
+
import invariant from 'tiny-invariant';
|
|
9
|
+
import type {Page as PageType} from '@shopify/hydrogen/storefront-api-types';
|
|
10
|
+
import type {SeoHandleFunction} from '@shopify/hydrogen';
|
|
11
|
+
|
|
12
|
+
export async function loader({params, context}: LoaderArgs) {
|
|
13
|
+
invariant(params.pageHandle, 'Missing page handle');
|
|
14
|
+
|
|
15
|
+
const {page} = await context.storefront.query<{page: PageType}>(PAGE_QUERY, {
|
|
16
|
+
variables: {
|
|
17
|
+
handle: params.pageHandle,
|
|
18
|
+
},
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
if (!page) {
|
|
22
|
+
throw new Response('Not Found', {
|
|
23
|
+
status: 404,
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return json({page});
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const seo: SeoHandleFunction<typeof loader> = ({data}) => ({
|
|
31
|
+
title: data?.page?.seo?.title,
|
|
32
|
+
description: data?.page?.seo?.description,
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
export const handle = {
|
|
36
|
+
seo,
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export const meta: MetaFunction = ({data}) => {
|
|
40
|
+
const {title, description} = data?.page.seo ?? {};
|
|
41
|
+
|
|
42
|
+
return {
|
|
43
|
+
title,
|
|
44
|
+
description,
|
|
45
|
+
};
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export default function Page() {
|
|
49
|
+
const {page} = useLoaderData<typeof loader>();
|
|
50
|
+
|
|
51
|
+
return (
|
|
52
|
+
<>
|
|
53
|
+
<header>
|
|
54
|
+
<h1>{page.title}</h1>
|
|
55
|
+
</header>
|
|
56
|
+
<main dangerouslySetInnerHTML={{__html: page.body}} />
|
|
57
|
+
</>
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const PAGE_QUERY = `#graphql
|
|
62
|
+
query PageDetails($language: LanguageCode, $handle: String!)
|
|
63
|
+
@inContext(language: $language) {
|
|
64
|
+
page(handle: $handle) {
|
|
65
|
+
id
|
|
66
|
+
title
|
|
67
|
+
body
|
|
68
|
+
seo {
|
|
69
|
+
description
|
|
70
|
+
title
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
`;
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import {json, type MetaFunction, type LoaderArgs} from '@shopify/remix-oxygen';
|
|
2
|
+
import {useLoaderData} from '@remix-run/react';
|
|
3
|
+
|
|
4
|
+
import {ShopPolicy} from '@shopify/hydrogen/storefront-api-types';
|
|
5
|
+
|
|
6
|
+
export async function loader({request, params, context}: LoaderArgs) {
|
|
7
|
+
const handle = params.policyHandle;
|
|
8
|
+
|
|
9
|
+
if (!handle) {
|
|
10
|
+
throw new Response(null, {status: 404});
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const policyName = handle.replace(/-([a-z])/g, (_: unknown, m1: string) =>
|
|
14
|
+
m1.toUpperCase(),
|
|
15
|
+
);
|
|
16
|
+
|
|
17
|
+
const data = await context.storefront.query<{
|
|
18
|
+
shop: Record<string, ShopPolicy>;
|
|
19
|
+
}>(POLICY_CONTENT_QUERY, {
|
|
20
|
+
variables: {
|
|
21
|
+
privacyPolicy: false,
|
|
22
|
+
shippingPolicy: false,
|
|
23
|
+
termsOfService: false,
|
|
24
|
+
refundPolicy: false,
|
|
25
|
+
[policyName]: true,
|
|
26
|
+
language: context.storefront.i18n?.language,
|
|
27
|
+
},
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
const policy = data.shop?.[policyName];
|
|
31
|
+
|
|
32
|
+
if (!policy) {
|
|
33
|
+
throw new Response(null, {status: 404});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return json({policy});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export const meta: MetaFunction<typeof loader> = ({data}) => {
|
|
40
|
+
return {
|
|
41
|
+
title: data?.policy?.title ?? 'Policies',
|
|
42
|
+
};
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export default function Policies() {
|
|
46
|
+
const {policy} = useLoaderData<typeof loader>();
|
|
47
|
+
|
|
48
|
+
return (
|
|
49
|
+
<>
|
|
50
|
+
<h1>{policy.title}</h1>
|
|
51
|
+
<div dangerouslySetInnerHTML={{__html: policy.body}} />
|
|
52
|
+
</>
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const POLICY_CONTENT_QUERY = `#graphql
|
|
57
|
+
fragment Policy on ShopPolicy {
|
|
58
|
+
body
|
|
59
|
+
handle
|
|
60
|
+
id
|
|
61
|
+
title
|
|
62
|
+
url
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
query PoliciesQuery(
|
|
66
|
+
$language: LanguageCode
|
|
67
|
+
$privacyPolicy: Boolean!
|
|
68
|
+
$shippingPolicy: Boolean!
|
|
69
|
+
$termsOfService: Boolean!
|
|
70
|
+
$refundPolicy: Boolean!
|
|
71
|
+
) @inContext(language: $language) {
|
|
72
|
+
shop {
|
|
73
|
+
privacyPolicy @include(if: $privacyPolicy) {
|
|
74
|
+
...Policy
|
|
75
|
+
}
|
|
76
|
+
shippingPolicy @include(if: $shippingPolicy) {
|
|
77
|
+
...Policy
|
|
78
|
+
}
|
|
79
|
+
termsOfService @include(if: $termsOfService) {
|
|
80
|
+
...Policy
|
|
81
|
+
}
|
|
82
|
+
refundPolicy @include(if: $refundPolicy) {
|
|
83
|
+
...Policy
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
`;
|
|
@@ -0,0 +1,67 @@
|
|
|
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';
|
|
4
|
+
|
|
5
|
+
export async function loader({context: {storefront}}: LoaderArgs) {
|
|
6
|
+
const data = await storefront.query<{
|
|
7
|
+
shop: Record<string, ShopPolicy>;
|
|
8
|
+
}>(POLICIES_QUERY);
|
|
9
|
+
|
|
10
|
+
const policies = Object.values(data.shop || {});
|
|
11
|
+
|
|
12
|
+
if (policies.length === 0) {
|
|
13
|
+
throw new Response('Not found', {status: 404});
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
return json({
|
|
17
|
+
policies,
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export default function Policies() {
|
|
22
|
+
const {policies} = useLoaderData<typeof loader>();
|
|
23
|
+
|
|
24
|
+
return (
|
|
25
|
+
<>
|
|
26
|
+
{policies.map((policy) => {
|
|
27
|
+
return (
|
|
28
|
+
policy && (
|
|
29
|
+
<Link key={policy.id} to={`/policies/${policy.handle}`}>
|
|
30
|
+
{policy.title}
|
|
31
|
+
</Link>
|
|
32
|
+
)
|
|
33
|
+
);
|
|
34
|
+
})}
|
|
35
|
+
</>
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const POLICIES_QUERY = `#graphql
|
|
40
|
+
fragment Policy on ShopPolicy {
|
|
41
|
+
id
|
|
42
|
+
title
|
|
43
|
+
handle
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
query PoliciesQuery {
|
|
47
|
+
shop {
|
|
48
|
+
privacyPolicy {
|
|
49
|
+
...Policy
|
|
50
|
+
}
|
|
51
|
+
shippingPolicy {
|
|
52
|
+
...Policy
|
|
53
|
+
}
|
|
54
|
+
termsOfService {
|
|
55
|
+
...Policy
|
|
56
|
+
}
|
|
57
|
+
refundPolicy {
|
|
58
|
+
...Policy
|
|
59
|
+
}
|
|
60
|
+
subscriptionPolicy {
|
|
61
|
+
id
|
|
62
|
+
title
|
|
63
|
+
handle
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
`;
|
|
@@ -0,0 +1,57 @@
|
|
|
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';
|
|
7
|
+
|
|
8
|
+
export async function loader({params, context}: LoaderArgs) {
|
|
9
|
+
const {productHandle} = params;
|
|
10
|
+
|
|
11
|
+
const {product} = await context.storefront.query<{
|
|
12
|
+
product: ProductType & {selectedVariant?: ProductVariant};
|
|
13
|
+
}>(PRODUCT_QUERY, {
|
|
14
|
+
variables: {
|
|
15
|
+
handle: productHandle,
|
|
16
|
+
country: context.storefront.i18n?.country,
|
|
17
|
+
language: context.storefront.i18n?.language,
|
|
18
|
+
},
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
if (!product?.id) {
|
|
22
|
+
throw new Response(null, {status: 404});
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return defer({
|
|
26
|
+
product,
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export default function Product() {
|
|
31
|
+
const {product} = useLoaderData<typeof loader>();
|
|
32
|
+
const {title, vendor, descriptionHtml} = product;
|
|
33
|
+
|
|
34
|
+
return (
|
|
35
|
+
<>
|
|
36
|
+
<h1>{title}</h1>
|
|
37
|
+
<h2>{vendor}</h2>
|
|
38
|
+
<div dangerouslySetInnerHTML={{__html: descriptionHtml}} />
|
|
39
|
+
</>
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const PRODUCT_QUERY = `#graphql
|
|
44
|
+
|
|
45
|
+
query Product(
|
|
46
|
+
$country: CountryCode
|
|
47
|
+
$language: LanguageCode
|
|
48
|
+
$handle: String!
|
|
49
|
+
) @inContext(country: $country, language: $language) {
|
|
50
|
+
product(handle: $handle) {
|
|
51
|
+
id
|
|
52
|
+
title
|
|
53
|
+
descriptionHtml
|
|
54
|
+
vendor
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
`;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { spawnSync } from 'child_process';
|
|
2
|
+
import { output } from '@shopify/cli-kit';
|
|
3
|
+
|
|
4
|
+
const EXPERIMENTAL_VM_MODULES_FLAG = "--experimental-vm-modules";
|
|
5
|
+
const hook = async function(options) {
|
|
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(
|
|
8
|
+
`Restarting CLI process with ${EXPERIMENTAL_VM_MODULES_FLAG} flag.`
|
|
9
|
+
);
|
|
10
|
+
const [command, ...args] = process.argv;
|
|
11
|
+
args.unshift(EXPERIMENTAL_VM_MODULES_FLAG);
|
|
12
|
+
spawnSync(command, args, { stdio: "inherit" });
|
|
13
|
+
process.exit(0);
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
var init_default = hook;
|
|
17
|
+
|
|
18
|
+
export { init_default as default };
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { file, path, git } from '@shopify/cli-kit';
|
|
2
|
+
import { renderWarning } from '@shopify/cli-kit/node/ui';
|
|
3
|
+
import { lockfiles } from '@shopify/cli-kit/node/node-package-manager';
|
|
4
|
+
|
|
5
|
+
function missingLockfileWarning() {
|
|
6
|
+
renderWarning({
|
|
7
|
+
headline: "No lockfile found",
|
|
8
|
+
body: `If you don\u2019t commit a lockfile, then your app might install the wrong package versions when deploying. To avoid versioning issues, generate a new lockfile and commit it to your repository.`,
|
|
9
|
+
nextSteps: [
|
|
10
|
+
[
|
|
11
|
+
"Generate a lockfile. Run",
|
|
12
|
+
{
|
|
13
|
+
command: "npm|yarn|pnpm install"
|
|
14
|
+
}
|
|
15
|
+
],
|
|
16
|
+
"Commit the new file to your repository"
|
|
17
|
+
]
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
function multipleLockfilesWarning(lockfiles2) {
|
|
21
|
+
const packageManagers = {
|
|
22
|
+
"yarn.lock": "yarn",
|
|
23
|
+
"package-lock.json": "npm",
|
|
24
|
+
"pnpm-lock.yaml": "pnpm"
|
|
25
|
+
};
|
|
26
|
+
const lockfileList = lockfiles2.map((lockfile) => {
|
|
27
|
+
return `${lockfile} (created by ${packageManagers[lockfile]})`;
|
|
28
|
+
});
|
|
29
|
+
renderWarning({
|
|
30
|
+
headline: "Multiple lockfiles found",
|
|
31
|
+
body: [
|
|
32
|
+
`Your project contains more than one lockfile. This can cause version conflicts when installing and deploying your app. The following lockfiles were detected:
|
|
33
|
+
`,
|
|
34
|
+
{ list: { items: lockfileList } }
|
|
35
|
+
],
|
|
36
|
+
nextSteps: [
|
|
37
|
+
"Delete any unneeded lockfiles",
|
|
38
|
+
"Commit the change to your repository"
|
|
39
|
+
]
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
function lockfileIgnoredWarning(lockfile) {
|
|
43
|
+
renderWarning({
|
|
44
|
+
headline: "Lockfile ignored by Git",
|
|
45
|
+
body: `Your project\u2019s lockfile isn\u2019t being tracked by Git. If you don\u2019t commit a lockfile, then your app might install the wrong package versions when deploying.`,
|
|
46
|
+
nextSteps: [
|
|
47
|
+
`In your project\u2019s .gitignore file, delete any references to ${lockfile}`,
|
|
48
|
+
"Commit the change to your repository"
|
|
49
|
+
]
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
async function checkLockfileStatus(directory) {
|
|
53
|
+
if (process.env.LOCAL_DEV)
|
|
54
|
+
return;
|
|
55
|
+
const availableLockfiles = [];
|
|
56
|
+
for (const lockFileName of lockfiles) {
|
|
57
|
+
if (await file.exists(path.resolve(directory, lockFileName))) {
|
|
58
|
+
availableLockfiles.push(lockFileName);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
if (!availableLockfiles.length) {
|
|
62
|
+
return missingLockfileWarning();
|
|
63
|
+
}
|
|
64
|
+
if (availableLockfiles.length > 1) {
|
|
65
|
+
return multipleLockfilesWarning(availableLockfiles);
|
|
66
|
+
}
|
|
67
|
+
try {
|
|
68
|
+
const repo = git.factory(directory);
|
|
69
|
+
const lockfile = availableLockfiles[0];
|
|
70
|
+
const ignoredLockfile = await repo.checkIgnore([lockfile]);
|
|
71
|
+
if (ignoredLockfile.length) {
|
|
72
|
+
lockfileIgnoredWarning(lockfile);
|
|
73
|
+
}
|
|
74
|
+
} catch {
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export { checkLockfileStatus };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|