@shopify/cli-hydrogen 4.1.1 → 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 +5 -0
- package/dist/commands/hydrogen/dev.js +48 -6
- 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 +23 -0
- package/dist/commands/hydrogen/env/pull.js +68 -0
- package/dist/commands/hydrogen/env/pull.test.d.ts +1 -0
- package/dist/commands/hydrogen/env/pull.test.js +112 -0
- package/dist/commands/hydrogen/init.js +3 -1
- package/dist/commands/hydrogen/link.d.ts +24 -0
- package/dist/commands/hydrogen/link.js +102 -0
- package/dist/commands/hydrogen/link.test.d.ts +1 -0
- package/dist/commands/hydrogen/link.test.js +137 -0
- package/dist/commands/hydrogen/list.d.ts +21 -0
- package/dist/commands/hydrogen/list.js +83 -0
- package/dist/commands/hydrogen/list.test.d.ts +1 -0
- package/dist/commands/hydrogen/list.test.js +116 -0
- package/dist/commands/hydrogen/unlink.d.ts +17 -0
- package/dist/commands/hydrogen/unlink.js +29 -0
- package/dist/commands/hydrogen/unlink.test.d.ts +1 -0
- package/dist/commands/hydrogen/unlink.test.js +36 -0
- package/dist/generator-templates/routes/[robots.txt].tsx +111 -19
- package/dist/generator-templates/routes/collections/index.tsx +102 -0
- package/dist/lib/admin-session.d.ts +5 -0
- package/dist/lib/admin-session.js +16 -0
- package/dist/lib/admin-session.test.d.ts +1 -0
- package/dist/lib/admin-session.test.js +27 -0
- package/dist/lib/admin-urls.d.ts +8 -0
- package/dist/lib/admin-urls.js +18 -0
- 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 +2 -0
- package/dist/lib/flags.js +13 -0
- package/dist/lib/graphql/admin/link-storefront.d.ts +11 -0
- package/dist/lib/graphql/admin/link-storefront.js +11 -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/list-storefronts.d.ts +17 -0
- package/dist/lib/graphql/admin/list-storefronts.js +16 -0
- package/dist/lib/graphql/admin/pull-variables.d.ts +16 -0
- package/dist/lib/graphql/admin/pull-variables.js +15 -0
- package/dist/lib/graphql.d.ts +21 -0
- package/dist/lib/graphql.js +18 -0
- package/dist/lib/graphql.test.d.ts +1 -0
- package/dist/lib/graphql.test.js +15 -0
- package/dist/lib/mini-oxygen.d.ts +4 -1
- package/dist/lib/mini-oxygen.js +7 -3
- package/dist/lib/missing-storefronts.d.ts +5 -0
- package/dist/lib/missing-storefronts.js +18 -0
- 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/dist/lib/shop.d.ts +7 -0
- package/dist/lib/shop.js +32 -0
- package/dist/lib/shop.test.d.ts +1 -0
- package/dist/lib/shop.test.js +78 -0
- package/dist/lib/shopify-config.d.ts +35 -0
- package/dist/lib/shopify-config.js +86 -0
- package/dist/lib/shopify-config.test.d.ts +1 -0
- package/dist/lib/shopify-config.test.js +209 -0
- package/oclif.manifest.json +1 -1
- package/package.json +7 -5
|
@@ -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
|
@@ -4,6 +4,8 @@ declare const commonFlags: {
|
|
|
4
4
|
path: _oclif_core_lib_interfaces_parser_js.OptionFlag<string | undefined, _oclif_core_lib_interfaces_parser_js.CustomOptions>;
|
|
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
|
+
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>;
|
|
7
9
|
};
|
|
8
10
|
declare function flagsToCamelObject(obj: Record<string, any>): any;
|
|
9
11
|
/**
|
package/dist/lib/flags.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Flags } from '@oclif/core';
|
|
2
2
|
import { camelize } from '@shopify/cli-kit/common/string';
|
|
3
3
|
import { renderInfo } from '@shopify/cli-kit/node/ui';
|
|
4
|
+
import { normalizeStoreFqdn } from '@shopify/cli-kit/node/context/fqdn';
|
|
4
5
|
import { colors } from './colors.js';
|
|
5
6
|
|
|
6
7
|
const commonFlags = {
|
|
@@ -17,6 +18,18 @@ const commonFlags = {
|
|
|
17
18
|
description: "Overwrite the destination directory and files if they already exist.",
|
|
18
19
|
env: "SHOPIFY_HYDROGEN_FLAG_FORCE",
|
|
19
20
|
char: "f"
|
|
21
|
+
}),
|
|
22
|
+
shop: Flags.string({
|
|
23
|
+
char: "s",
|
|
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
|
+
env: "SHOPIFY_SHOP",
|
|
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
|
|
20
33
|
})
|
|
21
34
|
};
|
|
22
35
|
function flagsToCamelObject(obj) {
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
declare const LinkStorefrontQuery = "#graphql\n query LinkStorefront {\n hydrogenStorefronts {\n id\n title\n productionUrl\n }\n }\n";
|
|
2
|
+
interface HydrogenStorefront {
|
|
3
|
+
id: string;
|
|
4
|
+
title: string;
|
|
5
|
+
productionUrl: string;
|
|
6
|
+
}
|
|
7
|
+
interface LinkStorefrontSchema {
|
|
8
|
+
hydrogenStorefronts: HydrogenStorefront[];
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export { LinkStorefrontQuery, LinkStorefrontSchema };
|
|
@@ -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 };
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
declare const ListStorefrontsQuery = "#graphql\n query ListStorefronts {\n hydrogenStorefronts {\n id\n title\n productionUrl\n currentProductionDeployment {\n id\n createdAt\n commitMessage\n }\n }\n }\n";
|
|
2
|
+
interface Deployment {
|
|
3
|
+
id: string;
|
|
4
|
+
createdAt: string;
|
|
5
|
+
commitMessage: string | null;
|
|
6
|
+
}
|
|
7
|
+
interface HydrogenStorefront {
|
|
8
|
+
id: string;
|
|
9
|
+
title: string;
|
|
10
|
+
productionUrl?: string;
|
|
11
|
+
currentProductionDeployment: Deployment | null;
|
|
12
|
+
}
|
|
13
|
+
interface ListStorefrontsSchema {
|
|
14
|
+
hydrogenStorefronts: HydrogenStorefront[];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export { Deployment, ListStorefrontsQuery, ListStorefrontsSchema };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
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
|
+
interface EnvironmentVariable {
|
|
3
|
+
id: string;
|
|
4
|
+
isSecret: boolean;
|
|
5
|
+
key: string;
|
|
6
|
+
value: string;
|
|
7
|
+
}
|
|
8
|
+
interface HydrogenStorefront {
|
|
9
|
+
id: string;
|
|
10
|
+
environmentVariables: EnvironmentVariable[];
|
|
11
|
+
}
|
|
12
|
+
interface PullVariablesSchema {
|
|
13
|
+
hydrogenStorefront: HydrogenStorefront | null;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export { EnvironmentVariable, PullVariablesQuery, PullVariablesSchema };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
const PullVariablesQuery = `#graphql
|
|
2
|
+
query PullVariables($id: ID!, $branch: String) {
|
|
3
|
+
hydrogenStorefront(id: $id) {
|
|
4
|
+
id
|
|
5
|
+
environmentVariables(branchName: $branch) {
|
|
6
|
+
id
|
|
7
|
+
isSecret
|
|
8
|
+
key
|
|
9
|
+
value
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
`;
|
|
14
|
+
|
|
15
|
+
export { PullVariablesQuery };
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { GraphQLVariables } from '@shopify/cli-kit/node/api/graphql';
|
|
2
|
+
import { AdminSession } from '@shopify/cli-kit/node/session';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* This is a temporary workaround until cli-kit includes a way to specify
|
|
6
|
+
* API versions for the Admin API because we need to target the unstable
|
|
7
|
+
* branch for this early access phase.
|
|
8
|
+
*
|
|
9
|
+
* @param query - GraphQL query to execute.
|
|
10
|
+
* @param session - Shopify admin session including token and Store FQDN.
|
|
11
|
+
* @param variables - GraphQL variables to pass to the query.
|
|
12
|
+
* @returns The response of the query of generic type <T>.
|
|
13
|
+
*/
|
|
14
|
+
declare function adminRequest<T>(query: string, session: AdminSession, variables?: GraphQLVariables): Promise<T>;
|
|
15
|
+
/**
|
|
16
|
+
* @param gid a Global ID to parse (e.g. 'gid://shopify/HydrogenStorefront/1')
|
|
17
|
+
* @returns the ID of the record (e.g. '1')
|
|
18
|
+
*/
|
|
19
|
+
declare function parseGid(gid: string): string;
|
|
20
|
+
|
|
21
|
+
export { adminRequest, parseGid };
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { graphqlRequest } from '@shopify/cli-kit/node/api/graphql';
|
|
2
|
+
import { AbortError } from '@shopify/cli-kit/node/error';
|
|
3
|
+
|
|
4
|
+
async function adminRequest(query, session, variables) {
|
|
5
|
+
const api = "Admin";
|
|
6
|
+
const url = `https://${session.storeFqdn}/admin/api/unstable/graphql.json`;
|
|
7
|
+
return graphqlRequest({ query, api, url, token: session.token, variables });
|
|
8
|
+
}
|
|
9
|
+
const GID_REGEXP = /gid:\/\/shopify\/\w*\/(\d+)/;
|
|
10
|
+
function parseGid(gid) {
|
|
11
|
+
const matches = GID_REGEXP.exec(gid);
|
|
12
|
+
if (matches && matches[1] !== void 0) {
|
|
13
|
+
return matches[1];
|
|
14
|
+
}
|
|
15
|
+
throw new AbortError(`Invalid Global ID: ${gid}`);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export { adminRequest, parseGid };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { AbortError } from '@shopify/cli-kit/node/error';
|
|
3
|
+
import { parseGid } from './graphql.js';
|
|
4
|
+
|
|
5
|
+
describe("parseGid", () => {
|
|
6
|
+
it("returns an ID", () => {
|
|
7
|
+
const id = parseGid("gid://shopify/HydrogenStorefront/324");
|
|
8
|
+
expect(id).toStrictEqual("324");
|
|
9
|
+
});
|
|
10
|
+
describe("when the global ID is invalid", () => {
|
|
11
|
+
it("throws an error", () => {
|
|
12
|
+
expect(() => parseGid("321asd")).toThrow(AbortError);
|
|
13
|
+
});
|
|
14
|
+
});
|
|
15
|
+
});
|
|
@@ -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,
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { renderInfo } from '@shopify/cli-kit/node/ui';
|
|
2
|
+
import { newHydrogenStorefrontUrl } from './admin-urls.js';
|
|
3
|
+
|
|
4
|
+
function logMissingStorefronts(adminSession) {
|
|
5
|
+
renderInfo({
|
|
6
|
+
headline: "Hydrogen storefronts",
|
|
7
|
+
body: "There are no Hydrogen storefronts on your Shop.",
|
|
8
|
+
nextSteps: [
|
|
9
|
+
`Ensure you have specified the correct shop (you specified: ${adminSession.storeFqdn})`,
|
|
10
|
+
`Ensure you have the Hydrogen sales channel installed https://apps.shopify.com/hydrogen`,
|
|
11
|
+
`Create a new Hydrogen storefront: ${newHydrogenStorefrontUrl(
|
|
12
|
+
adminSession
|
|
13
|
+
)}`
|
|
14
|
+
]
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export { logMissingStorefronts };
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { EnvironmentVariable } from './graphql/admin/pull-variables.js';
|
|
2
|
+
|
|
3
|
+
interface Arguments {
|
|
4
|
+
envBranch?: string;
|
|
5
|
+
root: string;
|
|
6
|
+
/**
|
|
7
|
+
* Optional shop override that developers would have passed using the --shop
|
|
8
|
+
* flag.
|
|
9
|
+
*/
|
|
10
|
+
flagShop?: string;
|
|
11
|
+
/**
|
|
12
|
+
* Does not prompt the user to fix any errors that are encountered (e.g. no
|
|
13
|
+
* linked storefront)
|
|
14
|
+
*/
|
|
15
|
+
silent?: boolean;
|
|
16
|
+
}
|
|
17
|
+
declare function pullRemoteEnvironmentVariables({ envBranch, root, flagShop, silent, }: Arguments): Promise<EnvironmentVariable[]>;
|
|
18
|
+
|
|
19
|
+
export { pullRemoteEnvironmentVariables };
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { renderConfirmationPrompt } from '@shopify/cli-kit/node/ui';
|
|
2
|
+
import { outputContent, outputToken, outputInfo } from '@shopify/cli-kit/node/output';
|
|
3
|
+
import { linkStorefront } from '../commands/hydrogen/link.js';
|
|
4
|
+
import { adminRequest } from './graphql.js';
|
|
5
|
+
import { getHydrogenShop } from './shop.js';
|
|
6
|
+
import { getAdminSession } from './admin-session.js';
|
|
7
|
+
import { getConfig } from './shopify-config.js';
|
|
8
|
+
import { renderMissingLink, renderMissingStorefront } from './render-errors.js';
|
|
9
|
+
import { PullVariablesQuery } from './graphql/admin/pull-variables.js';
|
|
10
|
+
|
|
11
|
+
async function pullRemoteEnvironmentVariables({
|
|
12
|
+
envBranch,
|
|
13
|
+
root,
|
|
14
|
+
flagShop,
|
|
15
|
+
silent
|
|
16
|
+
}) {
|
|
17
|
+
const shop = await getHydrogenShop({ path: root, shop: flagShop });
|
|
18
|
+
const adminSession = await getAdminSession(shop);
|
|
19
|
+
let configStorefront = (await getConfig(root)).storefront;
|
|
20
|
+
if (!configStorefront?.id) {
|
|
21
|
+
if (!silent) {
|
|
22
|
+
renderMissingLink({ adminSession });
|
|
23
|
+
const runLink = await renderConfirmationPrompt({
|
|
24
|
+
message: outputContent`Run ${outputToken.genericShellCommand(
|
|
25
|
+
`npx shopify hydrogen link`
|
|
26
|
+
)}?`.value
|
|
27
|
+
});
|
|
28
|
+
if (!runLink) {
|
|
29
|
+
return [];
|
|
30
|
+
}
|
|
31
|
+
await linkStorefront({ path: root, shop: flagShop, silent });
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
configStorefront = (await getConfig(root)).storefront;
|
|
35
|
+
if (!configStorefront) {
|
|
36
|
+
return [];
|
|
37
|
+
}
|
|
38
|
+
if (!silent) {
|
|
39
|
+
outputInfo(
|
|
40
|
+
`Fetching environment variables from ${configStorefront.title}...`
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
const result = await adminRequest(
|
|
44
|
+
PullVariablesQuery,
|
|
45
|
+
adminSession,
|
|
46
|
+
{
|
|
47
|
+
id: configStorefront.id,
|
|
48
|
+
branch: envBranch
|
|
49
|
+
}
|
|
50
|
+
);
|
|
51
|
+
const storefront = result.hydrogenStorefront;
|
|
52
|
+
if (!storefront) {
|
|
53
|
+
if (!silent) {
|
|
54
|
+
renderMissingStorefront({ adminSession, storefront: configStorefront });
|
|
55
|
+
}
|
|
56
|
+
return [];
|
|
57
|
+
}
|
|
58
|
+
if (!storefront.environmentVariables.length) {
|
|
59
|
+
if (!silent) {
|
|
60
|
+
outputInfo(`No environment variables found.`);
|
|
61
|
+
}
|
|
62
|
+
return [];
|
|
63
|
+
}
|
|
64
|
+
return storefront.environmentVariables;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export { pullRemoteEnvironmentVariables };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1,174 @@
|
|
|
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 { PullVariablesQuery } from './graphql/admin/pull-variables.js';
|
|
6
|
+
import { getAdminSession } from './admin-session.js';
|
|
7
|
+
import { adminRequest } from './graphql.js';
|
|
8
|
+
import { getConfig } from './shopify-config.js';
|
|
9
|
+
import { renderMissingLink, renderMissingStorefront } from './render-errors.js';
|
|
10
|
+
import { linkStorefront } from '../commands/hydrogen/link.js';
|
|
11
|
+
import { pullRemoteEnvironmentVariables } from './pull-environment-variables.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("../commands/hydrogen/link.js");
|
|
21
|
+
vi.mock("./admin-session.js");
|
|
22
|
+
vi.mock("./shopify-config.js");
|
|
23
|
+
vi.mock("./render-errors.js");
|
|
24
|
+
vi.mock("./graphql.js", async () => {
|
|
25
|
+
const original = await vi.importActual(
|
|
26
|
+
"./graphql.js"
|
|
27
|
+
);
|
|
28
|
+
return {
|
|
29
|
+
...original,
|
|
30
|
+
adminRequest: vi.fn()
|
|
31
|
+
};
|
|
32
|
+
});
|
|
33
|
+
vi.mock("./shop.js", () => ({
|
|
34
|
+
getHydrogenShop: () => "my-shop"
|
|
35
|
+
}));
|
|
36
|
+
describe("pullRemoteEnvironmentVariables", () => {
|
|
37
|
+
const ENVIRONMENT_VARIABLES_RESPONSE = [
|
|
38
|
+
{
|
|
39
|
+
id: "gid://shopify/HydrogenStorefrontEnvironmentVariable/1",
|
|
40
|
+
key: "PUBLIC_API_TOKEN",
|
|
41
|
+
value: "abc123",
|
|
42
|
+
isSecret: false
|
|
43
|
+
}
|
|
44
|
+
];
|
|
45
|
+
const ADMIN_SESSION = {
|
|
46
|
+
token: "abc123",
|
|
47
|
+
storeFqdn: "my-shop"
|
|
48
|
+
};
|
|
49
|
+
beforeEach(async () => {
|
|
50
|
+
vi.mocked(getAdminSession).mockResolvedValue(ADMIN_SESSION);
|
|
51
|
+
vi.mocked(getConfig).mockResolvedValue({
|
|
52
|
+
storefront: {
|
|
53
|
+
id: "gid://shopify/HydrogenStorefront/2",
|
|
54
|
+
title: "Existing Link"
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
vi.mocked(adminRequest).mockResolvedValue({
|
|
58
|
+
hydrogenStorefront: {
|
|
59
|
+
id: "gid://shopify/HydrogenStorefront/1",
|
|
60
|
+
environmentVariables: ENVIRONMENT_VARIABLES_RESPONSE
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
afterEach(() => {
|
|
65
|
+
vi.resetAllMocks();
|
|
66
|
+
mockAndCaptureOutput().clear();
|
|
67
|
+
});
|
|
68
|
+
it("makes a GraphQL call to fetch environment variables", async () => {
|
|
69
|
+
await inTemporaryDirectory(async (tmpDir) => {
|
|
70
|
+
await pullRemoteEnvironmentVariables({
|
|
71
|
+
root: tmpDir,
|
|
72
|
+
envBranch: "staging"
|
|
73
|
+
});
|
|
74
|
+
expect(adminRequest).toHaveBeenCalledWith(
|
|
75
|
+
PullVariablesQuery,
|
|
76
|
+
ADMIN_SESSION,
|
|
77
|
+
{
|
|
78
|
+
id: "gid://shopify/HydrogenStorefront/2",
|
|
79
|
+
branch: "staging"
|
|
80
|
+
}
|
|
81
|
+
);
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
it("returns environment variables", async () => {
|
|
85
|
+
await inTemporaryDirectory(async (tmpDir) => {
|
|
86
|
+
const environmentVariables = await pullRemoteEnvironmentVariables({
|
|
87
|
+
root: tmpDir
|
|
88
|
+
});
|
|
89
|
+
expect(environmentVariables).toBe(ENVIRONMENT_VARIABLES_RESPONSE);
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
describe("when environment variables are empty", () => {
|
|
93
|
+
beforeEach(() => {
|
|
94
|
+
vi.mocked(adminRequest).mockResolvedValue({
|
|
95
|
+
hydrogenStorefront: {
|
|
96
|
+
id: "gid://shopify/HydrogenStorefront/1",
|
|
97
|
+
environmentVariables: []
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
it("renders a message", async () => {
|
|
102
|
+
await inTemporaryDirectory(async (tmpDir) => {
|
|
103
|
+
const outputMock = mockAndCaptureOutput();
|
|
104
|
+
await pullRemoteEnvironmentVariables({ root: tmpDir });
|
|
105
|
+
expect(outputMock.info()).toMatch(/No environment variables found\./);
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
it("returns an empty array", async () => {
|
|
109
|
+
await inTemporaryDirectory(async (tmpDir) => {
|
|
110
|
+
const environmentVariables = await pullRemoteEnvironmentVariables({
|
|
111
|
+
root: tmpDir
|
|
112
|
+
});
|
|
113
|
+
expect(environmentVariables).toStrictEqual([]);
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
describe("when there is no linked storefront", () => {
|
|
118
|
+
beforeEach(() => {
|
|
119
|
+
vi.mocked(getConfig).mockResolvedValue({
|
|
120
|
+
storefront: void 0
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
it("calls renderMissingLink", async () => {
|
|
124
|
+
await inTemporaryDirectory(async (tmpDir) => {
|
|
125
|
+
await pullRemoteEnvironmentVariables({ root: tmpDir });
|
|
126
|
+
expect(renderMissingLink).toHaveBeenCalledOnce();
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
it("prompts the user to create a link", async () => {
|
|
130
|
+
vi.mocked(renderConfirmationPrompt).mockResolvedValue(true);
|
|
131
|
+
await inTemporaryDirectory(async (tmpDir) => {
|
|
132
|
+
await pullRemoteEnvironmentVariables({ root: tmpDir });
|
|
133
|
+
expect(renderConfirmationPrompt).toHaveBeenCalledWith({
|
|
134
|
+
message: expect.stringMatching(/Run .*npx shopify hydrogen link.*\?/)
|
|
135
|
+
});
|
|
136
|
+
expect(linkStorefront).toHaveBeenCalledWith({
|
|
137
|
+
path: tmpDir
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
describe("and the user does not create a new link", () => {
|
|
142
|
+
it("returns an empty array", async () => {
|
|
143
|
+
vi.mocked(renderConfirmationPrompt).mockResolvedValue(false);
|
|
144
|
+
await inTemporaryDirectory(async (tmpDir) => {
|
|
145
|
+
const environmentVariables = await pullRemoteEnvironmentVariables({
|
|
146
|
+
root: tmpDir
|
|
147
|
+
});
|
|
148
|
+
expect(environmentVariables).toStrictEqual([]);
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
describe("when there is no matching storefront in the shop", () => {
|
|
154
|
+
beforeEach(() => {
|
|
155
|
+
vi.mocked(adminRequest).mockResolvedValue({
|
|
156
|
+
hydrogenStorefront: null
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
it("calls renderMissingStorefront", async () => {
|
|
160
|
+
await inTemporaryDirectory(async (tmpDir) => {
|
|
161
|
+
await pullRemoteEnvironmentVariables({ root: tmpDir });
|
|
162
|
+
expect(renderMissingStorefront).toHaveBeenCalledOnce();
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
it("returns an empty array", async () => {
|
|
166
|
+
await inTemporaryDirectory(async (tmpDir) => {
|
|
167
|
+
const environmentVariables = await pullRemoteEnvironmentVariables({
|
|
168
|
+
root: tmpDir
|
|
169
|
+
});
|
|
170
|
+
expect(environmentVariables).toStrictEqual([]);
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
});
|
|
174
|
+
});
|