Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support custom .env files #2392

Merged
merged 3 commits into from
Aug 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading