diff --git a/.changeset/stupid-weeks-fetch.md b/.changeset/stupid-weeks-fetch.md new file mode 100644 index 0000000000..12e0e370de --- /dev/null +++ b/.changeset/stupid-weeks-fetch.md @@ -0,0 +1,7 @@ +--- +'@shopify/cli-hydrogen': patch +--- + +Disable deploy command + +This command was marked as hidden and is unstable. To use the deploy command, upgrade cli-hydrogen to v7.0.0+. diff --git a/package-lock.json b/package-lock.json index 91ae349b13..e7060ccda8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -40,7 +40,7 @@ "yorkie": "^2.0.0" }, "engines": { - "node": ">=16.13" + "node": ">=18.0.0" } }, "docs/preview": { @@ -72,14 +72,14 @@ } }, "examples/customer-api": { - "version": "0.0.1", + "version": "0.0.2", "dependencies": { "@remix-run/react": "2.1.0", "@remix-run/server-runtime": "2.1.0", "@shopify/cli": "3.51.0", - "@shopify/cli-hydrogen": "^6.0.0", - "@shopify/hydrogen": "^2023.10.0", - "@shopify/remix-oxygen": "^2.0.0", + "@shopify/cli-hydrogen": "^6.1.0", + "@shopify/hydrogen": "^2023.10.3", + "@shopify/remix-oxygen": "^2.0.2", "graphql": "^16.6.0", "graphql-tag": "^2.12.6", "isbot": "^3.6.6", @@ -99,7 +99,7 @@ "typescript": "^5.2.2" }, "engines": { - "node": ">=16.13" + "node": ">=18.0.0" } }, "examples/express": { @@ -110,7 +110,7 @@ "@remix-run/node": "2.1.0", "@remix-run/react": "2.1.0", "@remix-run/server-runtime": "2.1.0", - "@shopify/hydrogen": "2023.10.2", + "@shopify/hydrogen": "2023.10.4", "compression": "^1.7.4", "cross-env": "^7.0.3", "express": "^4.18.2", @@ -123,7 +123,7 @@ "@remix-run/dev": "2.1.0", "@remix-run/eslint-config": "2.1.0", "@shopify/cli": "3.51.0", - "@shopify/cli-hydrogen": "^6.0.2", + "@shopify/cli-hydrogen": "^6.1.0", "@types/compression": "^1.7.2", "@types/express": "^4.17.17", "@types/morgan": "^1.9.4", @@ -136,17 +136,17 @@ "typescript": "^5.2.2" }, "engines": { - "node": ">=14" + "node": ">=18.0.0" } }, "examples/multipass": { - "version": "1.0.0", + "version": "1.0.1", "dependencies": { "@remix-run/react": "2.1.0", "@shopify/cli": "3.50.0", - "@shopify/cli-hydrogen": "^6.0.0", - "@shopify/hydrogen": "^2023.10.0", - "@shopify/remix-oxygen": "^2.0.1", + "@shopify/cli-hydrogen": "^6.1.0", + "@shopify/hydrogen": "^2023.10.3", + "@shopify/remix-oxygen": "^2.0.2", "crypto-js": "^4.2.0", "graphql": "^16.6.0", "graphql-tag": "^2.12.6", @@ -171,7 +171,7 @@ "typescript": "^5.2.2" }, "engines": { - "node": ">=16.13" + "node": ">=18.0.0" } }, "examples/multipass/node_modules/@shopify/cli": { @@ -30329,7 +30329,7 @@ }, "packages/cli": { "name": "@shopify/cli-hydrogen", - "version": "6.0.2", + "version": "6.1.0", "license": "MIT", "dependencies": { "@ast-grep/napi": "0.11.0", @@ -30337,7 +30337,7 @@ "@oclif/core": "2.11.7", "@shopify/cli-kit": "3.51.0", "@shopify/hydrogen-codegen": "^0.1.0", - "@shopify/mini-oxygen": "^2.2.3", + "@shopify/mini-oxygen": "^2.2.4", "@shopify/oxygen-cli": "2.6.2", "ansi-escapes": "^6.2.0", "cli-truncate": "^4.0.0", @@ -30375,7 +30375,7 @@ "vitest": "^0.33.0" }, "engines": { - "node": ">=16.13" + "node": ">=18.0.0" }, "optionalDependencies": { "@parcel/watcher": "^2.3.0" @@ -30662,10 +30662,10 @@ }, "packages/create-hydrogen": { "name": "@shopify/create-hydrogen", - "version": "4.3.3", + "version": "4.3.4", "license": "MIT", "dependencies": { - "@shopify/cli-hydrogen": "^6.0.1" + "@shopify/cli-hydrogen": "^6.1.0" }, "bin": { "create-hydrogen": "dist/create-app.js" @@ -30673,10 +30673,10 @@ }, "packages/hydrogen": { "name": "@shopify/hydrogen", - "version": "2023.10.2", + "version": "2023.10.4", "license": "MIT", "dependencies": { - "@shopify/hydrogen-react": "2023.10.0", + "@shopify/hydrogen-react": "2023.10.1", "content-security-policy-builder": "^2.1.1", "type-fest": "^4.5.0" }, @@ -30715,7 +30715,7 @@ }, "packages/hydrogen-react": { "name": "@shopify/hydrogen-react", - "version": "2023.10.0", + "version": "2023.10.1", "license": "MIT", "dependencies": { "@google/model-viewer": "^1.12.1", @@ -30761,7 +30761,7 @@ "vitest": "^0.33.0" }, "engines": { - "node": ">=14" + "node": ">=18.0.0" }, "peerDependencies": { "react": "^18.0.0", @@ -30971,7 +30971,7 @@ }, "packages/mini-oxygen": { "name": "@shopify/mini-oxygen", - "version": "2.2.3", + "version": "2.2.4", "license": "MIT", "dependencies": { "@miniflare/cache": "^2.14.1", @@ -31001,7 +31001,7 @@ "vitest": "^0.33.0" }, "engines": { - "node": ">=16.13.0" + "node": ">=18.0.0" } }, "packages/mini-oxygen/node_modules/body-parser": { @@ -31081,7 +31081,7 @@ }, "packages/remix-oxygen": { "name": "@shopify/remix-oxygen", - "version": "2.0.1", + "version": "2.0.2", "license": "MIT", "devDependencies": { "@remix-run/server-runtime": "^2.1.0", @@ -31099,9 +31099,9 @@ "@remix-run/react": "2.1.0", "@remix-run/server-runtime": "2.1.0", "@shopify/cli": "3.51.0", - "@shopify/cli-hydrogen": "^6.0.2", - "@shopify/hydrogen": "~2023.10.2", - "@shopify/remix-oxygen": "^2.0.1", + "@shopify/cli-hydrogen": "^6.1.0", + "@shopify/hydrogen": "~2023.10.4", + "@shopify/remix-oxygen": "^2.0.2", "clsx": "^1.2.1", "cross-env": "^7.0.3", "graphql": "^16.6.0", @@ -31139,7 +31139,7 @@ "typescript": "^5.2.2" }, "engines": { - "node": ">=16.13" + "node": ">=18.0.0" } }, "templates/hello-world": { @@ -31148,9 +31148,9 @@ "@remix-run/react": "2.1.0", "@remix-run/server-runtime": "2.1.0", "@shopify/cli": "3.51.0", - "@shopify/cli-hydrogen": "^6.0.2", - "@shopify/hydrogen": "~2023.10.2", - "@shopify/remix-oxygen": "^2.0.1", + "@shopify/cli-hydrogen": "^6.1.0", + "@shopify/hydrogen": "~2023.10.4", + "@shopify/remix-oxygen": "^2.0.2", "@total-typescript/ts-reset": "^0.4.2", "graphql": "^16.6.0", "graphql-tag": "^2.12.6", @@ -31171,18 +31171,18 @@ "typescript": "^5.2.2" }, "engines": { - "node": ">=16.13" + "node": ">=18.0.0" } }, "templates/skeleton": { - "version": "1.0.0", + "version": "1.0.1", "dependencies": { "@remix-run/react": "2.1.0", "@remix-run/server-runtime": "2.1.0", "@shopify/cli": "3.51.0", - "@shopify/cli-hydrogen": "^6.0.0", - "@shopify/hydrogen": "~2023.10.2", - "@shopify/remix-oxygen": "^2.0.1", + "@shopify/cli-hydrogen": "^6.1.0", + "@shopify/hydrogen": "~2023.10.3", + "@shopify/remix-oxygen": "^2.0.2", "graphql": "^16.6.0", "graphql-tag": "^2.12.6", "isbot": "^3.6.6", @@ -31204,7 +31204,7 @@ "typescript": "^5.2.2" }, "engines": { - "node": ">=16.13" + "node": ">=18.0.0" } } }, @@ -36141,7 +36141,7 @@ "@parcel/watcher": "^2.3.0", "@shopify/cli-kit": "3.51.0", "@shopify/hydrogen-codegen": "^0.1.0", - "@shopify/mini-oxygen": "^2.2.3", + "@shopify/mini-oxygen": "^2.2.4", "@shopify/oxygen-cli": "2.6.2", "@types/diff": "^5.0.2", "@types/fs-extra": "^11.0.1", @@ -36517,7 +36517,7 @@ "@shopify/create-hydrogen": { "version": "file:packages/create-hydrogen", "requires": { - "@shopify/cli-hydrogen": "^6.0.1" + "@shopify/cli-hydrogen": "^6.1.0" } }, "@shopify/eslint-plugin": { @@ -36595,7 +36595,7 @@ "version": "file:packages/hydrogen", "requires": { "@shopify/generate-docs": "0.11.1", - "@shopify/hydrogen-react": "2023.10.0", + "@shopify/hydrogen-react": "2023.10.1", "@testing-library/react": "^14.0.0", "content-security-policy-builder": "^2.1.1", "happy-dom": "^8.9.0", @@ -39629,11 +39629,11 @@ "@remix-run/react": "2.1.0", "@remix-run/server-runtime": "2.1.0", "@shopify/cli": "3.51.0", - "@shopify/cli-hydrogen": "^6.0.0", - "@shopify/hydrogen": "^2023.10.0", + "@shopify/cli-hydrogen": "^6.1.0", + "@shopify/hydrogen": "^2023.10.3", "@shopify/oxygen-workers-types": "^4.0.0", "@shopify/prettier-config": "^1.1.2", - "@shopify/remix-oxygen": "^2.0.0", + "@shopify/remix-oxygen": "^2.0.2", "@types/eslint": "^8.4.10", "@types/react": "^18.2.22", "@types/react-dom": "^18.2.7", @@ -39872,12 +39872,12 @@ "@remix-run/react": "2.1.0", "@remix-run/server-runtime": "2.1.0", "@shopify/cli": "3.51.0", - "@shopify/cli-hydrogen": "^6.0.2", + "@shopify/cli-hydrogen": "^6.1.0", "@shopify/eslint-plugin": "^42.0.1", - "@shopify/hydrogen": "~2023.10.2", + "@shopify/hydrogen": "~2023.10.4", "@shopify/oxygen-workers-types": "^4.0.0", "@shopify/prettier-config": "^1.1.2", - "@shopify/remix-oxygen": "^2.0.1", + "@shopify/remix-oxygen": "^2.0.2", "@tailwindcss/forms": "^0.5.3", "@tailwindcss/typography": "^0.5.9", "@total-typescript/ts-reset": "^0.4.2", @@ -42044,11 +42044,11 @@ "@remix-run/react": "2.1.0", "@remix-run/server-runtime": "2.1.0", "@shopify/cli": "3.51.0", - "@shopify/cli-hydrogen": "^6.0.2", - "@shopify/hydrogen": "~2023.10.2", + "@shopify/cli-hydrogen": "^6.1.0", + "@shopify/hydrogen": "~2023.10.4", "@shopify/oxygen-workers-types": "^4.0.0", "@shopify/prettier-config": "^1.1.2", - "@shopify/remix-oxygen": "^2.0.1", + "@shopify/remix-oxygen": "^2.0.2", "@total-typescript/ts-reset": "^0.4.2", "@types/eslint": "^8.4.10", "@types/react": "^18.2.22", @@ -42203,8 +42203,8 @@ "@remix-run/react": "2.1.0", "@remix-run/server-runtime": "2.1.0", "@shopify/cli": "3.51.0", - "@shopify/cli-hydrogen": "^6.0.2", - "@shopify/hydrogen": "2023.10.2", + "@shopify/cli-hydrogen": "^6.1.0", + "@shopify/hydrogen": "2023.10.4", "@types/compression": "^1.7.2", "@types/express": "^4.17.17", "@types/morgan": "^1.9.4", @@ -44610,11 +44610,11 @@ "@remix-run/eslint-config": "2.1.0", "@remix-run/react": "2.1.0", "@shopify/cli": "3.50.0", - "@shopify/cli-hydrogen": "^6.0.0", - "@shopify/hydrogen": "^2023.10.0", + "@shopify/cli-hydrogen": "^6.1.0", + "@shopify/hydrogen": "^2023.10.3", "@shopify/oxygen-workers-types": "^3.17.3", "@shopify/prettier-config": "^1.1.2", - "@shopify/remix-oxygen": "^2.0.1", + "@shopify/remix-oxygen": "^2.0.2", "@total-typescript/ts-reset": "^0.4.2", "@types/crypto-js": "^4.2.1", "@types/eslint": "^8.4.10", @@ -49284,11 +49284,11 @@ "@remix-run/react": "2.1.0", "@remix-run/server-runtime": "2.1.0", "@shopify/cli": "3.51.0", - "@shopify/cli-hydrogen": "^6.0.0", - "@shopify/hydrogen": "~2023.10.2", + "@shopify/cli-hydrogen": "^6.1.0", + "@shopify/hydrogen": "~2023.10.3", "@shopify/oxygen-workers-types": "^4.0.0", "@shopify/prettier-config": "^1.1.2", - "@shopify/remix-oxygen": "^2.0.1", + "@shopify/remix-oxygen": "^2.0.2", "@total-typescript/ts-reset": "^0.4.2", "@types/eslint": "^8.4.10", "@types/react": "^18.2.22", diff --git a/packages/cli/src/commands/hydrogen/deploy.test.ts b/packages/cli/src/commands/hydrogen/deploy.test.ts deleted file mode 100644 index 3bb5aca1bc..0000000000 --- a/packages/cli/src/commands/hydrogen/deploy.test.ts +++ /dev/null @@ -1,387 +0,0 @@ -import {describe, it, expect, vi, beforeEach, afterEach} from 'vitest'; -import {type AdminSession, login} from '../../lib/auth.js'; -import {getStorefronts} from '../../lib/graphql/admin/link-storefront.js'; -import {AbortError} from '@shopify/cli-kit/node/error'; -import {writeFile} from '@shopify/cli-kit/node/fs'; -import { - renderSelectPrompt, - renderFatalError, - renderSuccess, - renderWarning, -} from '@shopify/cli-kit/node/ui'; -import { - ensureIsClean, - getLatestGitCommit, - GitDirectoryNotCleanError, -} from '@shopify/cli-kit/node/git'; - -import {deploymentLogger, oxygenDeploy} from './deploy.js'; -import {getOxygenDeploymentData} from '../../lib/get-oxygen-deployment-data.js'; -import {createDeploy, parseToken} from '@shopify/oxygen-cli/deploy'; -import {ciPlatform} from '@shopify/cli-kit/node/context/local'; - -vi.mock('../../lib/get-oxygen-deployment-data.js'); -vi.mock('@shopify/oxygen-cli/deploy'); -vi.mock('@shopify/cli-kit/node/fs'); -vi.mock('@shopify/cli-kit/node/context/local'); -vi.mock('../../lib/auth.js'); -vi.mock('../../lib/shopify-config.js'); -vi.mock('../../lib/graphql/admin/link-storefront.js'); -vi.mock('../../lib/graphql/admin/create-storefront.js'); -vi.mock('../../lib/graphql/admin/fetch-job.js'); -vi.mock('../../lib/shell.js', () => ({getCliCommand: () => 'h2'})); -vi.mock('@shopify/cli-kit/node/output', async () => { - return { - outputContent: () => ({value: ''}), - outputInfo: () => {}, - outputWarn: () => {}, - }; -}); -vi.mock('@shopify/cli-kit/node/ui', async () => { - return { - renderFatalError: vi.fn(), - renderSelectPrompt: vi.fn(), - renderSuccess: vi.fn(), - renderTasks: vi.fn(), - renderWarning: vi.fn(), - }; -}); -vi.mock('@shopify/cli-kit/node/git', async () => { - const actual = await vi.importActual('@shopify/cli-kit/node/git'); - return { - ...(actual as object), - getLatestGitCommit: vi.fn(), - ensureIsClean: vi.fn(), - }; -}); - -describe('deploy', () => { - const ADMIN_SESSION: AdminSession = { - token: 'abc123', - storeFqdn: 'my-shop.myshopify.com', - }; - - const FULL_SHOPIFY_CONFIG = { - shop: 'my-shop.myshopify.com', - shopName: 'My Shop', - email: 'email', - storefront: { - id: 'gid://shopify/HydrogenStorefront/1', - title: 'Hydrogen', - }, - }; - - const UNLINKED_SHOPIFY_CONFIG = { - ...FULL_SHOPIFY_CONFIG, - storefront: undefined, - }; - const originalExit = process.exit; - - const deployParams = { - force: false, - noJsonOutput: false, - path: './', - shop: 'snowdevil.myshopify.com', - publicDeployment: false, - metadataUrl: 'https://example.com', - metadataUser: 'user', - metadataVersion: '1.0.0', - }; - - const mockToken = { - accessToken: 'some-token', - allowedResource: 'some-resource', - appId: '1', - client: '1', - expiresAt: 'some-time', - namespace: 'some-namespace', - namespaceId: '1', - }; - - const expectedConfig = { - assetsDir: 'dist/client', - bugsnag: true, - deploymentUrl: 'https://oxygen.shopifyapps.com', - deploymentToken: mockToken, - verificationMaxDuration: 180, - metadata: { - url: deployParams.metadataUrl, - user: deployParams.metadataUser, - version: deployParams.metadataVersion, - }, - publicDeployment: deployParams.publicDeployment, - skipVerification: false, - rootPath: deployParams.path, - skipBuild: false, - workerOnly: false, - workerDir: 'dist/worker', - }; - - const expectedHooks = { - buildFunction: expect.any(Function), - onVerificationComplete: expect.any(Function), - onUploadFilesStart: expect.any(Function), - onUploadFilesComplete: expect.any(Function), - onVerificationError: expect.any(Function), - onUploadFilesError: expect.any(Function), - }; - - beforeEach(async () => { - process.exit = vi.fn() as any; - vi.mocked(login).mockResolvedValue({ - session: ADMIN_SESSION, - config: UNLINKED_SHOPIFY_CONFIG, - }); - vi.mocked(ciPlatform).mockReturnValue({isCI: false}); - vi.mocked(getStorefronts).mockResolvedValue([ - { - ...FULL_SHOPIFY_CONFIG.storefront, - parsedId: '1', - productionUrl: 'https://example.com', - }, - ]); - vi.mocked(renderSelectPrompt).mockResolvedValue(FULL_SHOPIFY_CONFIG.shop); - vi.mocked(createDeploy).mockResolvedValue( - 'https://a-lovely-deployment.com', - ); - vi.mocked(getOxygenDeploymentData).mockResolvedValue({ - oxygenDeploymentToken: 'some-encoded-token', - environments: [], - }); - vi.mocked(parseToken).mockReturnValue(mockToken); - }); - - afterEach(() => { - vi.resetAllMocks(); - process.exit = originalExit; - }); - - it('calls getOxygenDeploymentData with the correct parameters', async () => { - await oxygenDeploy(deployParams); - expect(getOxygenDeploymentData).toHaveBeenCalledWith({ - root: './', - flagShop: 'snowdevil.myshopify.com', - }); - expect(getOxygenDeploymentData).toHaveBeenCalledTimes(1); - }); - - it('calls createDeploy with the correct parameters', async () => { - await oxygenDeploy(deployParams); - - expect(vi.mocked(createDeploy)).toHaveBeenCalledWith({ - config: expectedConfig, - hooks: expectedHooks, - logger: deploymentLogger, - }); - expect(vi.mocked(renderSuccess)).toHaveBeenCalled; - }); - - it('errors when there are uncommited changes', async () => { - vi.mocked(ensureIsClean).mockRejectedValue( - new GitDirectoryNotCleanError('Uncommitted changes'), - ); - await expect(oxygenDeploy(deployParams)).rejects.toThrowError( - 'Uncommitted changes detected', - ); - expect(vi.mocked(createDeploy)).not.toHaveBeenCalled; - }); - - it('proceeds with warning and modified description when there are uncommited changes and the force flag is used', async () => { - vi.mocked(ensureIsClean).mockRejectedValue( - new GitDirectoryNotCleanError('Uncommitted changes'), - ); - vi.mocked(getLatestGitCommit).mockResolvedValue({ - hash: '123', - message: 'test commit', - date: '2021-01-01', - author_name: 'test author', - author_email: 'test@author.com', - body: 'test body', - refs: 'HEAD -> main', - }); - - await oxygenDeploy({ - ...deployParams, - force: true, - }); - - expect(vi.mocked(renderWarning)).toHaveBeenCalledWith({ - headline: 'No deployment description provided', - body: expect.anything(), - }); - expect(vi.mocked(createDeploy)).toHaveBeenCalledWith({ - config: { - ...expectedConfig, - environmentTag: 'main', - metadata: { - ...expectedConfig.metadata, - description: '123 with additional changes', - }, - }, - hooks: expectedHooks, - logger: deploymentLogger, - }); - }); - - it('proceeds with provided description without warning when there are uncommited changes and the force flag is used', async () => { - vi.mocked(ensureIsClean).mockRejectedValue( - new GitDirectoryNotCleanError('Uncommitted changes'), - ); - vi.mocked(getLatestGitCommit).mockResolvedValue({ - hash: '123', - message: 'test commit', - date: '2021-01-01', - author_name: 'test author', - author_email: 'test@author.com', - body: 'test body', - refs: 'HEAD -> main', - }); - - await oxygenDeploy({ - ...deployParams, - force: true, - metadataDescription: 'cool new stuff', - }); - - expect(vi.mocked(renderWarning)).not.toHaveBeenCalled; - expect(vi.mocked(createDeploy)).toHaveBeenCalledWith({ - config: { - ...expectedConfig, - environmentTag: 'main', - metadata: { - ...expectedConfig.metadata, - description: 'cool new stuff', - }, - }, - hooks: expectedHooks, - logger: deploymentLogger, - }); - }); - - it('calls createDeploy with the checked out branch name', async () => { - vi.mocked(getLatestGitCommit).mockResolvedValue({ - hash: '123', - message: 'test commit', - date: '2021-01-01', - author_name: 'test author', - author_email: 'test@author.com', - body: 'test body', - refs: 'HEAD -> main', - }); - - await oxygenDeploy(deployParams); - - expect(vi.mocked(createDeploy)).toHaveBeenCalledWith({ - config: {...expectedConfig, environmentTag: 'main'}, - hooks: expectedHooks, - logger: deploymentLogger, - }); - expect(vi.mocked(renderSuccess)).toHaveBeenCalled; - }); - - it('calls renderSelectPrompt when there are multiple environments', async () => { - vi.mocked(getOxygenDeploymentData).mockResolvedValue({ - oxygenDeploymentToken: 'some-encoded-token', - environments: [ - {name: 'production', branch: 'main'}, - {name: 'preview', branch: 'staging'}, - ], - }); - - await oxygenDeploy(deployParams); - - expect(vi.mocked(renderSelectPrompt)).toHaveBeenCalledWith({ - message: 'Select an environment to deploy to', - choices: [ - {label: 'production', value: 'main'}, - {label: 'preview', value: 'staging'}, - ], - }); - }); - - it('writes a file with JSON content in CI environments', async () => { - vi.mocked(ciPlatform).mockReturnValue({ - isCI: true, - name: 'github', - metadata: {}, - }); - const ciDeployParams = { - ...deployParams, - token: 'some-token', - metadataDescription: 'cool new stuff', - }; - - await oxygenDeploy(ciDeployParams); - - expect(vi.mocked(writeFile)).toHaveBeenCalledWith( - 'h2_deploy_log.json', - JSON.stringify({url: 'https://a-lovely-deployment.com'}), - ); - - vi.mocked(writeFile).mockClear(); - ciDeployParams.noJsonOutput = true; - await oxygenDeploy(ciDeployParams); - expect(vi.mocked(writeFile)).not.toHaveBeenCalled(); - }); - - it('handles error during uploadFiles', async () => { - const mockRenderFatalError = vi.fn(); - vi.mocked(renderFatalError).mockImplementation(mockRenderFatalError); - - const error = new Error('Wonky internet!'); - - vi.mocked(createDeploy).mockImplementation((options) => { - options.hooks?.onUploadFilesStart?.(); - options.hooks?.onUploadFilesError?.(error); - - return new Promise((_resolve, reject) => { - reject(error); - }) as Promise<string | undefined>; - }); - - try { - await oxygenDeploy(deployParams); - expect(true).toBe(false); - } catch (err) { - if (err instanceof AbortError) { - expect(err.message).toBe(error.message); - expect(err.tryMessage).toBe( - 'Check your connection and try again. If the problem persists, try again later or contact support.', - ); - } else { - expect(true).toBe(false); - } - } - }); - - it('handles error during deployment verification', async () => { - const mockRenderFatalError = vi.fn(); - vi.mocked(renderFatalError).mockImplementation(mockRenderFatalError); - - const error = new Error('Cloudflare is down!'); - - vi.mocked(createDeploy).mockImplementation((options) => { - options.hooks?.onUploadFilesStart?.(); - options.hooks?.onUploadFilesComplete?.(); - options.hooks?.onVerificationError?.(error); - - return new Promise((_resolve, reject) => { - reject(error); - }) as Promise<string | undefined>; - }); - - try { - await oxygenDeploy(deployParams); - expect(true).toBe(false); - } catch (err) { - if (err instanceof AbortError) { - expect(err.message).toBe(error.message); - expect(err.tryMessage).toBe( - 'Please verify the deployment status in the Shopify Admin and retry deploying if necessary.', - ); - } else { - expect(true).toBe(false); - } - } - }); -}); diff --git a/packages/cli/src/commands/hydrogen/deploy.ts b/packages/cli/src/commands/hydrogen/deploy.ts index eec824f18d..125e52fd48 100644 --- a/packages/cli/src/commands/hydrogen/deploy.ts +++ b/packages/cli/src/commands/hydrogen/deploy.ts @@ -1,39 +1,10 @@ import {Flags} from '@oclif/core'; import Command from '@shopify/cli-kit/node/base-command'; -import colors from '@shopify/cli-kit/node/colors'; -import { - outputContent, - outputInfo, - outputWarn, -} from '@shopify/cli-kit/node/output'; -import {AbortError} from '@shopify/cli-kit/node/error'; -import {writeFile} from '@shopify/cli-kit/node/fs'; -import { - ensureIsClean, - getLatestGitCommit, - GitDirectoryNotCleanError, -} from '@shopify/cli-kit/node/git'; -import {resolvePath} from '@shopify/cli-kit/node/path'; -import { - renderFatalError, - renderSelectPrompt, - renderSuccess, - renderTasks, - renderWarning, -} from '@shopify/cli-kit/node/ui'; +import {outputWarn} from '@shopify/cli-kit/node/output'; +import {renderWarning} from '@shopify/cli-kit/node/ui'; import {Logger, LogLevel} from '@shopify/cli-kit/node/output'; -import {ciPlatform} from '@shopify/cli-kit/node/context/local'; -import { - createDeploy, - DeploymentConfig, - DeploymentHooks, - parseToken, -} from '@shopify/oxygen-cli/deploy'; -import {commonFlags, flagsToCamelObject} from '../../lib/flags.js'; -import {getOxygenDeploymentData} from '../../lib/get-oxygen-deployment-data.js'; -import {OxygenDeploymentData} from '../../lib/graphql/admin/get-oxygen-data.js'; -import {runBuild} from './build.js'; +import {commonFlags} from '../../lib/flags.js'; export const deploymentLogger: Logger = ( message: string, @@ -108,277 +79,11 @@ export default class Deploy extends Command { static hidden = true; async run() { - const {flags} = await this.parse(Deploy); - const deploymentOptions = this.flagsToOxygenDeploymentOptions(flags); - - await oxygenDeploy(deploymentOptions) - .catch((error) => { - renderFatalError(error); - process.exit(1); - }) - .finally(() => { - // The Remix compiler hangs due to a bug in ESBuild: - // https://github.com/evanw/esbuild/issues/2727 - // The actual build has already finished so we can kill the process - process.exit(0); - }); - } - - private flagsToOxygenDeploymentOptions(flags: { - [x: string]: any; - }): OxygenDeploymentOptions { - const camelFlags = flagsToCamelObject(flags); - return { - ...camelFlags, - environmentTag: flags['env-branch'], - path: flags.path ? resolvePath(flags.path) : process.cwd(), - } as OxygenDeploymentOptions; - } -} - -interface OxygenDeploymentOptions { - environmentTag?: string; - force: boolean; - noJsonOutput: boolean; - path: string; - publicDeployment: boolean; - shop: string; - token?: string; - metadataDescription?: string; - metadataUrl?: string; - metadataUser?: string; - metadataVersion?: string; -} - -interface GitCommit { - refs: string; - hash: string; -} - -export async function oxygenDeploy( - options: OxygenDeploymentOptions, -): Promise<void> { - const { - environmentTag, - force: forceOnUncommitedChanges, - noJsonOutput, - path, - shop, - publicDeployment, - metadataUrl, - metadataUser, - metadataVersion, - } = options; - let {metadataDescription} = options; - - let isCleanGit = true; - try { - await ensureIsClean(path); - } catch (error) { - if (error instanceof GitDirectoryNotCleanError) { - isCleanGit = false; - } - - if (!forceOnUncommitedChanges && !isCleanGit) { - throw new AbortError('Uncommitted changes detected.', null, [ - [ - 'Commit your changes before deploying or use the ', - {command: '--force'}, - ' flag to deploy with uncommitted changes.', - ], - ]); - } - } - - const isCI = ciPlatform().isCI; - let token = options.token; - let branch: string | undefined; - let commitHash: string | undefined; - let deploymentData: OxygenDeploymentData | undefined; - let deploymentEnvironmentTag: string | undefined = undefined; - let gitCommit: GitCommit; - - try { - gitCommit = await getLatestGitCommit(path); - branch = (/HEAD -> ([^,]*)/.exec(gitCommit.refs) || [])[1]; - commitHash = gitCommit.hash; - } catch (error) { - outputWarn('Could not retrieve Git history.'); - branch = undefined; - } - - if (!metadataDescription && !isCleanGit) { renderWarning({ - headline: 'No deployment description provided', - body: [ - 'Deploying uncommited changes, but no description has been provided. Use the ', - {command: '--metadata-description'}, - 'flag to provide a description. If no description is provided, the description defaults to ', - {userInput: '<sha> with additional changes'}, - ' using the SHA of the last commit.', + body: 'Deploy command unavailable.', + nextSteps: [ + 'To use the deploy command, upgrade cli-hydrogen to v7.0.0+.', ], }); - metadataDescription = `${commitHash} with additional changes`; } - - if (!isCI) { - deploymentData = await getOxygenDeploymentData({ - root: path, - flagShop: shop, - }); - - if (!deploymentData) { - return; - } - - token = token || deploymentData.oxygenDeploymentToken; - } - - if (!token) { - const errMessage = isCI - ? [ - 'No deployment token provided. Use the ', - {command: '--token'}, - ' flag to provide a token.', - ] - : `Could not obtain an Oxygen deployment token, please try again or contact Shopify support.`; - throw new AbortError(errMessage); - } - - if (!isCI && !environmentTag && deploymentData?.environments) { - if (deploymentData.environments.length > 1) { - const choices = [ - ...deploymentData.environments.map(({name, branch}) => ({ - label: name, - value: branch, - })), - ]; - - deploymentEnvironmentTag = await renderSelectPrompt({ - message: 'Select an environment to deploy to', - choices, - defaultValue: branch, - }); - } else { - outputInfo( - `Using current checked out branch ${branch} as environment tag`, - ); - } - } - - let deploymentUrl = 'https://oxygen.shopifyapps.com'; - if (process.env.UNSAFE_SHOPIFY_HYDROGEN_DEPLOYMENT_URL) { - deploymentUrl = process.env.UNSAFE_SHOPIFY_HYDROGEN_DEPLOYMENT_URL; - outputWarn( - "Using a custom deployment service. Don't do this in production!", - ); - } - - const config: DeploymentConfig = { - assetsDir: 'dist/client', - bugsnag: true, - deploymentUrl, - deploymentToken: parseToken(token as string), - environmentTag: environmentTag || deploymentEnvironmentTag || branch, - verificationMaxDuration: 180, - metadata: { - ...(metadataDescription ? {description: metadataDescription} : {}), - ...(metadataUrl ? {url: metadataUrl} : {}), - ...(metadataUser ? {user: metadataUser} : {}), - ...(metadataVersion ? {version: metadataVersion} : {}), - }, - publicDeployment: publicDeployment, - skipVerification: false, - rootPath: path, - skipBuild: false, - workerOnly: false, - workerDir: 'dist/worker', - }; - - let resolveUpload: () => void; - const uploadPromise = new Promise<void>((resolve) => { - resolveUpload = resolve; - }); - - let resolveHealthCheck: () => void; - const healthCheckPromise = new Promise<void>((resolve) => { - resolveHealthCheck = resolve; - }); - - let deployError: AbortError | null = null; - let resolveDeploy: () => void; - let rejectDeploy: (reason?: AbortError) => void; - const deployPromise = new Promise<void>((resolve, reject) => { - resolveDeploy = resolve; - rejectDeploy = reject; - }); - - const hooks: DeploymentHooks = { - buildFunction: async (assetPath: string | undefined): Promise<void> => { - outputInfo( - outputContent`${colors.whiteBright('Building project...')}`.value, - ); - await runBuild({ - directory: path, - assetPath, - sourcemap: false, - useCodegen: false, - }); - }, - onVerificationComplete: () => resolveHealthCheck(), - onUploadFilesStart: () => uploadStart(), - onUploadFilesComplete: () => resolveUpload(), - onVerificationError: (error: Error) => { - deployError = new AbortError( - error.message, - 'Please verify the deployment status in the Shopify Admin and retry deploying if necessary.', - ); - }, - onUploadFilesError: (error: Error) => { - deployError = new AbortError( - error.message, - 'Check your connection and try again. If the problem persists, try again later or contact support.', - ); - }, - }; - - const uploadStart = async () => { - outputInfo( - outputContent`${colors.whiteBright('Deploying to Oxygen..\n')}`.value, - ); - await renderTasks([ - { - title: 'Uploading files', - task: async () => await uploadPromise, - }, - { - title: 'Verifying deployment', - task: async () => await healthCheckPromise, - }, - ]); - }; - - await createDeploy({config, hooks, logger: deploymentLogger}) - .then(async (url: string | undefined) => { - const deploymentType = config.publicDeployment ? 'public' : 'private'; - renderSuccess({ - body: ['Successfully deployed to Oxygen'], - nextSteps: [ - [ - `Open ${url!} in your browser to view your ${deploymentType} deployment`, - ], - ], - }); - // in CI environments, output to a file so consequent steps can access the URL - // the formatting of this file is likely to change in future versions. - if (isCI && !noJsonOutput) { - await writeFile('h2_deploy_log.json', JSON.stringify({url: url!})); - } - resolveDeploy(); - }) - .catch((error) => { - rejectDeploy(deployError || error); - }); - - return deployPromise; }