From 11879b175d78e3326de090a56a044d1e55d0bae8 Mon Sep 17 00:00:00 2001 From: Alok Swamy Date: Mon, 25 Mar 2024 10:06:14 -0400 Subject: [PATCH] Add `--env` & deprecate `--env-branch` flag (#1841) - Adding --env flag to standardize how we refer to environments in the CLI - pass in an environment's handle you can view from running env list - remove existing --env flag which was used to identify environment by name (used only by env push__unstable) - deprecate --env-branch from env pull, env push__unstable, deploy, preview, dev-vite, dev --- .changeset/lovely-donkeys-destroy.md | 10 ++ packages/cli/oclif.manifest.json | 92 ++++++++++++++-- .../cli/src/commands/hydrogen/deploy.test.ts | 104 +++++++++++++++++- packages/cli/src/commands/hydrogen/deploy.ts | 41 +++++-- .../cli/src/commands/hydrogen/dev-vite.ts | 4 + packages/cli/src/commands/hydrogen/dev.ts | 12 +- .../src/commands/hydrogen/env/list.test.ts | 57 +++------- .../cli/src/commands/hydrogen/env/list.ts | 30 +++-- .../src/commands/hydrogen/env/pull.test.ts | 53 +++++++-- .../cli/src/commands/hydrogen/env/pull.ts | 24 +++- .../hydrogen/env/push__unstable.test.ts | 91 +++++---------- .../commands/hydrogen/env/push__unstable.ts | 45 ++------ packages/cli/src/commands/hydrogen/preview.ts | 10 +- packages/cli/src/lib/common.ts | 40 +++++++ .../cli/src/lib/environment-variables.test.ts | 26 ++++- packages/cli/src/lib/environment-variables.ts | 46 +++++--- packages/cli/src/lib/flags.ts | 24 ++-- .../lib/get-oxygen-deployment-data.test.ts | 2 + .../src/lib/graphql/admin/get-oxygen-data.ts | 2 + .../graphql/admin/list-environments.test.ts | 18 +-- .../lib/graphql/admin/list-environments.ts | 4 +- .../lib/graphql/admin/pull-variables.test.ts | 6 +- .../src/lib/graphql/admin/pull-variables.ts | 8 +- .../cli/src/lib/graphql/admin/test-helper.ts | 39 +++++++ 24 files changed, 555 insertions(+), 233 deletions(-) create mode 100644 .changeset/lovely-donkeys-destroy.md create mode 100644 packages/cli/src/lib/common.ts create mode 100644 packages/cli/src/lib/graphql/admin/test-helper.ts diff --git a/.changeset/lovely-donkeys-destroy.md b/.changeset/lovely-donkeys-destroy.md new file mode 100644 index 0000000000..c4d4de4af6 --- /dev/null +++ b/.changeset/lovely-donkeys-destroy.md @@ -0,0 +1,10 @@ +--- +'@shopify/cli-hydrogen': minor +--- + +`--env` flag has deprecated the `--env-branch` flag for several Hydrogen CLI commands + +- `--env` will allow the user to provide an environment's handle when performing Hydrogen CLI commands + - Run `env list` to display all the environments and its associated handles +- All Hydrogen CLI commands that contain the `--env-branch` flag will also contain the `--env` flag +- `--env-branch` flag will be deprecated on all Hydrogen CLI commands diff --git a/packages/cli/oclif.manifest.json b/packages/cli/oclif.manifest.json index 409f9405fb..92daae2223 100644 --- a/packages/cli/oclif.manifest.json +++ b/packages/cli/oclif.manifest.json @@ -261,10 +261,24 @@ "args": {}, "description": "Builds and deploys a Hydrogen storefront to Oxygen.", "flags": { + "env": { + "description": "Specifies the environment to perform the operation using its handle. Run `env list` to view an environment's handle ", + "exclusive": [ + "env-branch" + ], + "name": "env", + "hasDynamicHelp": false, + "multiple": false, + "type": "option" + }, "env-branch": { - "description": "Environment branch (tag) for environment to deploy to.", + "deprecated": { + "to": "env", + "message": "--env-branch is deprecated. Use --env instead." + }, + "description": "Specifies the environment to perform the operation using its Git branch name.", + "env": "SHOPIFY_HYDROGEN_ENVIRONMENT_BRANCH", "name": "env-branch", - "required": false, "hasDynamicHelp": false, "multiple": false, "type": "option" @@ -494,9 +508,22 @@ "allowNo": false, "type": "boolean" }, + "env": { + "description": "Specifies the environment to perform the operation using its handle. Run `env list` to view an environment's handle ", + "exclusive": [ + "env-branch" + ], + "name": "env", + "hasDynamicHelp": false, + "multiple": false, + "type": "option" + }, "env-branch": { - "char": "e", - "description": "Specifies the environment to pull variables from using its Git branch name.", + "deprecated": { + "to": "env", + "message": "--env-branch is deprecated. Use --env instead." + }, + "description": "Specifies the environment to perform the operation using its Git branch name.", "env": "SHOPIFY_HYDROGEN_ENVIRONMENT_BRANCH", "name": "env-branch", "hasDynamicHelp": false, @@ -624,9 +651,22 @@ "multiple": false, "type": "option" }, + "env": { + "description": "Specifies the environment to perform the operation using its handle. Run `env list` to view an environment's handle ", + "exclusive": [ + "env-branch" + ], + "name": "env", + "hasDynamicHelp": false, + "multiple": false, + "type": "option" + }, "env-branch": { - "char": "e", - "description": "Specifies the environment to pull variables from using its Git branch name.", + "deprecated": { + "to": "env", + "message": "--env-branch is deprecated. Use --env instead." + }, + "description": "Specifies the environment to perform the operation using its Git branch name.", "env": "SHOPIFY_HYDROGEN_ENVIRONMENT_BRANCH", "name": "env-branch", "hasDynamicHelp": false, @@ -980,9 +1020,22 @@ "allowNo": false, "type": "boolean" }, + "env": { + "description": "Specifies the environment to perform the operation using its handle. Run `env list` to view an environment's handle ", + "exclusive": [ + "env-branch" + ], + "name": "env", + "hasDynamicHelp": false, + "multiple": false, + "type": "option" + }, "env-branch": { - "char": "e", - "description": "Specifies the environment to pull variables from using its Git branch name.", + "deprecated": { + "to": "env", + "message": "--env-branch is deprecated. Use --env instead." + }, + "description": "Specifies the environment to perform the operation using its Git branch name.", "env": "SHOPIFY_HYDROGEN_ENVIRONMENT_BRANCH", "name": "env-branch", "hasDynamicHelp": false, @@ -1316,9 +1369,22 @@ "args": {}, "description": "Populate your .env with variables from your Hydrogen storefront.", "flags": { + "env": { + "description": "Specifies the environment to perform the operation using its handle. Run `env list` to view an environment's handle ", + "exclusive": [ + "env-branch" + ], + "name": "env", + "hasDynamicHelp": false, + "multiple": false, + "type": "option" + }, "env-branch": { - "char": "e", - "description": "Specifies the environment to pull variables from using its Git branch name.", + "deprecated": { + "to": "env", + "message": "--env-branch is deprecated. Use --env instead." + }, + "description": "Specifies the environment to perform the operation using its Git branch name.", "env": "SHOPIFY_HYDROGEN_ENVIRONMENT_BRANCH", "name": "env-branch", "hasDynamicHelp": false, @@ -1364,8 +1430,10 @@ "description": "Push environment variables from the local .env file to your linked Hydrogen storefront.", "flags": { "env": { - "description": "Specifies an environment's name when using remote environment variables.", - "env": "SHOPIFY_HYDROGEN_ENVIRONMENT_NAME", + "description": "Specifies the environment to perform the operation using its handle. Run `env list` to view an environment's handle ", + "exclusive": [ + "env-branch" + ], "name": "env", "hasDynamicHelp": false, "multiple": false, diff --git a/packages/cli/src/commands/hydrogen/deploy.test.ts b/packages/cli/src/commands/hydrogen/deploy.test.ts index 44824528fc..dedc285390 100644 --- a/packages/cli/src/commands/hydrogen/deploy.test.ts +++ b/packages/cli/src/commands/hydrogen/deploy.test.ts @@ -221,6 +221,92 @@ describe('deploy', () => { }); }); + it('calls createDeploy against a environment selected by env', async () => { + vi.mocked(getOxygenDeploymentData).mockResolvedValue({ + oxygenDeploymentToken: 'some-encoded-token', + environments: [ + { + name: 'Production', + handle: 'production', + branch: 'main', + type: 'PRODUCTION', + }, + {name: 'Preview', handle: 'preview', branch: null, type: 'PREVIEW'}, + {name: 'Staging', handle: 'staging', branch: 'stage-1', type: 'CUSTOM'}, + ], + }); + + await runDeploy({ + ...deployParams, + env: 'staging', + }); + + expect(vi.mocked(createDeploy)).toHaveBeenCalledWith({ + config: { + ...expectedConfig, + environmentTag: 'stage-1', + }, + hooks: expectedHooks, + logger: deploymentLogger, + }); + expect(vi.mocked(renderSuccess)).toHaveBeenCalled; + }); + + it('calls createDeploy against a environment selected by envBranch', async () => { + vi.mocked(getOxygenDeploymentData).mockResolvedValue({ + oxygenDeploymentToken: 'some-encoded-token', + environments: [ + { + name: 'Production', + handle: 'production', + branch: 'main', + type: 'PRODUCTION', + }, + {name: 'Preview', handle: 'preview', branch: null, type: 'PREVIEW'}, + {name: 'Staging', handle: 'staging', branch: 'stage-1', type: 'CUSTOM'}, + ], + }); + + await runDeploy({ + ...deployParams, + envBranch: 'stage-1', + }); + + expect(vi.mocked(createDeploy)).toHaveBeenCalledWith({ + config: { + ...expectedConfig, + environmentTag: 'stage-1', + }, + hooks: expectedHooks, + logger: deploymentLogger, + }); + expect(vi.mocked(renderSuccess)).toHaveBeenCalled; + }); + + it("errors when the env provided doesn't match any environment", async () => { + await expect( + runDeploy({ + ...deployParams, + env: 'fake-handle', + }), + ).rejects.toThrowError('Environment not found'); + }); + + it('errors when env is provided while running in CI', async () => { + vi.mocked(ciPlatform).mockReturnValue({ + isCI: true, + name: 'github', + metadata: {}, + }); + + await expect( + runDeploy({ + ...deployParams, + env: 'fake-handle', + }), + ).rejects.toThrowError("Can't specify an environment handle in CI"); + }); + it('errors when there are uncommited changes', async () => { vi.mocked(ensureIsClean).mockRejectedValue( new GitDirectoryNotCleanError('Uncommitted changes'), @@ -328,8 +414,13 @@ describe('deploy', () => { vi.mocked(getOxygenDeploymentData).mockResolvedValue({ oxygenDeploymentToken: 'some-encoded-token', environments: [ - {name: 'Production', branch: 'main', type: 'PRODUCTION'}, - {name: 'Preview', branch: null, type: 'PREVIEW'}, + { + name: 'Production', + handle: 'production', + branch: 'main', + type: 'PRODUCTION', + }, + {name: 'Preview', handle: 'preview', branch: null, type: 'PREVIEW'}, ], }); @@ -358,8 +449,13 @@ describe('deploy', () => { vi.mocked(getOxygenDeploymentData).mockResolvedValue({ oxygenDeploymentToken: 'some-encoded-token', environments: [ - {name: 'Production', branch: 'main', type: 'PRODUCTION'}, - {name: 'Preview', branch: null, type: 'PREVIEW'}, + { + name: 'Production', + handle: 'production', + branch: 'main', + type: 'PRODUCTION', + }, + {name: 'Preview', handle: 'preview', branch: null, type: 'PREVIEW'}, ], }); diff --git a/packages/cli/src/commands/hydrogen/deploy.ts b/packages/cli/src/commands/hydrogen/deploy.ts index 6e57976c9b..00cca05934 100644 --- a/packages/cli/src/commands/hydrogen/deploy.ts +++ b/packages/cli/src/commands/hydrogen/deploy.ts @@ -32,6 +32,10 @@ import { parseToken, } from '@shopify/oxygen-cli/deploy'; +import { + findEnvironmentByBranchOrThrow, + findEnvironmentOrThrow, +} from '../../lib/common.js'; 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'; @@ -54,10 +58,8 @@ export const deploymentLogger: Logger = ( export default class Deploy extends Command { static description = 'Builds and deploys a Hydrogen storefront to Oxygen.'; static flags: any = { - 'env-branch': Flags.string({ - description: 'Environment branch (tag) for environment to deploy to.', - required: false, - }), + ...commonFlags.env, + ...commonFlags.envBranch, 'env-file': Flags.string({ description: 'Path to an environment file to override existing environment variables for the deployment.', @@ -161,7 +163,6 @@ export default class Deploy extends Command { return { ...camelFlags, defaultEnvironment: flags.preview, - environmentTag: flags['env-branch'], environmentFile: flags['env-file'], path: flags.path ? resolvePath(flags.path) : process.cwd(), } as OxygenDeploymentOptions; @@ -172,7 +173,8 @@ interface OxygenDeploymentOptions { authBypassToken: boolean; buildCommand?: string; defaultEnvironment: boolean; - environmentTag?: string; + env?: string; + envBranch?: string; environmentFile?: string; force: boolean; noVerify: boolean; @@ -218,7 +220,8 @@ export async function runDeploy( authBypassToken: generateAuthBypassToken, buildCommand, defaultEnvironment, - environmentTag, + env: envHandle, + envBranch, environmentFile, force: forceOnUncommitedChanges, noVerify, @@ -295,6 +298,15 @@ export async function runDeploy( ); } + let userProvidedEnvironmentTag: string | null = null; + + if (isCI && envHandle) { + throw new AbortError( + "Can't specify an environment handle in CI", + 'Environments are automatically picked up by the current Git branch.', + ); + } + if (!isCI) { deploymentData = await getOxygenDeploymentData({ root, @@ -306,6 +318,15 @@ export async function runDeploy( } token = token || deploymentData.oxygenDeploymentToken; + + if (envHandle) { + userProvidedEnvironmentTag = findEnvironmentOrThrow( + deploymentData.environments || [], + envHandle, + ).branch; + } else if (envBranch) { + userProvidedEnvironmentTag = envBranch; + } } if (!token) { @@ -322,7 +343,7 @@ export async function runDeploy( if ( !isCI && !defaultEnvironment && - !environmentTag && + !userProvidedEnvironmentTag && deploymentData?.environments ) { if (deploymentData.environments.length > 1) { @@ -386,7 +407,9 @@ export async function runDeploy( defaultEnvironment: defaultEnvironment || isPreview, deploymentToken: parseToken(token as string), environmentTag: - environmentTag || deploymentEnvironmentTag || fallbackEnvironmentTag, + userProvidedEnvironmentTag || + deploymentEnvironmentTag || + fallbackEnvironmentTag, generateAuthBypassToken, verificationMaxDuration: 180, metadata: { diff --git a/packages/cli/src/commands/hydrogen/dev-vite.ts b/packages/cli/src/commands/hydrogen/dev-vite.ts index 13b2496e0a..d3e95c1899 100644 --- a/packages/cli/src/commands/hydrogen/dev-vite.ts +++ b/packages/cli/src/commands/hydrogen/dev-vite.ts @@ -50,6 +50,7 @@ export default class DevVite extends Command { default: false, required: false, }), + ...commonFlags.env, ...commonFlags.envBranch, 'disable-version-check': Flags.boolean({ description: 'Skip the version check when running `hydrogen dev`', @@ -87,6 +88,7 @@ type DevOptions = { disableVirtualRoutes?: boolean; disableVersionCheck?: boolean; envBranch?: string; + env?: string; debug?: boolean; sourcemap?: boolean; inspectorPort: number; @@ -104,6 +106,7 @@ export async function runDev({ codegenConfigPath, disableVirtualRoutes, envBranch, + env: envHandle, debug = false, disableVersionCheck = false, inspectorPort, @@ -127,6 +130,7 @@ export async function runDev({ getAllEnvironmentVariables({ root, envBranch, + envHandle, fetchRemote, localVariables, }), diff --git a/packages/cli/src/commands/hydrogen/dev.ts b/packages/cli/src/commands/hydrogen/dev.ts index 31b893278c..8f07a83633 100644 --- a/packages/cli/src/commands/hydrogen/dev.ts +++ b/packages/cli/src/commands/hydrogen/dev.ts @@ -59,6 +59,7 @@ export default class Dev extends Command { }), ...commonFlags.debug, ...commonFlags.inspectorPort, + ...commonFlags.env, ...commonFlags.envBranch, 'disable-version-check': Flags.boolean({ description: 'Skip the version check when running `hydrogen dev`', @@ -93,6 +94,7 @@ type DevOptions = { codegenConfigPath?: string; disableVirtualRoutes?: boolean; disableVersionCheck?: boolean; + env?: string; envBranch?: string; debug?: boolean; sourcemap?: boolean; @@ -108,6 +110,7 @@ export async function runDev({ legacyRuntime = false, codegenConfigPath, disableVirtualRoutes, + env: envHandle, envBranch, debug = false, sourcemap = true, @@ -177,7 +180,13 @@ export async function runDev({ assertOxygenChecks(remixConfig); const envPromise = backgroundPromise.then(({fetchRemote, localVariables}) => - getAllEnvironmentVariables({root, fetchRemote, envBranch, localVariables}), + getAllEnvironmentVariables({ + root, + fetchRemote, + envBranch, + envHandle, + localVariables, + }), ); const [{watch}, {createFileWatchCache}] = await Promise.all([ @@ -337,6 +346,7 @@ export async function runDev({ root, fetchRemote, envBranch, + envHandle, }), }); } diff --git a/packages/cli/src/commands/hydrogen/env/list.test.ts b/packages/cli/src/commands/hydrogen/env/list.test.ts index 1e9a75df0f..bbd0a6ecb3 100644 --- a/packages/cli/src/commands/hydrogen/env/list.test.ts +++ b/packages/cli/src/commands/hydrogen/env/list.test.ts @@ -3,10 +3,8 @@ import {mockAndCaptureOutput} from '@shopify/cli-kit/node/testing/output'; import {inTemporaryDirectory} from '@shopify/cli-kit/node/fs'; import {renderConfirmationPrompt} from '@shopify/cli-kit/node/ui'; -import { - getStorefrontEnvironments, - type Environment, -} from '../../../lib/graphql/admin/list-environments.js'; +import {getStorefrontEnvironments} from '../../../lib/graphql/admin/list-environments.js'; +import {dummyListEnvironments} from '../../../lib/graphql/admin/test-helper.js'; import {type AdminSession, login} from '../../../lib/auth.js'; import { renderMissingLink, @@ -49,48 +47,15 @@ describe('listEnvironments', () => { }, }; - const PRODUCTION_ENVIRONMENT: Environment = { - id: 'gid://shopify/HydrogenStorefrontEnvironment/1', - branch: 'main', - type: 'PRODUCTION', - name: 'Production', - createdAt: '2023-02-16T22:35:42Z', - url: 'https://oxygen-123.example.com', - }; - - const CUSTOM_ENVIRONMENT: Environment = { - id: 'gid://shopify/HydrogenStorefrontEnvironment/3', - branch: 'staging', - type: 'CUSTOM', - name: 'Staging', - createdAt: '2023-05-08T20:52:29Z', - url: 'https://oxygen-456.example.com', - }; - - const PREVIEW_ENVIRONMENT: Environment = { - id: 'gid://shopify/HydrogenStorefrontEnvironment/2', - branch: null, - type: 'PREVIEW', - name: 'Preview', - createdAt: '2023-02-16T22:35:42Z', - url: null, - }; - beforeEach(async () => { vi.mocked(login).mockResolvedValue({ session: ADMIN_SESSION, config: SHOPIFY_CONFIG, }); - vi.mocked(getStorefrontEnvironments).mockResolvedValue({ - id: 'gid://shopify/HydrogenStorefront/1', - productionUrl: 'https://example.com', - environments: [ - PRODUCTION_ENVIRONMENT, - CUSTOM_ENVIRONMENT, - PREVIEW_ENVIRONMENT, - ], - }); + vi.mocked(getStorefrontEnvironments).mockResolvedValue( + dummyListEnvironments(SHOPIFY_CONFIG.storefront.id), + ); }); afterEach(() => { @@ -119,11 +84,15 @@ describe('listEnvironments', () => { /Showing 3 environments for the Hydrogen storefront Existing Link/i, ); - expect(output.info()).toMatch(/Production \(Branch: main\)/); - expect(output.info()).toMatch(/https:\/\/example\.com/); - expect(output.info()).toMatch(/Staging \(Branch: staging\)/); + expect(output.info()).toMatch( + /Production \(handle: production, branch: main\)/, + ); + expect(output.info()).toMatch(/https:\/\/my-shop\.myshopify\.com/); + expect(output.info()).toMatch( + /Staging \(handle: staging, branch: staging\)/, + ); expect(output.info()).toMatch(/https:\/\/oxygen-456\.example\.com/); - expect(output.info()).toMatch(/Preview/); + expect(output.info()).toMatch(/Preview \(handle: preview\)/); }); }); diff --git a/packages/cli/src/commands/hydrogen/env/list.ts b/packages/cli/src/commands/hydrogen/env/list.ts index 35cd1cb2ae..6513667c8e 100644 --- a/packages/cli/src/commands/hydrogen/env/list.ts +++ b/packages/cli/src/commands/hydrogen/env/list.ts @@ -88,8 +88,6 @@ export async function runEnvList({path: root = process.cwd()}: Flags) { ); storefront.environments.push(previewEnvironment[0]!); - outputNewline(); - outputInfo( pluralizedEnvironments({ environments: storefront.environments, @@ -97,7 +95,7 @@ export async function runEnvList({path: root = process.cwd()}: Flags) { }).toString(), ); - storefront.environments.forEach(({name, branch, type, url}) => { + storefront.environments.forEach(({name, handle, branch, type, url}) => { outputNewline(); // If a custom domain is set it will be available on the storefront itself @@ -105,17 +103,31 @@ export async function runEnvList({path: root = process.cwd()}: Flags) { const environmentUrl = type === 'PRODUCTION' ? storefront.productionUrl : url; + const metadata = { + handle, + branch, + }; + + const metadataStringified = Object.entries(metadata) + .reduce((acc, [key, val]) => { + if (val) { + acc.push(`${key}: ${val}`); + } + return acc; + }, [] as Array) + .join(', '); + outputInfo( - outputContent`${colors.whiteBright(name)}${ - branch ? ` ${colors.dim(`(Branch: ${branch})`)}` : '' - }`.value, + outputContent`${colors.bold(name)} ${colors.dim( + `(${metadataStringified})`, + )}`.value, ); if (environmentUrl) { - outputInfo( - outputContent` ${colors.whiteBright(environmentUrl)}`.value, - ); + outputInfo(outputContent` ${environmentUrl}`.value); } }); + + outputNewline(); } const pluralizedEnvironments = ({ diff --git a/packages/cli/src/commands/hydrogen/env/pull.test.ts b/packages/cli/src/commands/hydrogen/env/pull.test.ts index c3a4df84e3..614ce07ef4 100644 --- a/packages/cli/src/commands/hydrogen/env/pull.test.ts +++ b/packages/cli/src/commands/hydrogen/env/pull.test.ts @@ -10,7 +10,9 @@ import {joinPath} from '@shopify/cli-kit/node/path'; import {renderConfirmationPrompt} from '@shopify/cli-kit/node/ui'; import {type AdminSession, login} from '../../../lib/auth.js'; +import {getStorefrontEnvironments} from '../../../lib/graphql/admin/list-environments.js'; import {getStorefrontEnvVariables} from '../../../lib/graphql/admin/pull-variables.js'; +import {dummyListEnvironments} from '../../../lib/graphql/admin/test-helper.js'; import {runEnvPull} from './pull.js'; import { @@ -31,6 +33,7 @@ vi.mock('@shopify/cli-kit/node/ui', async () => { vi.mock('../link.js'); vi.mock('../../../lib/auth.js'); vi.mock('../../../lib/render-errors.js'); +vi.mock('../../../lib/graphql/admin/list-environments.js'); vi.mock('../../../lib/graphql/admin/pull-variables.js'); describe('pullVariables', () => { @@ -55,6 +58,10 @@ describe('pullVariables', () => { config: SHOPIFY_CONFIG, }); + vi.mocked(getStorefrontEnvironments).mockResolvedValue( + dummyListEnvironments(SHOPIFY_CONFIG.storefront.id), + ); + vi.mocked(getStorefrontEnvVariables).mockResolvedValue({ id: SHOPIFY_CONFIG.storefront.id, environmentVariables: [ @@ -81,15 +88,45 @@ describe('pullVariables', () => { mockAndCaptureOutput().clear(); }); - it('calls getStorefrontEnvVariables', async () => { - await inTemporaryDirectory(async (tmpDir) => { - await runEnvPull({path: tmpDir, envBranch: 'staging'}); + describe('when environment is provided', () => { + it('calls getStorefrontEnvVariables when handle is provided', async () => { + await inTemporaryDirectory(async (tmpDir) => { + await runEnvPull({path: tmpDir, env: 'staging'}); - expect(getStorefrontEnvVariables).toHaveBeenCalledWith( - ADMIN_SESSION, - SHOPIFY_CONFIG.storefront.id, - 'staging', - ); + expect(getStorefrontEnvVariables).toHaveBeenCalledWith( + ADMIN_SESSION, + SHOPIFY_CONFIG.storefront.id, + 'staging', + ); + }); + }); + + it('calls getStorefrontEnvVariables when branch is provided', async () => { + await inTemporaryDirectory(async (tmpDir) => { + await runEnvPull({path: tmpDir, envBranch: 'main'}); + + expect(getStorefrontEnvVariables).toHaveBeenCalledWith( + ADMIN_SESSION, + SHOPIFY_CONFIG.storefront.id, + 'production', + ); + }); + }); + + it('throws error if handle does not map to any environment', async () => { + await inTemporaryDirectory(async (tmpDir) => { + await expect( + runEnvPull({path: tmpDir, env: 'fake'}), + ).rejects.toThrowError('Environment not found'); + }); + }); + + it('throws error if branch does not map to any environment', async () => { + await inTemporaryDirectory(async (tmpDir) => { + await expect( + runEnvPull({path: tmpDir, envBranch: 'fake'}), + ).rejects.toThrowError('Environment not found'); + }); }); }); diff --git a/packages/cli/src/commands/hydrogen/env/pull.ts b/packages/cli/src/commands/hydrogen/env/pull.ts index 1dd9c02ce0..dd6591cc73 100644 --- a/packages/cli/src/commands/hydrogen/env/pull.ts +++ b/packages/cli/src/commands/hydrogen/env/pull.ts @@ -19,11 +19,16 @@ import colors from '@shopify/cli-kit/node/colors'; import {commonFlags, flagsToCamelObject} from '../../../lib/flags.js'; import {login} from '../../../lib/auth.js'; import {getCliCommand} from '../../../lib/shell.js'; +import { + findEnvironmentByBranchOrThrow, + findEnvironmentOrThrow, +} from '../../../lib/common.js'; import { renderMissingLink, renderMissingStorefront, } from '../../../lib/render-errors.js'; import {linkStorefront} from '../link.js'; +import {getStorefrontEnvironments} from '../../../lib/graphql/admin/list-environments.js'; import {getStorefrontEnvVariables} from '../../../lib/graphql/admin/pull-variables.js'; export default class EnvPull extends Command { @@ -31,6 +36,7 @@ export default class EnvPull extends Command { 'Populate your .env with variables from your Hydrogen storefront.'; static flags = { + ...commonFlags.env, ...commonFlags.envBranch, ...commonFlags.path, ...commonFlags.force, @@ -43,12 +49,14 @@ export default class EnvPull extends Command { } interface Flags { + env?: string; envBranch?: string; force?: boolean; path?: string; } export async function runEnvPull({ + env: envHandle, envBranch, path: root = process.cwd(), force, @@ -76,10 +84,24 @@ export async function runEnvPull({ if (!config.storefront?.id) return; + if (envHandle || envBranch) { + const environments = + (await getStorefrontEnvironments(session, config.storefront.id)) + ?.environments || []; + if (envHandle) { + findEnvironmentOrThrow(environments, envHandle); + } else if (envBranch) { + envHandle = findEnvironmentByBranchOrThrow( + environments, + envBranch, + ).handle; + } + } + const storefront = await getStorefrontEnvVariables( session, config.storefront.id, - envBranch, + envHandle, ); if (!storefront) { diff --git a/packages/cli/src/commands/hydrogen/env/push__unstable.test.ts b/packages/cli/src/commands/hydrogen/env/push__unstable.test.ts index 459e729ff6..158794d271 100644 --- a/packages/cli/src/commands/hydrogen/env/push__unstable.test.ts +++ b/packages/cli/src/commands/hydrogen/env/push__unstable.test.ts @@ -14,6 +14,7 @@ import { getStorefrontEnvironments, } from '../../../lib/graphql/admin/list-environments.js'; import {pushStorefrontEnvVariables} from '../../../lib/graphql/admin/push-variables.js'; +import {dummyListEnvironments} from '../../../lib/graphql/admin/test-helper.js'; import {runEnvPush} from './push__unstable.js'; @@ -49,32 +50,9 @@ const SHOPIFY_CONFIG = { }, }; -const PRODUCTION_ENV: Environment = { - id: 'gid://shopify/HydrogenStorefrontEnvironment/1', - createdAt: '2024-01-01', - branch: 'main', - name: 'Production', - type: 'PRODUCTION', - url: 'production.com', -}; - -const PREVIEW_ENV: Environment = { - id: 'gid://shopify/HydrogenStorefrontEnvironment/2', - createdAt: '2024-01-01', - branch: null, - name: 'Preview', - type: 'PREVIEW', - url: null, -}; - -const CUSTOM_ENV: Environment = { - id: 'gid://shopify/HydrogenStorefrontEnvironment/3', - createdAt: '2024-01-01', - branch: 'staging', - name: 'Staging', - type: 'CUSTOM', - url: 'custom.com', -}; +const storefrontWithEnvironments = dummyListEnvironments( + SHOPIFY_CONFIG.storefront.id, +); const outputMock = mockAndCaptureOutput(); const processExit = vi.spyOn(process, 'exit'); @@ -110,11 +88,9 @@ describe('pushVariables', () => { ], }); - vi.mocked(getStorefrontEnvironments).mockResolvedValue({ - id: SHOPIFY_CONFIG.storefront.id, - productionUrl: 'prod.com', - environments: [PRODUCTION_ENV, PREVIEW_ENV, CUSTOM_ENV], - }); + vi.mocked(getStorefrontEnvironments).mockResolvedValue( + storefrontWithEnvironments, + ); vi.mocked(pushStorefrontEnvVariables).mockResolvedValue({ userErrors: [], @@ -135,7 +111,7 @@ describe('pushVariables', () => { await writeFile(filePath, 'EXISTING_TOKEN=1\nSECOND_TOKEN=2'); await expect( - runEnvPush({path: tmpDir, env: 'Preview'}), + runEnvPush({path: tmpDir, env: 'preview'}), ).resolves.not.toThrow(); expect(getStorefrontEnvironments).toHaveBeenCalledWith( @@ -156,13 +132,15 @@ describe('pushVariables', () => { const filePath = joinPath(tmpDir, '.env'); await writeFile(filePath, 'EXISTING_TOKEN=1\nSECOND_TOKEN=2'); await expect( - runEnvPush({path: tmpDir, env: 'Preview'}), + runEnvPush({path: tmpDir, env: 'preview'}), ).rejects.toThrowError('No environments found'); }); }); it('prompts the user to select an environment', async () => { - vi.mocked(renderSelectPrompt).mockResolvedValue(PREVIEW_ENV.id); + vi.mocked(renderSelectPrompt).mockResolvedValue( + storefrontWithEnvironments.environments[0]!.id, + ); await inTemporaryDirectory(async (tmpDir) => { const filePath = joinPath(tmpDir, '.env'); @@ -170,7 +148,8 @@ describe('pushVariables', () => { await expect(runEnvPush({path: tmpDir})).resolves.not.toThrow(); expect(renderSelectPrompt).toHaveBeenCalledWith({ - message: 'Select a set of environment variables to overwrite:', + message: + 'Select an environment to overwrite its environment variables:', choices: [ expect.objectContaining({label: expect.stringContaining('Preview')}), expect.objectContaining({label: expect.stringContaining('Staging')}), @@ -182,42 +161,30 @@ describe('pushVariables', () => { }); }); - describe('when an environment is passed', () => { + describe('when env is passed', () => { it('errors when the environment does not match graphql', async () => { await inTemporaryDirectory(async (tmpDir) => { const filePath = joinPath(tmpDir, '.env'); await writeFile(filePath, 'EXISTING_TOKEN=1\nSECOND_TOKEN=2'); await expect( - runEnvPush({path: tmpDir, env: 'Something random'}), + runEnvPush({path: tmpDir, env: 'something-random'}), ).rejects.toThrowError('Environment not found'); }); }); - it('prompts the user if there are multiple matches', async () => { - vi.mocked(renderSelectPrompt).mockResolvedValue(PREVIEW_ENV.id); - - vi.mocked(getStorefrontEnvironments).mockResolvedValue({ - id: SHOPIFY_CONFIG.storefront.id, - productionUrl: 'prod.com', - environments: [ - PRODUCTION_ENV, - PREVIEW_ENV, - {...CUSTOM_ENV, name: 'Preview'}, - ], - }); - + it("ensures getStorefrontEnvVariables is called with environment's handle", async () => { await inTemporaryDirectory(async (tmpDir) => { const filePath = joinPath(tmpDir, '.env'); await writeFile(filePath, 'EXISTING_TOKEN=1\nSECOND_TOKEN=2'); await expect( - runEnvPush({path: tmpDir, env: 'Preview'}), + runEnvPush({path: tmpDir, env: 'production'}), ).resolves.not.toThrow(); - expect(renderSelectPrompt).toHaveBeenCalledWith({ - message: - 'There were multiple environments found with the name Preview:', - choices: expect.any(Array), - }); + await expect(getStorefrontEnvVariables).toHaveBeenCalledWith( + ADMIN_SESSION, + SHOPIFY_CONFIG.storefront.id, + 'production', + ); }); }); }); @@ -247,7 +214,7 @@ describe('pushVariables', () => { const filePath = joinPath(tmpDir, '.env'); await writeFile(filePath, 'EXISTING_TOKEN=1\nSECOND_TOKEN=2'); await expect( - runEnvPush({path: tmpDir, env: 'Preview'}), + runEnvPush({path: tmpDir, env: 'preview'}), ).resolves.not.toThrow(); expect(outputMock.info()).toMatch( @@ -283,7 +250,7 @@ describe('pushVariables', () => { const filePath = joinPath(tmpDir, '.env'); await writeFile(filePath, 'EXISTING_TOKEN=1\nSECOND_TOKEN=2'); await expect( - runEnvPush({path: tmpDir, env: 'Preview'}), + runEnvPush({path: tmpDir, env: 'preview'}), ).resolves.not.toThrow(); expect(renderConfirmationPrompt).toHaveBeenCalled(); @@ -317,7 +284,7 @@ describe('pushVariables', () => { const filePath = joinPath(tmpDir, '.env'); await writeFile(filePath, 'EXISTING_TOKEN=1\nSECOND_TOKEN=2'); await expect( - runEnvPush({path: tmpDir, env: 'Preview'}), + runEnvPush({path: tmpDir, env: 'preview'}), ).resolves.not.toThrow(); }); @@ -353,7 +320,7 @@ describe('pushVariables', () => { const filePath = joinPath(tmpDir, '.env'); await writeFile(filePath, 'EXISTING_TOKEN=1\nSECOND_TOKEN=2'); await expect( - runEnvPush({path: tmpDir, env: 'Preview'}), + runEnvPush({path: tmpDir, env: 'preview'}), ).resolves.not.toThrow(); expect(outputMock.info()).toMatch( @@ -389,7 +356,7 @@ describe('pushVariables', () => { const filePath = joinPath(tmpDir, '.env'); await writeFile(filePath, 'EXISTING_TOKEN=1\nSECOND_TOKEN=2'); await expect( - runEnvPush({path: tmpDir, env: 'Preview'}), + runEnvPush({path: tmpDir, env: 'preview'}), ).resolves.not.toThrow(); expect(pushStorefrontEnvVariables).not.toHaveBeenCalled(); @@ -423,7 +390,7 @@ describe('pushVariables', () => { const filePath = joinPath(tmpDir, '.env'); await writeFile(filePath, 'EXISTING_TOKEN=1\nSECOND_TOKEN=NEW_VALUE'); await expect( - runEnvPush({path: tmpDir, env: 'Preview'}), + runEnvPush({path: tmpDir, env: 'preview'}), ).resolves.not.toThrow(); expect(pushStorefrontEnvVariables).toHaveBeenCalledWith( diff --git a/packages/cli/src/commands/hydrogen/env/push__unstable.ts b/packages/cli/src/commands/hydrogen/env/push__unstable.ts index b38cc8fe9c..6a89269b99 100644 --- a/packages/cli/src/commands/hydrogen/env/push__unstable.ts +++ b/packages/cli/src/commands/hydrogen/env/push__unstable.ts @@ -16,6 +16,7 @@ import { outputToken, outputWarn, } from '@shopify/cli-kit/node/output'; +import {findEnvironmentOrThrow} from '../../../lib/common.js'; import {renderMissingLink} from '../../../lib/render-errors.js'; import { Environment, @@ -55,11 +56,11 @@ interface Flags { } export async function runEnvPush({ - env: environmentName, + env: envHandle, envFile = '.env', path = process.cwd(), }: Flags) { - let validatedEnvironment: Partial = {}; + let validatedEnvironment: Environment; // Ensure local .env file const dotEnvPath = resolvePath(path, envFile); @@ -111,34 +112,8 @@ export async function runEnvPush({ } // Select and validate an environment, if not passed via the flag - if (environmentName) { - // If an environment was passed in, ensure the parameter is a valid environment, and unique - const matchedEnvironments = environments.filter( - ({name}) => name === environmentName, - ); - if (matchedEnvironments.length === 0) { - throw new AbortError( - 'Environment not found', - `We could not find an environment matching the name '${environmentName}'.`, - ); - } else if (matchedEnvironments.length === 1) { - const {id, name, branch, type} = matchedEnvironments[0] ?? {}; - validatedEnvironment = {id, name, branch, type}; - } else { - // Prompt the user for a selection if there are multiple matches - const selection = await renderSelectPrompt({ - message: `There were multiple environments found with the name ${environmentName}:`, - choices: [ - ...matchedEnvironments.map(({id, name, branch, type, url}) => ({ - label: `${name} (${branch}) ${type} ${url}`, - value: id, - })), - ], - }); - const {id, name, branch, type} = - matchedEnvironments.find(({id}) => id === selection) ?? {}; - validatedEnvironment = {id, name, branch, type}; - } + if (envHandle) { + validatedEnvironment = findEnvironmentOrThrow(environments, envHandle); } else { // Environment flag not passed const choices = [ @@ -149,13 +124,13 @@ export async function runEnvPush({ ]; const pushToBranchSelection = await renderSelectPrompt({ - message: 'Select a set of environment variables to overwrite:', + message: 'Select an environment to overwrite its environment variables:', choices, }); - const {id, name, branch, type} = - environments.find(({id}) => id === pushToBranchSelection) ?? {}; - validatedEnvironment = {id, name, branch, type}; + validatedEnvironment = environments.find( + ({id}) => id === pushToBranchSelection, + )!; } // Fetch remote variables @@ -163,7 +138,7 @@ export async function runEnvPush({ (await getStorefrontEnvVariables( session, config.storefront.id, - validatedEnvironment.branch ?? undefined, + validatedEnvironment.handle, )) ?? {}; // Normalize variables diff --git a/packages/cli/src/commands/hydrogen/preview.ts b/packages/cli/src/commands/hydrogen/preview.ts index 686ade6d52..a894534c2c 100644 --- a/packages/cli/src/commands/hydrogen/preview.ts +++ b/packages/cli/src/commands/hydrogen/preview.ts @@ -19,6 +19,7 @@ export default class Preview extends Command { ...commonFlags.port, worker: deprecated('--worker', {isBoolean: true}), ...commonFlags.legacyRuntime, + ...commonFlags.env, ...commonFlags.envBranch, ...commonFlags.inspectorPort, ...commonFlags.debug, @@ -37,6 +38,7 @@ type PreviewOptions = { port: number; path?: string; legacyRuntime?: boolean; + env?: string; envBranch?: string; inspectorPort: number; debug: boolean; @@ -46,6 +48,7 @@ export async function runPreview({ port: appPort, path: appPath, legacyRuntime = false, + env: envHandle, envBranch, inspectorPort, debug, @@ -63,7 +66,12 @@ export async function runPreview({ const {shop, storefront} = await getConfig(root); const fetchRemote = !!shop && !!storefront?.id; - const env = await getAllEnvironmentVariables({root, fetchRemote, envBranch}); + const env = await getAllEnvironmentVariables({ + root, + fetchRemote, + envBranch, + envHandle, + }); appPort = legacyRuntime ? appPort : await findPort(appPort); inspectorPort = debug ? await findPort(inspectorPort) : inspectorPort; diff --git a/packages/cli/src/lib/common.ts b/packages/cli/src/lib/common.ts new file mode 100644 index 0000000000..0666472e52 --- /dev/null +++ b/packages/cli/src/lib/common.ts @@ -0,0 +1,40 @@ +import {AbortError} from '@shopify/cli-kit/node/error'; + +export function findEnvironmentOrThrow( + environments: Array, + envHandle: string, +): Environment { + const matchedEnvironment = environments.find( + ({handle}) => handle === envHandle, + ); + if (!matchedEnvironment) { + throw environmentNotFound('handle', envHandle); + } + + return matchedEnvironment; +} + +export function findEnvironmentByBranchOrThrow< + Environment extends {branch: string | null}, +>(environments: Array, branch: string): Environment { + const matchedEnvironment = environments.find(({branch: b}) => b === branch); + if (!matchedEnvironment) { + throw environmentNotFound('branch', branch); + } + + return matchedEnvironment; +} + +function environmentNotFound(criterion: string, value: string) { + return new AbortError( + 'Environment not found', + `We could not find an environment matching the ${criterion} '${value}'.`, + [ + [ + 'Run', + {command: 'env list'}, + 'to view a list of available environments.', + ], + ], + ); +} diff --git a/packages/cli/src/lib/environment-variables.test.ts b/packages/cli/src/lib/environment-variables.test.ts index 19f79a93b7..707b69e118 100644 --- a/packages/cli/src/lib/environment-variables.test.ts +++ b/packages/cli/src/lib/environment-variables.test.ts @@ -5,10 +5,13 @@ import {mockAndCaptureOutput} from '@shopify/cli-kit/node/testing/output'; import {getAllEnvironmentVariables} from './environment-variables.js'; import {getStorefrontEnvVariables} from './graphql/admin/pull-variables.js'; +import {getStorefrontEnvironments} from './graphql/admin/list-environments.js'; +import {dummyListEnvironments} from './graphql/admin/test-helper.js'; import {login} from './auth.js'; vi.mock('./auth.js'); vi.mock('./graphql/admin/pull-variables.js'); +vi.mock('./graphql/admin/list-environments.js'); describe('getAllEnvironmentVariables()', () => { const ADMIN_SESSION = { @@ -44,6 +47,10 @@ describe('getAllEnvironmentVariables()', () => { }, ], }); + + vi.mocked(getStorefrontEnvironments).mockResolvedValue( + dummyListEnvironments(SHOPIFY_CONFIG.storefront.id), + ); }); afterEach(() => { @@ -51,7 +58,22 @@ describe('getAllEnvironmentVariables()', () => { mockAndCaptureOutput().clear(); }); - it('calls pullRemoteEnvironmentVariables', async () => { + it('calls pullRemoteEnvironmentVariables using handle', async () => { + await inTemporaryDirectory(async (tmpDir) => { + await getAllEnvironmentVariables({ + envHandle: 'production', + root: tmpDir, + }); + + expect(getStorefrontEnvVariables).toHaveBeenCalledWith( + ADMIN_SESSION, + SHOPIFY_CONFIG.storefront.id, + 'production', + ); + }); + }); + + it('calls pullRemoteEnvironmentVariables using branch', async () => { await inTemporaryDirectory(async (tmpDir) => { await getAllEnvironmentVariables({ envBranch: 'main', @@ -61,7 +83,7 @@ describe('getAllEnvironmentVariables()', () => { expect(getStorefrontEnvVariables).toHaveBeenCalledWith( ADMIN_SESSION, SHOPIFY_CONFIG.storefront.id, - 'main', + 'production', ); }); }); diff --git a/packages/cli/src/lib/environment-variables.ts b/packages/cli/src/lib/environment-variables.ts index b304c33588..2ef1edc6ad 100644 --- a/packages/cli/src/lib/environment-variables.ts +++ b/packages/cli/src/lib/environment-variables.ts @@ -7,13 +7,16 @@ import {type AbortError} from '@shopify/cli-kit/node/error'; import {renderWarning} from '@shopify/cli-kit/node/ui'; import colors from '@shopify/cli-kit/node/colors'; import {getStorefrontEnvVariables} from './graphql/admin/pull-variables.js'; +import {getStorefrontEnvironments} from './graphql/admin/list-environments.js'; +import {findEnvironmentByBranchOrThrow} from './common.js'; import {login} from './auth.js'; type EnvMap = Record; interface Arguments { - envBranch?: string; root: string; + envHandle?: string; + envBranch?: string; fetchRemote?: boolean; localVariables?: EnvMap; } @@ -25,6 +28,7 @@ const createEmptyRemoteVars = () => ({ export async function getAllEnvironmentVariables({ root, + envHandle, envBranch, fetchRemote = true, localVariables: inlineLocalVariables, @@ -33,17 +37,19 @@ export async function getAllEnvironmentVariables({ await Promise.all([ // Get remote vars fetchRemote - ? getRemoteVariables(root, envBranch).catch((error: AbortError) => { - renderWarning({ - headline: - 'Failed to load environment variables from Shopify. The development server will still start, but the following error occurred:', - body: [error.message, error.tryMessage, error.nextSteps] - .filter(Boolean) - .join('\n\n'), - }); - - return createEmptyRemoteVars(); - }) + ? getRemoteVariables(root, envHandle, envBranch).catch( + (error: AbortError) => { + renderWarning({ + headline: + 'Failed to load environment variables from Shopify. The development server will still start, but the following error occurred:', + body: [error.message, error.tryMessage, error.nextSteps] + .filter(Boolean) + .join('\n\n'), + }); + + return createEmptyRemoteVars(); + }, + ) : createEmptyRemoteVars(), // Get local vars inlineLocalVariables @@ -85,11 +91,23 @@ export async function getAllEnvironmentVariables({ }; } -async function getRemoteVariables(root: string, envBranch?: string) { +async function getRemoteVariables( + root: string, + envHandle?: string, + envBranch?: string, +) { const {session, config} = await login(root); + if (envBranch) { + const environments = + (await getStorefrontEnvironments(session, config.storefront!.id)) + ?.environments || []; + + envHandle = findEnvironmentByBranchOrThrow(environments, envBranch).handle; + } + const envVariables = - (await getStorefrontEnvVariables(session, config.storefront!.id, envBranch)) + (await getStorefrontEnvVariables(session, config.storefront!.id, envHandle)) ?.environmentVariables || []; const remoteVariables: EnvMap = {}; diff --git a/packages/cli/src/lib/flags.ts b/packages/cli/src/lib/flags.ts index 04fc1e7f52..c6264d5d22 100644 --- a/packages/cli/src/lib/flags.ts +++ b/packages/cli/src/lib/flags.ts @@ -59,19 +59,25 @@ export const commonFlags = { allowNo: true, }), }, - envBranch: { - 'env-branch': Flags.string({ + env: { + env: Flags.string({ description: - 'Specifies the environment to pull variables from using its Git branch name.', - env: 'SHOPIFY_HYDROGEN_ENVIRONMENT_BRANCH', - char: 'e', + "Specifies the environment to perform the operation using its handle. Run `env list` to view an environment's handle ", + exclusive: ['env-branch'], }), }, - env: { - env: Flags.string({ + /** + * @deprecated use `env` instead. + */ + envBranch: { + 'env-branch': Flags.string({ description: - "Specifies an environment's name when using remote environment variables.", - env: 'SHOPIFY_HYDROGEN_ENVIRONMENT_NAME', + 'Specifies the environment to perform the operation using its Git branch name.', + env: 'SHOPIFY_HYDROGEN_ENVIRONMENT_BRANCH', + deprecated: { + to: 'env', + message: '--env-branch is deprecated. Use --env instead.', + }, }), }, sourcemap: { diff --git a/packages/cli/src/lib/get-oxygen-deployment-data.test.ts b/packages/cli/src/lib/get-oxygen-deployment-data.test.ts index 6a042df469..c8d02086b5 100644 --- a/packages/cli/src/lib/get-oxygen-deployment-data.test.ts +++ b/packages/cli/src/lib/get-oxygen-deployment-data.test.ts @@ -32,11 +32,13 @@ describe('getOxygenDeploymentData', () => { const environments: OxygenDeploymentData['environments'] = [ { name: 'production', + handle: 'production', branch: 'main', type: 'PRODUCTION', }, { name: 'preview', + handle: 'preview', branch: null, type: 'PREVIEW', }, diff --git a/packages/cli/src/lib/graphql/admin/get-oxygen-data.ts b/packages/cli/src/lib/graphql/admin/get-oxygen-data.ts index 80e815f674..4caf3e9b77 100644 --- a/packages/cli/src/lib/graphql/admin/get-oxygen-data.ts +++ b/packages/cli/src/lib/graphql/admin/get-oxygen-data.ts @@ -6,6 +6,7 @@ export const GetDeploymentDataQuery = `#graphql oxygenDeploymentToken environments { name + handle branch type } @@ -18,6 +19,7 @@ export interface OxygenDeploymentData { environments: Array<{ name: string; branch: string | null; + handle: string; type: 'PREVIEW' | 'CUSTOM' | 'PRODUCTION'; }> | null; } diff --git a/packages/cli/src/lib/graphql/admin/list-environments.test.ts b/packages/cli/src/lib/graphql/admin/list-environments.test.ts index 0f89dcdbd8..5873049217 100644 --- a/packages/cli/src/lib/graphql/admin/list-environments.test.ts +++ b/packages/cli/src/lib/graphql/admin/list-environments.test.ts @@ -4,6 +4,7 @@ import { getStorefrontEnvironments, type ListEnvironmentsSchema, } from './list-environments.js'; +import {dummyListEnvironments} from './test-helper.js'; vi.mock('./client.js'); @@ -19,20 +20,9 @@ describe('getStorefrontEnvironments', () => { it('calls the graphql client and returns Hydrogen storefronts', async () => { const mockedResponse: ListEnvironmentsSchema = { - hydrogenStorefront: { - id: 'gid://shopify/HydrogenStorefront/123', - productionUrl: 'https://...', - environments: [ - { - createdAt: '2021-01-01T00:00:00Z', - id: 'e123', - name: 'Staging', - type: 'CUSTOM', - branch: 'staging', - url: 'https://...', - }, - ], - }, + hydrogenStorefront: dummyListEnvironments( + 'gid://shopify/HydrogenStorefront/1', + ), }; vi.mocked(adminRequest).mockResolvedValue( diff --git a/packages/cli/src/lib/graphql/admin/list-environments.ts b/packages/cli/src/lib/graphql/admin/list-environments.ts index ed7fa52ed7..e566d40171 100644 --- a/packages/cli/src/lib/graphql/admin/list-environments.ts +++ b/packages/cli/src/lib/graphql/admin/list-environments.ts @@ -10,6 +10,7 @@ const ListEnvironmentsQuery = `#graphql createdAt id name + handle type url } @@ -24,11 +25,12 @@ export interface Environment { createdAt: string; id: string; name: string; + handle: string; type: EnvironmentType; url: string | null; } -interface HydrogenStorefront { +export interface HydrogenStorefront { id: string; environments: Environment[]; productionUrl: string; diff --git a/packages/cli/src/lib/graphql/admin/pull-variables.test.ts b/packages/cli/src/lib/graphql/admin/pull-variables.test.ts index 526b492856..da61af8885 100644 --- a/packages/cli/src/lib/graphql/admin/pull-variables.test.ts +++ b/packages/cli/src/lib/graphql/admin/pull-variables.test.ts @@ -38,16 +38,16 @@ describe('getStorefrontEnvVariables', () => { ); const id = '123'; - const branch = 'staging'; + const envHandle = 'staging'; await expect( - getStorefrontEnvVariables(ADMIN_SESSION, id, branch), + getStorefrontEnvVariables(ADMIN_SESSION, id, envHandle), ).resolves.toStrictEqual(mockedResponse.hydrogenStorefront); expect(adminRequest).toHaveBeenCalledWith( expect.stringMatching(/^#graphql.+query.+hydrogenStorefront\(/s), ADMIN_SESSION, - {id, branch}, + {id, handle: envHandle}, ); }); }); diff --git a/packages/cli/src/lib/graphql/admin/pull-variables.ts b/packages/cli/src/lib/graphql/admin/pull-variables.ts index 4c0fff765f..3ed9f51a2e 100644 --- a/packages/cli/src/lib/graphql/admin/pull-variables.ts +++ b/packages/cli/src/lib/graphql/admin/pull-variables.ts @@ -1,10 +1,10 @@ import {adminRequest, type AdminSession} from './client.js'; const PullVariablesQuery = `#graphql - query PullVariables($id: ID!, $branch: String) { + query PullVariables($id: ID!, $handle: String) { hydrogenStorefront(id: $id) { id - environmentVariables(branchName: $branch) { + environmentVariables(handle: $handle) { id isSecret readOnly @@ -35,14 +35,14 @@ export interface PullVariablesSchema { export async function getStorefrontEnvVariables( adminSession: AdminSession, storefrontId: string, - envBranch?: string, + envHandle?: string, ) { const {hydrogenStorefront} = await adminRequest( PullVariablesQuery, adminSession, { id: storefrontId, - branch: envBranch, + handle: envHandle, }, ); diff --git a/packages/cli/src/lib/graphql/admin/test-helper.ts b/packages/cli/src/lib/graphql/admin/test-helper.ts new file mode 100644 index 0000000000..b227da66ed --- /dev/null +++ b/packages/cli/src/lib/graphql/admin/test-helper.ts @@ -0,0 +1,39 @@ +import {type HydrogenStorefront} from './list-environments.js'; + +export function dummyListEnvironments( + storefrontId: string, +): HydrogenStorefront { + return { + id: storefrontId, + productionUrl: 'https://my-shop.myshopify.com', + environments: [ + { + id: 'gid://shopify/HydrogenStorefrontEnvironment/1', + branch: 'main', + type: 'PRODUCTION', + name: 'Production', + handle: 'production', + createdAt: '2023-02-16T22:35:42Z', + url: 'https://oxygen-123.example.com', + }, + { + id: 'gid://shopify/HydrogenStorefrontEnvironment/2', + branch: null, + type: 'PREVIEW', + name: 'Preview', + handle: 'preview', + createdAt: '2023-02-16T22:35:42Z', + url: null, + }, + { + id: 'gid://shopify/HydrogenStorefrontEnvironment/3', + branch: 'staging', + type: 'CUSTOM', + name: 'Staging', + handle: 'staging', + createdAt: '2023-05-08T20:52:29Z', + url: 'https://oxygen-456.example.com', + }, + ], + }; +}