@shopify/oxygen-cli 1.4.0 → 1.6.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.
Files changed (40) hide show
  1. package/README.md +2 -0
  2. package/dist/commands/oxygen/deploy.d.ts +9 -9
  3. package/dist/commands/oxygen/deploy.js +58 -43
  4. package/dist/deploy/build-cancel.d.ts +9 -2
  5. package/dist/deploy/build-cancel.js +4 -3
  6. package/dist/deploy/build-cancel.test.js +21 -4
  7. package/dist/deploy/build-initiate.d.ts +9 -2
  8. package/dist/deploy/build-initiate.js +4 -3
  9. package/dist/deploy/build-initiate.test.js +19 -8
  10. package/dist/deploy/build-project.d.ts +7 -2
  11. package/dist/deploy/build-project.js +34 -19
  12. package/dist/deploy/build-project.test.js +14 -8
  13. package/dist/deploy/deployment-cancel.d.ts +9 -2
  14. package/dist/deploy/deployment-cancel.js +4 -3
  15. package/dist/deploy/deployment-cancel.test.js +19 -16
  16. package/dist/deploy/deployment-complete.d.ts +2 -2
  17. package/dist/deploy/deployment-initiate.d.ts +10 -4
  18. package/dist/deploy/deployment-initiate.js +4 -3
  19. package/dist/deploy/deployment-initiate.test.js +26 -10
  20. package/dist/deploy/get-upload-files.d.ts +2 -2
  21. package/dist/deploy/health-check.d.ts +12 -0
  22. package/dist/deploy/health-check.js +44 -0
  23. package/dist/deploy/health-check.test.d.ts +2 -0
  24. package/dist/deploy/health-check.test.js +92 -0
  25. package/dist/deploy/index.d.ts +10 -3
  26. package/dist/deploy/index.js +54 -26
  27. package/dist/deploy/metadata.d.ts +4 -3
  28. package/dist/deploy/metadata.js +3 -3
  29. package/dist/deploy/metadata.test.js +4 -4
  30. package/dist/deploy/types.d.ts +17 -2
  31. package/dist/deploy/types.js +3 -1
  32. package/dist/deploy/upload-files.d.ts +9 -2
  33. package/dist/deploy/upload-files.js +7 -4
  34. package/dist/deploy/upload-files.test.js +37 -18
  35. package/dist/utils/test-helper.d.ts +2 -2
  36. package/dist/utils/test-helper.js +3 -0
  37. package/dist/utils/utils.d.ts +3 -3
  38. package/dist/utils/utils.js +1 -5
  39. package/oclif.manifest.json +50 -33
  40. package/package.json +8 -8
@@ -1,10 +1,11 @@
1
1
  import { graphqlRequest } from '@shopify/cli-kit/node/api/graphql';
2
2
  import { AbortError } from '@shopify/cli-kit/node/error';
3
3
  import { outputCompleted } from '@shopify/cli-kit/node/output';
4
- import { Header, stderrLogger, errorHandler } from '../utils/utils.js';
4
+ import { Header, errorHandler } from '../utils/utils.js';
5
5
  import { DeploymentInitiateQuery } from './graphql/deployment-initiate.js';
6
6
 
