@shopify/oxygen-cli 1.4.0 → 1.6.0

Sign up to get free protection for your applications and to get access to all the features.
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) {