@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.
- package/dist/commands/hydrogen/codegen-unstable.d.ts +14 -0
- package/dist/commands/hydrogen/codegen-unstable.js +64 -0
- package/dist/commands/hydrogen/dev.d.ts +4 -0
- package/dist/commands/hydrogen/dev.js +41 -7
- package/dist/commands/hydrogen/env/list.d.ts +19 -0
- package/dist/commands/hydrogen/env/list.js +96 -0
- package/dist/commands/hydrogen/env/list.test.d.ts +1 -0
- package/dist/commands/hydrogen/env/list.test.js +151 -0
- package/dist/commands/hydrogen/env/pull.d.ts +3 -1
- package/dist/commands/hydrogen/env/pull.js +20 -67
- package/dist/commands/hydrogen/env/pull.test.js +22 -115
- package/dist/lib/codegen.d.ts +25 -0
- package/dist/lib/codegen.js +128 -0
- package/dist/lib/colors.d.ts +3 -0
- package/dist/lib/colors.js +4 -1
- package/dist/lib/combined-environment-variables.d.ts +8 -0
- package/dist/lib/combined-environment-variables.js +74 -0
- package/dist/lib/combined-environment-variables.test.d.ts +1 -0
- package/dist/lib/combined-environment-variables.test.js +111 -0
- package/dist/lib/flags.d.ts +1 -0
- package/dist/lib/flags.js +6 -0
- package/dist/lib/graphql/admin/list-environments.d.ts +20 -0
- package/dist/lib/graphql/admin/list-environments.js +18 -0
- package/dist/lib/graphql/admin/pull-variables.d.ts +1 -1
- package/dist/lib/graphql/admin/pull-variables.js +2 -2
- package/dist/lib/mini-oxygen.d.ts +4 -1
- package/dist/lib/mini-oxygen.js +7 -3
- package/dist/lib/pull-environment-variables.d.ts +19 -0
- package/dist/lib/pull-environment-variables.js +67 -0
- package/dist/lib/pull-environment-variables.test.d.ts +1 -0
- package/dist/lib/pull-environment-variables.test.js +174 -0
- package/dist/lib/render-errors.d.ts +16 -0
- package/dist/lib/render-errors.js +37 -0
- package/oclif.manifest.json +1 -1
- 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({
|
|
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
|
-
|
|
45
|
-
|
|
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
|
-
|
|
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 @@
|
|
|
1
|
+
|
|
@@ -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 {
|
|
3
|
-
import {
|
|
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 {
|
|
7
|
-
import {
|
|
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({
|
|
29
|
-
|
|
30
|
-
|
|
24
|
+
async function pullVariables({
|
|
25
|
+
envBranch,
|
|
26
|
+
force,
|
|
27
|
+
path,
|
|
28
|
+
shop: flagShop
|
|
29
|
+
}) {
|
|
31
30
|
const actualPath = path ?? process.cwd();
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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 =
|
|
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
|
);
|