@shopify/oxygen-cli 1.6.0 → 1.8.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/oxygen/deploy.js +0 -1
- package/dist/deploy/build-project.js +9 -13
- package/dist/deploy/build-project.test.js +20 -13
- package/dist/deploy/index.js +6 -1
- package/dist/deploy/types.d.ts +3 -5
- package/dist/deploy/upload-files.js +3 -0
- package/dist/deploy/upload-files.test.js +18 -7
- package/dist/utils/test-helper.js +0 -1
- package/oclif.manifest.json +1 -1
- package/package.json +6 -6
@@ -107,7 +107,6 @@ class Deploy extends Command {
|
|
107
107
|
const config = {
|
108
108
|
assetsDir: normalizePath(flags.assetsFolder),
|
109
109
|
buildCommand: flags.buildCommand,
|
110
|
-
buildOutput: true,
|
111
110
|
deploymentToken: parseToken(flags.token),
|
112
111
|
environmentTag: flags.environmentTag,
|
113
112
|
deploymentUrl,
|
@@ -2,12 +2,19 @@ import { spawn } from 'child_process';
|
|
2
2
|
|
3
3
|
async function buildProject(options) {
|
4
4
|
const { config, assetPath, hooks } = options;
|
5
|
-
hooks?.
|
5
|
+
if (hooks?.buildFunction) {
|
6
|
+
try {
|
7
|
+
await hooks.buildFunction(assetPath);
|
8
|
+
} catch (error) {
|
9
|
+
throw new Error(`Build function failed with error: ${error}`);
|
10
|
+
}
|
11
|
+
return;
|
12
|
+
}
|
6
13
|
const assetPathEnvironment = assetPath ? { HYDROGEN_ASSET_BASE_URL: assetPath } : {};
|
7
14
|
try {
|
8
15
|
await new Promise((resolve, reject) => {
|
9
16
|
const buildCommand = spawn(config.buildCommand, [], {
|
10
|
-
stdio:
|
17
|
+
stdio: ["inherit", process.stderr, "inherit"],
|
11
18
|
env: {
|
12
19
|
// eslint-disable-next-line no-process-env
|
13
20
|
...process.env,
|
@@ -16,22 +23,11 @@ async function buildProject(options) {
|
|
16
23
|
cwd: config.rootPath,
|
17
24
|
shell: true
|
18
25
|
});
|
19
|
-
let stderrOutput = "";
|
20
|
-
if (buildCommand.stderr) {
|
21
|
-
buildCommand.stderr.on("data", (data) => {
|
22
|
-
stderrOutput += data;
|
23
|
-
});
|
24
|
-
}
|
25
|
-
if (config.buildOutput && buildCommand.stdout) {
|
26
|
-
buildCommand.stdout.pipe(process.stderr);
|
27
|
-
}
|
28
26
|
buildCommand.on("close", (code) => {
|
29
27
|
if (code !== 0) {
|
30
|
-
hooks?.onBuildError?.(new Error(stderrOutput));
|
31
28
|
reject(code);
|
32
29
|
return;
|
33
30
|
}
|
34
|
-
hooks?.onBuildComplete?.();
|
35
31
|
resolve(code);
|
36
32
|
});
|
37
33
|
});
|
@@ -30,32 +30,39 @@ test("BuildProject builds the project successfully", async () => {
|
|
30
30
|
buildCommand: "npm run build"
|
31
31
|
};
|
32
32
|
const assetPath = "https://example.com/assets";
|
33
|
-
|
34
|
-
onBuildStart: vi.fn(),
|
35
|
-
onBuildComplete: vi.fn()
|
36
|
-
};
|
37
|
-
await buildProject({ config, assetPath, hooks });
|
33
|
+
await buildProject({ config, assetPath });
|
38
34
|
expect(spawn).toBeCalledWith("npm run build", [], {
|
39
35
|
cwd: "rootFolder",
|
40
36
|
shell: true,
|
41
|
-
stdio: ["inherit",
|
37
|
+
stdio: ["inherit", process.stderr, "inherit"],
|
42
38
|
env: {
|
43
39
|
// eslint-disable-next-line no-process-env
|
44
40
|
...process.env,
|
45
41
|
HYDROGEN_ASSET_BASE_URL: "https://example.com/assets"
|
46
42
|
}
|
47
43
|
});
|
48
|
-
expect(hooks.onBuildStart).toBeCalled();
|
49
|
-
expect(hooks.onBuildComplete).toBeCalled();
|
50
44
|
});
|
51
45
|
test("should throw error on build command failure", async () => {
|
52
46
|
returnCode = 1;
|
53
|
-
const hooks = {
|
54
|
-
onBuildError: vi.fn()
|
55
|
-
};
|
56
47
|
const assetPath = "https://example.com/assets";
|
57
48
|
await expect(
|
58
|
-
() => buildProject({ config: testConfig, assetPath
|
49
|
+
() => buildProject({ config: testConfig, assetPath })
|
59
50
|
).rejects.toThrow("Build failed with error code: 1");
|
60
|
-
|
51
|
+
});
|
52
|
+
test("should call buildFunction hook if provided", async () => {
|
53
|
+
const buildFunction = vi.fn().mockResolvedValue(void 0);
|
54
|
+
const hooks = { buildFunction };
|
55
|
+
const assetPath = "https://example.com/assets";
|
56
|
+
await buildProject({ config: testConfig, assetPath, hooks });
|
57
|
+
expect(buildFunction).toBeCalledWith(assetPath);
|
58
|
+
});
|
59
|
+
test("should throw error if buildFunction hook fails", async () => {
|
60
|
+
const buildFunction = vi.fn().mockRejectedValue(new Error("Oops! I tripped over a cable."));
|
61
|
+
const hooks = { buildFunction };
|
62
|
+
const assetPath = "https://example.com/assets";
|
63
|
+
await expect(
|
64
|
+
() => buildProject({ config: testConfig, assetPath, hooks })
|
65
|
+
).rejects.toThrow(
|
66
|
+
"Build function failed with error: Error: Oops! I tripped over a cable."
|
67
|
+
);
|
61
68
|
});
|
package/dist/deploy/index.js
CHANGED
@@ -49,7 +49,12 @@ async function createDeploy(options) {
|
|
49
49
|
input: deploymentInitiateInput,
|
50
50
|
logger
|
51
51
|
});
|
52
|
-
await uploadFiles({
|
52
|
+
await uploadFiles({
|
53
|
+
config,
|
54
|
+
targets: deployment.deploymentTargets,
|
55
|
+
hooks,
|
56
|
+
logger
|
57
|
+
});
|
53
58
|
const deploymentCompleteOp = await deploymentComplete(
|
54
59
|
config,
|
55
60
|
deployment.deployment.id
|
package/dist/deploy/types.d.ts
CHANGED
@@ -6,19 +6,17 @@ interface ClientError extends Error {
|
|
6
6
|
statusCode: number;
|
7
7
|
}
|
8
8
|
interface DeploymentHooks {
|
9
|
-
|
10
|
-
onBuildComplete?: () => void;
|
11
|
-
onBuildError?: (error: Error) => void;
|
9
|
+
buildFunction?: (urlPath?: string) => Promise<void>;
|
12
10
|
onHealthCheckStart?: () => void;
|
13
11
|
onHealthCheckComplete?: () => void;
|
14
12
|
onHealthCheckError?: (error: Error) => void;
|
15
13
|
onUploadFilesStart?: () => void;
|
14
|
+
onUploadFilesError?: (error: Error) => void;
|
16
15
|
onUploadFilesComplete?: () => void;
|
17
16
|
}
|
18
17
|
interface DeploymentConfig {
|
19
18
|
assetsDir?: string;
|
20
|
-
buildCommand
|
21
|
-
buildOutput: boolean;
|
19
|
+
buildCommand?: string;
|
22
20
|
deploymentToken: DeploymentToken;
|
23
21
|
deploymentUrl: string;
|
24
22
|
environmentTag?: string;
|
@@ -14,6 +14,9 @@ async function uploadFiles(options) {
|
|
14
14
|
}).then(() => {
|
15
15
|
hooks?.onUploadFilesComplete?.();
|
16
16
|
outputCompleted(`Files uploaded successfully`, logger);
|
17
|
+
}).catch((err) => {
|
18
|
+
hooks?.onUploadFilesError?.(err);
|
19
|
+
throw err;
|
17
20
|
});
|
18
21
|
}
|
19
22
|
async function uploadFile(config, target) {
|
@@ -30,15 +30,17 @@ vi.mock("@shopify/cli-kit/node/fs", () => {
|
|
30
30
|
};
|
31
31
|
});
|
32
32
|
const testConfig = createTestConfig("/tmp/deploymentRoot");
|
33
|
+
let hooks;
|
33
34
|
describe("UploadFiles", () => {
|
34
35
|
beforeEach(() => {
|
35
36
|
vi.mocked(fetch).mockReset();
|
37
|
+
hooks = {
|
38
|
+
onUploadFilesComplete: vi.fn(),
|
39
|
+
onUploadFilesError: vi.fn(),
|
40
|
+
onUploadFilesStart: vi.fn()
|
41
|
+
};
|
36
42
|
});
|
37
43
|
it("Performs a form upload", async () => {
|
38
|
-
const hooks = {
|
39
|
-
onUploadFilesStart: vi.fn(),
|
40
|
-
onUploadFilesComplete: vi.fn()
|
41
|
-
};
|
42
44
|
const response = new Response();
|
43
45
|
vi.mocked(fetch).mockResolvedValueOnce(response);
|
44
46
|
const testWorkerUpload = [
|
@@ -90,12 +92,15 @@ describe("UploadFiles", () => {
|
|
90
92
|
uploadFiles({
|
91
93
|
config: testConfig,
|
92
94
|
targets: testWorkerUpload,
|
93
|
-
logger: stderrLogger
|
95
|
+
logger: stderrLogger,
|
96
|
+
hooks
|
94
97
|
})
|
95
98
|
).rejects.toThrow("Failed to upload file index.js");
|
96
99
|
expect(vi.mocked(fetch)).toHaveBeenCalledTimes(
|
97
100
|
Number(deployDefaults.maxUploadAttempts) + 1
|
98
101
|
);
|
102
|
+
expect(hooks.onUploadFilesError).toBeCalled();
|
103
|
+
expect(hooks.onUploadFilesComplete).not.toBeCalled();
|
99
104
|
});
|
100
105
|
it("Performs a resumable upload", async () => {
|
101
106
|
const headers = {
|
@@ -116,7 +121,8 @@ describe("UploadFiles", () => {
|
|
116
121
|
await uploadFiles({
|
117
122
|
config: testConfig,
|
118
123
|
targets: testWorkerUpload,
|
119
|
-
logger: stderrLogger
|
124
|
+
logger: stderrLogger,
|
125
|
+
hooks
|
120
126
|
});
|
121
127
|
expect(vi.mocked(fetch)).toHaveBeenCalledTimes(2);
|
122
128
|
const secondCall = vi.mocked(fetch).mock.calls[1];
|
@@ -125,6 +131,8 @@ describe("UploadFiles", () => {
|
|
125
131
|
return req.method === "PUT" && req.body instanceof NamedReadable;
|
126
132
|
};
|
127
133
|
expect(validateRequestBody(secondCall[1])).toBe(true);
|
134
|
+
expect(hooks.onUploadFilesStart).toBeCalled();
|
135
|
+
expect(hooks.onUploadFilesComplete).toBeCalled();
|
128
136
|
});
|
129
137
|
it("Resumes a resumable upload", async () => {
|
130
138
|
console.error = () => {
|
@@ -200,7 +208,8 @@ describe("UploadFiles", () => {
|
|
200
208
|
uploadFiles({
|
201
209
|
config: testConfig,
|
202
210
|
targets: testWorkerUpload,
|
203
|
-
logger: stderrLogger
|
211
|
+
logger: stderrLogger,
|
212
|
+
hooks
|
204
213
|
})
|
205
214
|
).rejects.toThrow(
|
206
215
|
`Failed to upload file index.js after ${deployDefaults.maxResumabeUploadAttempts} attempts`
|
@@ -209,5 +218,7 @@ describe("UploadFiles", () => {
|
|
209
218
|
// for each attempt, we make two calls to fetch, plus the initial attempt makes two calls
|
210
219
|
Number(deployDefaults.maxResumabeUploadAttempts) * 2 + 2
|
211
220
|
);
|
221
|
+
expect(hooks.onUploadFilesError).toBeCalled();
|
222
|
+
expect(hooks.onUploadFilesComplete).not.toBeCalled();
|
212
223
|
});
|
213
224
|
});
|
@@ -13,7 +13,6 @@ function createTestConfig(rootFolder) {
|
|
13
13
|
return {
|
14
14
|
assetsDir: "/assets/",
|
15
15
|
buildCommand: String(deployDefaults.buildCommandDefault),
|
16
|
-
buildOutput: true,
|
17
16
|
deploymentToken: testToken,
|
18
17
|
environmentTag: "environment",
|
19
18
|
deploymentUrl: "https://localhost:3000",
|
package/oclif.manifest.json
CHANGED
package/package.json
CHANGED
@@ -5,7 +5,7 @@
|
|
5
5
|
"@shopify:registry": "https://registry.npmjs.org"
|
6
6
|
},
|
7
7
|
"license": "MIT",
|
8
|
-
"version": "1.
|
8
|
+
"version": "1.8.0",
|
9
9
|
"type": "module",
|
10
10
|
"scripts": {
|
11
11
|
"build": "tsup --clean --config ./tsup.config.ts && oclif manifest",
|
@@ -31,8 +31,8 @@
|
|
31
31
|
"/oclif.manifest.json"
|
32
32
|
],
|
33
33
|
"dependencies": {
|
34
|
-
"@oclif/core": "2.
|
35
|
-
"@shopify/cli-kit": "^3.
|
34
|
+
"@oclif/core": "2.10.0",
|
35
|
+
"@shopify/cli-kit": "^3.48.0",
|
36
36
|
"async": "^3.2.4"
|
37
37
|
},
|
38
38
|
"devDependencies": {
|
@@ -40,14 +40,14 @@
|
|
40
40
|
"@shopify/eslint-plugin": "^42.1.0",
|
41
41
|
"@shopify/prettier-config": "^1.1.2",
|
42
42
|
"@types/async": "^3.2.18",
|
43
|
-
"@types/node": "^20.4.
|
43
|
+
"@types/node": "^20.4.5",
|
44
44
|
"@types/prettier": "^2.7.3",
|
45
45
|
"eslint": "^8.45.0",
|
46
|
-
"node-fetch": "^3.3.
|
46
|
+
"node-fetch": "^3.3.2",
|
47
47
|
"oclif": "^3",
|
48
48
|
"tsup": "^7.1.0",
|
49
49
|
"typescript": "^5.1.3",
|
50
|
-
"vite": "^4.4.
|
50
|
+
"vite": "^4.4.7",
|
51
51
|
"vitest": "^0.33.0"
|
52
52
|
},
|
53
53
|
"prettier": "@shopify/prettier-config",
|