@shopify/cli-hydrogen 4.2.1 → 5.0.1

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 (60) hide show
  1. package/dist/commands/hydrogen/build.d.ts +1 -1
  2. package/dist/commands/hydrogen/build.js +24 -22
  3. package/dist/commands/hydrogen/check.js +1 -1
  4. package/dist/commands/hydrogen/codegen-unstable.d.ts +1 -0
  5. package/dist/commands/hydrogen/codegen-unstable.js +6 -0
  6. package/dist/commands/hydrogen/dev.d.ts +2 -1
  7. package/dist/commands/hydrogen/dev.js +85 -65
  8. package/dist/commands/hydrogen/env/list.d.ts +2 -3
  9. package/dist/commands/hydrogen/env/list.js +42 -44
  10. package/dist/commands/hydrogen/env/list.test.js +18 -24
  11. package/dist/commands/hydrogen/env/pull.d.ts +2 -3
  12. package/dist/commands/hydrogen/env/pull.js +42 -23
  13. package/dist/commands/hydrogen/env/pull.test.js +16 -4
  14. package/dist/commands/hydrogen/init.js +3 -13
  15. package/dist/commands/hydrogen/link.d.ts +0 -1
  16. package/dist/commands/hydrogen/link.js +34 -36
  17. package/dist/commands/hydrogen/link.test.js +43 -27
  18. package/dist/commands/hydrogen/list.d.ts +2 -2
  19. package/dist/commands/hydrogen/list.js +43 -39
  20. package/dist/commands/hydrogen/list.test.js +24 -32
  21. package/dist/commands/hydrogen/shortcut.js +6 -7
  22. package/dist/commands/hydrogen/shortcut.test.js +8 -9
  23. package/dist/commands/hydrogen/unlink.d.ts +0 -1
  24. package/dist/commands/hydrogen/unlink.js +5 -3
  25. package/dist/lib/admin-session.d.ts +1 -0
  26. package/dist/lib/codegen.d.ts +3 -2
  27. package/dist/lib/codegen.js +20 -7
  28. package/dist/lib/combined-environment-variables.js +19 -36
  29. package/dist/lib/combined-environment-variables.test.js +7 -7
  30. package/dist/lib/config.d.ts +3 -1
  31. package/dist/lib/config.js +67 -63
  32. package/dist/lib/flags.d.ts +1 -0
  33. package/dist/lib/flags.js +8 -3
  34. package/dist/lib/graphql/admin/link-storefront.d.ts +12 -9
  35. package/dist/lib/graphql/admin/link-storefront.js +18 -1
  36. package/dist/lib/graphql/admin/list-environments.d.ts +6 -5
  37. package/dist/lib/graphql/admin/list-environments.js +11 -1
  38. package/dist/lib/graphql/admin/list-storefronts.d.ts +13 -5
  39. package/dist/lib/graphql/admin/list-storefronts.js +18 -1
  40. package/dist/lib/graphql/admin/pull-variables.d.ts +6 -1
  41. package/dist/lib/graphql/admin/pull-variables.js +14 -1
  42. package/dist/lib/log.d.ts +2 -1
  43. package/dist/lib/log.js +8 -1
  44. package/dist/lib/mini-oxygen.js +1 -1
  45. package/dist/lib/process.d.ts +6 -0
  46. package/dist/lib/process.js +17 -0
  47. package/dist/lib/pull-environment-variables.d.ts +1 -0
  48. package/dist/lib/pull-environment-variables.js +4 -14
  49. package/dist/lib/remix-version-interop.js +1 -1
  50. package/dist/lib/shell.d.ts +5 -6
  51. package/dist/lib/shell.js +65 -17
  52. package/dist/lib/shell.test.d.ts +1 -0
  53. package/dist/lib/shell.test.js +85 -0
  54. package/dist/lib/shopify-config.d.ts +1 -1
  55. package/dist/lib/shopify-config.js +2 -2
  56. package/dist/lib/transpile-ts.js +6 -0
  57. package/oclif.manifest.json +1 -1
  58. package/package.json +9 -8
  59. package/dist/lib/colors.d.ts +0 -11
  60. package/dist/lib/colors.js +0 -11
