@shopify/cli-hydrogen 4.1.2 → 4.2.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 (35) hide show
  1. package/dist/commands/hydrogen/codegen-unstable.d.ts +14 -0
  2. package/dist/commands/hydrogen/codegen-unstable.js +64 -0
  3. package/dist/commands/hydrogen/dev.d.ts +4 -0
  4. package/dist/commands/hydrogen/dev.js +41 -7
  5. package/dist/commands/hydrogen/env/list.d.ts +19 -0
  6. package/dist/commands/hydrogen/env/list.js +96 -0
  7. package/dist/commands/hydrogen/env/list.test.d.ts +1 -0
  8. package/dist/commands/hydrogen/env/list.test.js +151 -0
  9. package/dist/commands/hydrogen/env/pull.d.ts +3 -1
  10. package/dist/commands/hydrogen/env/pull.js +20 -67
  11. package/dist/commands/hydrogen/env/pull.test.js +22 -115
  12. package/dist/lib/codegen.d.ts +25 -0
  13. package/dist/lib/codegen.js +128 -0
  14. package/dist/lib/colors.d.ts +3 -0
  15. package/dist/lib/colors.js +4 -1
  16. package/dist/lib/combined-environment-variables.d.ts +8 -0
  17. package/dist/lib/combined-environment-variables.js +74 -0
  18. package/dist/lib/combined-environment-variables.test.d.ts +1 -0
  19. package/dist/lib/combined-environment-variables.test.js +111 -0
  20. package/dist/lib/flags.d.ts +1 -0
  21. package/dist/lib/flags.js +6 -0
  22. package/dist/lib/graphql/admin/list-environments.d.ts +20 -0
  23. package/dist/lib/graphql/admin/list-environments.js +18 -0
  24. package/dist/lib/graphql/admin/pull-variables.d.ts +1 -1
  25. package/dist/lib/graphql/admin/pull-variables.js +2 -2
  26. package/dist/lib/mini-oxygen.d.ts +4 -1
  27. package/dist/lib/mini-oxygen.js +7 -3
  28. package/dist/lib/pull-environment-variables.d.ts +19 -0
  29. package/dist/lib/pull-environment-variables.js +67 -0
  30. package/dist/lib/pull-environment-variables.test.d.ts +1 -0
  31. package/dist/lib/pull-environment-variables.test.js +174 -0
  32. package/dist/lib/render-errors.d.ts +16 -0
  33. package/dist/lib/render-errors.js +37 -0
  34. package/oclif.manifest.json +1 -1
  35. package/package.json +7 -5
