@shopify/cli-hydrogen 4.1.2 → 4.2.0
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 +6 -4
|
@@ -3,11 +3,9 @@ import { mockAndCaptureOutput } from '@shopify/cli-kit/node/testing/output';
|
|
|
3
3
|
import { inTemporaryDirectory, fileExists, readFile, writeFile } from '@shopify/cli-kit/node/fs';
|
|
4
4
|
import { joinPath } from '@shopify/cli-kit/node/path';
|
|
5
5
|
import { renderConfirmationPrompt } from '@shopify/cli-kit/node/ui';
|
|
6
|
-
import { PullVariablesQuery } from '../../../lib/graphql/admin/pull-variables.js';
|
|
7
6
|
import { getAdminSession } from '../../../lib/admin-session.js';
|
|
8
|
-
import {
|
|
7
|
+
import { pullRemoteEnvironmentVariables } from '../../../lib/pull-environment-variables.js';
|
|
9
8
|
import { getConfig } from '../../../lib/shopify-config.js';
|
|
10
|
-
import { linkStorefront } from '../link.js';
|
|
11
9
|
import { pullVariables } from './pull.js';
|
|
12
10
|
|
|
13
11
|
vi.mock("@shopify/cli-kit/node/ui", async () => {
|
|
@@ -20,13 +18,7 @@ vi.mock("@shopify/cli-kit/node/ui", async () => {
|
|
|
20
18
|
vi.mock("../link.js");
|
|
21
19
|
vi.mock("../../../lib/admin-session.js");
|
|
22
20
|
vi.mock("../../../lib/shopify-config.js");
|
|
23
|
-
vi.mock("../../../lib/
|
|
24
|
-
const original = await vi.importActual("../../../lib/graphql.js");
|
|
25
|
-
return {
|
|
26
|
-
...original,
|
|
27
|
-
adminRequest: vi.fn()
|
|
28
|
-
};
|
|
29
|
-
});
|
|
21
|
+
vi.mock("../../../lib/pull-environment-variables.js");
|
|
30
22
|
vi.mock("../../../lib/shop.js", () => ({
|
|
31
23
|
getHydrogenShop: () => "my-shop"
|
|
32
24
|
}));
|
|
@@ -43,40 +35,32 @@ describe("pullVariables", () => {
|
|
|
43
35
|
title: "Existing Link"
|
|
44
36
|
}
|
|
45
37
|
});
|
|
46
|
-
vi.mocked(
|
|
47
|
-
|
|
48
|
-
id: "gid://shopify/
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
key: "PRIVATE_API_TOKEN",
|
|
59
|
-
value: "",
|
|
60
|
-
isSecret: true
|
|
61
|
-
}
|
|
62
|
-
]
|
|
38
|
+
vi.mocked(pullRemoteEnvironmentVariables).mockResolvedValue([
|
|
39
|
+
{
|
|
40
|
+
id: "gid://shopify/HydrogenStorefrontEnvironmentVariable/1",
|
|
41
|
+
key: "PUBLIC_API_TOKEN",
|
|
42
|
+
value: "abc123",
|
|
43
|
+
isSecret: false
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
id: "gid://shopify/HydrogenStorefrontEnvironmentVariable/2",
|
|
47
|
+
key: "PRIVATE_API_TOKEN",
|
|
48
|
+
value: "",
|
|
49
|
+
isSecret: true
|
|
63
50
|
}
|
|
64
|
-
|
|
51
|
+
]);
|
|
65
52
|
});
|
|
66
53
|
afterEach(() => {
|
|
67
54
|
vi.resetAllMocks();
|
|
68
55
|
mockAndCaptureOutput().clear();
|
|
69
56
|
});
|
|
70
|
-
it("
|
|
57
|
+
it("calls pullRemoteEnvironmentVariables", async () => {
|
|
71
58
|
await inTemporaryDirectory(async (tmpDir) => {
|
|
72
|
-
await pullVariables({ path: tmpDir });
|
|
73
|
-
expect(
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
id: "gid://shopify/HydrogenStorefront/2"
|
|
78
|
-
}
|
|
79
|
-
);
|
|
59
|
+
await pullVariables({ path: tmpDir, envBranch: "staging" });
|
|
60
|
+
expect(pullRemoteEnvironmentVariables).toHaveBeenCalledWith({
|
|
61
|
+
root: tmpDir,
|
|
62
|
+
envBranch: "staging"
|
|
63
|
+
});
|
|
80
64
|
});
|
|
81
65
|
});
|
|
82
66
|
it("writes environment variables to a .env file", async () => {
|
|
@@ -89,20 +73,7 @@ describe("pullVariables", () => {
|
|
|
89
73
|
);
|
|
90
74
|
});
|
|
91
75
|
});
|
|
92
|
-
it("warns
|
|
93
|
-
vi.mocked(adminRequest).mockResolvedValue({
|
|
94
|
-
hydrogenStorefront: {
|
|
95
|
-
id: "gid://shopify/HydrogenStorefront/1",
|
|
96
|
-
environmentVariables: [
|
|
97
|
-
{
|
|
98
|
-
id: "gid://shopify/HydrogenStorefrontEnvironmentVariable/1",
|
|
99
|
-
key: "PRIVATE_API_TOKEN",
|
|
100
|
-
value: "",
|
|
101
|
-
isSecret: true
|
|
102
|
-
}
|
|
103
|
-
]
|
|
104
|
-
}
|
|
105
|
-
});
|
|
76
|
+
it("warns about secret environment variables", async () => {
|
|
106
77
|
await inTemporaryDirectory(async (tmpDir) => {
|
|
107
78
|
const outputMock = mockAndCaptureOutput();
|
|
108
79
|
await pullVariables({ path: tmpDir });
|
|
@@ -138,68 +109,4 @@ describe("pullVariables", () => {
|
|
|
138
109
|
});
|
|
139
110
|
});
|
|
140
111
|
});
|
|
141
|
-
describe("when there are no environment variables to update", () => {
|
|
142
|
-
beforeEach(() => {
|
|
143
|
-
vi.mocked(adminRequest).mockResolvedValue({
|
|
144
|
-
hydrogenStorefront: {
|
|
145
|
-
id: "gid://shopify/HydrogenStorefront/1",
|
|
146
|
-
environmentVariables: []
|
|
147
|
-
}
|
|
148
|
-
});
|
|
149
|
-
});
|
|
150
|
-
it("renders a message", async () => {
|
|
151
|
-
await inTemporaryDirectory(async (tmpDir) => {
|
|
152
|
-
const outputMock = mockAndCaptureOutput();
|
|
153
|
-
await pullVariables({ path: tmpDir });
|
|
154
|
-
expect(outputMock.info()).toMatch(
|
|
155
|
-
/No Preview environment variables found\./
|
|
156
|
-
);
|
|
157
|
-
});
|
|
158
|
-
});
|
|
159
|
-
});
|
|
160
|
-
describe("when there is no linked storefront", () => {
|
|
161
|
-
beforeEach(() => {
|
|
162
|
-
vi.mocked(getConfig).mockResolvedValue({
|
|
163
|
-
storefront: void 0
|
|
164
|
-
});
|
|
165
|
-
});
|
|
166
|
-
it("renders an error message", async () => {
|
|
167
|
-
await inTemporaryDirectory(async (tmpDir) => {
|
|
168
|
-
const outputMock = mockAndCaptureOutput();
|
|
169
|
-
await pullVariables({ path: tmpDir });
|
|
170
|
-
expect(outputMock.error()).toMatch(
|
|
171
|
-
/No linked Hydrogen storefront on my-shop/
|
|
172
|
-
);
|
|
173
|
-
});
|
|
174
|
-
});
|
|
175
|
-
it("prompts the user to create a link", async () => {
|
|
176
|
-
vi.mocked(renderConfirmationPrompt).mockResolvedValue(true);
|
|
177
|
-
await inTemporaryDirectory(async (tmpDir) => {
|
|
178
|
-
await pullVariables({ path: tmpDir });
|
|
179
|
-
expect(renderConfirmationPrompt).toHaveBeenCalledWith({
|
|
180
|
-
message: expect.stringMatching(/Run .*npx shopify hydrogen link.*\?/)
|
|
181
|
-
});
|
|
182
|
-
expect(linkStorefront).toHaveBeenCalledWith({
|
|
183
|
-
path: tmpDir,
|
|
184
|
-
silent: true
|
|
185
|
-
});
|
|
186
|
-
});
|
|
187
|
-
});
|
|
188
|
-
});
|
|
189
|
-
describe("when there is no matching storefront in the shop", () => {
|
|
190
|
-
beforeEach(() => {
|
|
191
|
-
vi.mocked(adminRequest).mockResolvedValue({
|
|
192
|
-
hydrogenStorefront: null
|
|
193
|
-
});
|
|
194
|
-
});
|
|
195
|
-
it("renders an error message", async () => {
|
|
196
|
-
await inTemporaryDirectory(async (tmpDir) => {
|
|
197
|
-
const outputMock = mockAndCaptureOutput();
|
|
198
|
-
await pullVariables({ path: tmpDir });
|
|
199
|
-
expect(outputMock.error()).toMatch(
|
|
200
|
-
/Couldn’t find Hydrogen storefront\./
|
|
201
|
-
);
|
|
202
|
-
});
|
|
203
|
-
});
|
|
204
|
-
});
|
|
205
112
|
});
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import * as child_process from 'child_process';
|
|
2
|
+
import * as stream from 'stream';
|
|
3
|
+
export { patchGqlPluck } from '@shopify/hydrogen-codegen';
|
|
4
|
+
|
|
5
|
+
declare function normalizeCodegenError(errorMessage: string, rootDirectory?: string): {
|
|
6
|
+
message: string;
|
|
7
|
+
details: string;
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* Spawns a child process to run GraphlQL CLI Codegen.
|
|
11
|
+
* Running on a separate process splits work from this processor
|
|
12
|
+
* and also allows us to filter out logs.
|
|
13
|
+
*/
|
|
14
|
+
declare function spawnCodegenProcess({ rootDirectory, appDirectory, configFilePath, }: CodegenOptions): child_process.ChildProcessByStdio<null, null, stream.Readable>;
|
|
15
|
+
type ProjectDirs = {
|
|
16
|
+
rootDirectory: string;
|
|
17
|
+
appDirectory: string;
|
|
18
|
+
};
|
|
19
|
+
type CodegenOptions = ProjectDirs & {
|
|
20
|
+
configFilePath?: string;
|
|
21
|
+
watch?: boolean;
|
|
22
|
+
};
|
|
23
|
+
declare function generateTypes({ configFilePath, watch, ...dirs }: CodegenOptions): Promise<string[]>;
|
|
24
|
+
|
|
25
|
+
export { generateTypes, normalizeCodegenError, spawnCodegenProcess };
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { loadCodegenConfig, generate } from '@graphql-codegen/cli';
|
|
2
|
+
import { patchGqlPluck, pluckConfig, preset, schema } from '@shopify/hydrogen-codegen';
|
|
3
|
+
export { patchGqlPluck } from '@shopify/hydrogen-codegen';
|
|
4
|
+
import { resolveFormatConfig, format } from './transpile-ts.js';
|
|
5
|
+
import { renderWarning, renderFatalError } from '@shopify/cli-kit/node/ui';
|
|
6
|
+
import { relativePath, joinPath } from '@shopify/cli-kit/node/path';
|
|
7
|
+
import { spawn } from 'node:child_process';
|
|
8
|
+
import { fileURLToPath } from 'node:url';
|
|
9
|
+
|
|
10
|
+
const nodePath = process.argv[1];
|
|
11
|
+
const modulePath = fileURLToPath(import.meta.url);
|
|
12
|
+
const isStandaloneProcess = nodePath === modulePath;
|
|
13
|
+
if (isStandaloneProcess) {
|
|
14
|
+
patchGqlPluck().then(
|
|
15
|
+
() => generateTypes({
|
|
16
|
+
rootDirectory: process.argv[2],
|
|
17
|
+
appDirectory: process.argv[3],
|
|
18
|
+
configFilePath: process.argv[4],
|
|
19
|
+
watch: true
|
|
20
|
+
})
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
function normalizeCodegenError(errorMessage, rootDirectory) {
|
|
24
|
+
const [first = "", ...rest] = errorMessage.replaceAll("[FAILED]", "").replace(/\s{2,}/g, "\n").replace(/\n,\n/, "\n").trim().split("\n");
|
|
25
|
+
const message = "[Codegen] " + first;
|
|
26
|
+
let details = rest.join("\n");
|
|
27
|
+
if (rootDirectory) {
|
|
28
|
+
const forwardSlashRootDir = rootDirectory.replaceAll("\\", "/") + "/";
|
|
29
|
+
details = details.replaceAll(forwardSlashRootDir, "");
|
|
30
|
+
}
|
|
31
|
+
return { message, details };
|
|
32
|
+
}
|
|
33
|
+
function spawnCodegenProcess({
|
|
34
|
+
rootDirectory,
|
|
35
|
+
appDirectory,
|
|
36
|
+
configFilePath
|
|
37
|
+
}) {
|
|
38
|
+
const child = spawn(
|
|
39
|
+
"node",
|
|
40
|
+
[
|
|
41
|
+
fileURLToPath(import.meta.url),
|
|
42
|
+
rootDirectory,
|
|
43
|
+
appDirectory,
|
|
44
|
+
configFilePath ?? ""
|
|
45
|
+
],
|
|
46
|
+
{ stdio: ["inherit", "ignore", "pipe"] }
|
|
47
|
+
);
|
|
48
|
+
child.stderr.on("data", (data) => {
|
|
49
|
+
const dataString = typeof data === "string" ? data : data?.toString?.("utf8") ?? "";
|
|
50
|
+
if (!dataString)
|
|
51
|
+
return;
|
|
52
|
+
const { message, details } = normalizeCodegenError(dataString, rootDirectory);
|
|
53
|
+
console.log("");
|
|
54
|
+
renderWarning({ headline: message, body: details });
|
|
55
|
+
});
|
|
56
|
+
child.on("close", (code) => {
|
|
57
|
+
if (code && code > 0) {
|
|
58
|
+
renderFatalError({
|
|
59
|
+
type: 0,
|
|
60
|
+
name: "CodegenError",
|
|
61
|
+
message: `Codegen process exited with code ${code}`,
|
|
62
|
+
tryMessage: "Try restarting the dev server."
|
|
63
|
+
});
|
|
64
|
+
process.exit(code);
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
return child;
|
|
68
|
+
}
|
|
69
|
+
async function generateTypes({
|
|
70
|
+
configFilePath,
|
|
71
|
+
watch,
|
|
72
|
+
...dirs
|
|
73
|
+
}) {
|
|
74
|
+
const { config: codegenConfig } = await loadCodegenConfig({
|
|
75
|
+
configFilePath,
|
|
76
|
+
searchPlaces: [dirs.rootDirectory]
|
|
77
|
+
}) || generateDefaultConfig(dirs);
|
|
78
|
+
await addHooksToHydrogenOptions(codegenConfig, dirs);
|
|
79
|
+
await generate(
|
|
80
|
+
{
|
|
81
|
+
...codegenConfig,
|
|
82
|
+
cwd: dirs.rootDirectory,
|
|
83
|
+
watch,
|
|
84
|
+
silent: !watch
|
|
85
|
+
},
|
|
86
|
+
true
|
|
87
|
+
);
|
|
88
|
+
return Object.keys(codegenConfig.generates);
|
|
89
|
+
}
|
|
90
|
+
function generateDefaultConfig({
|
|
91
|
+
rootDirectory,
|
|
92
|
+
appDirectory
|
|
93
|
+
}) {
|
|
94
|
+
const tsDefaultGlob = "*!(*.d).{ts,tsx}";
|
|
95
|
+
const appDirRelative = relativePath(rootDirectory, appDirectory);
|
|
96
|
+
return {
|
|
97
|
+
filepath: "virtual:codegen",
|
|
98
|
+
config: {
|
|
99
|
+
overwrite: true,
|
|
100
|
+
pluckConfig,
|
|
101
|
+
generates: {
|
|
102
|
+
["storefrontapi.generated.d.ts"]: {
|
|
103
|
+
preset,
|
|
104
|
+
schema,
|
|
105
|
+
documents: [
|
|
106
|
+
tsDefaultGlob,
|
|
107
|
+
joinPath(appDirRelative, "**", tsDefaultGlob)
|
|
108
|
+
]
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
async function addHooksToHydrogenOptions(codegenConfig, { rootDirectory }) {
|
|
115
|
+
const [, options] = Object.entries(codegenConfig.generates).find(
|
|
116
|
+
([, value]) => (Array.isArray(value) ? value[0] : value)?.schema === schema
|
|
117
|
+
) || [];
|
|
118
|
+
const hydrogenOptions = Array.isArray(options) ? options[0] : options;
|
|
119
|
+
if (hydrogenOptions) {
|
|
120
|
+
const formatConfig = await resolveFormatConfig(rootDirectory);
|
|
121
|
+
hydrogenOptions.hooks = {
|
|
122
|
+
beforeOneFileWrite: (file, content) => format(content, formatConfig, file),
|
|
123
|
+
...hydrogenOptions.hooks
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export { generateTypes, normalizeCodegenError, spawnCodegenProcess };
|
package/dist/lib/colors.d.ts
CHANGED
|
@@ -3,6 +3,9 @@ import ansiColors from 'ansi-colors';
|
|
|
3
3
|
declare const colors: {
|
|
4
4
|
dim: ansiColors.StyleFunction;
|
|
5
5
|
bold: ansiColors.StyleFunction;
|
|
6
|
+
green: ansiColors.StyleFunction;
|
|
7
|
+
whiteBright: ansiColors.StyleFunction;
|
|
8
|
+
yellow: ansiColors.StyleFunction;
|
|
6
9
|
};
|
|
7
10
|
|
|
8
11
|
export { colors };
|
package/dist/lib/colors.js
CHANGED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { fileExists } from '@shopify/cli-kit/node/fs';
|
|
2
|
+
import { resolvePath } from '@shopify/cli-kit/node/path';
|
|
3
|
+
import { outputInfo, outputContent, outputToken } from '@shopify/cli-kit/node/output';
|
|
4
|
+
import { readAndParseDotEnv } from '@shopify/cli-kit/node/dot-env';
|
|
5
|
+
import { colors } from './colors.js';
|
|
6
|
+
import { pullRemoteEnvironmentVariables } from './pull-environment-variables.js';
|
|
7
|
+
import { getConfig } from './shopify-config.js';
|
|
8
|
+
|
|
9
|
+
async function combinedEnvironmentVariables({
|
|
10
|
+
envBranch,
|
|
11
|
+
root,
|
|
12
|
+
shop
|
|
13
|
+
}) {
|
|
14
|
+
const remoteEnvironmentVariables = await pullRemoteEnvironmentVariables({
|
|
15
|
+
root,
|
|
16
|
+
flagShop: shop,
|
|
17
|
+
silent: true,
|
|
18
|
+
envBranch
|
|
19
|
+
});
|
|
20
|
+
const formattedRemoteVariables = remoteEnvironmentVariables?.reduce(
|
|
21
|
+
(a, v) => ({ ...a, [v.key]: v.value }),
|
|
22
|
+
{}
|
|
23
|
+
);
|
|
24
|
+
const dotEnvPath = resolvePath(root, ".env");
|
|
25
|
+
const localEnvironmentVariables = await fileExists(dotEnvPath) ? (await readAndParseDotEnv(dotEnvPath)).variables : {};
|
|
26
|
+
const remoteKeys = new Set(
|
|
27
|
+
remoteEnvironmentVariables.map((variable) => variable.key)
|
|
28
|
+
);
|
|
29
|
+
const localKeys = new Set(Object.keys(localEnvironmentVariables));
|
|
30
|
+
if ([...remoteKeys, ...localKeys].length) {
|
|
31
|
+
outputInfo(
|
|
32
|
+
`${colors.bold("Injecting environment variables into MiniOxygen...")}`
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
let storefrontTitle = "";
|
|
36
|
+
if (remoteEnvironmentVariables.length) {
|
|
37
|
+
const { storefront } = await getConfig(root);
|
|
38
|
+
if (storefront) {
|
|
39
|
+
storefrontTitle = storefront.title;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
remoteEnvironmentVariables.forEach(({ key, isSecret }) => {
|
|
43
|
+
if (localKeys.has(key)) {
|
|
44
|
+
outputIgnoringKey(key, `overwritten via ${colors.yellow(".env")}`);
|
|
45
|
+
} else if (isSecret) {
|
|
46
|
+
outputIgnoringKey(key, "value is marked as secret");
|
|
47
|
+
} else {
|
|
48
|
+
outputUsingKey(key, storefrontTitle);
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
[...localKeys].forEach((keyName) => {
|
|
52
|
+
outputUsingKey(keyName, ".env");
|
|
53
|
+
});
|
|
54
|
+
return {
|
|
55
|
+
...formattedRemoteVariables,
|
|
56
|
+
...localEnvironmentVariables
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
function outputUsingKey(keyName, source) {
|
|
60
|
+
outputInfo(
|
|
61
|
+
outputContent` Using ${outputToken.green(
|
|
62
|
+
keyName
|
|
63
|
+
)} from ${outputToken.yellow(source)}`.value
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
function outputIgnoringKey(keyName, reason) {
|
|
67
|
+
outputInfo(
|
|
68
|
+
outputContent`${colors.dim(
|
|
69
|
+
` Ignoring ${colors.green(keyName)} (${reason})`
|
|
70
|
+
)}`.value
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export { combinedEnvironmentVariables };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { vi, describe, beforeEach, afterEach, test, expect } from 'vitest';
|
|
2
|
+
import { inTemporaryDirectory, writeFile } from '@shopify/cli-kit/node/fs';
|
|
3
|
+
import { joinPath } from '@shopify/cli-kit/node/path';
|
|
4
|
+
import { mockAndCaptureOutput } from '@shopify/cli-kit/node/testing/output';
|
|
5
|
+
import { combinedEnvironmentVariables } from './combined-environment-variables.js';
|
|
6
|
+
import { pullRemoteEnvironmentVariables } from './pull-environment-variables.js';
|
|
7
|
+
import { getConfig } from './shopify-config.js';
|
|
8
|
+
|
|
9
|
+
vi.mock("./shopify-config.js");
|
|
10
|
+
vi.mock("./pull-environment-variables.js");
|
|
11
|
+
describe("combinedEnvironmentVariables()", () => {
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
vi.mocked(getConfig).mockResolvedValue({
|
|
14
|
+
storefront: {
|
|
15
|
+
id: "gid://shopify/HydrogenStorefront/1",
|
|
16
|
+
title: "Hydrogen"
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
vi.mocked(pullRemoteEnvironmentVariables).mockResolvedValue([
|
|
20
|
+
{
|
|
21
|
+
id: "gid://shopify/HydrogenStorefrontEnvironmentVariable/1",
|
|
22
|
+
key: "PUBLIC_API_TOKEN",
|
|
23
|
+
value: "abc123",
|
|
24
|
+
isSecret: false
|
|
25
|
+
}
|
|
26
|
+
]);
|
|
27
|
+
});
|
|
28
|
+
afterEach(() => {
|
|
29
|
+
vi.resetAllMocks();
|
|
30
|
+
mockAndCaptureOutput().clear();
|
|
31
|
+
});
|
|
32
|
+
test("calls pullRemoteEnvironmentVariables", async () => {
|
|
33
|
+
await inTemporaryDirectory(async (tmpDir) => {
|
|
34
|
+
await combinedEnvironmentVariables({
|
|
35
|
+
envBranch: "main",
|
|
36
|
+
root: tmpDir,
|
|
37
|
+
shop: "my-shop"
|
|
38
|
+
});
|
|
39
|
+
expect(pullRemoteEnvironmentVariables).toHaveBeenCalledWith({
|
|
40
|
+
envBranch: "main",
|
|
41
|
+
root: tmpDir,
|
|
42
|
+
flagShop: "my-shop",
|
|
43
|
+
silent: true
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
test("renders a message about injection", async () => {
|
|
48
|
+
await inTemporaryDirectory(async (tmpDir) => {
|
|
49
|
+
const outputMock = mockAndCaptureOutput();
|
|
50
|
+
await combinedEnvironmentVariables({ root: tmpDir, shop: "my-shop" });
|
|
51
|
+
expect(outputMock.info()).toMatch(
|
|
52
|
+
/Injecting environment variables into MiniOxygen/
|
|
53
|
+
);
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
test("lists all of the variables being used", async () => {
|
|
57
|
+
await inTemporaryDirectory(async (tmpDir) => {
|
|
58
|
+
const outputMock = mockAndCaptureOutput();
|
|
59
|
+
await combinedEnvironmentVariables({ root: tmpDir, shop: "my-shop" });
|
|
60
|
+
expect(outputMock.info()).toMatch(/Using PUBLIC_API_TOKEN from Hydrogen/);
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
describe("when one of the variables is a secret", () => {
|
|
64
|
+
beforeEach(() => {
|
|
65
|
+
vi.mocked(pullRemoteEnvironmentVariables).mockResolvedValue([
|
|
66
|
+
{
|
|
67
|
+
id: "gid://shopify/HydrogenStorefrontEnvironmentVariable/1",
|
|
68
|
+
key: "PUBLIC_API_TOKEN",
|
|
69
|
+
value: "",
|
|
70
|
+
isSecret: true
|
|
71
|
+
}
|
|
72
|
+
]);
|
|
73
|
+
});
|
|
74
|
+
test("uses special messaging to alert the user", async () => {
|
|
75
|
+
await inTemporaryDirectory(async (tmpDir) => {
|
|
76
|
+
const outputMock = mockAndCaptureOutput();
|
|
77
|
+
await combinedEnvironmentVariables({ root: tmpDir, shop: "my-shop" });
|
|
78
|
+
expect(outputMock.info()).toMatch(
|
|
79
|
+
/Ignoring PUBLIC_API_TOKEN \(value is marked as secret\)/
|
|
80
|
+
);
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
describe("when there are local variables", () => {
|
|
85
|
+
test("includes local variables in the list", async () => {
|
|
86
|
+
await inTemporaryDirectory(async (tmpDir) => {
|
|
87
|
+
const filePath = joinPath(tmpDir, ".env");
|
|
88
|
+
await writeFile(filePath, "LOCAL_TOKEN=1");
|
|
89
|
+
const outputMock = mockAndCaptureOutput();
|
|
90
|
+
await combinedEnvironmentVariables({ root: tmpDir });
|
|
91
|
+
expect(outputMock.info()).toMatch(/Using LOCAL_TOKEN from \.env/);
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
describe("and they overwrite remote variables", () => {
|
|
95
|
+
test("uses special messaging to alert the user", async () => {
|
|
96
|
+
await inTemporaryDirectory(async (tmpDir) => {
|
|
97
|
+
const filePath = joinPath(tmpDir, ".env");
|
|
98
|
+
await writeFile(filePath, "PUBLIC_API_TOKEN=abc");
|
|
99
|
+
const outputMock = mockAndCaptureOutput();
|
|
100
|
+
await combinedEnvironmentVariables({ root: tmpDir, shop: "my-shop" });
|
|
101
|
+
expect(outputMock.info()).toMatch(
|
|
102
|
+
/Ignoring PUBLIC_API_TOKEN \(overwritten via \.env\)/
|
|
103
|
+
);
|
|
104
|
+
expect(outputMock.info()).toMatch(
|
|
105
|
+
/Using PUBLIC_API_TOKEN from \.env/
|
|
106
|
+
);
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
});
|
package/dist/lib/flags.d.ts
CHANGED
|
@@ -5,6 +5,7 @@ declare const commonFlags: {
|
|
|
5
5
|
port: _oclif_core_lib_interfaces_parser_js.OptionFlag<number, _oclif_core_lib_interfaces_parser_js.CustomOptions>;
|
|
6
6
|
force: _oclif_core_lib_interfaces_parser_js.BooleanFlag<boolean>;
|
|
7
7
|
shop: _oclif_core_lib_interfaces_parser_js.OptionFlag<string | undefined, _oclif_core_lib_interfaces_parser_js.CustomOptions>;
|
|
8
|
+
"env-branch": _oclif_core_lib_interfaces_parser_js.OptionFlag<string | undefined, _oclif_core_lib_interfaces_parser_js.CustomOptions>;
|
|
8
9
|
};
|
|
9
10
|
declare function flagsToCamelObject(obj: Record<string, any>): any;
|
|
10
11
|
/**
|
package/dist/lib/flags.js
CHANGED
|
@@ -24,6 +24,12 @@ const commonFlags = {
|
|
|
24
24
|
description: "Shop URL. It can be the shop prefix (janes-apparel) or the full myshopify.com URL (janes-apparel.myshopify.com, https://janes-apparel.myshopify.com).",
|
|
25
25
|
env: "SHOPIFY_SHOP",
|
|
26
26
|
parse: async (input) => normalizeStoreFqdn(input)
|
|
27
|
+
}),
|
|
28
|
+
["env-branch"]: Flags.string({
|
|
29
|
+
description: "Specify an environment's branch name when using remote environment variables.",
|
|
30
|
+
env: "SHOPIFY_HYDROGEN_ENVIRONMENT_BRANCH",
|
|
31
|
+
char: "e",
|
|
32
|
+
hidden: true
|
|
27
33
|
})
|
|
28
34
|
};
|
|
29
35
|
function flagsToCamelObject(obj) {
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
declare const ListEnvironmentsQuery = "#graphql\n query ListStorefronts($id: ID!) {\n hydrogenStorefront(id: $id) {\n id\n productionUrl\n environments {\n branch\n createdAt\n id\n name\n type\n url\n }\n }\n }\n";
|
|
2
|
+
type EnvironmentType = 'PREVIEW' | 'PRODUCTION' | 'CUSTOM';
|
|
3
|
+
interface Environment {
|
|
4
|
+
branch: string | null;
|
|
5
|
+
createdAt: string;
|
|
6
|
+
id: string;
|
|
7
|
+
name: string;
|
|
8
|
+
type: EnvironmentType;
|
|
9
|
+
url: string | null;
|
|
10
|
+
}
|
|
11
|
+
interface HydrogenStorefront {
|
|
12
|
+
id: string;
|
|
13
|
+
environments: Environment[];
|
|
14
|
+
productionUrl: string;
|
|
15
|
+
}
|
|
16
|
+
interface ListEnvironmentsSchema {
|
|
17
|
+
hydrogenStorefront: HydrogenStorefront | null;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export { Environment, EnvironmentType, ListEnvironmentsQuery, ListEnvironmentsSchema };
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
const ListEnvironmentsQuery = `#graphql
|
|
2
|
+
query ListStorefronts($id: ID!) {
|
|
3
|
+
hydrogenStorefront(id: $id) {
|
|
4
|
+
id
|
|
5
|
+
productionUrl
|
|
6
|
+
environments {
|
|
7
|
+
branch
|
|
8
|
+
createdAt
|
|
9
|
+
id
|
|
10
|
+
name
|
|
11
|
+
type
|
|
12
|
+
url
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
`;
|
|
17
|
+
|
|
18
|
+
export { ListEnvironmentsQuery };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
declare const PullVariablesQuery = "#graphql\n query
|
|
1
|
+
declare const PullVariablesQuery = "#graphql\n query PullVariables($id: ID!, $branch: String) {\n hydrogenStorefront(id: $id) {\n id\n environmentVariables(branchName: $branch) {\n id\n isSecret\n key\n value\n }\n }\n }\n";
|
|
2
2
|
interface EnvironmentVariable {
|
|
3
3
|
id: string;
|
|
4
4
|
isSecret: boolean;
|
|
@@ -4,8 +4,11 @@ type MiniOxygenOptions = {
|
|
|
4
4
|
watch?: boolean;
|
|
5
5
|
buildPathClient: string;
|
|
6
6
|
buildPathWorkerFile: string;
|
|
7
|
+
environmentVariables?: {
|
|
8
|
+
[key: string]: string;
|
|
9
|
+
};
|
|
7
10
|
};
|
|
8
|
-
declare function startMiniOxygen({ root, port, watch, buildPathWorkerFile, buildPathClient, }: MiniOxygenOptions): Promise<void>;
|
|
11
|
+
declare function startMiniOxygen({ root, port, watch, buildPathWorkerFile, buildPathClient, environmentVariables, }: MiniOxygenOptions): Promise<void>;
|
|
9
12
|
declare function logResponse(request: Request, response: Response): void;
|
|
10
13
|
|
|
11
14
|
export { logResponse, startMiniOxygen };
|
package/dist/lib/mini-oxygen.js
CHANGED
|
@@ -8,7 +8,8 @@ async function startMiniOxygen({
|
|
|
8
8
|
port = 3e3,
|
|
9
9
|
watch = false,
|
|
10
10
|
buildPathWorkerFile,
|
|
11
|
-
buildPathClient
|
|
11
|
+
buildPathClient,
|
|
12
|
+
environmentVariables = {}
|
|
12
13
|
}) {
|
|
13
14
|
const { default: miniOxygen } = await import('@shopify/mini-oxygen');
|
|
14
15
|
const miniOxygenPreview = miniOxygen.default ?? miniOxygen;
|
|
@@ -21,8 +22,11 @@ async function startMiniOxygen({
|
|
|
21
22
|
watch,
|
|
22
23
|
autoReload: watch,
|
|
23
24
|
modules: true,
|
|
24
|
-
env:
|
|
25
|
-
|
|
25
|
+
env: {
|
|
26
|
+
...environmentVariables,
|
|
27
|
+
...process.env
|
|
28
|
+
},
|
|
29
|
+
envPath: !Object.keys(environmentVariables).length && await fileExists(dotenvPath) ? dotenvPath : void 0,
|
|
26
30
|
log: () => {
|
|
27
31
|
},
|
|
28
32
|
buildWatchPaths: watch ? [resolvePath(root, buildPathWorkerFile)] : void 0,
|