@shopify/oxygen-cli 1.2.0 → 1.3.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.
@@ -1,7 +1,7 @@
1
1
  import { graphqlRequest } from '@shopify/cli-kit/node/api/graphql';
2
2
  import { AbortError } from '@shopify/cli-kit/node/error';
3
3
  import { outputInfo } from '@shopify/cli-kit/node/output';
4
- import { Header, errorHandler } from '../utils/utils.js';
4
+ import { Header, stderrLogger, errorHandler } from '../utils/utils.js';
5
5
  import { BuildCancelQuery } from './graphql/build-cancel.js';
6
6
 
7
7
  async function buildCancel(config, buildId, reason) {
@@ -25,7 +25,7 @@ async function buildCancel(config, buildId, reason) {
25
25
  `Failed to cancel build: ${response.buildCancel.userErrors[0]?.message}`
26
26
  );
27
27
  }
28
- outputInfo(`Build with id ${buildId} cancelled.`);
28
+ outputInfo(`Build with id ${buildId} cancelled.`, stderrLogger);
29
29
  return response.buildCancel;
30
30
  } catch (error) {
31
31
  errorHandler(error);
@@ -1,7 +1,7 @@
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, errorHandler } from '../utils/utils.js';
4
+ import { Header, stderrLogger, errorHandler } from '../utils/utils.js';
5
5
  import { BuildInitiateQuery } from './graphql/build-initiate.js';
6
6
 
7
7
  async function buildInitiate(config, environment, labels = []) {
@@ -26,7 +26,8 @@ async function buildInitiate(config, environment, labels = []) {
26
26
  );
27
27
  }
28
28
  outputCompleted(
29
- `Build initiated successfully with id ${response.buildInitiate.build.id}.`
29
+ `Build initiated successfully with id ${response.buildInitiate.build.id}.`,
30
+ stderrLogger
30
31
  );
31
32
  return response.buildInitiate;
32
33
  } catch (error) {
@@ -2,7 +2,7 @@ import { AbortError } from '@shopify/cli-kit/node/error';
2
2
  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
- import { Header } from '../utils/utils.js';
5
+ import { Header, stderrLogger } from '../utils/utils.js';
6
6
  import { createTestConfig } from '../utils/test-helper.js';
7
7
  import { buildInitiate } from './build-initiate.js';
8
8
 
@@ -39,7 +39,8 @@ describe("BuildInitiate", () => {
39
39
  }
40
40
  });
41
41
  expect(outputCompleted).toHaveBeenCalledWith(
42
- "Build initiated successfully with id build-1."
42
+ "Build initiated successfully with id build-1.",
43
+ stderrLogger
43
44
  );
44
45
  });
