@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.
@@ -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",