@shopify/cli-hydrogen 4.1.0 → 4.1.2

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 (55) hide show
  1. package/dist/commands/hydrogen/dev.d.ts +1 -0
  2. package/dist/commands/hydrogen/dev.js +8 -0
  3. package/dist/commands/hydrogen/env/pull.d.ts +21 -0
  4. package/dist/commands/hydrogen/env/pull.js +115 -0
  5. package/dist/commands/hydrogen/env/pull.test.d.ts +1 -0
  6. package/dist/commands/hydrogen/env/pull.test.js +205 -0
  7. package/dist/commands/hydrogen/init.js +3 -1
  8. package/dist/commands/hydrogen/link.d.ts +24 -0
  9. package/dist/commands/hydrogen/link.js +102 -0
  10. package/dist/commands/hydrogen/link.test.d.ts +1 -0
  11. package/dist/commands/hydrogen/link.test.js +137 -0
  12. package/dist/commands/hydrogen/list.d.ts +21 -0
  13. package/dist/commands/hydrogen/list.js +83 -0
  14. package/dist/commands/hydrogen/list.test.d.ts +1 -0
  15. package/dist/commands/hydrogen/list.test.js +116 -0
  16. package/dist/commands/hydrogen/unlink.d.ts +17 -0
  17. package/dist/commands/hydrogen/unlink.js +29 -0
  18. package/dist/commands/hydrogen/unlink.test.d.ts +1 -0
  19. package/dist/commands/hydrogen/unlink.test.js +36 -0
  20. package/dist/generator-templates/routes/[robots.txt].tsx +111 -19
  21. package/dist/generator-templates/routes/collections/index.tsx +102 -0
  22. package/dist/lib/admin-session.d.ts +5 -0
  23. package/dist/lib/admin-session.js +16 -0
  24. package/dist/lib/admin-session.test.d.ts +1 -0
  25. package/dist/lib/admin-session.test.js +27 -0
  26. package/dist/lib/admin-urls.d.ts +8 -0
  27. package/dist/lib/admin-urls.js +18 -0
  28. package/dist/lib/flags.d.ts +1 -0
  29. package/dist/lib/flags.js +7 -0
  30. package/dist/lib/graphql/admin/link-storefront.d.ts +11 -0
  31. package/dist/lib/graphql/admin/link-storefront.js +11 -0
  32. package/dist/lib/graphql/admin/list-storefronts.d.ts +17 -0
  33. package/dist/lib/graphql/admin/list-storefronts.js +16 -0
  34. package/dist/lib/graphql/admin/pull-variables.d.ts +16 -0
  35. package/dist/lib/graphql/admin/pull-variables.js +15 -0
  36. package/dist/lib/graphql.d.ts +21 -0
  37. package/dist/lib/graphql.js +18 -0
  38. package/dist/lib/graphql.test.d.ts +1 -0
  39. package/dist/lib/graphql.test.js +15 -0
  40. package/dist/lib/missing-routes.d.ts +3 -1
  41. package/dist/lib/missing-routes.js +7 -6
  42. package/dist/lib/missing-routes.test.d.ts +1 -0
  43. package/dist/lib/missing-routes.test.js +45 -0
  44. package/dist/lib/missing-storefronts.d.ts +5 -0
  45. package/dist/lib/missing-storefronts.js +18 -0
  46. package/dist/lib/shop.d.ts +7 -0
  47. package/dist/lib/shop.js +32 -0
  48. package/dist/lib/shop.test.d.ts +1 -0
  49. package/dist/lib/shop.test.js +78 -0
  50. package/dist/lib/shopify-config.d.ts +35 -0
  51. package/dist/lib/shopify-config.js +86 -0
  52. package/dist/lib/shopify-config.test.d.ts +1 -0
  53. package/dist/lib/shopify-config.test.js +209 -0
  54. package/oclif.manifest.json +1 -1
  55. package/package.json +4 -4
