@shopify/cli-hydrogen 6.1.0 → 6.1.1

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,17 +1,8 @@
1
1
  import { Flags } from '@oclif/core';
2
2
  import Command from '@shopify/cli-kit/node/base-command';
3
- import colors from '@shopify/cli-kit/node/colors';
4
- import { outputWarn, outputInfo, outputContent } from '@shopify/cli-kit/node/output';
5
- import { AbortError } from '@shopify/cli-kit/node/error';
6
- import { writeFile } from '@shopify/cli-kit/node/fs';
7
- import { ensureIsClean, getLatestGitCommit, GitDirectoryNotCleanError } from '@shopify/cli-kit/node/git';
8
- import { resolvePath } from '@shopify/cli-kit/node/path';
9
- import { renderFatalError, renderWarning, renderSelectPrompt, renderSuccess, renderTasks } from '@shopify/cli-kit/node/ui';
10
- import { ciPlatform } from '@shopify/cli-kit/node/context/local';
11
- import { parseToken, createDeploy } from '@shopify/oxygen-cli/deploy';
12
- import { commonFlags, flagsToCamelObject } from '../../lib/flags.js';
13
- import { getOxygenDeploymentData } from '../../lib/get-oxygen-deployment-data.js';
14
- import { runBuild } from './build.js';
3
+ import { outputWarn } from '@shopify/cli-kit/node/output';
4
+ import { renderWarning } from '@shopify/cli-kit/node/ui';
5
+ import { commonFlags } from '../../lib/flags.js';
15
6
 
