Skip to content

Commit

Permalink
[cli] vc env pull should add .env*.local to .gitignore (#10085)
Browse files Browse the repository at this point in the history
This is a follow up to PR #9892 which changed the default to `.env.local`.

Now that we know local files should never be committed to git, we can automatically add `.env*.local` to `.gitignore`. Note that this is the same ignore pattern that ships with create-next-app [as seen here](https://github.com/vercel/next.js/blob/06abd634899095b6cc28e6e8315b1e8b9c8df939/packages/create-next-app/templates/app/js/gitignore#L28), so most Next.js users won't see anything change.

See the related [Linear ticket](https://linear.app/vercel/issue/VCCLI-461/)
  • Loading branch information
styfle committed Jun 8, 2023
1 parent 94d5612 commit fecebfa
Show file tree
Hide file tree
Showing 7 changed files with 106 additions and 13 deletions.
5 changes: 5 additions & 0 deletions .changeset/blue-rats-know.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"vercel": patch
---

[cli] vc env pull should add `.env*.local` to `.gitignore`
1 change: 1 addition & 0 deletions packages/cli/src/commands/env/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,7 @@ export default async function main(client: Client) {
case 'pull':
return pull(
client,
link,
project,
target,
argv,
Expand Down
25 changes: 21 additions & 4 deletions packages/cli/src/commands/env/pull.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@ import chalk from 'chalk';
import { outputFile } from 'fs-extra';
import { closeSync, openSync, readSync } from 'fs';
import { resolve } from 'path';
import type { Project, ProjectEnvTarget } from '@vercel-internals/types';
import type {
Project,
ProjectEnvTarget,
ProjectLinked,
} from '@vercel-internals/types';
import Client from '../../util/client';
import { emoji, prependEmoji } from '../../util/emoji';
import confirm from '../../util/input/confirm';
Expand All @@ -19,6 +23,7 @@ import {
createEnvObject,
} from '../../util/env/diff-env-files';
import { isErrnoException } from '@vercel/error-utils';
import { addToGitIgnore } from '../../util/link/add-to-gitignore';

const CONTENTS_PREFIX = '# Created by Vercel CLI\n';

Expand Down Expand Up @@ -51,6 +56,7 @@ function tryReadHeadSync(path: string, length: number) {

export default async function pull(
client: Client,
link: ProjectLinked,
project: Project,
environment: ProjectEnvTarget,
opts: Partial<Options>,
Expand Down Expand Up @@ -136,11 +142,22 @@ export default async function pull(
output.log('No changes found.');
}

let isGitIgnoreUpdated = false;
if (filename === '.env.local') {
// When the file is `.env.local`, we also add it to `.gitignore`
// to avoid accidentally committing it to git.
// We use '.env*.local' to match the default .gitignore from
// create-next-app template. See:
// https://github.com/vercel/next.js/blob/06abd634899095b6cc28e6e8315b1e8b9c8df939/packages/create-next-app/templates/app/js/gitignore#L28
const rootPath = link.repoRoot ?? cwd;
isGitIgnoreUpdated = await addToGitIgnore(rootPath, '.env*.local');
}

output.print(
`${prependEmoji(
`${exists ? 'Updated' : 'Created'} ${chalk.bold(
filename
)} file ${chalk.gray(pullStamp())}`,
`${exists ? 'Updated' : 'Created'} ${chalk.bold(filename)} file ${
isGitIgnoreUpdated ? 'and added it to .gitignore' : ''
} ${chalk.gray(pullStamp())}`,
emoji('success')
)}\n`
);
Expand Down
9 changes: 8 additions & 1 deletion packages/cli/src/commands/pull.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import chalk from 'chalk';
import { join } from 'path';
import Client from '../util/client';
import type { Project, ProjectEnvTarget } from '@vercel-internals/types';
import type {
Project,
ProjectEnvTarget,
ProjectLinked,
} from '@vercel-internals/types';
import { emoji, prependEmoji } from '../util/emoji';
import getArgs from '../util/get-args';
import logo from '../util/output/logo';
Expand Down Expand Up @@ -82,13 +86,15 @@ function parseArgs(client: Client) {
async function pullAllEnvFiles(
environment: ProjectEnvTarget,
client: Client,
link: ProjectLinked,
project: Project,
argv: ReturnType<typeof processArgs>,
cwd: string
): Promise<number> {
const environmentFile = `.env.${environment}.local`;
return envPull(
client,
link,
project,
environment,
argv,
Expand Down Expand Up @@ -136,6 +142,7 @@ export default async function main(client: Client) {
const pullResultCode = await pullAllEnvFiles(
environment,
client,
link,
project,
argv,
cwd
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.next
yarn.lock
!.vercel
.env*.local
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"orgId": "team_dummy",
"projectId": "vercel-env-pull-with-gitignore"
}
71 changes: 63 additions & 8 deletions packages/cli/test/unit/commands/env.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,9 @@ describe('env', () => {
await expect(client.stderr).toOutput(
'Downloading `development` Environment Variables for Project vercel-env-pull'
);
await expect(client.stderr).toOutput('Created .env.local file');
await expect(client.stderr).toOutput(
'Created .env.local file and added it to .gitignore'
);
await expect(exitCodePromise).resolves.toEqual(0);

const rawDevEnv = await fs.readFile(path.join(cwd, '.env.local'));
Expand All @@ -50,7 +52,9 @@ describe('env', () => {
await expect(client.stderr).toOutput(
'Downloading `preview` Environment Variables for Project vercel-env-pull'
);
await expect(client.stderr).toOutput('Created .env.local file');
await expect(client.stderr).toOutput(
'Created .env.local file and added it to .gitignore'
);
await expect(exitCodePromise).resolves.toEqual(0);

// check for Preview env vars
Expand Down Expand Up @@ -86,7 +90,9 @@ describe('env', () => {
await expect(client.stderr).toOutput(
'Downloading `preview` Environment Variables for Project vercel-env-pull'
);
await expect(client.stderr).toOutput('Created .env.local file');
await expect(client.stderr).toOutput(
'Created .env.local file and added it to .gitignore'
);
await expect(exitCodePromise).resolves.toEqual(0);

// check for Preview env vars
Expand Down Expand Up @@ -122,6 +128,7 @@ describe('env', () => {
'Downloading `development` Environment Variables for Project vercel-env-pull'
);
await expect(client.stderr).toOutput('Created other.env file');
await expect(client.stderr).not.toOutput('and added it to .gitignore');
await expect(exitCodePromise).resolves.toEqual(0);

const rawDevEnv = await fs.readFile(path.join(cwd, 'other.env'));
Expand All @@ -146,7 +153,9 @@ describe('env', () => {
await expect(client.stderr).toOutput(
`Downloading \`production\` Environment Variables for Project vercel-env-pull`
);
await expect(client.stderr).toOutput('Created .env.local file');
await expect(client.stderr).toOutput(
'Created .env.local file and added it to .gitignore'
);
await expect(exitCodePromise).resolves.toEqual(0);

const rawProdEnv = await fs.readFile(path.join(cwd, '.env.local'));
Expand Down Expand Up @@ -201,6 +210,7 @@ describe('env', () => {
'Downloading `development` Environment Variables for Project vercel-env-pull'
);
await expect(client.stderr).toOutput('Created other.env file');
await expect(client.stderr).not.toOutput('and added it to .gitignore');
await expect(exitCodePromise).resolves.toEqual(0);

const rawDevEnv = await fs.readFile(path.join(cwd, 'other.env'));
Expand Down Expand Up @@ -247,7 +257,9 @@ describe('env', () => {
await expect(client.stderr).toOutput(
'+ SPECIAL_FLAG (Updated)\n+ NEW_VAR\n- TEST\n'
);
await expect(client.stderr).toOutput('Updated .env.local file');
await expect(client.stderr).toOutput(
'Updated .env.local file and added it to .gitignore'
);

await expect(pullPromise).resolves.toEqual(0);
} finally {
Expand All @@ -268,7 +280,9 @@ describe('env', () => {
client.cwd = cwd;
client.setArgv('env', 'pull', '--yes');
const pullPromise = env(client);
await expect(client.stderr).toOutput('Updated .env.local file');
await expect(client.stderr).toOutput(
'Updated .env.local file and added it to .gitignore'
);
await expect(pullPromise).resolves.toEqual(0);
});

Expand All @@ -284,7 +298,9 @@ describe('env', () => {
client.setArgv('env', 'pull', '--yes');
const pullPromise = env(client);
await expect(client.stderr).toOutput('> No changes found.');
await expect(client.stderr).toOutput('Updated .env.local file');
await expect(client.stderr).toOutput(
'Updated .env.local file and added it to .gitignore'
);
await expect(pullPromise).resolves.toEqual(0);
});

Expand Down Expand Up @@ -321,7 +337,9 @@ describe('env', () => {
'Downloading `development` Environment Variables for Project env-pull-delta'
);
await expect(client.stderr).toOutput('No changes found.\n');
await expect(client.stderr).toOutput('Updated .env.local file');
await expect(client.stderr).toOutput(
'Updated .env.local file and added it to .gitignore'
);

await expect(pullPromise).resolves.toEqual(0);
} finally {
Expand Down Expand Up @@ -371,5 +389,42 @@ describe('env', () => {
await env(client);
}
});

it('should not update .gitignore if it contains a match', async () => {
const prj = 'vercel-env-pull-with-gitignore';
useUser();
useTeams('team_dummy');
useProject({
...defaultProject,
id: prj,
name: prj,
});
const cwd = setupUnitFixture(prj);
const gitignoreBefore = await fs.readFile(
path.join(cwd, '.gitignore'),
'utf8'
);
client.cwd = cwd;
client.setArgv('env', 'pull', '--yes');
const exitCodePromise = env(client);
await expect(client.stderr).toOutput(
'Downloading `development` Environment Variables for Project ' + prj
);
await expect(client.stderr).toOutput('Created .env.local file');
await expect(client.stderr).not.toOutput('and added it to .gitignore');
await expect(exitCodePromise).resolves.toEqual(0);

const rawDevEnv = await fs.readFile(path.join(cwd, '.env.local'));

// check for development env value
const devFileHasDevEnv = rawDevEnv.toString().includes('SPECIAL_FLAG');
expect(devFileHasDevEnv).toBeTruthy();

const gitignoreAfter = await fs.readFile(
path.join(cwd, '.gitignore'),
'utf8'
);
expect(gitignoreAfter).toBe(gitignoreBefore);
});
});
});

0 comments on commit fecebfa

Please sign in to comment.