@@ -0,0 +1,11 @@
1
+ const LinkStorefrontQuery = `#graphql
2
+ query LinkStorefront {
3
+ hydrogenStorefronts {
4
+ id
5
+ title
6
+ productionUrl
7
+ }
8
+ }
9
+ `;
10
+
11
+ export { LinkStorefrontQuery };
@@ -0,0 +1,17 @@
1
+ declare const ListStorefrontsQuery = "#graphql\n query ListStorefronts {\n hydrogenStorefronts {\n id\n title\n productionUrl\n currentProductionDeployment {\n id\n createdAt\n commitMessage\n }\n }\n }\n";
2
+ interface Deployment {
3
+ id: string;
4
+ createdAt: string;
5
+ commitMessage: string | null;
6
+ }
7
+ interface HydrogenStorefront {
8
+ id: string;
9
+ title: string;
10
+ productionUrl?: string;
11
+ currentProductionDeployment: Deployment | null;
12
+ }
13
+ interface ListStorefrontsSchema {
14
+ hydrogenStorefronts: HydrogenStorefront[];
15
+ }
16
+
17
+ export { Deployment, ListStorefrontsQuery, ListStorefrontsSchema };
@@ -0,0 +1,16 @@
1
+ const ListStorefrontsQuery = `#graphql
2
+ query ListStorefronts {
3
+ hydrogenStorefronts {
4
+ id
5
+ title
6
+ productionUrl
7
+ currentProductionDeployment {
8
+ id
9
+ createdAt
10
+ commitMessage
11
+ }
12
+ }
13
+ }
14
+ `;
15
+
16
+ export { ListStorefrontsQuery };
@@ -0,0 +1,16 @@
1
+ declare const PullVariablesQuery = "#graphql\n query ListStorefronts($id: ID!) {\n hydrogenStorefront(id: $id) {\n id\n environmentVariables {\n id\n isSecret\n key\n value\n }\n }\n }\n";
2
+ interface EnvironmentVariable {
3
+ id: string;
4
+ isSecret: boolean;
5
+ key: string;
6
+ value: string;
7
+ }
8
+ interface HydrogenStorefront {
9
+ id: string;
10
+ environmentVariables: EnvironmentVariable[];
11
+ }
12
+ interface PullVariablesSchema {
13
+ hydrogenStorefront: HydrogenStorefront | null;
14
+ }
15
+
16
+ export { EnvironmentVariable, PullVariablesQuery, PullVariablesSchema };
@@ -0,0 +1,15 @@
1
+ const PullVariablesQuery = `#graphql
2
+ query ListStorefronts($id: ID!) {
3
+ hydrogenStorefront(id: $id) {
4
+ id
5
+ environmentVariables {
6
+ id
7
+ isSecret
8
+ key
9
+ value
10
+ }
11
+ }
12
+ }
13
+ `;
14
+
15
+ export { PullVariablesQuery };
@@ -0,0 +1,21 @@
1
+ import { GraphQLVariables } from '@shopify/cli-kit/node/api/graphql';
2
+ import { AdminSession } from '@shopify/cli-kit/node/session';
3
+
4
+ /**
5
+ * This is a temporary workaround until cli-kit includes a way to specify
6
+ * API versions for the Admin API because we need to target the unstable
7
+ * branch for this early access phase.
8
+ *
9
+ * @param query - GraphQL query to execute.
10
+ * @param session - Shopify admin session including token and Store FQDN.
11
+ * @param variables - GraphQL variables to pass to the query.
12
+ * @returns The response of the query of generic type <T>.
13
+ */
14
+ declare function adminRequest<T>(query: string, session: AdminSession, variables?: GraphQLVariables): Promise<T>;
15
+ /**
16
+ * @param gid a Global ID to parse (e.g. 'gid://shopify/HydrogenStorefront/1')
17
+ * @returns the ID of the record (e.g. '1')
18
+ */
19
+ declare function parseGid(gid: string): string;
20
+
21
+ export { adminRequest, parseGid };
@@ -0,0 +1,18 @@
1
+ import { graphqlRequest } from '@shopify/cli-kit/node/api/graphql';
2
+ import { AbortError } from '@shopify/cli-kit/node/error';
3
+
4
+ async function adminRequest(query, session, variables) {
5
+ const api = "Admin";
6
+ const url = `https://${session.storeFqdn}/admin/api/unstable/graphql.json`;
7
+ return graphqlRequest({ query, api, url, token: session.token, variables });
8
+ }
9
+ const GID_REGEXP = /gid:\/\/shopify\/\w*\/(\d+)/;
10
+ function parseGid(gid) {
11
+ const matches = GID_REGEXP.exec(gid);
12
+ if (matches && matches[1] !== void 0) {
13
+ return matches[1];
14
+ }
15
+ throw new AbortError(`Invalid Global ID: ${gid}`);
16
+ }
17
+
18
+ export { adminRequest, parseGid };
@@ -0,0 +1 @@
1
+
@@ -0,0 +1,15 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { AbortError } from '@shopify/cli-kit/node/error';
3
+ import { parseGid } from './graphql.js';
4
+
5
+ describe("parseGid", () => {
6
+ it("returns an ID", () => {
7
+ const id = parseGid("gid://shopify/HydrogenStorefront/324");
8
+ expect(id).toStrictEqual("324");
9
+ });
10
+ describe("when the global ID is invalid", () => {
11
+ it("throws an error", () => {
12
+ expect(() => parseGid("321asd")).toThrow(AbortError);
13
+ });
14
+ });
15
+ });
@@ -1,6 +1,8 @@
1
1
  import { RemixConfig } from '@remix-run/dev/dist/config.js';