7
- async function deploymentInitiate(config, input) {
7
+ async function deploymentInitiate(options) {
8
+ const { config, input, logger } = options;
8
9
  const variables = {
9
10
  buildId: input.buildId,
10
11
  environment: input.environment,
@@ -30,7 +31,7 @@ async function deploymentInitiate(config, input) {
30
31
  }
31
32
  outputCompleted(
32
33
  `Deployment initiated, ${response.deploymentInitiate.deploymentTargets.length} files to upload.`,
33
- stderrLogger
34
+ logger
34
35
  );
35
36
  return response.deploymentInitiate;
36
37
  } catch (error) {
@@ -3,7 +3,7 @@ import { graphqlRequest } from '@shopify/cli-kit/node/api/graphql';
3
3
  import { outputCompleted } from '@shopify/cli-kit/node/output';
4
4
  import { vi, describe, test, expect } from 'vitest';
5
5
  import { createTestConfig } from '../utils/test-helper.js';
6
- import { Header, stderrLogger } from '../utils/utils.js';
6
+ import { stderrLogger, Header } from '../utils/utils.js';
7
7
  import { deploymentInitiate } from './deployment-initiate.js';
8
8
 
9
9
  vi.mock("@shopify/cli-kit/node/api/graphql");
@@ -38,9 +38,13 @@ const testResponse = {
38
38
  describe("DeploymentInitiate", () => {
39
39
  test("should initiate a deployment with a buildId", async () => {
40
40
  vi.mocked(graphqlRequest).mockResolvedValueOnce(testResponse);
41
- const initiateResponse = await deploymentInitiate(testConfig, {
42
- buildId: "build-1",
43
- manifest: testManifest
41
+ const initiateResponse = await deploymentInitiate({
42
+ config: testConfig,
43
+ input: {
44
+ buildId: "build-1",
45
+ manifest: testManifest
46
+ },
47
+ logger: stderrLogger
44
48
  });
45
49
  expect(initiateResponse).toEqual(testResponse.deploymentInitiate);
46
50
  expect(graphqlRequest).toHaveBeenCalledWith({
@@ -65,10 +69,14 @@ describe("DeploymentInitiate", () => {
65
69
  });
66
70
  test("should initiate a deployment with an environmentName", async () => {
67
71
  vi.mocked(graphqlRequest).mockResolvedValueOnce(testResponse);
68
- const initiateResponse = await deploymentInitiate(testConfig, {
69
- buildId: void 0,
70
- environment: { tag: "preview" },
71
- manifest: testManifest
72
+ const initiateResponse = await deploymentInitiate({
73
+ config: testConfig,
74
+ input: {
75
+ buildId: void 0,
76
+ environment: { tag: "preview" },
77
+ manifest: testManifest
78
+ },
79
+ logger: stderrLogger
72
80
  });
73
81
  expect(initiateResponse).toEqual(testResponse.deploymentInitiate);
74
82
  expect(graphqlRequest).toHaveBeenCalledWith({
@@ -108,7 +116,11 @@ describe("DeploymentInitiate", () => {
108
116
  manifest: testManifest
109
117
  };
110
118
  await expect(
111
- deploymentInitiate(testConfig, deploymentInitData)
119
+ deploymentInitiate({
120
+ config: testConfig,
121
+ input: deploymentInitData,
122
+ logger: stderrLogger
123
+ })
112
124
  ).rejects.toThrow(
113
125
  new AbortError(
114
126
  `Failed to create deployment. ${response.deploymentInitiate.userErrors[0]?.message}`
@@ -127,7 +139,11 @@ describe("DeploymentInitiate", () => {
127
139
  manifest: testManifest
128
140
  };
129
141
  await expect(
130
- deploymentInitiate(testConfig, deploymentInitData)
142
+ deploymentInitiate({
143
+ config: testConfig,
144
+ input: deploymentInitData,
145
+ logger: stderrLogger
146
+ })
131
147
  ).rejects.toThrow(
132
148
  new AbortError(
133
149
  "You are not authorized to perform this action. Please check your deployment token."
@@ -1,5 +1,5 @@
1
- import { DeployConfig, DeploymentManifestFile } from './types.js';
1
+ import { DeploymentConfig, DeploymentManifestFile } from './types.js';
2
2
 
3
- declare function getUploadFiles(config: DeployConfig): Promise<DeploymentManifestFile[]>;
3
+ declare function getUploadFiles(config: DeploymentConfig): Promise<DeploymentManifestFile[]>;
4
4
 
5
5
  export { getUploadFiles };
@@ -0,0 +1,12 @@
1
+ import { Logger } from '@shopify/cli-kit/node/output';
2
+ import { DeploymentConfig, DeploymentHooks } from './types.js';
3
+
4
+ interface HealthCheckOptions {
5
+ config: DeploymentConfig;
6
+ hooks?: DeploymentHooks;
7
+ logger: Logger;
8
+ url: string;
9
+ }
10
+ declare function healthCheck(options: HealthCheckOptions): Promise<void>;
11
+
12
+ export { healthCheck };
@@ -0,0 +1,44 @@
1
+ import { fetch } from '@shopify/cli-kit/node/http';
2
+ import { outputInfo } from '@shopify/cli-kit/node/output';
3
+ import { HealthCheckError } from './types.js';
4
+
5
+ async function healthCheck(options) {
6
+ const { config, url, logger, hooks } = options;
7
+ hooks?.onHealthCheckStart?.();
8
+ outputInfo("Performing health check on the deployment...", logger);
9
+ let attempts = 0;
10
+ let delay = 0;
11
+ const startTime = Date.now();
12
+ const handleInterval = async () => {
13
+ if (attempts < 10) {
14
+ delay = 500;
15
+ } else if (attempts % 5 === 0) {
16
+ delay += 5e3;
17
+ }
18
+ const elapsedTime = (Date.now() - startTime) / 1e3;
19
+ if (elapsedTime + delay / 1e3 > config.healthCheckMaxDuration) {
20
+ const error = new HealthCheckError("Unable to verify deployment health.");
21
+ hooks?.onHealthCheckError?.(error);
22
+ throw error;
23
+ }
24
+ attempts++;
25
+ await new Promise((resolve) => setTimeout(resolve, delay));
26
+ await check();
27
+ };
28
+ const check = async () => {
29
+ try {
30
+ const response = await fetch(`${url}/.oxygen/deployment`);
31
+ if (response.status === 200) {
32
+ outputInfo("Deployment health check passed", logger);
33
+ hooks?.onHealthCheckComplete?.();
34
+ return Promise.resolve();
35
+ }
36
+ await handleInterval();
37
+ } catch {
38
+ await handleInterval();
39
+ }
40
+ };
41
+ await check();
42
+ }
43
+
44
+ export { healthCheck };
@@ -0,0 +1,2 @@
1
+
2
+ export { }
@@ -0,0 +1,92 @@
1
+ import { vi, describe, beforeEach, it, expect } from 'vitest';
2
+ import { fetch } from '@shopify/cli-kit/node/http';
3
+ import { Response } from 'node-fetch';
4
+ import { stderrLogger } from '../utils/utils.js';
5
+ import { createTestConfig } from '../utils/test-helper.js';
6
+ import { HealthCheckError } from './types.js';
7
+ import { healthCheck } from './health-check.js';
8
+
9
+ vi.mock("@shopify/cli-kit/node/http", async () => {
10
+ const actual = await vi.importActual("@shopify/cli-kit/node/http");
11
+ return {
12
+ ...actual,
13
+ fetch: vi.fn()
14
+ };
15
+ });
16
+ vi.mock("@shopify/cli-kit/node/output");
17
+ const testConfig = createTestConfig("rootFolder");
18
+ describe("healthCheck", () => {
19
+ let setTimeoutSpy;
20
+ const hooks = {
21
+ onHealthCheckError: vi.fn(),
22
+ onHealthCheckStart: vi.fn(),
23
+ onHealthCheckComplete: vi.fn()
24
+ };
25
+ const url = "http://example.com";
26
+ beforeEach(() => {
27
+ vi.mocked(fetch).mockReset();
28
+ setTimeoutSpy = vi.spyOn(global, "setTimeout").mockImplementation((cb, _delay) => {
29
+ cb();
30
+ return {};
31
+ });
32
+ });
33
+ it("resolves when URL is accessible", async () => {
34
+ const response = new Response();
35
+ vi.mocked(fetch).mockResolvedValueOnce(response);
36
+ await healthCheck({ config: testConfig, url, logger: stderrLogger, hooks });
37
+ expect(fetch).toHaveBeenCalledTimes(1);
38
+ expect(fetch).toHaveBeenCalledWith(`${url}/.oxygen/deployment`);
39
+ expect(hooks.onHealthCheckStart).toBeCalled();
40
+ expect(hooks.onHealthCheckComplete).toBeCalled();
41
+ });
42
+ it("repeats request until URL is accessible with progressive timeout", async () => {
43
+ const responseFail = new Response(null, { status: 404 });
44
+ const responseSuccess = new Response(null, { status: 200 });
45
+ let attempts = 0;
46
+ vi.mocked(fetch).mockImplementation(() => {
47
+ if (attempts === 30) {
48
+ return Promise.resolve(responseSuccess);
49
+ }
50
+ attempts++;
51
+ return Promise.resolve(responseFail);
52
+ });
53
+ await new Promise((resolve, reject) => {
54
+ healthCheck({ config: testConfig, url, logger: stderrLogger, hooks }).then(() => {
55
+ expect(fetch).toHaveBeenCalledTimes(attempts + 1);
56
+ expect(fetch).toHaveBeenCalledWith(`${url}/.oxygen/deployment`);
57
+ expect(setTimeoutSpy).toHaveBeenCalledTimes(attempts);
58
+ expect(hooks.onHealthCheckStart).toBeCalled();
59
+ expect(hooks.onHealthCheckComplete).toBeCalled();
60
+ let expectedDuration = 500;
61
+ setTimeoutSpy.mock.calls.forEach(
62
+ (call, index) => {
63
+ if (index >= 10 && index % 5 === 0) {
64
+ expectedDuration += 5e3;
65
+ }
66
+ expect(call[1]).toBe(expectedDuration);
67
+ }
68
+ );
69
+ resolve();
70
+ }).catch((error) => {
71
+ reject(error);
72
+ });
73
+ });
74
+ });
75
+ it("throws an error after max duration", async () => {
76
+ vi.useFakeTimers();
77
+ const responseFail = new Response(null, { status: 404 });
78
+ vi.mocked(fetch).mockResolvedValue(responseFail);
79
+ const healthCheckPromise = healthCheck({
80
+ config: testConfig,
81
+ url,
82
+ logger: stderrLogger,
83
+ hooks
84
+ });
85
+ vi.setSystemTime(Date.now() + testConfig.healthCheckMaxDuration * 1e3);
86
+ await expect(healthCheckPromise).rejects.toThrow(HealthCheckError);
87
+ expect(fetch).toHaveBeenCalledOnce();
88
+ expect(hooks.onHealthCheckStart).toBeCalled();
89
+ expect(hooks.onHealthCheckError).toBeCalled();
90
+ vi.useRealTimers();
91
+ });
92
+ });
@@ -1,5 +1,12 @@
1
- import { DeployConfig } from './types.js';
1
+ import { Logger } from '@shopify/cli-kit/node/output';
2
+ import { DeploymentConfig, DeploymentHooks } from './types.js';
3
+ export { parseToken } from '../utils/utils.js';
2
4
 
3
- declare function createDeploy(config: DeployConfig): Promise<void>;
5
+ interface CreateDeployOptions {
6
+ config: DeploymentConfig;
7
+ hooks?: DeploymentHooks;
8
+ logger?: Logger;
9
+ }
10
+ declare function createDeploy(options: CreateDeployOptions): Promise<string | undefined>;
4
11
 
5
- export { createDeploy };
12
+ export { DeploymentConfig, DeploymentHooks, createDeploy };
@@ -1,88 +1,116 @@
1
- import { outputSuccess, outputInfo, outputWarn, consoleError } from '@shopify/cli-kit/node/output';
2
- import { verifyConfig, stderrLogger } from '../utils/utils.js';
1
+ import { outputSuccess, outputInfo, outputWarn } from '@shopify/cli-kit/node/output';
2
+ import { stderrLogger, verifyConfig } from '../utils/utils.js';
3
+ export { parseToken } from '../utils/utils.js';
3
4
  import { buildInitiate } from './build-initiate.js';
4
5
  import { buildCancel } from './build-cancel.js';
5
6
  import { getUploadFiles } from './get-upload-files.js';
6
7
  import { deploymentInitiate } from './deployment-initiate.js';
7
8
  import { deploymentComplete } from './deployment-complete.js';
9
+ import { healthCheck } from './health-check.js';
8
10
  import { deploymentCancel, DeploymentCancelReason } from './deployment-cancel.js';
9
11
  import { uploadFiles } from './upload-files.js';
12
+ import { HealthCheckError } from './types.js';
10
13
  import { buildProject } from './build-project.js';
11
14
  import { getMetadata, createLabels, getEnvironmentInput } from './metadata.js';
12
15
 
13
- async function createDeploy(config) {
16
+ async function createDeploy(options) {
17
+ const { config, hooks } = options;
18
+ const logger = options.logger ?? stderrLogger;
14
19
  const build = {};
15
20
  let buildCompleted;
16
21
  let deployment;
17
22
  try {
18
- const metadata = await getMetadata(config);
23
+ const metadata = await getMetadata(config, logger);
19
24
  const labels = createLabels(metadata);
20
25
  const environment = getEnvironmentInput(config, metadata);
21
26
  if (!config.workerOnly && !config.skipBuild) {
22
- const buildInitiateResponse = await buildInitiate(
27
+ const buildInitiateResponse = await buildInitiate({
23
28
  config,
24
29
  environment,
25
- labels
26
- );
30
+ labels,
31
+ logger
32
+ });
27
33
  build.id = buildInitiateResponse.build.id;
28
34
  build.assetPath = buildInitiateResponse.build.assetPath;
29
35
  }
30
36
  if (!config.skipBuild) {
31
- await buildProject(config, build.assetPath);
37
+ await buildProject({
38
+ config,
39
+ assetPath: build.assetPath,
40
+ hooks
41
+ });
32
42
  verifyConfig({ config, performedBuild: true });
33
43
  }
34
44
  buildCompleted = true;
35
45
  const manifest = await getUploadFiles(config);
36
46
  const deploymentInitiateInput = build.id ? { buildId: build.id, manifest } : { environment, manifest, labels };
37
- deployment = await deploymentInitiate(config, deploymentInitiateInput);
38
- await uploadFiles(config, deployment.deploymentTargets);
47
+ deployment = await deploymentInitiate({
48
+ config,
49
+ input: deploymentInitiateInput,
50
+ logger
51
+ });
52
+ await uploadFiles({ config, targets: deployment.deploymentTargets, logger });
39
53
  const deploymentCompleteOp = await deploymentComplete(
40
54
  config,
41
55
  deployment.deployment.id
42
56
  );
57
+ if (!config.skipHealthCheck) {
58
+ await healthCheck({
59
+ config,
60
+ url: deploymentCompleteOp.deployment.url,
61
+ logger,
62
+ hooks
63
+ });
64
+ }
43
65
  const urlMessage = config.publicDeployment ? "Public" : "Private";
44
66
  outputSuccess(
45
67
  `Deployment complete.
46
68
  ${urlMessage} preview URL: ${deploymentCompleteOp.deployment.url}`,
47
- stderrLogger
69
+ logger
48
70
  );
49
71
  if (metadata.name !== "none") {
50
72
  outputInfo(deploymentCompleteOp.deployment.url);
51
73
  }
74
+ return deploymentCompleteOp.deployment.url;
52
75
  } catch (error) {
53
76
  if (!(error instanceof Error)) {
54
77
  console.error("Unknown error", error);
55
- return;
78
+ return Promise.reject(new Error("Unknown error"));
56
79
  }
57
- if (build.id && !buildCompleted) {
80
+ if (error instanceof HealthCheckError) {
81
+ outputWarn(error.message, logger);
82
+ } else if (build.id && !buildCompleted) {
58
83
  outputWarn(
59
84
  `Build failed with: ${error.message}, cancelling build.`,
60
- stderrLogger
85
+ logger
61
86
  );
62
- await buildCancel(config, build.id, error.message).catch((err) => {
87
+ await buildCancel({
88
+ config,
89
+ buildId: build.id,
90
+ reason: error.message,
91
+ logger
92
+ }).catch((err) => {
63
93
  if (err instanceof Error) {
64
- outputWarn(`Failed to cancel build: ${err.message}`, stderrLogger);
94
+ outputWarn(`Failed to cancel build: ${err.message}`, logger);
65
95
  }
66
96
  });
67
97
  } else if (deployment?.deployment.id) {
68
98
  outputWarn(
69
99
  `Deployment failed with: ${error.message}, cancelling deployment.`,
70
- stderrLogger
100
+ logger
71
101
  );
72
- await deploymentCancel(
102
+ await deploymentCancel({
73
103
  config,
74
- deployment.deployment.id,
75
- DeploymentCancelReason.Failed
76
- ).catch((err) => {
104
+ deploymentId: deployment.deployment.id,
105
+ reason: DeploymentCancelReason.Failed,
106
+ logger
107
+ }).catch((err) => {
77
108
  if (err instanceof Error) {
78
- outputWarn(
79
- `Failed to cancel deployment: ${err.message}`,
80
- stderrLogger
81
- );
109
+ outputWarn(`Failed to cancel deployment: ${err.message}`, logger);
82
110
  }
83
111
  });
84
112
  }
85
- consoleError(error.message);
113
+ return Promise.reject(error);
86
114
  }
87
115
  }
88
116
 
@@ -1,11 +1,12 @@
1
1
  import { CIMetadata } from '@shopify/cli-kit/node/context/local';
2
- import { DeployConfig, EnvironmentInput } from './types.js';
2
+ import { Logger } from '@shopify/cli-kit/node/output';
3
+ import { DeploymentConfig, EnvironmentInput } from './types.js';
3
4
 
4
5
  type Metadata = CIMetadata & {
5
6
  name: string;
6
7
  };
7
- declare function getMetadata(config: DeployConfig): Promise<Metadata>;
8
- declare function getEnvironmentInput(config: DeployConfig, metadata: CIMetadata): EnvironmentInput | undefined;
8
+ declare function getMetadata(config: DeploymentConfig, logger: Logger): Promise<Metadata>;
9
+ declare function getEnvironmentInput(config: DeploymentConfig, metadata: CIMetadata): EnvironmentInput | undefined;
9
10
  declare function createLabels(metadata: Metadata): string[];
10
11
 
11
12
  export { createLabels, getEnvironmentInput, getMetadata };
@@ -1,9 +1,9 @@
1
1
  import { ciPlatform } from '@shopify/cli-kit/node/context/local';
2
2
  import { getLatestGitCommit } from '@shopify/cli-kit/node/git';
3
3
  import { outputWarn } from '@shopify/cli-kit/node/output';
4
- import { stderrLogger, maxLabelLength } from '../utils/utils.js';
4
+ import { maxLabelLength } from '../utils/utils.js';
5
5
 
6
- async function getMetadata(config) {
6
+ async function getMetadata(config, logger) {
7
7
  const ciInfo = ciPlatform();
8
8
  let metadata = {};
9
9
  if (ciInfo.isCI && ciInfo.name !== "unknown") {
@@ -19,7 +19,7 @@ async function getMetadata(config) {
19
19
  commitMessage: gitCommit.message
20
20
  };
21
21
  } catch (error) {
22
- outputWarn("No CI metadata loaded from environment", stderrLogger);
22
+ outputWarn("No CI metadata loaded from environment", logger);
23
23
  }
24
24
  }
25
25
  return {
@@ -1,7 +1,7 @@
1
1
  import { getLatestGitCommit } from '@shopify/cli-kit/node/git';
2
2
  import { ciPlatform } from '@shopify/cli-kit/node/context/local';
3
3
  import { vi, describe, test, expect } from 'vitest';
4
- import { maxLabelLength } from '../utils/utils.js';
4
+ import { stderrLogger, maxLabelLength } from '../utils/utils.js';
5
5
  import { createTestConfig } from '../utils/test-helper.js';
6
6
  import { getMetadata, getEnvironmentInput, createLabels } from './metadata.js';
7
7
 
@@ -19,7 +19,7 @@ describe("getMetadata", () => {
19
19
  }
20
20
  });
21
21
  const testConfig = createTestConfig("/tmp/deploymentRoot/");
22
- const metadataResult = await getMetadata(testConfig);
22
+ const metadataResult = await getMetadata(testConfig, stderrLogger);
23
23
  expect(metadataResult.actor).toBe("circle_actor");
24
24
  expect(metadataResult.commitSha).toBe("circle_sha");
25
25
  expect(metadataResult.name).toBe("circle");
@@ -41,7 +41,7 @@ describe("getMetadata", () => {
41
41
  version: "custom_version",
42
42
  url: "custom_url"
43
43
  };
44
- const metadataResult = await getMetadata(testConfig);
44
+ const metadataResult = await getMetadata(testConfig, stderrLogger);
45
45
  expect(metadataResult.actor).toBe("custom_user");
46
46
  expect(metadataResult.commitSha).toBe("custom_version");
47
47
  expect(metadataResult.name).toBe("circle");
@@ -61,7 +61,7 @@ describe("getMetadata", () => {
61
61
  body: "gh_body"
62
62
  });
63
63
  const testConfig = createTestConfig("/tmp/deploymentRoot/");
64
- const metadataResult = await getMetadata(testConfig);
64
+ const metadataResult = await getMetadata(testConfig, stderrLogger);
65
65
  expect(metadataResult.actor).toBe("gh_author");
66
66
  expect(metadataResult.commitSha).toBe("gh_hash");
67
67
  expect(metadataResult.name).toBe("none");
@@ -5,12 +5,24 @@ interface Build {
5
5
  interface ClientError extends Error {
6
6
  statusCode: number;
7
7
  }
8
- interface DeployConfig {
8
+ interface DeploymentHooks {
9
+ onBuildStart?: () => void;
10
+ onBuildComplete?: () => void;
11
+ onBuildError?: (error: Error) => void;
12
+ onHealthCheckStart?: () => void;
13
+ onHealthCheckComplete?: () => void;
14
+ onHealthCheckError?: (error: Error) => void;
15
+ onUploadFilesStart?: () => void;
16
+ onUploadFilesComplete?: () => void;
17
+ }
18
+ interface DeploymentConfig {
9
19
  assetsDir?: string;
10
20
  buildCommand: string;
21
+ buildOutput: boolean;
11
22
  deploymentToken: DeploymentToken;
12
23
  deploymentUrl: string;
13
24
  environmentTag?: string;
25
+ healthCheckMaxDuration: number;
14
26
  metadata: {
15
27
  user?: string;
16
28
  version?: string;
@@ -19,6 +31,7 @@ interface DeployConfig {
19
31
  publicDeployment: boolean;
20
32
  rootPath?: string;
21
33
  skipBuild: boolean;
34
+ skipHealthCheck: boolean;
22
35
  workerDir?: string;
23
36
  workerOnly: boolean;
24
37
  }
@@ -49,5 +62,7 @@ declare enum FileType {
49
62
  interface OxygenError {
50
63
  message: string;
51
64
  }
65
+ declare class HealthCheckError extends Error {
66
+ }
52
67
 
53
- export { Build, ClientError, DeployConfig, DeploymentManifestFile, DeploymentToken, EnvironmentInput, FileType, OxygenError };
68
+ export { Build, ClientError, DeploymentConfig, DeploymentHooks, DeploymentManifestFile, DeploymentToken, EnvironmentInput, FileType, HealthCheckError, OxygenError };
@@ -3,5 +3,7 @@ var FileType = /* @__PURE__ */ ((FileType2) => {
3
3
  FileType2["Asset"] = "ASSET";
4
4
  return FileType2;
5
5
  })(FileType || {});
6
+ class HealthCheckError extends Error {
7
+ }
6
8
 
7
- export { FileType };
9
+ export { FileType, HealthCheckError };
@@ -1,6 +1,13 @@
1
- import { DeployConfig } from './types.js';
1
+ import { Logger } from '@shopify/cli-kit/node/output';
2
+ import { DeploymentConfig, DeploymentHooks } from './types.js';
2
3
  import { DeploymentTargetResponse } from './graphql/deployment-initiate.js';
3
4
 
4
- declare function uploadFiles(config: DeployConfig, targets: DeploymentTargetResponse[]): Promise<void>;
5
+ interface UploadFilesOptions {
6
+ config: DeploymentConfig;
7
+ hooks?: DeploymentHooks;
8
+ logger: Logger;
9
+ targets: DeploymentTargetResponse[];
10
+ }
11
+ declare function uploadFiles(options: UploadFilesOptions): Promise<void>;
5
12
 
6
13
  export { uploadFiles };
@@ -3,14 +3,17 @@ import { createFileReadStream } from '@shopify/cli-kit/node/fs';
3
3
  import { outputInfo, outputCompleted } from '@shopify/cli-kit/node/output';
4
4
  import { joinPath } from '@shopify/cli-kit/node/path';
5
5
  import { mapLimit } from 'async';
6
- import { stderrLogger, deployDefaults } from '../utils/utils.js';
6
+ import { deployDefaults } from '../utils/utils.js';
7
7
 
8
- async function uploadFiles(config, targets) {
9
- outputInfo(`Uploading ${targets.length} files...`, stderrLogger);
8
+ async function uploadFiles(options) {
9
+ const { config, logger, targets, hooks } = options;
10
+ outputInfo(`Uploading ${targets.length} files...`, logger);
11
+ hooks?.onUploadFilesStart?.();
10
12
  return mapLimit(targets, 6, async (target) => {
11
13
  await uploadFile(config, target);
12
14
  }).then(() => {
13
- outputCompleted(`Files uploaded successfully`, stderrLogger);
15
+ hooks?.onUploadFilesComplete?.();
16
+ outputCompleted(`Files uploaded successfully`, logger);
14
17
  });
15
18
  }
16
19
  async function uploadFile(config, target) {