@shopify/oxygen-cli 1.5.0 → 1.7.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/README.md CHANGED
@@ -42,6 +42,8 @@ oxygen:deploy [options]
42
42
  - -o, --workerOnly: Worker only deployment.
43
43
  - -s, --skipBuild: Skip running build command.
44
44
  - -b, --buildCommand <buildCommand>: Build command (default: `yarn build`).
45
+ - -h, --skipHealthCheck: Skip running the health check on the deployment
46
+ - -d, --healthCheckMaxDuration: The maximum duration (in seconds) that the health check is allowed to run before it is considered failed. Accepts values between 10 and 300.
45
47
  - --publicDeployment: set the deployment to be publicly accessible.
46
48
  - --metadataUrl <metadataUrl>: URL that links to the deployment.
47
49
  - --metadataUser <metadataUser>: User that initiated the deployment.
@@ -1,20 +1,21 @@
1
1
  import * as _oclif_core_lib_interfaces_parser_js from '@oclif/core/lib/interfaces/parser.js';
2
2
  import { Command } from '@oclif/core';
3
- import { DeploymentConfig } from '../../deploy/types.js';
4
3
 
5
4
  declare class Deploy extends Command {
6
5
  static description: string;
7
6
  static hidden: boolean;
8
7
  static flags: {
9
- token: _oclif_core_lib_interfaces_parser_js.OptionFlag<string, _oclif_core_lib_interfaces_parser_js.CustomOptions>;
10
- path: _oclif_core_lib_interfaces_parser_js.OptionFlag<string, _oclif_core_lib_interfaces_parser_js.CustomOptions>;
11
- environmentTag: _oclif_core_lib_interfaces_parser_js.OptionFlag<string | undefined, _oclif_core_lib_interfaces_parser_js.CustomOptions>;
12
- workerFolder: _oclif_core_lib_interfaces_parser_js.OptionFlag<string, _oclif_core_lib_interfaces_parser_js.CustomOptions>;
13
8
  assetsFolder: _oclif_core_lib_interfaces_parser_js.OptionFlag<string, _oclif_core_lib_interfaces_parser_js.CustomOptions>;
14
- workerOnly: _oclif_core_lib_interfaces_parser_js.BooleanFlag<boolean>;
15
- skipBuild: _oclif_core_lib_interfaces_parser_js.BooleanFlag<boolean>;
16
9
  buildCommand: _oclif_core_lib_interfaces_parser_js.OptionFlag<string, _oclif_core_lib_interfaces_parser_js.CustomOptions>;
10
+ environmentTag: _oclif_core_lib_interfaces_parser_js.OptionFlag<string | undefined, _oclif_core_lib_interfaces_parser_js.CustomOptions>;
11
+ healthCheckMaxDuration: _oclif_core_lib_interfaces_parser_js.OptionFlag<number, _oclif_core_lib_interfaces_parser_js.CustomOptions>;
12
+ path: _oclif_core_lib_interfaces_parser_js.OptionFlag<string, _oclif_core_lib_interfaces_parser_js.CustomOptions>;
17
13
  publicDeployment: _oclif_core_lib_interfaces_parser_js.BooleanFlag<boolean>;
14
+ skipBuild: _oclif_core_lib_interfaces_parser_js.BooleanFlag<boolean>;
15
+ skipHealthCheck: _oclif_core_lib_interfaces_parser_js.BooleanFlag<boolean>;
16
+ token: _oclif_core_lib_interfaces_parser_js.OptionFlag<string, _oclif_core_lib_interfaces_parser_js.CustomOptions>;
17
+ workerFolder: _oclif_core_lib_interfaces_parser_js.OptionFlag<string, _oclif_core_lib_interfaces_parser_js.CustomOptions>;
18
+ workerOnly: _oclif_core_lib_interfaces_parser_js.BooleanFlag<boolean>;
18
19
  metadataUrl: _oclif_core_lib_interfaces_parser_js.OptionFlag<string | undefined, _oclif_core_lib_interfaces_parser_js.CustomOptions>;
19
20
  metadataUser: _oclif_core_lib_interfaces_parser_js.OptionFlag<string | undefined, _oclif_core_lib_interfaces_parser_js.CustomOptions>;
20
21
  metadataVersion: _oclif_core_lib_interfaces_parser_js.OptionFlag<string | undefined, _oclif_core_lib_interfaces_parser_js.CustomOptions>;
@@ -22,6 +23,5 @@ declare class Deploy extends Command {
22
23
  static hasCustomBuildCommand: boolean;
23
24
  run(): Promise<void>;
24
25
  }
25
- declare function runInit(config: DeploymentConfig): void;
26
26
 
27
- export { Deploy, runInit };
27
+ export { Deploy };
@@ -3,52 +3,18 @@ import { consoleError } from '@shopify/cli-kit/node/output';
3
3
  import { normalizePath } from '@shopify/cli-kit/node/path';
4
4
  import { createDeploy } from '../../deploy/index.js';
5
5
  import { deployDefaults, parseToken, verifyConfig, getBuildCommandFromLockFile } from '../../utils/utils.js';
6
+ import { HealthCheckError } from '../../deploy/types.js';
6
7
 
7
8
  class Deploy extends Command {
8
9
  static description = "Creates a deployment to Oxygen";
9
10
  static hidden = false;
10
11
  static flags = {
11
- token: Flags.string({
12
- char: "t",
13
- description: "Oxygen deployment token",
14
- env: "OXYGEN_DEPLOYMENT_TOKEN",
15
- required: true
16
- }),
17
- path: Flags.string({
18
- char: "p",
19
- description: "Root path",
20
- default: "./",
21
- required: false
22
- }),
23
- environmentTag: Flags.string({
24
- char: "e",
25
- description: "Tag of the environment to deploy to",
26
- required: false
27
- }),
28
- workerFolder: Flags.string({
29
- char: "w",
30
- description: "Worker folder",
31
- default: String(deployDefaults.workerDirDefault),
32
- required: false
33
- }),
34
12
  assetsFolder: Flags.string({
35
13
  char: "a",
36
14
  description: "Assets folder",
37
15
  default: String(deployDefaults.assetsDirDefault),
38
16
  required: false
39
17
  }),
40
- workerOnly: Flags.boolean({
41
- char: "o",
42
- description: "Worker only deployment",
43
- default: false,
44
- required: false
45
- }),
46
- skipBuild: Flags.boolean({
47
- char: "s",
48
- description: "Skip running build command",
49
- required: false,
50
- default: false
51
- }),
52
18
  buildCommand: Flags.string({
53
19
  char: "b",
54
20
  description: "Build command",
@@ -59,12 +25,61 @@ class Deploy extends Command {
59
25
  return Promise.resolve(input);
60
26
  }
61
27
  }),
28
+ environmentTag: Flags.string({
29
+ char: "e",
30
+ description: "Tag of the environment to deploy to",
31
+ required: false
32
+ }),
33
+ healthCheckMaxDuration: Flags.integer({
34
+ char: "d",
35
+ description: "the maximum duration (in seconds) that the health check is allowed to run before it is considered failed.",
36
+ min: 10,
37
+ max: 300,
38
+ required: false,
39
+ default: deployDefaults.healthCheckMaxDurationDefault
40
+ }),
41
+ path: Flags.string({
42
+ char: "p",
43
+ description: "Root path",
44
+ default: "./",
45
+ required: false
46
+ }),
62
47
  publicDeployment: Flags.boolean({
63
48
  env: "OXYGEN_PUBLIC_DEPLOYMENT",
64
49
  description: "Marks a preview deployment as publicly accessible.",
65
50
  required: false,
66
51
  default: false
67
52
  }),
53
+ skipBuild: Flags.boolean({
54
+ char: "s",
55
+ description: "Skip running build command",
56
+ required: false,
57
+ default: false
58
+ }),
59
+ skipHealthCheck: Flags.boolean({
60
+ char: "h",
61
+ description: "Skip running deployment health check",
62
+ required: false,
63
+ default: false
64
+ }),
65
+ token: Flags.string({
66
+ char: "t",
67
+ description: "Oxygen deployment token",
68
+ env: "OXYGEN_DEPLOYMENT_TOKEN",
69
+ required: true
70
+ }),
71
+ workerFolder: Flags.string({
72
+ char: "w",
73
+ description: "Worker folder",
74
+ default: String(deployDefaults.workerDirDefault),
75
+ required: false
76
+ }),
77
+ workerOnly: Flags.boolean({
78
+ char: "o",
79
+ description: "Worker only deployment",
80
+ default: false,
81
+ required: false
82
+ }),
68
83
  metadataUrl: Flags.string({
69
84
  description: "URL that links to the deployment. Will be saved and displayed in the Shopify admin",
70
85
  required: false,
@@ -92,10 +107,10 @@ class Deploy extends Command {
92
107
  const config = {
93
108
  assetsDir: normalizePath(flags.assetsFolder),
94
109
  buildCommand: flags.buildCommand,
95
- buildOutput: true,
96
110
  deploymentToken: parseToken(flags.token),
97
111
  environmentTag: flags.environmentTag,
98
112
  deploymentUrl,
113
+ healthCheckMaxDuration: flags.healthCheckMaxDuration,
99
114
  metadata: {
100
115
  url: flags.metadataUrl,
101
116
  user: flags.metadataUser,
@@ -104,6 +119,7 @@ class Deploy extends Command {
104
119
  publicDeployment: flags.publicDeployment,
105
120
  rootPath: normalizePath(flags.path),
106
121
  skipBuild: flags.skipBuild,
122
+ skipHealthCheck: flags.skipHealthCheck,
107
123
  workerDir: normalizePath(flags.workerFolder),
108
124
  workerOnly: flags.workerOnly
109
125
  };
@@ -111,19 +127,16 @@ class Deploy extends Command {
111
127
  if (!Deploy.hasCustomBuildCommand && !config.skipBuild) {
112
128
  config.buildCommand = getBuildCommandFromLockFile(config);
113
129
  }
114
- runInit(config);
130
+ await createDeploy({ config });
115
131
  } catch (error) {
116
- if (error instanceof Error) {
132
+ if (!(error instanceof Error)) {
133
+ consoleError(error);
134
+ } else if (!(error instanceof HealthCheckError)) {
117
135
  consoleError(error.message);
118
- } else {
119
- console.error(error);
120
136
  }
121
137
  this.exit(1);
122
138
  }
123
139
  }
124
140
  }
125
- function runInit(config) {
126
- createDeploy({ config });
127
- }
128
141
 
129
- export { Deploy, runInit };
142
+ export { Deploy };
@@ -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
  });
@@ -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,4 +1,4 @@
1
- import { outputSuccess, outputInfo, outputWarn, consoleError } from '@shopify/cli-kit/node/output';
1
+ import { outputSuccess, outputInfo, outputWarn } from '@shopify/cli-kit/node/output';
2
2
  import { stderrLogger, verifyConfig } from '../utils/utils.js';
3
3
  export { parseToken } from '../utils/utils.js';
4
4
  import { buildInitiate } from './build-initiate.js';
@@ -6,8 +6,10 @@ import { buildCancel } from './build-cancel.js';
6
6
  import { getUploadFiles } from './get-upload-files.js';
7
7
  import { deploymentInitiate } from './deployment-initiate.js';
8
8
  import { deploymentComplete } from './deployment-complete.js';
9
+ import { healthCheck } from './health-check.js';
9
10
  import { deploymentCancel, DeploymentCancelReason } from './deployment-cancel.js';
10
11
  import { uploadFiles } from './upload-files.js';
12
+ import { HealthCheckError } from './types.js';
11
13
  import { buildProject } from './build-project.js';
12
14
  import { getMetadata, createLabels, getEnvironmentInput } from './metadata.js';
13
15
 
@@ -47,11 +49,24 @@ async function createDeploy(options) {
47
49
  input: deploymentInitiateInput,
48
50
  logger
49
51
  });
50
- await uploadFiles({ config, targets: deployment.deploymentTargets, logger });
52
+ await uploadFiles({
53
+ config,
54
+ targets: deployment.deploymentTargets,
55
+ hooks,
56
+ logger
57
+ });
51
58
  const deploymentCompleteOp = await deploymentComplete(
52
59
  config,
53
60
  deployment.deployment.id
54
61
  );
62
+ if (!config.skipHealthCheck) {
63
+ await healthCheck({
64
+ config,
65
+ url: deploymentCompleteOp.deployment.url,
66
+ logger,
67
+ hooks
68
+ });
69
+ }
55
70
  const urlMessage = config.publicDeployment ? "Public" : "Private";
56
71
  outputSuccess(
57
72
  `Deployment complete.
@@ -67,7 +82,9 @@ ${urlMessage} preview URL: ${deploymentCompleteOp.deployment.url}`,
67
82
  console.error("Unknown error", error);
68
83
  return Promise.reject(new Error("Unknown error"));
69
84
  }
70
- if (build.id && !buildCompleted) {
85
+ if (error instanceof HealthCheckError) {
86
+ outputWarn(error.message, logger);
87
+ } else if (build.id && !buildCompleted) {
71
88
  outputWarn(
72
89
  `Build failed with: ${error.message}, cancelling build.`,
73
90
  logger
@@ -98,7 +115,6 @@ ${urlMessage} preview URL: ${deploymentCompleteOp.deployment.url}`,
98
115
  }
99
116
  });
100
117
  }
101
- consoleError(error.message);
102
118
  return Promise.reject(error);
103
119
  }
104
120
  }
@@ -6,19 +6,20 @@ 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>;
10
+ onHealthCheckStart?: () => void;
11
+ onHealthCheckComplete?: () => void;
12
+ onHealthCheckError?: (error: Error) => void;
12
13
  onUploadFilesStart?: () => void;
13
14
  onUploadFilesComplete?: () => void;
14
15
  }
15
16
  interface DeploymentConfig {
16
17
  assetsDir?: string;
17
- buildCommand: string;
18
- buildOutput: boolean;
18
+ buildCommand?: string;
19
19
  deploymentToken: DeploymentToken;
20
20
  deploymentUrl: string;
21
21
  environmentTag?: string;
22
+ healthCheckMaxDuration: number;
22
23
  metadata: {
23
24
  user?: string;
24
25
  version?: string;
@@ -27,6 +28,7 @@ interface DeploymentConfig {
27
28
  publicDeployment: boolean;
28
29
  rootPath?: string;
29
30
  skipBuild: boolean;
31
+ skipHealthCheck: boolean;
30
32
  workerDir?: string;
31
33
  workerOnly: boolean;
32
34
  }
@@ -57,5 +59,7 @@ declare enum FileType {
57
59
  interface OxygenError {
58
60
  message: string;
59
61
  }
62
+ declare class HealthCheckError extends Error {
63
+ }
60
64
 
61
- export { Build, ClientError, DeploymentConfig, DeploymentHooks, DeploymentManifestFile, DeploymentToken, EnvironmentInput, FileType, OxygenError };
65
+ 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 };
@@ -13,14 +13,15 @@ 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",
19
+ healthCheckMaxDuration: 300,
20
20
  metadata: {},
21
21
  rootPath: rootFolder,
22
22
  publicDeployment: false,
23
23
  skipBuild: false,
24
+ skipHealthCheck: false,
24
25
  workerDir: "/worker/",
25
26
  workerOnly: false
26
27
  };
@@ -6,6 +6,7 @@ import { AbortError } from '@shopify/cli-kit/node/error';
6
6
  const deployDefaults = {
7
7
  assetsDirDefault: "dist/client/",
8
8
  buildCommandDefault: "yarn build",
9
+ healthCheckMaxDurationDefault: 180,
9
10
  maxUploadAttempts: 3,
10
11
  maxResumabeUploadAttempts: 9,
11
12
  workerDirDefault: "dist/worker/"
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.5.0",
2
+ "version": "1.7.0",
3
3
  "commands": {
4
4
  "oxygen:deploy": {
5
5
  "id": "oxygen:deploy",
@@ -11,22 +11,23 @@
11
11
  "hidden": false,
12
12
  "aliases": [],
13
13
  "flags": {
14
- "token": {
15
- "name": "token",
14
+ "assetsFolder": {
15
+ "name": "assetsFolder",
16
16
  "type": "option",
17
- "char": "t",
18
- "description": "Oxygen deployment token",
19
- "required": true,
20
- "multiple": false
17
+ "char": "a",
18
+ "description": "Assets folder",
19
+ "required": false,
20
+ "multiple": false,
21
+ "default": "dist/client/"
21
22
  },
22
- "path": {
23
- "name": "path",
23
+ "buildCommand": {
24
+ "name": "buildCommand",
24
25
  "type": "option",
25
- "char": "p",
26
- "description": "Root path",
26
+ "char": "b",
27
+ "description": "Build command",
27
28
  "required": false,
28
29
  "multiple": false,
29
- "default": "./"
30
+ "default": "yarn build"
30
31
  },
31
32
  "environmentTag": {
32
33
  "name": "environmentTag",
@@ -36,29 +37,28 @@
36
37
  "required": false,
37
38
  "multiple": false
38
39
  },
39
- "workerFolder": {
40
- "name": "workerFolder",
40
+ "healthCheckMaxDuration": {
41
+ "name": "healthCheckMaxDuration",
41
42
  "type": "option",
42
- "char": "w",
43
- "description": "Worker folder",
43
+ "char": "d",
44
+ "description": "the maximum duration (in seconds) that the health check is allowed to run before it is considered failed.",
44
45
  "required": false,
45
46
  "multiple": false,
46
- "default": "dist/worker/"
47
+ "default": 180
47
48
  },
48
- "assetsFolder": {
49
- "name": "assetsFolder",
49
+ "path": {
50
+ "name": "path",
50
51
  "type": "option",
51
- "char": "a",
52
- "description": "Assets folder",
52
+ "char": "p",
53
+ "description": "Root path",
53
54
  "required": false,
54
55
  "multiple": false,
55
- "default": "dist/client/"
56
+ "default": "./"
56
57
  },
57
- "workerOnly": {
58
- "name": "workerOnly",
58
+ "publicDeployment": {
59
+ "name": "publicDeployment",
59
60
  "type": "boolean",
60
- "char": "o",
61
- "description": "Worker only deployment",
61
+ "description": "Marks a preview deployment as publicly accessible.",
62
62
  "required": false,
63
63
  "allowNo": false
64
64
  },
@@ -70,19 +70,36 @@
70
70
  "required": false,
71
71
  "allowNo": false
72
72
  },
73
- "buildCommand": {
74
- "name": "buildCommand",
73
+ "skipHealthCheck": {
74
+ "name": "skipHealthCheck",
75
+ "type": "boolean",
76
+ "char": "h",
77
+ "description": "Skip running deployment health check",
78
+ "required": false,
79
+ "allowNo": false
80
+ },
81
+ "token": {
82
+ "name": "token",
75
83
  "type": "option",
76
- "char": "b",
77
- "description": "Build command",
84
+ "char": "t",
85
+ "description": "Oxygen deployment token",
86
+ "required": true,
87
+ "multiple": false
88
+ },
89
+ "workerFolder": {
90
+ "name": "workerFolder",
91
+ "type": "option",
92
+ "char": "w",
93
+ "description": "Worker folder",
78
94
  "required": false,
79
95
  "multiple": false,
80
- "default": "yarn build"
96
+ "default": "dist/worker/"
81
97
  },
82
- "publicDeployment": {
83
- "name": "publicDeployment",
98
+ "workerOnly": {
99
+ "name": "workerOnly",
84
100
  "type": "boolean",
85
- "description": "Marks a preview deployment as publicly accessible.",
101
+ "char": "o",
102
+ "description": "Worker only deployment",
86
103
  "required": false,
87
104
  "allowNo": false
88
105
  },
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.5.0",
8
+ "version": "1.7.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.8.11",
35
- "@shopify/cli-kit": "^3.46.5",
34
+ "@oclif/core": "2.9.4",
35
+ "@shopify/cli-kit": "^3.47.5",
36
36
  "async": "^3.2.4"
37
37
  },
38
38
  "devDependencies": {
@@ -40,15 +40,15 @@
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.3.1",
43
+ "@types/node": "^20.4.4",
44
44
  "@types/prettier": "^2.7.3",
45
- "eslint": "^8.43.0",
45
+ "eslint": "^8.45.0",
46
46
  "node-fetch": "^3.3.1",
47
47
  "oclif": "^3",
48
48
  "tsup": "^7.1.0",
49
49
  "typescript": "^5.1.3",
50
- "vite": "^4.3.9",
51
- "vitest": "^0.32.2"
50
+ "vite": "^4.4.6",
51
+ "vitest": "^0.33.0"
52
52
  },
53
53
  "prettier": "@shopify/prettier-config",
54
54
  "oclif": {