2
2
 
3
- declare function findMissingRoutes(config: RemixConfig): string[];
3
+ declare function findMissingRoutes(config: {
4
+ routes: RemixConfig['routes'];
5
+ }, requiredRoutes?: string[]): string[];
4
6
  declare function logMissingRoutes(routes: string[]): void;
5
7
 
6
8
  export { findMissingRoutes, logMissingRoutes };
@@ -20,13 +20,13 @@ const REQUIRED_ROUTES = [
20
20
  "account/reset/:id/:token",
21
21
  "account/activate/:id/:token"
22
22
  ];
23
- function findMissingRoutes(config) {
23
+ function findMissingRoutes(config, requiredRoutes = REQUIRED_ROUTES) {
24
24
  const userRoutes = Object.values(config.routes);
25
- const requiredRoutes = new Set(REQUIRED_ROUTES);
25
+ const missingRoutes = new Set(requiredRoutes);
26
26
  for (const requiredRoute of requiredRoutes) {
27
27
  for (const userRoute of userRoutes) {
28
28
  if (!requiredRoute && !userRoute.path) {
29
- requiredRoutes.delete(requiredRoute);
29
+ missingRoutes.delete(requiredRoute);
30
30
  } else if (requiredRoute && userRoute.path) {
31
31
  const currentRoute = {
32
32
  path: userRoute.path,
@@ -41,14 +41,15 @@ function findMissingRoutes(config) {
41
41
  currentRoute.path = `${parentRoute.path}/${currentRoute.path}`;
42
42
  currentRoute.parentId = parentRoute.parentId;
43
43
  }
44
- const reString = "^(:[^\\/\\?]+\\?\\/)?" + requiredRoute.replaceAll(".", "\\.").replace(/:[^/]+/g, ":[^\\/]+") + "$";
44
+ const optionalSegment = ":?[^\\/\\?]+\\?";
45
+ const reString = `^(${optionalSegment}\\/)?` + requiredRoute.replaceAll(".", "\\.").replace(/\//g, `\\/(${optionalSegment}\\/)?`).replace(/:[^/)?]+/g, ":[^\\/]+") + `(\\/${optionalSegment})?$`;
45
46
  if (new RegExp(reString).test(currentRoute.path)) {
46
- requiredRoutes.delete(requiredRoute);
47
+ missingRoutes.delete(requiredRoute);
47
48
  }
48
49
  }
49
50
  }
50
51
  }
51
- return [...requiredRoutes];
52
+ return [...missingRoutes];
52
53
  }
53
54
  const LINE_LIMIT = 100;
54
55
  function logMissingRoutes(routes) {
@@ -0,0 +1 @@
1
+
@@ -0,0 +1,45 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { findMissingRoutes } from './missing-routes.js';
3
+
4
+ const createRoute = (path) => ({
5
+ routes: {
6
+ "route-id": {
7
+ file: "a/file",
8
+ id: "route-id",
9
+ path
10
+ }
11
+ }
12
+ });
13
+ describe("missing-routes", () => {
14
+ it("matches routes with dots", async () => {
15
+ const requiredRoutes = ["sitemap.xml"];
16
+ expect(findMissingRoutes({ routes: {} }, requiredRoutes)).toHaveLength(1);
17
+ expect(
18
+ findMissingRoutes(createRoute("sitemap.xml"), requiredRoutes)
19
+ ).toHaveLength(0);
20
+ });
21
+ it("matches routes with different parameter names", async () => {
22
+ const requiredRoutes = ["collections/:collectionHandle"];
23
+ expect(findMissingRoutes({ routes: {} }, requiredRoutes)).toHaveLength(1);
24
+ expect(
25
+ findMissingRoutes(createRoute("collections/:param"), requiredRoutes)
26
+ ).toHaveLength(0);
27
+ });
28
+ it("matches optional segments in different positions", async () => {
29
+ const requiredRoutes = ["collections/products"];
30
+ const validRoutes = [
31
+ "segment?/collections/products",
32
+ ":segment?/collections/products",
33
+ "collections/segment?/products",
34
+ "collections/:segment?/products",
35
+ "collections/products/segment?",
36
+ "collections/products/:segment?"
37
+ ];
38
+ expect(findMissingRoutes({ routes: {} }, requiredRoutes)).toHaveLength(1);
39
+ for (const validRoute of validRoutes) {
40
+ expect(
41
+ findMissingRoutes(createRoute(validRoute), requiredRoutes)
42
+ ).toHaveLength(0);
43
+ }
44
+ });
45
+ });
@@ -0,0 +1,5 @@
1
+ import { AdminSession } from '@shopify/cli-kit/node/session';
2
+
3
+ declare function logMissingStorefronts(adminSession: AdminSession): void;
4
+
5
+ export { logMissingStorefronts };
@@ -0,0 +1,18 @@
1
+ import { renderInfo } from '@shopify/cli-kit/node/ui';
2
+ import { newHydrogenStorefrontUrl } from './admin-urls.js';
3
+
4
+ function logMissingStorefronts(adminSession) {
5
+ renderInfo({
6
+ headline: "Hydrogen storefronts",
7
+ body: "There are no Hydrogen storefronts on your Shop.",
8
+ nextSteps: [
9
+ `Ensure you have specified the correct shop (you specified: ${adminSession.storeFqdn})`,
10
+ `Ensure you have the Hydrogen sales channel installed https://apps.shopify.com/hydrogen`,
11
+ `Create a new Hydrogen storefront: ${newHydrogenStorefrontUrl(
12
+ adminSession
13
+ )}`
14
+ ]
15
+ });
16
+ }
17
+
18
+ export { logMissingStorefronts };
@@ -0,0 +1,7 @@
1
+ interface Flags {
2
+ shop?: string;
3
+ path?: string;
4
+ }
5
+ declare function getHydrogenShop(flags: Flags): Promise<string>;
6
+
7
+ export { getHydrogenShop };
@@ -0,0 +1,32 @@
1
+ import { renderTextPrompt } from '@shopify/cli-kit/node/ui';
2
+ import { AbortError } from '@shopify/cli-kit/node/error';
3
+ import { outputContent, outputToken } from '@shopify/cli-kit/node/output';
4
+ import { getConfig, setShop } from './shopify-config.js';
5
+
6
+ async function getHydrogenShop(flags) {
7
+ const { shop: flagShop, path: flagPath } = flags;
8
+ const targetPath = flagPath ?? process.cwd();
9
+ const { shop: configShop } = await getConfig(targetPath);
10
+ let promptShop;
11
+ if (!flagShop && !configShop) {
12
+ promptShop = await renderTextPrompt({
13
+ message: "Specify which Shop you would like to use (e.g. janes-goods.myshopify.com)",
14
+ allowEmpty: false
15
+ });
16
+ }
17
+ const shop = flagShop || configShop || promptShop;
18
+ if (!shop) {
19
+ throw new AbortError(
20
+ "A shop is required",
21
+ `Specify the shop passing ${outputContent`${outputToken.genericShellCommand(
22
+ `--shop={your_shop_url}}`
23
+ )}`.value} or set the ${outputContent`${outputToken.genericShellCommand("SHOPIFY_SHOP")}`.value} environment variable.`
24
+ );
25
+ }
26
+ if (!configShop || flagShop && flagShop != configShop) {
27
+ await setShop(targetPath, shop);
28
+ }
29
+ return shop;
30
+ }
31
+
32
+ export { getHydrogenShop };
@@ -0,0 +1 @@
1
+
@@ -0,0 +1,78 @@
1
+ import { vi, describe, beforeEach, afterEach, it, expect } from 'vitest';
2
+ import { inTemporaryDirectory } from '@shopify/cli-kit/node/fs';
3
+ import { AbortError } from '@shopify/cli-kit/node/error';
4
+ import { mockAndCaptureOutput } from '@shopify/cli-kit/node/testing/output';
5
+ import { renderTextPrompt } from '@shopify/cli-kit/node/ui';
6
+ import { getHydrogenShop } from './shop.js';
7
+ import { getConfig, setShop } from './shopify-config.js';
8
+
9
+ vi.mock("@shopify/cli-kit/node/ui");
10
+ vi.mock("./shopify-config.js");
11
+ describe("getHydrogenShop()", () => {
12
+ beforeEach(() => {
13
+ vi.mocked(getConfig).mockResolvedValue({});
14
+ });
15
+ afterEach(() => {
16
+ mockAndCaptureOutput().clear();
17
+ vi.clearAllMocks();
18
+ });
19
+ describe("when a shop is passed via flag", () => {
20
+ it("returns the shop", async () => {
21
+ await inTemporaryDirectory(async (tmpDir) => {
22
+ const shop = await getHydrogenShop({ shop: "my-shop", path: tmpDir });
23
+ expect(shop).toBe("my-shop");
24
+ });
25
+ });
26
+ });
27
+ describe("when a shop is not provided via flag", () => {
28
+ describe("and there is no existing SHOP_NAME file", () => {
29
+ it("prompts the user to enter a new name", async () => {
30
+ await inTemporaryDirectory(async (tmpDir) => {
31
+ vi.mocked(renderTextPrompt).mockResolvedValue("my-prompted-shop");
32
+ const shop = await getHydrogenShop({ path: tmpDir });
33
+ expect(renderTextPrompt).toHaveBeenCalledWith({
34
+ message: "Specify which Shop you would like to use (e.g. janes-goods.myshopify.com)",
35
+ allowEmpty: false
36
+ });
37
+ expect(shop).toBe("my-prompted-shop");
38
+ });
39
+ });
40
+ describe("and the user does not enter a value", () => {
41
+ it("throws an error", async () => {
42
+ await inTemporaryDirectory(async (tmpDir) => {
43
+ vi.mocked(renderTextPrompt).mockResolvedValue("");
44
+ await expect(getHydrogenShop({ path: tmpDir })).rejects.toThrow(
45
+ AbortError
46
+ );
47
+ });
48
+ });
49
+ });
50
+ });
51
+ describe("and there is an existing shop from the config file", () => {
52
+ it("returns the shop", async () => {
53
+ vi.mocked(getConfig).mockResolvedValue({ shop: "previous-shop" });
54
+ await inTemporaryDirectory(async (tmpDir) => {
55
+ const shop = await getHydrogenShop({ path: tmpDir });
56
+ expect(shop).toBe("previous-shop");
57
+ });
58
+ });
59
+ });
60
+ });
61
+ describe("when the SHOP_NAME file does not exist", () => {
62
+ it("gets created", async () => {
63
+ await inTemporaryDirectory(async (tmpDir) => {
64
+ await getHydrogenShop({ shop: "new-shop", path: tmpDir });
65
+ expect(setShop).toHaveBeenCalledWith(tmpDir, "new-shop");
66
+ });
67
+ });
68
+ });
69
+ describe("when the shop is different from the value in SHOP_NAME", () => {
70
+ it("overwrites SHOP_NAME with the new value", async () => {
71
+ vi.mocked(getConfig).mockResolvedValue({ shop: "previous-shop" });
72
+ await inTemporaryDirectory(async (tmpDir) => {
73
+ await getHydrogenShop({ shop: "new-shop", path: tmpDir });
74
+ expect(setShop).toHaveBeenCalledWith(tmpDir, "new-shop");
75
+ });
76
+ });
77
+ });
78
+ });
@@ -0,0 +1,35 @@
1
+ declare const SHOPIFY_DIR = ".shopify";
2
+ declare const SHOPIFY_DIR_PROJECT = "project.json";
3
+ interface Storefront {
4
+ id: string;
5
+ title: string;
6
+ }
7
+ interface ShopifyConfig {
8
+ shop?: string;
9
+ storefront?: Storefront;
10
+ }
11
+ declare function getConfig(root: string): Promise<ShopifyConfig>;
12
+ declare function setShop(root: string, shop: string): Promise<ShopifyConfig>;
13
+ /**
14
+ * Adds storefront information to the config
15
+ *
16
+ * @param root the target directory
17
+ * @returns the updated config
18
+ */
19
+ declare function setStorefront(root: string, storefront: Storefront): Promise<ShopifyConfig>;
20
+ /**
21
+ * Removes storefront information from the config
22
+ *
23
+ * @param root the target directory
24
+ * @returns the updated config
25
+ */
26
+ declare function unsetStorefront(root: string): Promise<ShopifyConfig>;
27
+ /**
28
+ * Conditionally adds .shopify to the directory's .gitignore file
29
+ * @param root
30
+ * @returns A boolean; true if the .gitignore was updated, false if there was
31
+ * an error or the .gitignore didn't need updating
32
+ */
33
+ declare function ensureShopifyGitIgnore(root: string): Promise<boolean>;
34
+
35
+ export { SHOPIFY_DIR, SHOPIFY_DIR_PROJECT, ShopifyConfig, ensureShopifyGitIgnore, getConfig, setShop, setStorefront, unsetStorefront };
@@ -0,0 +1,86 @@
1
+ import { resolvePath, dirname } from '@shopify/cli-kit/node/path';
2
+ import { fileExists, readFile, mkdir, writeFile } from '@shopify/cli-kit/node/fs';
3
+ import { AbortError } from '@shopify/cli-kit/node/error';
4
+ import { outputInfo } from '@shopify/cli-kit/node/output';
5
+
6
+ const SHOPIFY_DIR = ".shopify";
7
+ const SHOPIFY_DIR_PROJECT = "project.json";
8
+ async function getConfig(root) {
9
+ const filePath = resolvePath(root, SHOPIFY_DIR, SHOPIFY_DIR_PROJECT);
10
+ if (!await fileExists(filePath)) {
11
+ return {};
12
+ }
13
+ return JSON.parse(await readFile(filePath));
14
+ }
15
+ async function setShop(root, shop) {
16
+ const filePath = resolvePath(root, SHOPIFY_DIR, SHOPIFY_DIR_PROJECT);
17
+ if (!await fileExists(filePath)) {
18
+ await mkdir(dirname(filePath));
19
+ const newConfig = {
20
+ shop
21
+ };
22
+ await writeFile(filePath, JSON.stringify(newConfig));
23
+ await ensureShopifyGitIgnore(root);
24
+ return newConfig;
25
+ }
26
+ const existingConfig = JSON.parse(await readFile(filePath));
27
+ const config = {
28
+ ...existingConfig,
29
+ shop
30
+ };
31
+ await writeFile(filePath, JSON.stringify(config));
32
+ await ensureShopifyGitIgnore(root);
33
+ return config;
34
+ }
35
+ async function setStorefront(root, storefront) {
36
+ try {
37
+ const filePath = resolvePath(root, SHOPIFY_DIR, SHOPIFY_DIR_PROJECT);
38
+ const existingConfig = JSON.parse(await readFile(filePath));
39
+ const config = {
40
+ ...existingConfig,
41
+ storefront
42
+ };
43
+ await writeFile(filePath, JSON.stringify(config));
44
+ await ensureShopifyGitIgnore(root);
45
+ return config;
46
+ } catch {
47
+ throw new AbortError("Project configuration could not be found.");
48
+ }
49
+ }
50
+ async function unsetStorefront(root) {
51
+ try {
52
+ const filePath = resolvePath(root, SHOPIFY_DIR, SHOPIFY_DIR_PROJECT);
53
+ const existingConfig = JSON.parse(await readFile(filePath));
54
+ const config = {
55
+ ...existingConfig,
56
+ storefront: void 0
57
+ };
58
+ await writeFile(filePath, JSON.stringify(config));
59
+ await ensureShopifyGitIgnore(root);
60
+ return config;
61
+ } catch {
62
+ throw new AbortError("Project configuration could not be found.");
63
+ }
64
+ }
65
+ async function ensureShopifyGitIgnore(root) {
66
+ try {
67
+ const gitIgnoreFilePath = resolvePath(root, ".gitignore");
68
+ let gitIgnoreContents = await fileExists(gitIgnoreFilePath) ? await readFile(gitIgnoreFilePath) : "";
69
+ if (gitIgnoreContents.includes(".shopify")) {
70
+ return false;
71
+ }
72
+ if (gitIgnoreContents.length > 0) {
73
+ gitIgnoreContents += `
74
+ `;
75
+ }
76
+ gitIgnoreContents += `${SHOPIFY_DIR}\r
77
+ `;
78
+ outputInfo("Adding .shopify to .gitignore...");
79
+ await writeFile(gitIgnoreFilePath, gitIgnoreContents);
80
+ return true;
81
+ } catch {
82
+ return false;
83
+ }
84
+ }
85
+
86
+ export { SHOPIFY_DIR, SHOPIFY_DIR_PROJECT, ensureShopifyGitIgnore, getConfig, setShop, setStorefront, unsetStorefront };
@@ -0,0 +1 @@
1
+