16
7
  const deploymentLogger = (message, level = "info") => {
17
8
  if (level === "error" || level === "warn") {
@@ -74,222 +65,13 @@ class Deploy extends Command {
74
65
  };
75
66
  static hidden = true;
76
67
  async run() {
77
- const { flags } = await this.parse(Deploy);
78
- const deploymentOptions = this.flagsToOxygenDeploymentOptions(flags);
79
- await oxygenDeploy(deploymentOptions).catch((error) => {
80
- renderFatalError(error);
81
- process.exit(1);
82
- }).finally(() => {
83
- process.exit(0);
84
- });
85
- }
86
- flagsToOxygenDeploymentOptions(flags) {
87
- const camelFlags = flagsToCamelObject(flags);
88
- return {
89
- ...camelFlags,
90
- environmentTag: flags["env-branch"],
91
- path: flags.path ? resolvePath(flags.path) : process.cwd()
92
- };
93
- }
94
- }
95
- async function oxygenDeploy(options) {
96
- const {
97
- environmentTag,
98
- force: forceOnUncommitedChanges,
99
- noJsonOutput,
100
- path,
101
- shop,
102
- publicDeployment,
103
- metadataUrl,
104
- metadataUser,
105
- metadataVersion
106
- } = options;
107
- let { metadataDescription } = options;
108
- let isCleanGit = true;
109
- try {
110
- await ensureIsClean(path);
111
- } catch (error) {
112
- if (error instanceof GitDirectoryNotCleanError) {
113
- isCleanGit = false;
114
- }
115
- if (!forceOnUncommitedChanges && !isCleanGit) {
116
- throw new AbortError("Uncommitted changes detected.", null, [
117
- [
118
- "Commit your changes before deploying or use the ",
119
- { command: "--force" },
120
- " flag to deploy with uncommitted changes."
121
- ]
122
- ]);
123
- }
124
- }
125
- const isCI = ciPlatform().isCI;
126
- let token = options.token;
127
- let branch;
128
- let commitHash;
129
- let deploymentData;
130
- let deploymentEnvironmentTag = void 0;
131
- let gitCommit;
132
- try {
133
- gitCommit = await getLatestGitCommit(path);
134
- branch = (/HEAD -> ([^,]*)/.exec(gitCommit.refs) || [])[1];
135
- commitHash = gitCommit.hash;
136
- } catch (error) {
137
- outputWarn("Could not retrieve Git history.");
138
- branch = void 0;
139
- }
140
- if (!metadataDescription && !isCleanGit) {
141
68
  renderWarning({
142
- headline: "No deployment description provided",
143
- body: [
144
- "Deploying uncommited changes, but no description has been provided. Use the ",
145
- { command: "--metadata-description" },
146
- "flag to provide a description. If no description is provided, the description defaults to ",
147
- { userInput: "<sha> with additional changes" },
148
- " using the SHA of the last commit."
149
- ]
150
- });
151
- metadataDescription = `${commitHash} with additional changes`;
152
- }
153
- if (!isCI) {
154
- deploymentData = await getOxygenDeploymentData({
155
- root: path,
156
- flagShop: shop
157
- });
158
- if (!deploymentData) {
159
- return;
160
- }
161
- token = token || deploymentData.oxygenDeploymentToken;
162
- }
163
- if (!token) {
164
- const errMessage = isCI ? [
165
- "No deployment token provided. Use the ",
166
- { command: "--token" },
167
- " flag to provide a token."
168
- ] : `Could not obtain an Oxygen deployment token, please try again or contact Shopify support.`;
169
- throw new AbortError(errMessage);
170
- }
171
- if (!isCI && !environmentTag && deploymentData?.environments) {
172
- if (deploymentData.environments.length > 1) {
173
- const choices = [
174
- ...deploymentData.environments.map(({ name, branch: branch2 }) => ({
175
- label: name,
176
- value: branch2
177
- }))
178
- ];
179
- deploymentEnvironmentTag = await renderSelectPrompt({
180
- message: "Select an environment to deploy to",
181
- choices,
182
- defaultValue: branch
183
- });
184
- } else {
185
- outputInfo(
186
- `Using current checked out branch ${branch} as environment tag`
187
- );
188
- }
189
- }
190
- let deploymentUrl = "https://oxygen.shopifyapps.com";
191
- if (process.env.UNSAFE_SHOPIFY_HYDROGEN_DEPLOYMENT_URL) {
192
- deploymentUrl = process.env.UNSAFE_SHOPIFY_HYDROGEN_DEPLOYMENT_URL;
193
- outputWarn(
194
- "Using a custom deployment service. Don't do this in production!"
195
- );
196
- }
197
- const config = {
198
- assetsDir: "dist/client",
199
- bugsnag: true,
200
- deploymentUrl,
201
- deploymentToken: parseToken(token),
202
- environmentTag: environmentTag || deploymentEnvironmentTag || branch,
203
- verificationMaxDuration: 180,
204
- metadata: {
205
- ...metadataDescription ? { description: metadataDescription } : {},
206
- ...metadataUrl ? { url: metadataUrl } : {},
207
- ...metadataUser ? { user: metadataUser } : {},
208
- ...metadataVersion ? { version: metadataVersion } : {}
209
- },
210
- publicDeployment,
211
- skipVerification: false,
212
- rootPath: path,
213
- skipBuild: false,
214
- workerOnly: false,
215
- workerDir: "dist/worker"
216
- };
217
- let resolveUpload;
218
- const uploadPromise = new Promise((resolve) => {
219
- resolveUpload = resolve;
220
- });
221
- let resolveHealthCheck;
222
- const healthCheckPromise = new Promise((resolve) => {
223
- resolveHealthCheck = resolve;
224
- });
225
- let deployError = null;
226
- let resolveDeploy;
227
- let rejectDeploy;
228
- const deployPromise = new Promise((resolve, reject) => {
229
- resolveDeploy = resolve;
230
- rejectDeploy = reject;
231
- });
232
- const hooks = {
233
- buildFunction: async (assetPath) => {
234
- outputInfo(
235
- outputContent`${colors.whiteBright("Building project...")}`.value
236
- );
237
- await runBuild({
238
- directory: path,
239
- assetPath,
240
- sourcemap: false,
241
- useCodegen: false
242
- });
243
- },
244
- onVerificationComplete: () => resolveHealthCheck(),
245
- onUploadFilesStart: () => uploadStart(),
246
- onUploadFilesComplete: () => resolveUpload(),
247
- onVerificationError: (error) => {
248
- deployError = new AbortError(
249
- error.message,
250
- "Please verify the deployment status in the Shopify Admin and retry deploying if necessary."
251
- );
252
- },
253
- onUploadFilesError: (error) => {
254
- deployError = new AbortError(
255
- error.message,
256
- "Check your connection and try again. If the problem persists, try again later or contact support."
257
- );
258
- }
259
- };
260
- const uploadStart = async () => {
261
- outputInfo(
262
- outputContent`${colors.whiteBright("Deploying to Oxygen..\n")}`.value
263
- );
264
- await renderTasks([
265
- {
266
- title: "Uploading files",
267
- task: async () => await uploadPromise
268
- },
269
- {
270
- title: "Verifying deployment",
271
- task: async () => await healthCheckPromise
272
- }
273
- ]);
274
- };
275
- await createDeploy({ config, hooks, logger: deploymentLogger }).then(async (url) => {
276
- const deploymentType = config.publicDeployment ? "public" : "private";
277
- renderSuccess({
278
- body: ["Successfully deployed to Oxygen"],
69
+ body: "Deploy command unavailable.",
279
70
  nextSteps: [
280
- [
281
- `Open ${url} in your browser to view your ${deploymentType} deployment`
282
- ]
71
+ "To use the deploy command, upgrade cli-hydrogen to v7.0.0+."
283
72
  ]
284
73
  });
285
- if (isCI && !noJsonOutput) {
286
- await writeFile("h2_deploy_log.json", JSON.stringify({ url }));
287
- }
288
- resolveDeploy();
289
- }).catch((error) => {
290
- rejectDeploy(deployError || error);
291
- });
292
- return deployPromise;
74
+ }
293
75
  }