@@ -0,0 +1,14 @@
1
+ import * as _oclif_core_lib_interfaces_parser_js from '@oclif/core/lib/interfaces/parser.js';
2
+ import Command from '@shopify/cli-kit/node/base-command';
3
+
4
+ declare class Codegen extends Command {
5
+ static description: string;
6
+ static flags: {
7
+ path: _oclif_core_lib_interfaces_parser_js.OptionFlag<string | undefined, _oclif_core_lib_interfaces_parser_js.CustomOptions>;
8
+ "codegen-config-path": _oclif_core_lib_interfaces_parser_js.OptionFlag<string | undefined, _oclif_core_lib_interfaces_parser_js.CustomOptions>;
9
+ watch: _oclif_core_lib_interfaces_parser_js.BooleanFlag<boolean>;
10
+ };
11
+ run(): Promise<void>;
12
+ }
13
+
14
+ export { Codegen as default };
@@ -0,0 +1,64 @@
1
+ import path from 'path';
2
+ import Command from '@shopify/cli-kit/node/base-command';
3
+ import { AbortError } from '@shopify/cli-kit/node/error';
4
+ import { renderSuccess } from '@shopify/cli-kit/node/ui';
5
+ import { Flags } from '@oclif/core';
6
+ import { getProjectPaths, getRemixConfig } from '../../lib/config.js';
7
+ import { commonFlags, flagsToCamelObject } from '../../lib/flags.js';
8
+ import { patchGqlPluck, generateTypes, normalizeCodegenError } from '../../lib/codegen.js';
9
+
10
+ class Codegen extends Command {
11
+ static description = "Generate types for the Storefront API queries found in your project.";
12
+ static flags = {
13
+ path: commonFlags.path,
14
+ ["codegen-config-path"]: Flags.string({
15
+ description: "Specify a path to a codegen configuration file. Defaults to `<root>/codegen.ts` if it exists.",
16
+ required: false
17
+ }),
18
+ watch: Flags.boolean({
19
+ description: "Watch the project for changes to update types on file save.",
20
+ required: false,
21
+ default: false
22
+ })
23
+ };
24
+ async run() {
25
+ const { flags } = await this.parse(Codegen);
26
+ const directory = flags.path ? path.resolve(flags.path) : process.cwd();
27
+ await runCodegen({
28
+ ...flagsToCamelObject(flags),
29
+ path: directory
30
+ });
31
+ }
32
+ }
33
+ async function runCodegen({
34
+ path: appPath,
35
+ codegenConfigPath,
36
+ watch
37
+ }) {
38
+ const { root } = getProjectPaths(appPath);
39
+ const remixConfig = await getRemixConfig(root);
40
+ await patchGqlPluck();
41
+ try {
42
+ const generatedFiles = await generateTypes({
43
+ ...remixConfig,
44
+ configFilePath: codegenConfigPath,
45
+ watch
46
+ });
47
+ if (!watch) {
48
+ console.log("");
49
+ renderSuccess({
50
+ headline: "Generated types for GraphQL:",
51
+ body: generatedFiles.map((file) => `- ${file}`).join("\n")
52
+ });
53
+ }
54
+ } catch (error) {
55
+ const { message, details } = normalizeCodegenError(
56
+ error.message,
57
+ remixConfig.rootDirectory
58
+ );
59
+ console.log("");
60
+ throw new AbortError(message, details);
61
+ }
62
+ }
63
+
64
+ export { Codegen as default };
@@ -6,9 +6,13 @@ declare class Dev extends Command {
6
6
  static flags: {
7
7
  path: _oclif_core_lib_interfaces_parser_js.OptionFlag<string | undefined, _oclif_core_lib_interfaces_parser_js.CustomOptions>;
8
8
  port: _oclif_core_lib_interfaces_parser_js.OptionFlag<number, _oclif_core_lib_interfaces_parser_js.CustomOptions>;
9
+ "codegen-unstable": _oclif_core_lib_interfaces_parser_js.BooleanFlag<boolean>;
10
+ "codegen-config-path": _oclif_core_lib_interfaces_parser_js.OptionFlag<string | undefined, _oclif_core_lib_interfaces_parser_js.CustomOptions>;
9
11
  "disable-virtual-routes": _oclif_core_lib_interfaces_parser_js.BooleanFlag<boolean>;
12
+ shop: _oclif_core_lib_interfaces_parser_js.OptionFlag<string | undefined, _oclif_core_lib_interfaces_parser_js.CustomOptions>;
10
13
  debug: _oclif_core_lib_interfaces_parser_js.BooleanFlag<boolean>;
11
14
  host: _oclif_core_lib_interfaces_parser_js.OptionFlag<unknown, _oclif_core_lib_interfaces_parser_js.CustomOptions>;
15
+ "env-branch": _oclif_core_lib_interfaces_parser_js.OptionFlag<string | undefined, _oclif_core_lib_interfaces_parser_js.CustomOptions>;
12
16
  };
13
17
  run(): Promise<void>;
14
18
  }
@@ -11,6 +11,9 @@ import { Flags } from '@oclif/core';
11
11
  import { startMiniOxygen } from '../../lib/mini-oxygen.js';
12
12
  import { checkHydrogenVersion } from '../../lib/check-version.js';
13
13
  import { addVirtualRoutes } from '../../lib/virtual-routes.js';
