@shopify/oxygen-cli 1.6.0 → 1.8.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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?.onBuildStart?.();
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: config.buildOutput ? ["inherit", "pipe", "inherit"] : ["ignore", "ignore", "pipe"],
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
- const hooks = {
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", "pipe", "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, hooks })
49
+ () => buildProject({ config: testConfig, assetPath })
59
50
  ).rejects.toThrow("Build failed with error code: 1");
60
- expect(hooks.onBuildError).toBeCalled();
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
  });
@@ -49,7 +49,12 @@ async function createDeploy(options) {
49
49
  input: deploymentInitiateInput,
50
50
  logger
51
51
  });
52
- await uploadFiles({ config, targets: deployment.deploymentTargets, logger });
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
@@ -6,19 +6,17 @@ interface ClientError extends Error {
6
6
  statusCode: number;
7
7
  }
8
8
  interface DeploymentHooks {
9
- onBuildStart?: () => void;
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: string;
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",
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.6.0",
2
+ "version": "1.8.0",
3
3
  "commands": {
4
4
  "oxygen:deploy": {
5
5
  "id": "oxygen:deploy",
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.6.0",
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.9.4",
35
- "@shopify/cli-kit": "^3.47.5",
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.2",
43
+ "@types/node": "^20.4.5",
44
44
  "@types/prettier": "^2.7.3",
45
45
  "eslint": "^8.45.0",
46
- "node-fetch": "^3.3.1",
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.4",
50
+ "vite": "^4.4.7",
51
51
  "vitest": "^0.33.0"
52
52
  },
53
53
  "prettier": "@shopify/prettier-config",