294
76
 
295
- export { Deploy as default, deploymentLogger, oxygenDeploy };
77
+ export { Deploy as default, deploymentLogger };
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "6.1.0",
2
+ "version": "6.1.1",
3
3
  "commands": {
4
4
  "hydrogen:build": {
5
5
  "id": "hydrogen:build",
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "access": "public",
5
5
  "@shopify:registry": "https://registry.npmjs.org"
6
6
  },
7
- "version": "6.1.0",
7
+ "version": "6.1.1",
8
8
  "license": "MIT",
9
9
  "type": "module",
10
10
  "scripts": {
@@ -1,340 +0,0 @@
1
- import { vi, describe, expect, beforeEach, afterEach, it } from 'vitest';
2
- import { login } from '../../lib/auth.js';
3
- import { getStorefronts } from '../../lib/graphql/admin/link-storefront.js';
4
- import { AbortError } from '@shopify/cli-kit/node/error';
5
- import { writeFile } from '@shopify/cli-kit/node/fs';
6
- import { renderSelectPrompt, renderSuccess, renderWarning, renderFatalError } from '@shopify/cli-kit/node/ui';
7
- import { ensureIsClean, GitDirectoryNotCleanError, getLatestGitCommit } from '@shopify/cli-kit/node/git';
8
- import { oxygenDeploy, deploymentLogger } from './deploy.js';
9
- import { getOxygenDeploymentData } from '../../lib/get-oxygen-deployment-data.js';
10
- import { createDeploy, parseToken } from '@shopify/oxygen-cli/deploy';
11
- import { ciPlatform } from '@shopify/cli-kit/node/context/local';
12
-
13
- vi.mock("../../lib/get-oxygen-deployment-data.js");
14
- vi.mock("@shopify/oxygen-cli/deploy");
15
- vi.mock("@shopify/cli-kit/node/fs");
16
- vi.mock("@shopify/cli-kit/node/context/local");
17
- vi.mock("../../lib/auth.js");
18
- vi.mock("../../lib/shopify-config.js");
19
- vi.mock("../../lib/graphql/admin/link-storefront.js");
20
- vi.mock("../../lib/graphql/admin/create-storefront.js");
21
- vi.mock("../../lib/graphql/admin/fetch-job.js");
22
- vi.mock("../../lib/shell.js", () => ({ getCliCommand: () => "h2" }));
23
- vi.mock("@shopify/cli-kit/node/output", async () => {
24
- return {
25
- outputContent: () => ({ value: "" }),
26
- outputInfo: () => {
27
- },
28
- outputWarn: () => {
29
- }
30
- };
31
- });
32
- vi.mock("@shopify/cli-kit/node/ui", async () => {
33
- return {
34
- renderFatalError: vi.fn(),
35
- renderSelectPrompt: vi.fn(),
36
- renderSuccess: vi.fn(),
37
- renderTasks: vi.fn(),
38
- renderWarning: vi.fn()
39
- };
40
- });
41
- vi.mock("@shopify/cli-kit/node/git", async () => {
42
- const actual = await vi.importActual("@shopify/cli-kit/node/git");
43
- return {
44
- ...actual,
45
- getLatestGitCommit: vi.fn(),
46
- ensureIsClean: vi.fn()
47
- };
48
- });
49
- describe("deploy", () => {
50
- const ADMIN_SESSION = {
51
- token: "abc123",
52
- storeFqdn: "my-shop.myshopify.com"
53
- };
54
- const FULL_SHOPIFY_CONFIG = {
55
- shop: "my-shop.myshopify.com",
56
- shopName: "My Shop",
57
- email: "email",
58
- storefront: {
59
- id: "gid://shopify/HydrogenStorefront/1",
60
- title: "Hydrogen"
61
- }
62
- };
63
- const UNLINKED_SHOPIFY_CONFIG = {
64
- ...FULL_SHOPIFY_CONFIG,
65
- storefront: void 0
66
- };
67
- const originalExit = process.exit;
68
- const deployParams = {
69
- force: false,
70
- noJsonOutput: false,
71
- path: "./",
72
- shop: "snowdevil.myshopify.com",
73
- publicDeployment: false,
74
- metadataUrl: "https://example.com",
75
- metadataUser: "user",
76
- metadataVersion: "1.0.0"
77
- };
78
- const mockToken = {
79
- accessToken: "some-token",
80
- allowedResource: "some-resource",
81
- appId: "1",
82
- client: "1",
83
- expiresAt: "some-time",
84
- namespace: "some-namespace",
85
- namespaceId: "1"
86
- };
87
- const expectedConfig = {
88
- assetsDir: "dist/client",
89
- bugsnag: true,
90
- deploymentUrl: "https://oxygen.shopifyapps.com",
91
- deploymentToken: mockToken,
92
- verificationMaxDuration: 180,
93
- metadata: {
94
- url: deployParams.metadataUrl,
95
- user: deployParams.metadataUser,
96
- version: deployParams.metadataVersion
97
- },
98
- publicDeployment: deployParams.publicDeployment,
99
- skipVerification: false,
100
- rootPath: deployParams.path,
101
- skipBuild: false,
102
- workerOnly: false,
103
- workerDir: "dist/worker"
104
- };
105
- const expectedHooks = {
106
- buildFunction: expect.any(Function),
107
- onVerificationComplete: expect.any(Function),
108
- onUploadFilesStart: expect.any(Function),
109
- onUploadFilesComplete: expect.any(Function),
110
- onVerificationError: expect.any(Function),
111
- onUploadFilesError: expect.any(Function)
112
- };
113
- beforeEach(async () => {
114
- process.exit = vi.fn();
115
- vi.mocked(login).mockResolvedValue({
116
- session: ADMIN_SESSION,
117
- config: UNLINKED_SHOPIFY_CONFIG
118
- });
119
- vi.mocked(ciPlatform).mockReturnValue({ isCI: false });
120
- vi.mocked(getStorefronts).mockResolvedValue([
121
- {
122
- ...FULL_SHOPIFY_CONFIG.storefront,
123
- parsedId: "1",
124
- productionUrl: "https://example.com"
125
- }
126
- ]);
127
- vi.mocked(renderSelectPrompt).mockResolvedValue(FULL_SHOPIFY_CONFIG.shop);
128
- vi.mocked(createDeploy).mockResolvedValue(
129
- "https://a-lovely-deployment.com"
130
- );
131
- vi.mocked(getOxygenDeploymentData).mockResolvedValue({
132
- oxygenDeploymentToken: "some-encoded-token",
133
- environments: []
134
- });
135
- vi.mocked(parseToken).mockReturnValue(mockToken);
136
- });
137
- afterEach(() => {
138
- vi.resetAllMocks();
139
- process.exit = originalExit;
140
- });
141
- it("calls getOxygenDeploymentData with the correct parameters", async () => {
142
- await oxygenDeploy(deployParams);
143
- expect(getOxygenDeploymentData).toHaveBeenCalledWith({
144
- root: "./",
145
- flagShop: "snowdevil.myshopify.com"
146
- });
147
- expect(getOxygenDeploymentData).toHaveBeenCalledTimes(1);
148
- });
149
- it("calls createDeploy with the correct parameters", async () => {
150
- await oxygenDeploy(deployParams);
151
- expect(vi.mocked(createDeploy)).toHaveBeenCalledWith({
152
- config: expectedConfig,
153
- hooks: expectedHooks,
154
- logger: deploymentLogger
155
- });
156
- expect(vi.mocked(renderSuccess)).toHaveBeenCalled;
157
- });
158
- it("errors when there are uncommited changes", async () => {
159
- vi.mocked(ensureIsClean).mockRejectedValue(
160
- new GitDirectoryNotCleanError("Uncommitted changes")
161
- );
162
- await expect(oxygenDeploy(deployParams)).rejects.toThrowError(
163
- "Uncommitted changes detected"
164
- );
165
- expect(vi.mocked(createDeploy)).not.toHaveBeenCalled;
166
- });
167
- it("proceeds with warning and modified description when there are uncommited changes and the force flag is used", async () => {
168
- vi.mocked(ensureIsClean).mockRejectedValue(
169
- new GitDirectoryNotCleanError("Uncommitted changes")
170
- );
171
- vi.mocked(getLatestGitCommit).mockResolvedValue({
172
- hash: "123",
173
- message: "test commit",
174
- date: "2021-01-01",
175
- author_name: "test author",
176
- author_email: "test@author.com",
177
- body: "test body",
178
- refs: "HEAD -> main"
179
- });
180
- await oxygenDeploy({
181
- ...deployParams,
182
- force: true
183
- });
184
- expect(vi.mocked(renderWarning)).toHaveBeenCalledWith({
185
- headline: "No deployment description provided",
186
- body: expect.anything()
187
- });
188
- expect(vi.mocked(createDeploy)).toHaveBeenCalledWith({
189
- config: {
190
- ...expectedConfig,
191
- environmentTag: "main",
192
- metadata: {
193
- ...expectedConfig.metadata,
194
- description: "123 with additional changes"
195
- }
196
- },
197
- hooks: expectedHooks,
198
- logger: deploymentLogger
199
- });
200
- });
201
- it("proceeds with provided description without warning when there are uncommited changes and the force flag is used", async () => {
202
- vi.mocked(ensureIsClean).mockRejectedValue(
203
- new GitDirectoryNotCleanError("Uncommitted changes")
204
- );
205
- vi.mocked(getLatestGitCommit).mockResolvedValue({
206
- hash: "123",
207
- message: "test commit",
208
- date: "2021-01-01",
209
- author_name: "test author",
210
- author_email: "test@author.com",
211
- body: "test body",
212
- refs: "HEAD -> main"
213
- });
214
- await oxygenDeploy({
215
- ...deployParams,
216
- force: true,
217
- metadataDescription: "cool new stuff"
218
- });
219
- expect(vi.mocked(renderWarning)).not.toHaveBeenCalled;
220
- expect(vi.mocked(createDeploy)).toHaveBeenCalledWith({
221
- config: {
222
- ...expectedConfig,
223
- environmentTag: "main",
224
- metadata: {
225
- ...expectedConfig.metadata,
226
- description: "cool new stuff"
227
- }
228
- },
229
- hooks: expectedHooks,
230
- logger: deploymentLogger
231
- });
232
- });
233
- it("calls createDeploy with the checked out branch name", async () => {
234
- vi.mocked(getLatestGitCommit).mockResolvedValue({
235
- hash: "123",
236
- message: "test commit",
237
- date: "2021-01-01",
238
- author_name: "test author",
239
- author_email: "test@author.com",
240
- body: "test body",
241
- refs: "HEAD -> main"
242
- });
243
- await oxygenDeploy(deployParams);
244
- expect(vi.mocked(createDeploy)).toHaveBeenCalledWith({
245
- config: { ...expectedConfig, environmentTag: "main" },
246
- hooks: expectedHooks,
247
- logger: deploymentLogger
248
- });
249
- expect(vi.mocked(renderSuccess)).toHaveBeenCalled;
250
- });
251
- it("calls renderSelectPrompt when there are multiple environments", async () => {
252
- vi.mocked(getOxygenDeploymentData).mockResolvedValue({
253
- oxygenDeploymentToken: "some-encoded-token",
254
- environments: [
255
- { name: "production", branch: "main" },
256
- { name: "preview", branch: "staging" }
257
- ]
258
- });
259
- await oxygenDeploy(deployParams);
260
- expect(vi.mocked(renderSelectPrompt)).toHaveBeenCalledWith({
261
- message: "Select an environment to deploy to",
262
- choices: [
263
- { label: "production", value: "main" },
264
- { label: "preview", value: "staging" }
265
- ]
266
- });
267
- });
268
- it("writes a file with JSON content in CI environments", async () => {
269
- vi.mocked(ciPlatform).mockReturnValue({
270
- isCI: true,
271
- name: "github",
272
- metadata: {}
273
- });
274
- const ciDeployParams = {
275
- ...deployParams,
276
- token: "some-token",
277
- metadataDescription: "cool new stuff"
278
- };
279
- await oxygenDeploy(ciDeployParams);
280
- expect(vi.mocked(writeFile)).toHaveBeenCalledWith(
281
- "h2_deploy_log.json",
282
- JSON.stringify({ url: "https://a-lovely-deployment.com" })
283
- );
284
- vi.mocked(writeFile).mockClear();
285
- ciDeployParams.noJsonOutput = true;
286
- await oxygenDeploy(ciDeployParams);
287
- expect(vi.mocked(writeFile)).not.toHaveBeenCalled();
288
- });
289
- it("handles error during uploadFiles", async () => {
290
- const mockRenderFatalError = vi.fn();
291
- vi.mocked(renderFatalError).mockImplementation(mockRenderFatalError);
292
- const error = new Error("Wonky internet!");
293
- vi.mocked(createDeploy).mockImplementation((options) => {
294
- options.hooks?.onUploadFilesStart?.();
295
- options.hooks?.onUploadFilesError?.(error);
296
- return new Promise((_resolve, reject) => {
297
- reject(error);
298
- });
299
- });
300
- try {
301
- await oxygenDeploy(deployParams);
302
- expect(true).toBe(false);
303
- } catch (err) {
304
- if (err instanceof AbortError) {
305
- expect(err.message).toBe(error.message);
306
- expect(err.tryMessage).toBe(
307
- "Check your connection and try again. If the problem persists, try again later or contact support."
308
- );
309
- } else {
310
- expect(true).toBe(false);
311
- }
312
- }
313
- });
314
- it("handles error during deployment verification", async () => {
315
- const mockRenderFatalError = vi.fn();
316
- vi.mocked(renderFatalError).mockImplementation(mockRenderFatalError);
317
- const error = new Error("Cloudflare is down!");
318
- vi.mocked(createDeploy).mockImplementation((options) => {
319
- options.hooks?.onUploadFilesStart?.();
320
- options.hooks?.onUploadFilesComplete?.();
321
- options.hooks?.onVerificationError?.(error);
322
- return new Promise((_resolve, reject) => {
323
- reject(error);
324
- });
325
- });
326
- try {
327
- await oxygenDeploy(deployParams);
328
- expect(true).toBe(false);
329
- } catch (err) {
330
- if (err instanceof AbortError) {
331
- expect(err.message).toBe(error.message);
332
- expect(err.tryMessage).toBe(
333
- "Please verify the deployment status in the Shopify Admin and retry deploying if necessary."
334
- );
335
- } else {
336
- expect(true).toBe(false);
337
- }
338
- }
339
- });
340
- });