14
+ import { spawnCodegenProcess } from '../../lib/codegen.js';
15
+ import { combinedEnvironmentVariables } from '../../lib/combined-environment-variables.js';
16
+ import { getConfig } from '../../lib/shopify-config.js';
14
17
 
15
18
  const LOG_INITIAL_BUILD = "\n\u{1F3C1} Initial build";
16
19
  const LOG_REBUILDING = "\u{1F9F1} Rebuilding...";
@@ -20,29 +23,49 @@ class Dev extends Command {
20
23
  static flags = {
21
24
  path: commonFlags.path,
22
25
  port: commonFlags.port,
26
+ ["codegen-unstable"]: Flags.boolean({
27
+ description: "Generate types for the Storefront API queries found in your project. It updates the types on file save.",
28
+ required: false,
29
+ default: false
30
+ }),
31
+ ["codegen-config-path"]: Flags.string({
32
+ description: "Specify a path to a codegen configuration file. Defaults to `<root>/codegen.ts` if it exists.",
33
+ required: false,
34
+ dependsOn: ["codegen-unstable"]
35
+ }),
23
36
  ["disable-virtual-routes"]: Flags.boolean({
24
- description: "Disable rendering fallback routes when a route file doesn't exist",
37
+ description: "Disable rendering fallback routes when a route file doesn't exist.",
25
38
  env: "SHOPIFY_HYDROGEN_FLAG_DISABLE_VIRTUAL_ROUTES",
26
39
  default: false
27
40
  }),
41
+ shop: commonFlags.shop,
28
42
  debug: Flags.boolean({
29
43
  description: "Attaches a Node inspector",
30
44
  env: "SHOPIFY_HYDROGEN_FLAG_DEBUG",
31
45
  default: false
32
46
  }),
33
- host: deprecated("--host")()
47
+ host: deprecated("--host")(),
48
+ ["env-branch"]: commonFlags["env-branch"]
34
49
  };
35
50
  async run() {
36
51
  const { flags } = await this.parse(Dev);
37
52
  const directory = flags.path ? path.resolve(flags.path) : process.cwd();
38
- await runDev({ ...flagsToCamelObject(flags), path: directory });
53
+ await runDev({
54
+ ...flagsToCamelObject(flags),
55
+ codegen: flags["codegen-unstable"],
56
+ path: directory
57
+ });
39
58
  }
40
59
  }
