@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.
Files changed (81) hide show
  1. package/LICENSE.md +7 -0
  2. package/README.md +14 -0
  3. package/dist/commands/hydrogen/build.d.ts +23 -0
  4. package/dist/commands/hydrogen/build.js +100 -0
  5. package/dist/commands/hydrogen/check.d.ts +18 -0
  6. package/dist/commands/hydrogen/check.js +35 -0
  7. package/dist/commands/hydrogen/dev.d.ts +15 -0
  8. package/dist/commands/hydrogen/dev.js +134 -0
  9. package/dist/commands/hydrogen/generate/route.d.ts +34 -0
  10. package/dist/commands/hydrogen/generate/route.js +166 -0
  11. package/dist/commands/hydrogen/generate/route.test.d.ts +1 -0
  12. package/dist/commands/hydrogen/generate/route.test.js +126 -0
  13. package/dist/commands/hydrogen/generate/routes.d.ts +16 -0
  14. package/dist/commands/hydrogen/generate/routes.js +22 -0
  15. package/dist/commands/hydrogen/init.d.ts +24 -0
  16. package/dist/commands/hydrogen/init.js +199 -0
  17. package/dist/commands/hydrogen/init.test.d.ts +1 -0
  18. package/dist/commands/hydrogen/init.test.js +79 -0
  19. package/dist/commands/hydrogen/preview.d.ts +17 -0
  20. package/dist/commands/hydrogen/preview.js +34 -0
  21. package/dist/create-app.d.ts +1 -0
  22. package/dist/create-app.js +4 -0
  23. package/dist/generator-templates/routes/[robots.txt].tsx +41 -0
  24. package/dist/generator-templates/routes/[sitemap.xml].tsx +204 -0
  25. package/dist/generator-templates/routes/account/login.tsx +74 -0
  26. package/dist/generator-templates/routes/account/register.tsx +74 -0
  27. package/dist/generator-templates/routes/cart.tsx +41 -0
  28. package/dist/generator-templates/routes/collections/$collectionHandle.tsx +65 -0
  29. package/dist/generator-templates/routes/graphiql.tsx +10 -0
  30. package/dist/generator-templates/routes/index.tsx +7 -0
  31. package/dist/generator-templates/routes/pages/$pageHandle.tsx +74 -0
  32. package/dist/generator-templates/routes/policies/$policyHandle.tsx +87 -0
  33. package/dist/generator-templates/routes/policies/index.tsx +67 -0
  34. package/dist/generator-templates/routes/products/$productHandle.tsx +57 -0
  35. package/dist/hooks/init.d.ts +5 -0
  36. package/dist/hooks/init.js +18 -0
  37. package/dist/utils/check-lockfile.d.ts +3 -0
  38. package/dist/utils/check-lockfile.js +78 -0
  39. package/dist/utils/check-lockfile.test.d.ts +1 -0
  40. package/dist/utils/check-lockfile.test.js +74 -0
  41. package/dist/utils/check-version.d.ts +16 -0
  42. package/dist/utils/check-version.js +55 -0
  43. package/dist/utils/check-version.test.d.ts +1 -0
  44. package/dist/utils/check-version.test.js +60 -0
  45. package/dist/utils/config.d.ts +18 -0
  46. package/dist/utils/config.js +146 -0
  47. package/dist/utils/flags.d.ts +24 -0
  48. package/dist/utils/flags.js +60 -0
  49. package/dist/utils/flags.test.d.ts +1 -0
  50. package/dist/utils/flags.test.js +43 -0
  51. package/dist/utils/log.d.ts +5 -0
  52. package/dist/utils/log.js +20 -0
  53. package/dist/utils/mini-oxygen.d.ts +11 -0
  54. package/dist/utils/mini-oxygen.js +73 -0
  55. package/dist/utils/missing-routes.d.ts +6 -0
  56. package/dist/utils/missing-routes.js +71 -0
  57. package/dist/utils/template-downloader.d.ts +11 -0
  58. package/dist/utils/template-downloader.js +70 -0
  59. package/dist/utils/transpile-ts.d.ts +16 -0
  60. package/dist/utils/transpile-ts.js +169 -0
  61. package/dist/utils/virtual-routes.d.ts +7 -0
  62. package/dist/utils/virtual-routes.js +44 -0
  63. package/dist/utils/virtual-routes.test.d.ts +1 -0
  64. package/dist/utils/virtual-routes.test.js +49 -0
  65. package/dist/virtual-routes/assets/favicon.svg +28 -0
  66. package/dist/virtual-routes/assets/inter-variable-font.woff2 +0 -0
  67. package/dist/virtual-routes/assets/jetbrainsmono-variable-font.woff2 +0 -0
  68. package/dist/virtual-routes/assets/styles.css +238 -0
  69. package/dist/virtual-routes/components/HydrogenLogoBaseBW.jsx +7 -0
  70. package/dist/virtual-routes/components/HydrogenLogoBaseColor.jsx +13 -0
  71. package/dist/virtual-routes/components/IconBanner.jsx +47 -0
  72. package/dist/virtual-routes/components/IconDiscord.jsx +4 -0
  73. package/dist/virtual-routes/components/IconError.jsx +20 -0
  74. package/dist/virtual-routes/components/IconGithub.jsx +4 -0
  75. package/dist/virtual-routes/components/IconTwitter.jsx +4 -0
  76. package/dist/virtual-routes/components/Layout.jsx +6 -0
  77. package/dist/virtual-routes/routes/graphiql.jsx +5 -0
  78. package/dist/virtual-routes/routes/index.jsx +163 -0
  79. package/dist/virtual-routes/virtual-root.jsx +38 -0
  80. package/oclif.manifest.json +1 -0
  81. 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&rsquo;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,7 @@
1
+ export default function Index() {
2
+ return (
3
+ <p>
4
+ Edit this route in <em>app/routes/index.tsx</em>.
5
+ </p>
6
+ );
7
+ }
@@ -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,5 @@
1
+ import { Hook } from '@oclif/core';
2
+
3
+ declare const hook: Hook<'init'>;
4
+
5
+ export { hook as default };
@@ -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,3 @@
1
+ declare function checkLockfileStatus(directory: string): Promise<void>;
2
+
3
+ export { checkLockfileStatus };
@@ -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 };