@@ -1,15 +1,17 @@
1
+ import { diffLines } from 'diff';
1
2
  import Command from '@shopify/cli-kit/node/base-command';
2
- import { renderConfirmationPrompt } from '@shopify/cli-kit/node/ui';
3
- import { outputWarn, outputSuccess } from '@shopify/cli-kit/node/output';
4
- import { fileExists, writeFile } from '@shopify/cli-kit/node/fs';
3
+ import { renderInfo, renderConfirmationPrompt, renderWarning, renderSuccess } from '@shopify/cli-kit/node/ui';
4
+ import { outputContent, outputToken } from '@shopify/cli-kit/node/output';
5
+ import { fileExists, readFile, writeFile } from '@shopify/cli-kit/node/fs';
5
6
  import { resolvePath } from '@shopify/cli-kit/node/path';
7
+ import { patchEnvFile } from '@shopify/cli-kit/node/dot-env';
8
+ import colors from '@shopify/cli-kit/node/colors';
6
9
  import { commonFlags, flagsToCamelObject } from '../../../lib/flags.js';
7
10
  import { pullRemoteEnvironmentVariables } from '../../../lib/pull-environment-variables.js';
8
11
  import { getConfig } from '../../../lib/shopify-config.js';
9
12
 
10
- class Pull extends Command {
13
+ class EnvPull extends Command {
11
14
  static description = "Populate your .env with variables from your Hydrogen storefront.";
12
- static hidden = true;
13
15
  static flags = {
14
16
  ["env-branch"]: commonFlags["env-branch"],
15
17
  path: commonFlags.path,
@@ -17,7 +19,7 @@ class Pull extends Command {
17
19
  force: commonFlags.force
18
20
  };
19
21
  async run() {
20
- const { flags } = await this.parse(Pull);
22
+ const { flags } = await this.parse(EnvPull);
21
23
  await pullVariables({ ...flagsToCamelObject(flags) });
22
24
  }
23
25
  }
@@ -36,33 +38,50 @@ async function pullVariables({
36
38
  if (!environmentVariables.length) {
37
39
  return;
38
40
  }
41
+ const fileName = colors.whiteBright(`.env`);
39
42
  const dotEnvPath = resolvePath(actualPath, ".env");
43
+ const fetchedEnv = {};
44
+ environmentVariables.forEach(({ isSecret, key, value }) => {
45
+ fetchedEnv[key] = isSecret ? `""` : value;
46
+ });
40
47
  if (await fileExists(dotEnvPath) && !force) {
48
+ const existingEnv = await readFile(dotEnvPath);
49
+ const patchedEnv = patchEnvFile(existingEnv, fetchedEnv);
50
+ if (existingEnv === patchedEnv) {
51
+ renderInfo({
52
+ body: `No changes to your ${fileName} file`
53
+ });
54
+ return;
55
+ }
56
+ const diff = diffLines(existingEnv, patchedEnv);
41
57
  const overwrite = await renderConfirmationPrompt({
42
- message: "Warning: .env file already exists. Do you want to overwrite it?"
58
+ confirmationMessage: `Yes, confirm changes`,
59
+ cancellationMessage: `No, make changes later`,
60
+ message: outputContent`We'll make the following changes to your .env file:
61
+
62
+ ${outputToken.linesDiff(diff)}
63
+ Continue?`.value
43
64
  });
44
65
  if (!overwrite) {
45
66
  return;
46
67
  }
68
+ await writeFile(dotEnvPath, patchedEnv);
69
+ } else {
70
+ const newEnv = patchEnvFile(null, fetchedEnv);
71
+ await writeFile(dotEnvPath, newEnv);
47
72
  }
48
- let hasSecretVariables = false;
49
- const contents = environmentVariables.map(({ key, value, isSecret }) => {
50
- let line = `${key}="${value}"`;
51
- if (isSecret) {
52
- hasSecretVariables = true;
53
- line = `# ${key} is marked as secret and its value is hidden
54
- ` + line;
55
- }
56
- return line;
57
- }).join("\n") + "\n";
73
+ const hasSecretVariables = environmentVariables.some(
74
+ ({ isSecret }) => isSecret
75
+ );
58
76
  if (hasSecretVariables) {
59
77
  const { storefront: configStorefront } = await getConfig(actualPath);
60
- outputWarn(
61
- `${configStorefront.title} contains environment variables marked as secret, so their values weren\u2019t pulled.`
62
- );
78
+ renderWarning({
79
+ body: `${configStorefront.title} contains environment variables marked as secret, so their values weren\u2019t pulled.`
80
+ });
63
81
  }
64
- await writeFile(dotEnvPath, contents);
65
- outputSuccess("Updated .env");
82
+ renderSuccess({
83
+ body: ["Changes have been made to your", { filePath: fileName }, "file"]
84
+ });
66
85
  }
67
86
 
68
- export { Pull as default, pullVariables };
87
+ export { EnvPull as default, pullVariables };
@@ -69,7 +69,7 @@ describe("pullVariables", () => {
69
69
  expect(await fileExists(filePath)).toBeFalsy();
70
70
  await pullVariables({ path: tmpDir });
71
71
  expect(await readFile(filePath)).toStrictEqual(
72
- 'PUBLIC_API_TOKEN="abc123"\n# PRIVATE_API_TOKEN is marked as secret and its value is hidden\nPRIVATE_API_TOKEN=""\n'
72
+ 'PUBLIC_API_TOKEN=abc123\nPRIVATE_API_TOKEN=""'
73
73
  );
74
74
  });
75
75
  });
@@ -77,8 +77,18 @@ describe("pullVariables", () => {
77
77
  await inTemporaryDirectory(async (tmpDir) => {
78
78
  const outputMock = mockAndCaptureOutput();
79
79
  await pullVariables({ path: tmpDir });
80
- expect(outputMock.warn()).toStrictEqual(
81
- "Existing Link contains environment variables marked as secret, so their values weren\u2019t pulled."
80
+ expect(outputMock.warn()).toMatch(
81
+ /Existing Link contains environment variables marked as secret, so their/
82
+ );
83
+ expect(outputMock.warn()).toMatch(/values weren’t pulled./);
84
+ });
85
+ });
86
+ it("renders a success message", async () => {
87
+ await inTemporaryDirectory(async (tmpDir) => {
88
+ const outputMock = mockAndCaptureOutput();
89
+ await pullVariables({ path: tmpDir });
90
+ expect(outputMock.info()).toMatch(
91
+ /Changes have been made to your \.env file/
82
92
  );
83
93
  });
84
94
  });
@@ -92,8 +102,10 @@ describe("pullVariables", () => {
92
102
  await writeFile(filePath, "EXISTING_TOKEN=1");
93
103
  await pullVariables({ path: tmpDir });
94
104
  expect(renderConfirmationPrompt).toHaveBeenCalledWith({
105
+ confirmationMessage: `Yes, confirm changes`,
106
+ cancellationMessage: `No, make changes later`,
95
107
  message: expect.stringMatching(
96
- /Warning: \.env file already exists\. Do you want to overwrite it\?/
108
+ /We'll make the following changes to your \.env file:/
97
109
  )
98
110
  });
99
111
  });
@@ -1,4 +1,6 @@
1
1
  import Command from '@shopify/cli-kit/node/base-command';
2
+ import { readdir } from 'node:fs/promises';
3
+ import { fileURLToPath } from 'node:url';
2
4
  import { packageManagerUsedForCreating, installNodeModules } from '@shopify/cli-kit/node/node-package-manager';
3
5
  import { renderFatalError, renderSelectPrompt, renderTextPrompt, renderConfirmationPrompt, renderInfo, renderTasks, renderSuccess } from '@shopify/cli-kit/node/ui';
4
6
  import { Flags } from '@oclif/core';
@@ -9,8 +11,7 @@ import { commonFlags, flagsToCamelObject, parseProcessFlags } from '../../lib/fl
9
11
  import { transpileProject } from '../../lib/transpile-ts.js';
10
12
  import { getLatestTemplates } from '../../lib/template-downloader.js';
11
13
  import { checkHydrogenVersion } from '../../lib/check-version.js';
12
- import { readdir } from 'fs/promises';
13
- import { fileURLToPath } from 'url';
14
+ import { supressNodeExperimentalWarnings } from '../../lib/process.js';
14
15
 
15
16
  const STARTER_TEMPLATES = ["hello-world", "demo-store"];
16
17
  const FLAG_MAP = { f: "force" };
@@ -167,16 +168,5 @@ async function runInit(options = parseProcessFlags(process.argv, FLAG_MAP)) {
167
168
  async function projectExists(projectDir) {
168
169
  return await fileExists(projectDir) && await isDirectory(projectDir) && (await readdir(projectDir)).length > 0;
169
170
  }
170
- function supressNodeExperimentalWarnings() {
171
- const warningListener = process.listeners("warning")[0];
172
- if (warningListener) {
173
- process.removeAllListeners("warning");
174
- process.prependListener("warning", (warning) => {
175
- if (warning.name != "ExperimentalWarning") {
176
- warningListener(warning);
177
- }
178
- });
179
- }
180
- }
181
171
 
182
172
  export { Init as default, runInit };
@@ -3,7 +3,6 @@ import Command from '@shopify/cli-kit/node/base-command';
3
3
 
4
4
  declare class Link extends Command {
5
5
  static description: string;
6
- static hidden: boolean;
7
6
  static flags: {
8
7
  force: _oclif_core_lib_interfaces_parser_js.BooleanFlag<boolean>;
9
8
  path: _oclif_core_lib_interfaces_parser_js.OptionFlag<string | undefined, _oclif_core_lib_interfaces_parser_js.CustomOptions>;
@@ -1,25 +1,20 @@
1
1
  import { Flags } from '@oclif/core';
2
2
  import Command from '@shopify/cli-kit/node/base-command';
3
- import { renderConfirmationPrompt, renderWarning, renderSelectPrompt } from '@shopify/cli-kit/node/ui';
4
- import { outputContent, outputToken, outputSuccess, outputInfo } from '@shopify/cli-kit/node/output';
5
- import { adminRequest, parseGid } from '../../lib/graphql.js';
3
+ import { renderConfirmationPrompt, renderWarning, renderSelectPrompt, renderSuccess } from '@shopify/cli-kit/node/ui';
6
4
  import { commonFlags } from '../../lib/flags.js';
7
5
  import { getHydrogenShop } from '../../lib/shop.js';
8
- import { getAdminSession } from '../../lib/admin-session.js';
9
- import { hydrogenStorefrontUrl } from '../../lib/admin-urls.js';
10
- import { LinkStorefrontQuery } from '../../lib/graphql/admin/link-storefront.js';
6
+ import { getStorefronts } from '../../lib/graphql/admin/link-storefront.js';
11
7
  import { getConfig, setStorefront } from '../../lib/shopify-config.js';
12
8
  import { logMissingStorefronts } from '../../lib/missing-storefronts.js';
9
+ import { getCliCommand } from '../../lib/shell.js';
13
10
 
14
11
  class Link extends Command {
15
12
  static description = "Link a local project to one of your shop's Hydrogen storefronts.";
16
- static hidden = true;
17
13
  static flags = {
18
14
  force: commonFlags.force,
19
15
  path: commonFlags.path,
20
16
  shop: commonFlags.shop,
21
17
  storefront: Flags.string({
22
- char: "h",
23
18
  description: `The name of a Hydrogen Storefront (e.g. "Jane's Apparel")`,
24
19
  env: "SHOPIFY_HYDROGEN_STOREFRONT"
25
20
  })
@@ -46,56 +41,59 @@ async function linkStorefront({
46
41
  return;
47
42
  }
48
43
  }
49
- const adminSession = await getAdminSession(shop);
50
- const result = await adminRequest(
51
- LinkStorefrontQuery,
52
- adminSession
53
- );
54
- if (!result.hydrogenStorefronts.length) {
44
+ const { storefronts, adminSession } = await getStorefronts(shop);
45
+ if (storefronts.length === 0) {
55
46
  logMissingStorefronts(adminSession);
56
47
  return;
57
48
  }
58
49
  let selectedStorefront;
50
+ const cliCommand = await getCliCommand();
59
51
  if (flagStorefront) {
60
- selectedStorefront = result.hydrogenStorefronts.find(
61
- (storefront) => storefront.title === flagStorefront
52
+ selectedStorefront = storefronts.find(
53
+ ({ title }) => title === flagStorefront
62
54
  );
63
55
  if (!selectedStorefront) {
64
56
  renderWarning({
65
57
  headline: `Couldn't find ${flagStorefront}`,
66
- body: outputContent`There's no storefront matching ${flagStorefront} on your ${shop} shop. To see all available Hydrogen storefronts, run ${outputToken.genericShellCommand(
67
- `npx shopify hydrogen list`
68
- )}`.value
58
+ body: [
59
+ "There's no storefront matching",
60
+ { userInput: flagStorefront },
61
+ "on your",
62
+ { userInput: shop },
63
+ "shop. To see all available Hydrogen storefronts, run",
64
+ {
65
+ command: `${cliCommand} list`
66
+ }
67
+ ]
69
68
  });
70
69
  return;
71
70
  }
72
71
  } else {
73
- const choices = result.hydrogenStorefronts.map((storefront) => ({
74
- label: `${storefront.title} ${storefront.productionUrl}${storefront.id === configStorefront?.id ? " (Current)" : ""}`,
75
- value: storefront.id
72
+ const choices = storefronts.map(({ id, title, productionUrl }) => ({
73
+ value: id,
74
+ label: `${title} (${productionUrl})`
76
75
  }));
77
76
  const storefrontId = await renderSelectPrompt({
78
- message: "Choose a Hydrogen storefront to link this project to:",
79
- choices,
80
- defaultValue: "true"
77
+ message: "Choose a Hydrogen storefront to link",
78
+ choices
81
79
  });
82
- selectedStorefront = result.hydrogenStorefronts.find(
83
- (storefront) => storefront.id === storefrontId
84
- );
80
+ selectedStorefront = storefronts.find(({ id }) => id === storefrontId);
85
81
  }
86
82
  if (!selectedStorefront) {
87
83
  return;
88
84
  }
89
85
  await setStorefront(path ?? process.cwd(), selectedStorefront);
90
- outputSuccess(`Linked to ${selectedStorefront.title}`);
91
86
  if (!silent) {
92
- outputInfo(
93
- `Admin URL: ${hydrogenStorefrontUrl(
94
- adminSession,
95
- parseGid(selectedStorefront.id)
96
- )}`
97
- );
98
- outputInfo(`Site URL: ${selectedStorefront.productionUrl}`);
87
+ renderSuccess({
88
+ body: [{ userInput: selectedStorefront.title }, "is now linked"],
89
+ nextSteps: [
90
+ [
91
+ "Run",
92
+ { command: `${cliCommand} dev` },
93
+ "to start your local development server and start building"
94
+ ]
95
+ ]
96
+ });
99
97
  }
100
98
  }
101
99
 
@@ -2,11 +2,15 @@ import { vi, describe, beforeEach, afterEach, it, expect } from 'vitest';
2
2
  import { mockAndCaptureOutput } from '@shopify/cli-kit/node/testing/output';
3
3
  import { renderSelectPrompt, renderConfirmationPrompt } from '@shopify/cli-kit/node/ui';
4
4
  import { adminRequest } from '../../lib/graphql.js';
5
- import { LinkStorefrontQuery } from '../../lib/graphql/admin/link-storefront.js';
6
- import { getAdminSession } from '../../lib/admin-session.js';
5
+ import { getStorefronts } from '../../lib/graphql/admin/link-storefront.js';
7
6
  import { getConfig, setStorefront } from '../../lib/shopify-config.js';
8
7
  import { linkStorefront } from './link.js';
9
8
 
9
+ const SHOP = "my-shop";
10
+ const ADMIN_SESSION = {
11
+ token: "abc123",
12
+ storeFqdn: SHOP
13
+ };
10
14
  vi.mock("@shopify/cli-kit/node/ui", async () => {
11
15
  const original = await vi.importActual("@shopify/cli-kit/node/ui");
12
16
  return {
@@ -17,22 +21,22 @@ vi.mock("@shopify/cli-kit/node/ui", async () => {
17
21
  });
18
22
  vi.mock("../../lib/graphql.js");
19
23
  vi.mock("../../lib/shopify-config.js");
20
- vi.mock("../../lib/admin-session.js");
24
+ vi.mock("../../lib/graphql/admin/link-storefront.js");
21
25
  vi.mock("../../lib/shop.js", () => ({
22
- getHydrogenShop: () => "my-shop"
26
+ getHydrogenShop: () => SHOP
27
+ }));
28
+ vi.mock("../../lib/shell.js", () => ({
29
+ getCliCommand: () => "h2"
23
30
  }));
24
- const ADMIN_SESSION = {
25
- token: "abc123",
26
- storeFqdn: "my-shop"
27
- };
28
31
  describe("link", () => {
29
32
  const outputMock = mockAndCaptureOutput();
30
33
  beforeEach(async () => {
31
- vi.mocked(getAdminSession).mockResolvedValue(ADMIN_SESSION);
32
- vi.mocked(adminRequest).mockResolvedValue({
33
- hydrogenStorefronts: [
34
+ vi.mocked(getStorefronts).mockResolvedValue({
35
+ adminSession: ADMIN_SESSION,
36
+ storefronts: [
34
37
  {
35
38
  id: "gid://shopify/HydrogenStorefront/1",
39
+ parsedId: "1",
36
40
  title: "Hydrogen",
37
41
  productionUrl: "https://example.com"
38
42
  }
@@ -46,26 +50,36 @@ describe("link", () => {
46
50
  });
47
51
  it("makes a GraphQL call to fetch the storefronts", async () => {
48
52
  await linkStorefront({});
49
- expect(adminRequest).toHaveBeenCalledWith(
50
- LinkStorefrontQuery,
51
- ADMIN_SESSION
52
- );
53
+ expect(getStorefronts).toHaveBeenCalledWith(SHOP);
53
54
  });
54
55
  it("renders a list of choices and forwards the selection to setStorefront", async () => {
55
56
  vi.mocked(renderSelectPrompt).mockResolvedValue(
56
57
  "gid://shopify/HydrogenStorefront/1"
57
58
  );
58
59
  await linkStorefront({ path: "my-path" });
59
- expect(setStorefront).toHaveBeenCalledWith("my-path", {
60
- id: "gid://shopify/HydrogenStorefront/1",
61
- title: "Hydrogen",
62
- productionUrl: "https://example.com"
63
- });
60
+ expect(setStorefront).toHaveBeenCalledWith(
61
+ "my-path",
62
+ expect.objectContaining({
63
+ id: "gid://shopify/HydrogenStorefront/1",
64
+ title: "Hydrogen"
65
+ })
66
+ );
67
+ });
68
+ it("renders a success message", async () => {
69
+ vi.mocked(renderSelectPrompt).mockResolvedValue(
70
+ "gid://shopify/HydrogenStorefront/1"
71
+ );
72
+ await linkStorefront({ path: "my-path" });
73
+ expect(outputMock.info()).toMatch(/Hydrogen is now linked/g);
74
+ expect(outputMock.info()).toMatch(
75
+ /Run `h2 dev` to start your local development server and start building/g
76
+ );
64
77
  });
65
78
  describe("when there are no Hydrogen storefronts", () => {
66
79
  it("renders a message and returns early", async () => {
67
- vi.mocked(adminRequest).mockResolvedValue({
68
- hydrogenStorefronts: []
80
+ vi.mocked(getStorefronts).mockResolvedValue({
81
+ adminSession: ADMIN_SESSION,
82
+ storefronts: []
69
83
  });
70
84
  await linkStorefront({});
71
85
  expect(outputMock.info()).toMatch(
@@ -119,11 +133,13 @@ describe("link", () => {
119
133
  it("does not prompt the user to make a selection", async () => {
120
134
  await linkStorefront({ path: "my-path", storefront: "Hydrogen" });
121
135
  expect(renderSelectPrompt).not.toHaveBeenCalled();
122
- expect(setStorefront).toHaveBeenCalledWith("my-path", {
123
- id: "gid://shopify/HydrogenStorefront/1",
124
- title: "Hydrogen",
125
- productionUrl: "https://example.com"
126
- });
136
+ expect(setStorefront).toHaveBeenCalledWith(
137
+ "my-path",
138
+ expect.objectContaining({
139
+ id: "gid://shopify/HydrogenStorefront/1",
140
+ title: "Hydrogen"
141
+ })
142
+ );
127
143
  });
128
144
  describe("and there is no matching storefront", () => {
129
145
  it("renders a warning message and returns early", async () => {
@@ -1,10 +1,10 @@
1
1
  import * as _oclif_core_lib_interfaces_parser_js from '@oclif/core/lib/interfaces/parser.js';
2
2
  import Command from '@shopify/cli-kit/node/base-command';
3
3
  import { Deployment } from '../../lib/graphql/admin/list-storefronts.js';
4
+ import '@shopify/cli-kit/node/session';
4
5
 
5
6
  declare class List extends Command {
6
7
  static description: string;
7
- static hidden: boolean;
8
8
  static flags: {
9
9
  path: _oclif_core_lib_interfaces_parser_js.OptionFlag<string | undefined, _oclif_core_lib_interfaces_parser_js.CustomOptions>;
10
10
  shop: _oclif_core_lib_interfaces_parser_js.OptionFlag<string | undefined, _oclif_core_lib_interfaces_parser_js.CustomOptions>;
@@ -16,6 +16,6 @@ interface Flags {
16
16
  shop?: string;
17
17
  }
18
18
  declare function listStorefronts({ path, shop: flagShop }: Flags): Promise<void>;
19
- declare function formatDeployment(deployment: Deployment | null): string;
19
+ declare function formatDeployment(deployment: Deployment): string;
20
20
 
21
21
  export { List as default, formatDeployment, listStorefronts };
@@ -1,16 +1,15 @@
1
1
  import Command from '@shopify/cli-kit/node/base-command';
2
- import { renderTable } from '@shopify/cli-kit/node/ui';
3
- import { outputInfo, outputContent } from '@shopify/cli-kit/node/output';
4
- import { adminRequest, parseGid } from '../../lib/graphql.js';
2
+ import { pluralize } from '@shopify/cli-kit/common/string';
3
+ import colors from '@shopify/cli-kit/node/colors';
4
+ import { outputNewline, outputInfo, outputContent } from '@shopify/cli-kit/node/output';
5
5
  import { commonFlags } from '../../lib/flags.js';
6
6
  import { getHydrogenShop } from '../../lib/shop.js';
7
- import { getAdminSession } from '../../lib/admin-session.js';
8
- import { ListStorefrontsQuery } from '../../lib/graphql/admin/list-storefronts.js';
7
+ import { parseGid } from '../../lib/graphql.js';
8
+ import { getStorefrontsWithDeployment } from '../../lib/graphql/admin/list-storefronts.js';
9
9
  import { logMissingStorefronts } from '../../lib/missing-storefronts.js';
10
10
 
11
11
  class List extends Command {
12
12
  static description = "Returns a list of Hydrogen storefronts available on a given shop.";
13
- static hidden = true;
14
13
  static flags = {
15
14
  path: commonFlags.path,
16
15
  shop: commonFlags.shop
@@ -22,49 +21,44 @@ class List extends Command {
22
21
  }
23
22
  async function listStorefronts({ path, shop: flagShop }) {
24
23
  const shop = await getHydrogenShop({ path, shop: flagShop });
25
- const adminSession = await getAdminSession(shop);
26
- const result = await adminRequest(
27
- ListStorefrontsQuery,
28
- adminSession
29
- );
30
- const storefrontsCount = result.hydrogenStorefronts.length;
31
- if (storefrontsCount > 0) {
24
+ const { storefronts, adminSession } = await getStorefrontsWithDeployment(shop);
25
+ if (storefronts.length > 0) {
26
+ outputNewline();
32
27
  outputInfo(
33
- outputContent`Found ${storefrontsCount.toString()} Hydrogen storefronts on ${shop}:\n`.value
34
- );
35
- const rows = result.hydrogenStorefronts.map(
36
- ({ id, title, productionUrl, currentProductionDeployment }) => ({
37
- id: parseGid(id),
38
- title,
39
- productionUrl,
40
- currentDeployment: formatDeployment(currentProductionDeployment)
41
- })
28
+ pluralizedStorefronts({
29
+ storefronts,
30
+ shop
31
+ }).toString()
42
32
  );
43
- renderTable({
44
- rows,
45
- columns: {
46
- id: {
47
- header: "ID"
48
- },
49
- title: {
50
- header: "Name",
51
- color: "whiteBright"
52
- },
53
- productionUrl: {
54
- header: "Production URL"
55
- },
56
- currentDeployment: {
57
- header: "Current deployment"
33
+ storefronts.forEach(
34
+ ({ currentProductionDeployment, id, productionUrl, title }) => {
35
+ outputNewline();
36
+ outputInfo(
37
+ outputContent`${colors.whiteBright(title)} ${colors.dim(
38
+ `(id: ${parseGid(id)})`
39
+ )}`.value
40
+ );
41
+ if (productionUrl) {
42
+ outputInfo(
43
+ outputContent` ${colors.whiteBright(productionUrl)}`.value
44
+ );
45
+ }
46
+ if (currentProductionDeployment) {
47
+ outputInfo(
48
+ outputContent` ${colors.dim(
49
+ formatDeployment(currentProductionDeployment)
50
+ )}`.value
51
+ );
58
52
  }
59
53
  }
60
- });
54
+ );
61
55
  } else {
62
56
  logMissingStorefronts(adminSession);
63
57
  }
64
58
  }
65
59
  const dateFormat = new Intl.DateTimeFormat("default", {
66
60
  year: "numeric",
67
- month: "long",
61
+ month: "numeric",
68
62
  day: "numeric"
69
63
  });
70
64
  function formatDeployment(deployment) {
@@ -79,5 +73,15 @@ function formatDeployment(deployment) {
79
73
  }
80
74
  return message;
81
75
  }
76
+ const pluralizedStorefronts = ({
77
+ storefronts,
78
+ shop
79
+ }) => {
80
+ return pluralize(
81
+ storefronts,
82
+ (storefronts2) => `Showing ${storefronts2.length} Hydrogen storefronts for the store ${shop}`,
83
+ (_storefront) => `Showing 1 Hydrogen storefront for the store ${shop}`
84
+ );
85
+ };
82
86
 
83
87
  export { List as default, formatDeployment, listStorefronts };