41
60
  async function runDev({
42
61
  port,
43
62
  path: appPath,
44
- debug = false,
45
- disableVirtualRoutes
63
+ codegen = false,
64
+ codegenConfigPath,
65
+ disableVirtualRoutes,
66
+ shop,
67
+ envBranch,
68
+ debug = false
46
69
  }) {
47
70
  if (!process.env.NODE_ENV)
48
71
  process.env.NODE_ENV = "development";
@@ -62,6 +85,12 @@ async function runDev({
62
85
  return [fileRelative, path.resolve(root, fileRelative)];
63
86
  };
64
87
  const serverBundleExists = () => fileExists(buildPathWorkerFile);
88
+ const hasLinkedStorefront = !!(await getConfig(root))?.storefront?.id;
89
+ const environmentVariables = hasLinkedStorefront ? await combinedEnvironmentVariables({
90
+ root,
91
+ shop,
92
+ envBranch
93
+ }) : void 0;
65
94
  let miniOxygenStarted = false;
66
95
  async function safeStartMiniOxygen() {
67
96
  if (miniOxygenStarted)
@@ -71,7 +100,8 @@ async function runDev({
71
100
  port,
72
101
  watch: true,
73
102
  buildPathWorkerFile,
74
- buildPathClient
103
+ buildPathClient,
104
+ environmentVariables
75
105
  });
76
106
  miniOxygenStarted = true;
77
107
  const showUpgrade = await checkingHydrogenVersion;
@@ -79,7 +109,11 @@ async function runDev({
79
109
  showUpgrade();
80
110
  }
81
111
  const { watch } = await import('@remix-run/dev/dist/compiler/watch.js');
82
- await watch(await reloadConfig(), {
112
+ const remixConfig = await reloadConfig();
113
+ if (codegen) {
114
+ spawnCodegenProcess({ ...remixConfig, configFilePath: codegenConfigPath });
115
+ }
116
+ await watch(remixConfig, {
83
117
  reloadConfig,
84
118
  mode: process.env.NODE_ENV,
85
119
  async onInitialBuild() {
@@ -0,0 +1,19 @@
1
+ import * as _oclif_core_lib_interfaces_parser_js from '@oclif/core/lib/interfaces/parser.js';
2
+ import Command from '@shopify/cli-kit/node/base-command';
3
+
4
+ declare class List extends Command {
5
+ static description: string;
6
+ static hidden: boolean;
7
+ static flags: {
8
+ path: _oclif_core_lib_interfaces_parser_js.OptionFlag<string | undefined, _oclif_core_lib_interfaces_parser_js.CustomOptions>;
9
+ shop: _oclif_core_lib_interfaces_parser_js.OptionFlag<string | undefined, _oclif_core_lib_interfaces_parser_js.CustomOptions>;
10
+ };
11
+ run(): Promise<void>;
12
+ }
13
+ interface Flags {
14
+ path?: string;
15
+ shop?: string;
16
+ }
17
+ declare function listEnvironments({ path, shop: flagShop }: Flags): Promise<void>;
18
+
19
+ export { List as default, listEnvironments };
@@ -0,0 +1,96 @@
1
+ import Command from '@shopify/cli-kit/node/base-command';
2
+ import { renderConfirmationPrompt, renderTable } from '@shopify/cli-kit/node/ui';
3
+ import { outputContent, outputToken, outputNewline } from '@shopify/cli-kit/node/output';
4
+ import { linkStorefront } from '../link.js';
5
+ import { adminRequest } from '../../../lib/graphql.js';
6
+ import { commonFlags } from '../../../lib/flags.js';
7
+ import { getHydrogenShop } from '../../../lib/shop.js';
8
+ import { getAdminSession } from '../../../lib/admin-session.js';
9
+ import { ListEnvironmentsQuery } from '../../../lib/graphql/admin/list-environments.js';
10
+ import { getConfig } from '../../../lib/shopify-config.js';
11
+ import { renderMissingLink, renderMissingStorefront } from '../../../lib/render-errors.js';
12
+
13
+ class List extends Command {
14
+ static description = "List the environments on your Hydrogen storefront.";
15
+ static hidden = true;
16
+ static flags = {
17
+ path: commonFlags.path,
18
+ shop: commonFlags.shop
19
+ };
20
+ async run() {
21
+ const { flags } = await this.parse(List);
22
+ await listEnvironments(flags);
23
+ }
24
+ }
25
+ async function listEnvironments({ path, shop: flagShop }) {
26
+ const shop = await getHydrogenShop({ path, shop: flagShop });
27
+ const adminSession = await getAdminSession(shop);
28
+ const actualPath = path ?? process.cwd();
29
+ let configStorefront = (await getConfig(actualPath)).storefront;
30
+ if (!configStorefront?.id) {
31
+ renderMissingLink({ adminSession });
32
+ const runLink = await renderConfirmationPrompt({
33
+ message: outputContent`Run ${outputToken.genericShellCommand(
34
+ `npx shopify hydrogen link`
35
+ )}?`.value
36
+ });
37
+ if (!runLink) {
38
+ return;
39
+ }
40
+ await linkStorefront({ path, shop: flagShop, silent: true });
41
+ }
42
+ configStorefront = (await getConfig(actualPath)).storefront;
43
+ if (!configStorefront) {
44
+ return;
45
+ }
46
+ const result = await adminRequest(
47
+ ListEnvironmentsQuery,
48
+ adminSession,
49
+ {
50
+ id: configStorefront.id
51
+ }
52
+ );
53
+ const hydrogenStorefront = result.hydrogenStorefront;
54
+ if (!hydrogenStorefront) {
55
+ renderMissingStorefront({ adminSession, storefront: configStorefront });
56
+ return;
57
+ }
58
+ const previewEnvironmentIndex = hydrogenStorefront.environments.findIndex(
59
+ (env) => env.type === "PREVIEW"
60
+ );
61
+ const previewEnvironment = hydrogenStorefront.environments.splice(
62
+ previewEnvironmentIndex,
63
+ 1
64
+ );
65
+ hydrogenStorefront.environments.push(previewEnvironment[0]);
66
+ const rows = hydrogenStorefront.environments.map(
67
+ ({ branch, name, url, type }) => {
68
+ const environmentUrl = type === "PRODUCTION" ? hydrogenStorefront.productionUrl : url;
69
+ return {
70
+ name,
71
+ branch: branch ? branch : "-",
72
+ url: environmentUrl ? environmentUrl : "-"
73
+ };
74
+ }
75
+ );
76
+ outputNewline();
77
+ renderTable({
78
+ rows,
79
+ columns: {
80
+ name: {
81
+ header: "Name",
82
+ color: "whiteBright"
83
+ },
84
+ branch: {
85
+ header: "Branch",
86
+ color: "yellow"
87
+ },
88
+ url: {
89
+ header: "URL",
90
+ color: "green"
91
+ }
92
+ }
93
+ });
94
+ }
95
+
96
+ export { List as default, listEnvironments };
@@ -0,0 +1,151 @@
1
+ import { vi, describe, beforeEach, afterEach, it, expect } from 'vitest';
2
+ import { mockAndCaptureOutput } from '@shopify/cli-kit/node/testing/output';
3
+ import { inTemporaryDirectory } from '@shopify/cli-kit/node/fs';
4
+ import { renderConfirmationPrompt } from '@shopify/cli-kit/node/ui';
5
+ import { ListEnvironmentsQuery } from '../../../lib/graphql/admin/list-environments.js';
6
+ import { getAdminSession } from '../../../lib/admin-session.js';
7
+ import { adminRequest } from '../../../lib/graphql.js';
8
+ import { getConfig } from '../../../lib/shopify-config.js';
9
+ import { renderMissingLink, renderMissingStorefront } from '../../../lib/render-errors.js';
10
+ import { linkStorefront } from '../link.js';
11
+ import { listEnvironments } from './list.js';
12
+
13
+ vi.mock("@shopify/cli-kit/node/ui", async () => {
14
+ const original = await vi.importActual("@shopify/cli-kit/node/ui");
15
+ return {
16
+ ...original,
17
+ renderConfirmationPrompt: vi.fn()
18
+ };
19
+ });
20
+ vi.mock("../link.js");
21
+ vi.mock("../../../lib/admin-session.js");
22
+ vi.mock("../../../lib/shopify-config.js");
23
+ vi.mock("../../../lib/render-errors.js");
24
+ vi.mock("../../../lib/graphql.js", async () => {
25
+ const original = await vi.importActual("../../../lib/graphql.js");
26
+ return {
27
+ ...original,
28
+ adminRequest: vi.fn()
29
+ };
30
+ });
31
+ vi.mock("../../../lib/shop.js", () => ({
32
+ getHydrogenShop: () => "my-shop"
33
+ }));
34
+ describe("listEnvironments", () => {
35
+ const ADMIN_SESSION = {
36
+ token: "abc123",
37
+ storeFqdn: "my-shop"
38
+ };
39
+ const PRODUCTION_ENVIRONMENT = {
40
+ id: "gid://shopify/HydrogenStorefrontEnvironment/1",
41
+ branch: "main",
42
+ type: "PRODUCTION",
43
+ name: "Production",
44
+ createdAt: "2023-02-16T22:35:42Z",
45
+ url: "https://oxygen-123.example.com"
46
+ };
47
+ const CUSTOM_ENVIRONMENT = {
48
+ id: "gid://shopify/HydrogenStorefrontEnvironment/3",
49
+ branch: "staging",
50
+ type: "CUSTOM",
51
+ name: "Staging",
52
+ createdAt: "2023-05-08T20:52:29Z",
53
+ url: "https://oxygen-456.example.com"
54
+ };
55
+ const PREVIEW_ENVIRONMENT = {
56
+ id: "gid://shopify/HydrogenStorefrontEnvironment/2",
57
+ branch: null,
58
+ type: "PREVIEW",
59
+ name: "Preview",
60
+ createdAt: "2023-02-16T22:35:42Z",
61
+ url: null
62
+ };
63
+ beforeEach(async () => {
64
+ vi.mocked(getAdminSession).mockResolvedValue(ADMIN_SESSION);
65
+ vi.mocked(getConfig).mockResolvedValue({
66
+ storefront: {
67
+ id: "gid://shopify/HydrogenStorefront/1",
68
+ title: "Existing Link"
69
+ }
70
+ });
71
+ vi.mocked(adminRequest).mockResolvedValue({
72
+ hydrogenStorefront: {
73
+ id: "gid://shopify/HydrogenStorefront/1",
74
+ productionUrl: "https://example.com",
75
+ environments: [
76
+ PRODUCTION_ENVIRONMENT,
77
+ CUSTOM_ENVIRONMENT,
78
+ PREVIEW_ENVIRONMENT
79
+ ]
80
+ }
81
+ });
82
+ });
83
+ afterEach(() => {
84
+ vi.resetAllMocks();
85
+ mockAndCaptureOutput().clear();
86
+ });
87
+ it("makes a GraphQL call to fetch environment variables", async () => {
88
+ await inTemporaryDirectory(async (tmpDir) => {
89
+ await listEnvironments({ path: tmpDir });
90
+ expect(adminRequest).toHaveBeenCalledWith(
91
+ ListEnvironmentsQuery,
92
+ ADMIN_SESSION,
93
+ {
94
+ id: "gid://shopify/HydrogenStorefront/1"
95
+ }
96
+ );
97
+ });
98
+ });
99
+ it("lists the environments", async () => {
100
+ await inTemporaryDirectory(async (tmpDir) => {
101
+ const output = mockAndCaptureOutput();
102
+ await listEnvironments({ path: tmpDir });
103
+ expect(output.info()).toMatch(
104
+ /Production\s*main\s*https:\/\/example\.com/
105
+ );
106
+ expect(output.info()).toMatch(
107
+ /Staging\s*staging\s*https:\/\/oxygen-456\.example\.com/
108
+ );
109
+ expect(output.info()).toMatch(/Preview\s*-\s*-/);
110
+ });
111
+ });
112
+ describe("when there is no linked storefront", () => {
113
+ beforeEach(() => {
114
+ vi.mocked(getConfig).mockResolvedValue({
115
+ storefront: void 0
116
+ });
117
+ });
118
+ it("calls renderMissingLink", async () => {
119
+ await inTemporaryDirectory(async (tmpDir) => {
120
+ await listEnvironments({ path: tmpDir });
121
+ expect(renderMissingLink).toHaveBeenCalledOnce();
122
+ });
123
+ });
124
+ it("prompts the user to create a link", async () => {
125
+ vi.mocked(renderConfirmationPrompt).mockResolvedValue(true);
126
+ await inTemporaryDirectory(async (tmpDir) => {
127
+ await listEnvironments({ path: tmpDir });
128
+ expect(renderConfirmationPrompt).toHaveBeenCalledWith({
129
+ message: expect.stringMatching(/Run .*npx shopify hydrogen link.*\?/)
130
+ });
131
+ expect(linkStorefront).toHaveBeenCalledWith({
132
+ path: tmpDir,
133
+ silent: true
134
+ });
135
+ });
136
+ });
137
+ });
138
+ describe("when there is no matching storefront in the shop", () => {
139
+ beforeEach(() => {
140
+ vi.mocked(adminRequest).mockResolvedValue({
141
+ hydrogenStorefront: null
142
+ });
143
+ });
144
+ it("calls renderMissingStorefront", async () => {
145
+ await inTemporaryDirectory(async (tmpDir) => {
146
+ await listEnvironments({ path: tmpDir });
147
+ expect(renderMissingStorefront).toHaveBeenCalledOnce();
148
+ });
149
+ });
150
+ });
151
+ });
@@ -5,6 +5,7 @@ declare class Pull extends Command {
5
5
  static description: string;
6
6
  static hidden: boolean;
7
7
  static flags: {
8
+ "env-branch": _oclif_core_lib_interfaces_parser_js.OptionFlag<string | undefined, _oclif_core_lib_interfaces_parser_js.CustomOptions>;
8
9
  path: _oclif_core_lib_interfaces_parser_js.OptionFlag<string | undefined, _oclif_core_lib_interfaces_parser_js.CustomOptions>;
9
10
  shop: _oclif_core_lib_interfaces_parser_js.OptionFlag<string | undefined, _oclif_core_lib_interfaces_parser_js.CustomOptions>;
10
11
  force: _oclif_core_lib_interfaces_parser_js.BooleanFlag<boolean>;
@@ -12,10 +13,11 @@ declare class Pull extends Command {
12
13
  run(): Promise<void>;
13
14
  }
14
15
  interface Flags {
16
+ envBranch?: string;
15
17
  force?: boolean;
16
18
  path?: string;
17
19
  shop?: string;
18
20
  }
19
- declare function pullVariables({ force, path, shop: flagShop }: Flags): Promise<void>;
21
+ declare function pullVariables({ envBranch, force, path, shop: flagShop, }: Flags): Promise<void>;
20
22
 
21
23
  export { Pull as default, pullVariables };
@@ -1,87 +1,39 @@
1
1
  import Command from '@shopify/cli-kit/node/base-command';
2
- import { renderFatalError, renderConfirmationPrompt } from '@shopify/cli-kit/node/ui';
3
- import { outputContent, outputToken, outputInfo, outputWarn, outputSuccess } from '@shopify/cli-kit/node/output';
2
+ import { renderConfirmationPrompt } from '@shopify/cli-kit/node/ui';
3
+ import { outputWarn, outputSuccess } from '@shopify/cli-kit/node/output';
4
4
  import { fileExists, writeFile } from '@shopify/cli-kit/node/fs';
5
5
  import { resolvePath } from '@shopify/cli-kit/node/path';
6
- import { linkStorefront } from '../link.js';
7
- import { adminRequest, parseGid } from '../../../lib/graphql.js';
8
- import { commonFlags } from '../../../lib/flags.js';
9
- import { getHydrogenShop } from '../../../lib/shop.js';
10
- import { getAdminSession } from '../../../lib/admin-session.js';
11
- import { PullVariablesQuery } from '../../../lib/graphql/admin/pull-variables.js';
6
+ import { commonFlags, flagsToCamelObject } from '../../../lib/flags.js';
7
+ import { pullRemoteEnvironmentVariables } from '../../../lib/pull-environment-variables.js';
12
8
  import { getConfig } from '../../../lib/shopify-config.js';
13
- import { hydrogenStorefrontsUrl } from '../../../lib/admin-urls.js';
14
9
 
15
10
  class Pull extends Command {
16
11
  static description = "Populate your .env with variables from your Hydrogen storefront.";
17
12
  static hidden = true;
18
13
  static flags = {
14
+ ["env-branch"]: commonFlags["env-branch"],
19
15
  path: commonFlags.path,
20
16
  shop: commonFlags.shop,
21
17
  force: commonFlags.force
22
18
  };
23
19
  async run() {
24
20
  const { flags } = await this.parse(Pull);
25
- await pullVariables(flags);
21
+ await pullVariables({ ...flagsToCamelObject(flags) });
26
22
  }
27
23
  }
28
- async function pullVariables({ force, path, shop: flagShop }) {
29
- const shop = await getHydrogenShop({ path, shop: flagShop });
30
- const adminSession = await getAdminSession(shop);
24
+ async function pullVariables({
25
+ envBranch,
26
+ force,
27
+ path,
28
+ shop: flagShop
29
+ }) {
31
30
  const actualPath = path ?? process.cwd();
32
- let configStorefront = (await getConfig(actualPath)).storefront;
33
- if (!configStorefront?.id) {
34
- renderFatalError({
35
- name: "NoLinkedStorefrontError",
36
- type: 0,
37
- message: `No linked Hydrogen storefront on ${adminSession.storeFqdn}`,
38
- tryMessage: outputContent`To pull environment variables, link this project to a Hydrogen storefront. To select a storefront to link, run ${outputToken.genericShellCommand(
39
- `npx shopify hydrogen link`
40
- )}.`.value
41
- });
42
- const runLink = await renderConfirmationPrompt({
43
- message: outputContent`Run ${outputToken.genericShellCommand(
44
- `npx shopify hydrogen link`
45
- )}?`.value
46
- });
47
- if (!runLink) {
48
- return;
49
- }
50
- await linkStorefront({ force, path, shop: flagShop, silent: true });
51
- }
52
- configStorefront = (await getConfig(actualPath)).storefront;
53
- if (!configStorefront) {
54
- return;
55
- }
56
- outputInfo(
57
- `Fetching Preview environment variables from ${configStorefront.title}...`
58
- );
59
- const result = await adminRequest(
60
- PullVariablesQuery,
61
- adminSession,
62
- {
63
- id: configStorefront.id
64
- }
65
- );
66
- const hydrogenStorefront = result.hydrogenStorefront;
67
- if (!hydrogenStorefront) {
68
- renderFatalError({
69
- name: "NoStorefrontError",
70
- type: 0,
71
- message: outputContent`${outputToken.errorText(
72
- "Couldn\u2019t find Hydrogen storefront."
73
- )}`.value,
74
- tryMessage: outputContent`Couldn’t find ${configStorefront.title} (ID: ${parseGid(configStorefront.id)}) on ${adminSession.storeFqdn}. Check that the storefront exists and run ${outputToken.genericShellCommand(
75
- `npx shopify hydrogen link`
76
- )} to link this project to it.\n\n${outputToken.link(
77
- "Hydrogen Storefronts Admin",
78
- hydrogenStorefrontsUrl(adminSession)
79
- )}`.value
80
- });
81
- return;
82
- }
83
- if (!hydrogenStorefront.environmentVariables.length) {
84
- outputInfo(`No Preview environment variables found.`);
31
+ const environmentVariables = await pullRemoteEnvironmentVariables({
32
+ root: actualPath,
33
+ flagShop,
34
+ envBranch
35
+ });
36
+ if (!environmentVariables.length) {
85
37
  return;
86
38
  }
87
39
  const dotEnvPath = resolvePath(actualPath, ".env");
@@ -94,7 +46,7 @@ async function pullVariables({ force, path, shop: flagShop }) {
94
46
  }
95
47
  }
96
48
  let hasSecretVariables = false;
97
- const contents = hydrogenStorefront.environmentVariables.map(({ key, value, isSecret }) => {
49
+ const contents = environmentVariables.map(({ key, value, isSecret }) => {
98
50
  let line = `${key}="${value}"`;
99
51
  if (isSecret) {
100
52
  hasSecretVariables = true;
@@ -104,6 +56,7 @@ async function pullVariables({ force, path, shop: flagShop }) {
104
56
  return line;
105
57
  }).join("\n") + "\n";
106
58
  if (hasSecretVariables) {
59
+ const { storefront: configStorefront } = await getConfig(actualPath);
107
60
  outputWarn(
108
61
  `${configStorefront.title} contains environment variables marked as secret, so their values weren\u2019t pulled.`
109
62
  );