Skip to content

Commit

Permalink
Support custom .env files (#2392)
Browse files Browse the repository at this point in the history
* Update env commands to accept --env-file

* Support --env-file in dev and preview commands

* Changesets
  • Loading branch information
frandiox authored Aug 5, 2024
1 parent bf4e3d3 commit c204eac
Show file tree
Hide file tree
Showing 16 changed files with 187 additions and 87 deletions.
5 changes: 5 additions & 0 deletions .changeset/lazy-seas-draw.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@shopify/cli-hydrogen': minor
---

Support `--env-file` in env:pull, dev, and preview commands to specify custom `.env` files.
31 changes: 30 additions & 1 deletion packages/cli/oclif.manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -599,6 +599,15 @@
"multiple": false,
"type": "option"
},
"env-file": {
"description": "Path to an environment file to override existing environment variables. Defaults to the '.env' located in your project path `--path`.",
"name": "env-file",
"required": false,
"default": ".env",
"hasDynamicHelp": false,
"multiple": false,
"type": "option"
},
"disable-version-check": {
"description": "Skip the version check when running `hydrogen dev`",
"name": "disable-version-check",
Expand Down Expand Up @@ -741,6 +750,15 @@
"multiple": false,
"type": "option"
},
"env-file": {
"description": "Path to an environment file to override existing environment variables. Defaults to the '.env' located in your project path `--path`.",
"name": "env-file",
"required": false,
"default": ".env",
"hasDynamicHelp": false,
"multiple": false,
"type": "option"
},
"path": {
"description": "The path to the directory of the Hydrogen storefront. Defaults to the current directory where the command is run.",
"env": "SHOPIFY_HYDROGEN_FLAG_PATH",
Expand Down Expand Up @@ -792,8 +810,10 @@
"type": "option"
},
"env-file": {
"description": "Path to an environment file to override existing environment variables for the selected environment. Defaults to the '.env' located in your project path `--path`.",
"description": "Path to an environment file to override existing environment variables. Defaults to the '.env' located in your project path `--path`.",
"name": "env-file",
"required": false,
"default": ".env",
"hasDynamicHelp": false,
"multiple": false,
"type": "option"
Expand Down Expand Up @@ -1322,6 +1342,15 @@
"multiple": false,
"type": "option"
},
"env-file": {
"description": "Path to an environment file to override existing environment variables. Defaults to the '.env' located in your project path `--path`.",
"name": "env-file",
"required": false,
"default": ".env",
"hasDynamicHelp": false,
"multiple": false,
"type": "option"
},
"inspector-port": {
"description": "The port where the inspector is available. Defaults to 9229.",
"env": "SHOPIFY_HYDROGEN_FLAG_INSPECTOR_PORT",
Expand Down
16 changes: 11 additions & 5 deletions packages/cli/src/commands/hydrogen/deploy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,11 @@ import {
orderEnvironmentsBySafety,
} from '../../lib/common.js';
import {execAsync} from '../../lib/process.js';
import {commonFlags, flagsToCamelObject} from '../../lib/flags.js';
import {
commonFlags,
flagsToCamelObject,
overrideFlag,
} from '../../lib/flags.js';
import {getOxygenDeploymentData} from '../../lib/get-oxygen-deployment-data.js';
import {OxygenDeploymentData} from '../../lib/graphql/admin/get-oxygen-data.js';
import {runClassicCompilerBuild} from '../../lib/classic-compiler/build.js';
Expand Down Expand Up @@ -69,10 +73,12 @@ export default class Deploy extends Command {
...commonFlags.entry,
...commonFlags.env,
...commonFlags.envBranch,
'env-file': Flags.string({
description:
'Path to an environment file to override existing environment variables for the deployment.',
required: false,
...overrideFlag(commonFlags.envFile, {
'env-file': {
description:
'Path to an environment file to override existing environment variables for the deployment.',
default: undefined,
},
}),
preview: Flags.boolean({
description:
Expand Down
1 change: 1 addition & 0 deletions packages/cli/src/commands/hydrogen/dev.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ describe('dev', () => {
disableVirtualRoutes: true,
disableVersionCheck: true,
cliConfig: {} as any,
envFile: '.env',
});

try {
Expand Down
5 changes: 5 additions & 0 deletions packages/cli/src/commands/hydrogen/dev.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ export default class Dev extends Command {
...commonFlags.inspectorPort,
...commonFlags.env,
...commonFlags.envBranch,
...commonFlags.envFile,
'disable-version-check': Flags.boolean({
description: 'Skip the version check when running `hydrogen dev`',
default: false,
Expand Down Expand Up @@ -167,6 +168,7 @@ type DevOptions = {
customerAccountPush?: boolean;
cliConfig: Config;
verbose?: boolean;
envFile: string;
};

export async function runDev({
Expand All @@ -184,6 +186,7 @@ export async function runDev({
disableVersionCheck = false,
inspectorPort,
customerAccountPush: customerAccountPushFlag = false,
envFile,
cliConfig,
verbose,
}: DevOptions) {
Expand All @@ -198,6 +201,7 @@ export async function runDev({
const backgroundPromise = getDevConfigInBackground(
root,
customerAccountPushFlag,
envFile,
);

const envPromise = backgroundPromise.then(({fetchRemote, localVariables}) =>
Expand All @@ -207,6 +211,7 @@ export async function runDev({
envHandle,
fetchRemote,
localVariables,
envFile,
}),
);

Expand Down
50 changes: 33 additions & 17 deletions packages/cli/src/commands/hydrogen/env/pull.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ vi.mock('../../../lib/verify-linked-storefront.js');
vi.mock('../../../lib/graphql/admin/pull-variables.js');

describe('pullVariables', () => {
const envFile = '.env';

const ADMIN_SESSION: AdminSession = {
token: 'abc123',
storeFqdn: 'my-shop',
Expand Down Expand Up @@ -95,7 +97,7 @@ describe('pullVariables', () => {
describe('when environment is provided', () => {
it('calls getStorefrontEnvVariables when handle is provided', async () => {
await inTemporaryDirectory(async (tmpDir) => {
await runEnvPull({path: tmpDir, env: 'staging'});
await runEnvPull({path: tmpDir, env: 'staging', envFile});

expect(getStorefrontEnvVariables).toHaveBeenCalledWith(
ADMIN_SESSION,
Expand All @@ -107,7 +109,7 @@ describe('pullVariables', () => {

it('calls getStorefrontEnvVariables when branch is provided', async () => {
await inTemporaryDirectory(async (tmpDir) => {
await runEnvPull({path: tmpDir, envBranch: 'main'});
await runEnvPull({path: tmpDir, envBranch: 'main', envFile});

expect(getStorefrontEnvVariables).toHaveBeenCalledWith(
ADMIN_SESSION,
Expand All @@ -120,27 +122,41 @@ describe('pullVariables', () => {
it('throws error if handle does not map to any environment', async () => {
await inTemporaryDirectory(async (tmpDir) => {
await expect(
runEnvPull({path: tmpDir, env: 'fake'}),
runEnvPull({path: tmpDir, env: 'fake', envFile}),
).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'}),
runEnvPull({path: tmpDir, envBranch: 'fake', envFile}),
).rejects.toThrowError('Environment not found');
});
});
});

it('writes environment variables to a .env file', async () => {
it('writes environment variables to a .env file by default', async () => {
await inTemporaryDirectory(async (tmpDir) => {
const filePath = joinPath(tmpDir, envFile);

expect(await fileExists(filePath)).toBeFalsy();

await runEnvPull({path: tmpDir, envFile});

expect(await readFile(filePath)).toStrictEqual(
'PUBLIC_API_TOKEN=abc123\n' + 'PRIVATE_API_TOKEN=""',
);
});
});

it('writes environment variables to a specified file', async () => {
await inTemporaryDirectory(async (tmpDir) => {
const filePath = joinPath(tmpDir, '.env');
const filePath = joinPath(tmpDir, '.env.test');

expect(await fileExists(filePath)).toBeFalsy();

await runEnvPull({path: tmpDir});
await runEnvPull({path: tmpDir, envFile: '.env.test'});

expect(await readFile(filePath)).toStrictEqual(
'PUBLIC_API_TOKEN=abc123\n' + 'PRIVATE_API_TOKEN=""',
Expand All @@ -152,7 +168,7 @@ describe('pullVariables', () => {
await inTemporaryDirectory(async (tmpDir) => {
const outputMock = mockAndCaptureOutput();

await runEnvPull({path: tmpDir});
await runEnvPull({path: tmpDir, envFile});

expect(outputMock.warn()).toMatch(
/Existing Link contains environment variables marked as secret, so their/,
Expand All @@ -165,7 +181,7 @@ describe('pullVariables', () => {
await inTemporaryDirectory(async (tmpDir) => {
const outputMock = mockAndCaptureOutput();

await runEnvPull({path: tmpDir});
await runEnvPull({path: tmpDir, envFile});

expect(outputMock.info()).toMatch(
/Changes have been made to your \.env file/,
Expand All @@ -185,7 +201,7 @@ describe('pullVariables', () => {
await inTemporaryDirectory(async (tmpDir) => {
const outputMock = mockAndCaptureOutput();

await runEnvPull({path: tmpDir});
await runEnvPull({path: tmpDir, envFile});

expect(outputMock.info()).toMatch(/No environment variables found\./);
});
Expand All @@ -199,7 +215,7 @@ describe('pullVariables', () => {

it('ends without requesting variables', async () => {
await inTemporaryDirectory(async (tmpDir) => {
await runEnvPull({path: tmpDir});
await runEnvPull({path: tmpDir, envFile});

expect(getStorefrontEnvVariables).not.toHaveBeenCalled();
});
Expand All @@ -210,7 +226,7 @@ describe('pullVariables', () => {
vi.mocked(renderConfirmationPrompt).mockResolvedValue(false);

await inTemporaryDirectory(async (tmpDir) => {
await runEnvPull({path: tmpDir});
await runEnvPull({path: tmpDir, envFile});

expect(getStorefrontEnvVariables).not.toHaveBeenCalled();
});
Expand All @@ -225,7 +241,7 @@ describe('pullVariables', () => {

it('renders missing storefronts message and ends', async () => {
await inTemporaryDirectory(async (tmpDir) => {
await runEnvPull({path: tmpDir});
await runEnvPull({path: tmpDir, envFile});

expect(renderMissingStorefront).toHaveBeenCalledOnce();
});
Expand All @@ -239,10 +255,10 @@ describe('pullVariables', () => {

it('prompts the user to confirm', async () => {
await inTemporaryDirectory(async (tmpDir) => {
const filePath = joinPath(tmpDir, '.env');
const filePath = joinPath(tmpDir, envFile);
await writeFile(filePath, 'EXISTING_TOKEN=1');

await runEnvPull({path: tmpDir});
await runEnvPull({path: tmpDir, envFile});

expect(renderConfirmationPrompt).toHaveBeenCalledWith({
confirmationMessage: `Yes, confirm changes`,
Expand All @@ -257,10 +273,10 @@ describe('pullVariables', () => {
describe('and --force is enabled', () => {
it('does not prompt the user to confirm', async () => {
await inTemporaryDirectory(async (tmpDir) => {
const filePath = joinPath(tmpDir, '.env');
const filePath = joinPath(tmpDir, envFile);
await writeFile(filePath, 'EXISTING_TOKEN=1');

await runEnvPull({path: tmpDir, force: true});
await runEnvPull({path: tmpDir, force: true, envFile});

expect(renderConfirmationPrompt).not.toHaveBeenCalled();
});
Expand Down
10 changes: 7 additions & 3 deletions packages/cli/src/commands/hydrogen/env/pull.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ export default class EnvPull extends Command {
static flags = {
...commonFlags.env,
...commonFlags.envBranch,
...commonFlags.envFile,
...commonFlags.path,
...commonFlags.force,
};
Expand All @@ -50,6 +51,7 @@ export default class EnvPull extends Command {
interface EnvPullOptions {
env?: string;
envBranch?: string;
envFile: string;
force?: boolean;
path?: string;
}
Expand All @@ -58,6 +60,7 @@ export async function runEnvPull({
env: envHandle,
envBranch,
path: root = process.cwd(),
envFile,
force,
}: EnvPullOptions) {
const [{session, config}, cliCommand] = await Promise.all([
Expand Down Expand Up @@ -114,8 +117,8 @@ export async function runEnvPull({
const variables = storefront.environmentVariables;
if (!variables.length) return;

const fileName = colors.whiteBright(`.env`);
const dotEnvPath = resolvePath(root, '.env');
const fileName = colors.whiteBright(envFile);
const dotEnvPath = resolvePath(root, envFile);
const fetchedEnv: Record<string, string> = {};

variables.forEach(({isSecret, key, value}) => {
Expand All @@ -140,7 +143,8 @@ export async function runEnvPull({
const overwrite = await renderConfirmationPrompt({
confirmationMessage: `Yes, confirm changes`,
cancellationMessage: `No, make changes later`,
message: outputContent`We'll make the following changes to your .env file:
message:
outputContent`We'll make the following changes to your ${fileName} file:
${outputToken.linesDiff(diff)}
Continue?`.value,
Expand Down
Loading

0 comments on commit c204eac

Please sign in to comment.