45
46
  test("should throw AbortError when build initiation fails due to user errors", async () => {
@@ -4,7 +4,7 @@ async function buildProject(config, assetPath) {
4
4
  const assetPathEnvironment = assetPath ? { HYDROGEN_ASSET_BASE_URL: assetPath } : {};
5
5
  await new Promise((resolve, reject) => {
6
6
  const buildCommand = spawn(config.buildCommand, [], {
7
- stdio: "inherit",
7
+ stdio: ["inherit", "pipe", "inherit"],
8
8
  env: {
9
9
  // eslint-disable-next-line no-process-env
10
10
  ...process.env,
@@ -13,11 +13,7 @@ async function buildProject(config, assetPath) {
13
13
  cwd: config.rootPath,
14
14
  shell: true
15
15
  });
16
- if (buildCommand.stdout) {
17
- buildCommand.stdout.on("data", (data) => {
18
- console.log(`stdout: ${data}`);
19
- });
20
- }
16
+ buildCommand.stdout.pipe(process.stderr);
21
17
  buildCommand.on("close", (code) => {
22
18
  if (code !== 0) {
23
19
  reject(code);
@@ -10,6 +10,8 @@ vi.mock("child_process", () => {
10
10
  return {
11
11
  stdout: {
12
12
  on: () => {
13
+ },
14
+ pipe: () => {
13
15
  }
14
16
  },
15
17
  on: (event, callback) => {
@@ -32,7 +34,7 @@ test("BuildProject builds the project successfully", async () => {
32
34
  expect(spawn).toBeCalledWith("npm run build", [], {
33
35
  cwd: "rootFolder",
34
36
  shell: true,
35
- stdio: "inherit",
37
+ stdio: ["inherit", "pipe", "inherit"],
36
38
  env: {
37
39
  // eslint-disable-next-line no-process-env
38
40
  ...process.env,
@@ -1,7 +1,7 @@
1
1
  import { graphqlRequest } from '@shopify/cli-kit/node/api/graphql';
2
2
  import { AbortError } from '@shopify/cli-kit/node/error';
3
3
  import { outputInfo } from '@shopify/cli-kit/node/output';
4
- import { Header, errorHandler } from '../utils/utils.js';
4
+ import { Header, stderrLogger, errorHandler } from '../utils/utils.js';
5
5
  import { DeploymentCancelQuery } from './graphql/deployment-cancel.js';
6
6
 
7
7
  async function deploymentCancel(config, deploymentId, reason) {
@@ -25,7 +25,7 @@ async function deploymentCancel(config, deploymentId, reason) {
25
25
  `Failed to cancel deployment: ${response.deploymentCancel.userErrors[0]?.message}`
26
26
  );
27
27
  }
28
- outputInfo(`Deployment with id ${deploymentId} cancelled.`);
28
+ outputInfo(`Deployment with id ${deploymentId} cancelled.`, stderrLogger);
29
29
  return response.deploymentCancel;
30
30
  } catch (error) {
31
31
  errorHandler(error);
@@ -1,7 +1,7 @@
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, errorHandler } from '../utils/utils.js';
4
+ import { Header, stderrLogger, errorHandler } from '../utils/utils.js';
5
5
  import { DeploymentInitiateQuery } from './graphql/deployment-initiate.js';
6
6
 
7
7
  async function deploymentInitiate(config, input) {
@@ -29,7 +29,8 @@ async function deploymentInitiate(config, input) {
29
29
  );
30
30
  }
31
31
  outputCompleted(
32
- `Deployment initiated, ${response.deploymentInitiate.deploymentTargets.length} files to upload.`
32
+ `Deployment initiated, ${response.deploymentInitiate.deploymentTargets.length} files to upload.`,
33
+ stderrLogger
33
34
  );
34
35
  return response.deploymentInitiate;
35
36
  } 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 } from '../utils/utils.js';
6
+ import { Header, stderrLogger } from '../utils/utils.js';
7
7
  import { deploymentInitiate } from './deployment-initiate.js';
8
8
 
9
9
  vi.mock("@shopify/cli-kit/node/api/graphql");
@@ -51,14 +51,16 @@ describe("DeploymentInitiate", () => {
51
51
  variables: {
52
52
  buildId: "build-1",
53
53
  environment: void 0,
54
- files: testManifest
54
+ files: testManifest,
55
+ isPrivate: true
55
56
  },
56
57
  addedHeaders: {
57
58
  [Header.OxygenNamespaceHandle]: `${testConfig.deploymentToken.namespace}`
58
59
  }
59
60
  });
60
61
  expect(outputCompleted).toHaveBeenCalledWith(
61
- `Deployment initiated, ${testManifest.length} files to upload.`
62
+ `Deployment initiated, ${testManifest.length} files to upload.`,
63
+ stderrLogger
62
64
  );
63
65
  });
64
66
  test("should initiate a deployment with an environmentName", async () => {
@@ -77,14 +79,16 @@ describe("DeploymentInitiate", () => {
77
79
  variables: {
78
80
  buildId: void 0,
79
81
  environment: { tag: "preview" },
80
- files: testManifest
82
+ files: testManifest,
83
+ isPrivate: true
81
84
  },
82
85
  addedHeaders: {
83
86
  [Header.OxygenNamespaceHandle]: `${testConfig.deploymentToken.namespace}`
84
87
  }
85
88
  });
86
89
  expect(outputCompleted).toHaveBeenCalledWith(
87
- `Deployment initiated, ${testManifest.length} files to upload.`
90
+ `Deployment initiated, ${testManifest.length} files to upload.`,
91
+ stderrLogger
88
92
  );
89
93
  });
90
94
  test("should throw AbortError when deployment initiation fails due to user errors", async () => {
@@ -1,5 +1,5 @@
1
- import { outputSuccess, outputWarn, consoleError } from '@shopify/cli-kit/node/output';
2
- import { verifyConfig } from '../utils/utils.js';
1
+ import { outputSuccess, outputInfo, outputWarn, consoleError } from '@shopify/cli-kit/node/output';
2
+ import { verifyConfig, stderrLogger } from '../utils/utils.js';
3
3
  import { buildInitiate } from './build-initiate.js';
4
4
  import { buildCancel } from './build-cancel.js';
5
5
  import { getUploadFiles } from './get-upload-files.js';
@@ -43,28 +43,39 @@ async function createDeploy(config) {
43
43
  const urlMessage = config.publicDeployment ? "Public" : "Private";
44
44
  outputSuccess(
45
45
  `Deployment complete.
46
- ${urlMessage} preview URL: ${deploymentCompleteOp.deployment.url}`
46
+ ${urlMessage} preview URL: ${deploymentCompleteOp.deployment.url}`,
47
+ stderrLogger
47
48
  );
49
+ if (metadata.name !== "none") {
50
+ outputInfo(deploymentCompleteOp.deployment.url);
51
+ }
48
52
  } catch (error) {
49
53
  if (!(error instanceof Error)) {
50
54
  console.error("Unknown error", error);
51
55
  return;
52
56
  }
53
57
  if (build.id && !buildCompleted) {
54
- outputWarn(`Build failed with: ${error.message}, cancelling build.`);
58
+ outputWarn(
59
+ `Build failed with: ${error.message}, cancelling build.`,
60
+ stderrLogger
61
+ );
55
62
  await buildCancel(config, build.id, error.message).catch((err) => {
56
63
  if (err instanceof Error) {
57
- outputWarn(`Failed to cancel build: ${err.message}`);
64
+ outputWarn(`Failed to cancel build: ${err.message}`, stderrLogger);
58
65
  }
59
66
  });
60
67
  } else if (deployment?.deployment.id) {
61
68
  outputWarn(
62
- `Deployment failed with: ${error.message}, cancelling deployment.`
69
+ `Deployment failed with: ${error.message}, cancelling deployment.`,
70
+ stderrLogger
63
71
  );
64
72
  await deploymentCancel(config, deployment.deployment.id, "failed").catch(
65
73
  (err) => {
66
74
  if (err instanceof Error) {
67
- outputWarn(`Failed to cancel deployment: ${err.message}`);
75
+ outputWarn(
76
+ `Failed to cancel deployment: ${err.message}`,
77
+ stderrLogger
78
+ );
68
79
  }
69
80
  }
70
81
  );
@@ -1,7 +1,7 @@
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 { maxLabelLength } from '../utils/utils.js';
4
+ import { stderrLogger, maxLabelLength } from '../utils/utils.js';
5
5
 
6
6
  async function getMetadata(config) {
7
7
  const ciInfo = ciPlatform();
@@ -19,11 +19,11 @@ async function getMetadata(config) {
19
19
  commitMessage: gitCommit.message
20
20
  };
21
21
  } catch (error) {
22
- outputWarn("No CI metadata loaded from environment");
22
+ outputWarn("No CI metadata loaded from environment", stderrLogger);
23
23
  }
24
24
  }
25
25
  return {
26
- name: ciInfo.isCI ? ciInfo.name : "unknown",
26
+ name: ciInfo.isCI ? ciInfo.name : "none",
27
27
  ...metadata,
28
28
  actor: config.metadata.user ?? metadata.actor,
29
29
  commitSha: config.metadata.version ?? metadata.commitSha,
@@ -64,7 +64,7 @@ describe("getMetadata", () => {
64
64
  const metadataResult = await getMetadata(testConfig);
65
65
  expect(metadataResult.actor).toBe("gh_author");
66
66
  expect(metadataResult.commitSha).toBe("gh_hash");
67
- expect(metadataResult.name).toBe("unknown");
67
+ expect(metadataResult.name).toBe("none");
68
68
  expect(metadataResult.url).toBe(void 0);
69
69
  });
70
70
  });
@@ -3,14 +3,14 @@ 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 { deployDefaults } from '../utils/utils.js';
6
+ import { stderrLogger, deployDefaults } from '../utils/utils.js';
7
7
 
8
8
  async function uploadFiles(config, targets) {
9
- outputInfo(`Uploading ${targets.length} files...`);
9
+ outputInfo(`Uploading ${targets.length} files...`, stderrLogger);
10
10
  return mapLimit(targets, 6, async (target) => {
11
11
  await uploadFile(config, target);
12
12
  }).then(() => {
13
- outputCompleted(`Files uploaded successfully`);
13
+ outputCompleted(`Files uploaded successfully`, stderrLogger);
14
14
  });
15
15
  }
16
16
  async function uploadFile(config, target) {
@@ -9,6 +9,7 @@ declare enum Header {
9
9
  OxygenNamespaceHandle = "X-Oxygen-Namespace-Handle"
10
10
  }
11
11
  declare function isClientError(error: unknown): error is ClientError;
12
+ declare function stderrLogger(log: string): void;
12
13
  declare const maxLabelLength = 90;
13
14
  declare function parseToken(inputToken: string): DeploymentToken;
14
15
  interface VerifyConfigParams {
@@ -17,4 +18,4 @@ interface VerifyConfigParams {
17
18
  }
18
19
  declare function verifyConfig({ config, performedBuild, }: VerifyConfigParams): Promise<void>;
19
20
 
20
- export { Header, deployDefaults, errorHandler, getBuildCommandFromLockFile, isClientError, maxLabelLength, parseToken, verifyConfig };
21
+ export { Header, deployDefaults, errorHandler, getBuildCommandFromLockFile, isClientError, maxLabelLength, parseToken, stderrLogger, verifyConfig };
@@ -43,13 +43,17 @@ function getBuildCommandFromLockFile(config) {
43
43
  }
44
44
  if (foundLockFiles.length > 1) {
45
45
  const lockFilesList = foundLockFiles.map(({ lockFileName }) => lockFileName).join(", ");
46
- outputWarn(`Warning: Multiple lock files found: (${lockFilesList}).`);
46
+ outputWarn(
47
+ `Warning: Multiple lock files found: (${lockFilesList}).`,
48
+ stderrLogger
49
+ );
47
50
  }
48
51
  if (foundLockFiles.length > 0) {
49
52
  const { lockFileName, buildCommand } = foundLockFiles[0];
50
53
  const infoMsg = foundLockFiles.length > 1 ? "" : `Found: ${lockFileName}. `;
51
54
  outputInfo(
52
- `${infoMsg}Assuming "${buildCommand}" as build command. Use the buildCommand flag to override.`
55
+ `${infoMsg}Assuming "${buildCommand}" as build command. Use the buildCommand flag to override.`,
56
+ stderrLogger
53
57
  );
54
58
  return buildCommand;
55
59
  }
@@ -62,6 +66,10 @@ var Header = /* @__PURE__ */ ((Header2) => {
62
66
  function isClientError(error) {
63
67
  return typeof error === "object" && error !== null && "statusCode" in error;
64
68
  }
69
+ function stderrLogger(log) {
70
+ process.stderr.write(`${log}
71
+ `);
72
+ }
65
73
  const maxLabelLength = 90;
66
74
  function parseToken(inputToken) {
67
75
  try {
@@ -100,7 +108,8 @@ async function checkPath(path, pathType) {
100
108
  if (!await fileExists(path)) {
101
109
  if (pathType === "assets") {
102
110
  outputWarn(
103
- `Use the "workerOnly" flag to perform a worker-only deployment.`
111
+ `Use the "workerOnly" flag to perform a worker-only deployment.`,
112
+ stderrLogger
104
113
  );
105
114
  }
106
115
  throw new Error(`Path not found: ${path}`);
@@ -123,4 +132,4 @@ function convertKeysToCamelCase(obj) {
123
132
  return obj;
124
133
  }
125
134
 
126
- export { Header, deployDefaults, errorHandler, getBuildCommandFromLockFile, isClientError, maxLabelLength, parseToken, verifyConfig };
135
+ export { Header, deployDefaults, errorHandler, getBuildCommandFromLockFile, isClientError, maxLabelLength, parseToken, stderrLogger, verifyConfig };
@@ -29,7 +29,8 @@ describe("getBuildCommandFromLockfile", () => {
29
29
  const buildCommand = utils.getBuildCommandFromLockFile(testConfig);
30
30
  expect(buildCommand).toBe("pnpm run build");
31
31
  expect(outputInfo).toHaveBeenCalledWith(
32
- 'Found: pnpm-lock.yaml. Assuming "pnpm run build" as build command. Use the buildCommand flag to override.'
32
+ 'Found: pnpm-lock.yaml. Assuming "pnpm run build" as build command. Use the buildCommand flag to override.',
33
+ utils.stderrLogger
33
34
  );
34
35
  });
35
36
  test("getBuildCommandFromLockfile the first build command and warns when multiple lockfiles are found", async () => {
@@ -37,10 +38,12 @@ describe("getBuildCommandFromLockfile", () => {
37
38
  const buildCommand = utils.getBuildCommandFromLockFile(testConfig);
38
39
  expect(buildCommand).toBe("pnpm run build");
39
40
  expect(outputInfo).toHaveBeenCalledWith(
40
- 'Assuming "pnpm run build" as build command. Use the buildCommand flag to override.'
41
+ 'Assuming "pnpm run build" as build command. Use the buildCommand flag to override.',
42
+ utils.stderrLogger
41
43
  );
42
44
  expect(outputWarn).toHaveBeenCalledWith(
43
- "Warning: Multiple lock files found: (pnpm-lock.yaml, yarn.lock)."
45
+ "Warning: Multiple lock files found: (pnpm-lock.yaml, yarn.lock).",
46
+ utils.stderrLogger
44
47
  );
45
48
  });
46
49
  });
@@ -94,7 +97,8 @@ describe("verifyConfig", () => {
94
97
  )
95
98
  );
96
99
  expect(outputWarn).toHaveBeenCalledWith(
97
- `Use the "workerOnly" flag to perform a worker-only deployment.`
100
+ `Use the "workerOnly" flag to perform a worker-only deployment.`,
101
+ utils.stderrLogger
98
102
  );
99
103
  });
100
104
  test("verifyConfig throws when the workerDir cannot be resolved after a build", async () => {
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.2.0",
2
+ "version": "1.3.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.2.0",
8
+ "version": "1.3.0",
9
9
  "type": "module",
10
10
  "scripts": {
11
11
  "build": "tsup --clean --config ./tsup.config.ts && oclif manifest",