Skip to content

feat: --env-file flag #4611

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

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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
80 changes: 67 additions & 13 deletions drizzle-kit/src/cli/commands/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,29 @@ import {
import { studioCliParams, studioConfig } from '../validations/studio';
import { error, grey } from '../views';

/**
* Load environment variables from a specified .env file path
*/
export const loadEnvFile = (envFilePath?: string) => {
if (!envFilePath) return;

const { config } = require('dotenv');
const resolvedPath = resolve(envFilePath);

if (!existsSync(resolvedPath)) {
console.log(error(`Environment file not found: ${resolvedPath}`));
process.exit(1);
}

try {
config({ path: resolvedPath });
} catch (e) {
console.log(error(`Failed to load environment file: ${resolvedPath}`));
console.log(error(String(e)));
process.exit(1);
}
};

// NextJs default config is target: es5, which esbuild-register can't consume
const assertES5 = async (unregister: () => void) => {
try {
Expand Down Expand Up @@ -95,11 +118,15 @@ export const prepareCheckParams = async (
config?: string;
dialect?: Dialect;
out?: string;
envFile?: string;
},
from: 'cli' | 'config',
): Promise<{ out: string; dialect: Dialect }> => {
const config = from === 'config'
? await drizzleConfigFromFile(options.config as string | undefined)
? await drizzleConfigFromFile({
configPath: options.config as string | undefined,
envFile: options.envFile as string | undefined
})
: options;

if (!config.out || !config.dialect) {
Expand All @@ -118,11 +145,15 @@ export const prepareDropParams = async (
out?: string;
driver?: Driver;
dialect?: Dialect;
'env-file'?: string;
},
from: 'cli' | 'config',
): Promise<{ out: string; bundle: boolean }> => {
const config = from === 'config'
? await drizzleConfigFromFile(options.config as string | undefined)
? await drizzleConfigFromFile({
configPath: options.config as string | undefined,
envFile: options['env-file'] as string | undefined
})
: options;

if (config.dialect === 'gel') {
Expand Down Expand Up @@ -168,10 +199,16 @@ export const prepareGenerateConfig = async (
driver?: Driver;
prefix?: Prefix;
casing?: CasingType;
"env-file"?: string;
},
from: 'config' | 'cli',
): Promise<GenerateConfig> => {
const config = from === 'config' ? await drizzleConfigFromFile(options.config) : options;
const config = from === 'config'
? await drizzleConfigFromFile({
configPath: options.config,
envFile: options['env-file']
})
: options;

const { schema, out, breakpoints, dialect, driver, casing } = config;

Expand Down Expand Up @@ -211,11 +248,16 @@ export const prepareExportConfig = async (
config?: string;
schema?: string;
dialect?: Dialect;
'env-file'?: string;
sql: boolean;
},
from: 'config' | 'cli',
): Promise<ExportConfig> => {
const config = from === 'config' ? await drizzleConfigFromFile(options.config, true) : options;
const config = from === 'config' ? await drizzleConfigFromFile({
configPath: options.config,
envFile: options['env-file'],
isExport: true,
}) : options;

const { schema, dialect, sql } = config;

Expand Down Expand Up @@ -299,7 +341,10 @@ export const preparePushConfig = async (
> => {
const raw = flattenDatabaseCredentials(
from === 'config'
? await drizzleConfigFromFile(options.config as string | undefined)
? await drizzleConfigFromFile({
configPath: options.config as string | undefined,
envFile: options['env-file'] as string | undefined,
})
: options,
);

Expand Down Expand Up @@ -490,7 +535,10 @@ export const preparePullConfig = async (
> => {
const raw = flattenPull(
from === 'config'
? await drizzleConfigFromFile(options.config as string | undefined)
? await drizzleConfigFromFile({
configPath: options.config as string | undefined,
envFile: options['env-file'] as string | undefined
})
: options,
);
const parsed = pullParams.safeParse(raw);
Expand Down Expand Up @@ -650,7 +698,7 @@ export const preparePullConfig = async (

export const prepareStudioConfig = async (options: Record<string, unknown>) => {
const params = studioCliParams.parse(options);
const config = await drizzleConfigFromFile(params.config);
const config = await drizzleConfigFromFile({ configPath: params.config, envFile: params['env-file'] });
const result = studioConfig.safeParse(config);
if (!result.success) {
if (!('dialect' in config)) {
Expand Down Expand Up @@ -765,8 +813,8 @@ export const migrateConfig = object({
migrations: configMigrations,
});

export const prepareMigrateConfig = async (configPath: string | undefined) => {
const config = await drizzleConfigFromFile(configPath);
export const prepareMigrateConfig = async (configPath: string | undefined, envFile: string | undefined) => {
const config = await drizzleConfigFromFile(configPath, envFile);
const parsed = migrateConfig.safeParse(config);
if (parsed.error) {
console.log(error('Please provide required params:'));
Expand Down Expand Up @@ -869,10 +917,12 @@ export const prepareMigrateConfig = async (configPath: string | undefined) => {
assertUnreachable(dialect);
};

export const drizzleConfigFromFile = async (
configPath?: string,
isExport?: boolean,
): Promise<CliConfig> => {
export const drizzleConfigFromFile = async (params: {
configPath: string | undefined;
envFile?: string;
isExport?: boolean;
}): Promise<CliConfig> => {
const { configPath, envFile, isExport } = params;
const prefix = process.env.TEST_CONFIG_PATH_PREFIX || '';

const defaultTsConfigExists = existsSync(resolve(join(prefix, 'drizzle.config.ts')));
Expand Down Expand Up @@ -904,6 +954,10 @@ export const drizzleConfigFromFile = async (

if (!isExport) console.log(chalk.grey(`Reading config file '${path}'`));

if (envFile) {
loadEnvFile(join(prefix, envFile));
}

const { unregister } = await safeRegister();
const required = require(`${path}`);
const content = required.default ?? required;
Expand Down
8 changes: 6 additions & 2 deletions drizzle-kit/src/cli/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ const optionDriver = string()

const optionCasing = string().enum('camelCase', 'snake_case').desc('Casing for serialization');

const optionEnvFile = string().desc('Path to environment file to load before running the command');

export const generate = command({
name: 'generate',
options: {
Expand All @@ -64,12 +66,13 @@ export const generate = command({
prefix: string()
.enum(...prefixes)
.default('index'),
'env-file': optionEnvFile,
},
transform: async (opts) => {
const from = assertCollisions(
'generate',
opts,
['prefix', 'name', 'custom'],
['prefix', 'name', 'custom', 'env-file'],
['driver', 'breakpoints', 'schema', 'out', 'dialect', 'casing'],
);
return prepareGenerateConfig(opts, from);
Expand Down Expand Up @@ -116,9 +119,10 @@ export const migrate = command({
name: 'migrate',
options: {
config: optionConfig,
'env-file': optionEnvFile,
},
transform: async (opts) => {
return await prepareMigrateConfig(opts.config);
return await prepareMigrateConfig(opts.config, opts['env-file']);
},
handler: async (opts) => {
await assertOrmCoreVersion();
Expand Down
1 change: 1 addition & 0 deletions drizzle-kit/src/cli/validations/studio.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export const studioCliParams = object({
port: coerce.number().optional().default(4983),
host: string().optional().default('127.0.0.1'),
config: string().optional(),
"env-file": string().optional(),
});

export const studioConfig = object({
Expand Down
21 changes: 21 additions & 0 deletions drizzle-kit/tests/cli-generate.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,27 @@ test('generate #9', async (t) => {
});
});

// env-file flag
test('generate #10 --env-file', async (t) => {
const res = await brotest(
generate,
'--config=env.config.ts --env-file=.env.test',
);
if (res.type !== 'handler') assert.fail(res.type, 'handler');
expect(res.options).toStrictEqual({
dialect: 'postgresql',
name: undefined,
custom: false,
prefix: 'index',
breakpoints: true,
schema: './schema.ts',
out: 'drizzle',
bundle: false,
casing: undefined,
driver: undefined,
});
});

// --- errors ---
test('err #1', async (t) => {
const res = await brotest(generate, '--schema=src/schema.ts');
Expand Down
15 changes: 15 additions & 0 deletions drizzle-kit/tests/cli-migrate.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,21 @@ test('migrate #5', async (t) => {
});
});

test('migrate #6 --env-file', async (t) => {
const res = await brotest(migrate, '--config=env.config.ts --env-file=.env.test');
if (res.type !== 'handler') assert.fail(res.type, 'handler');
expect(res.options).toStrictEqual({
dialect: 'postgresql',
out: 'drizzle',
credentials: {
url: 'postgresql://test:[email protected]:5432/db'
},
schema: undefined,
table: undefined,
});
});


// --- errors ---
test('err #1', async (t) => {
const res = await brotest(migrate, '--config=expo.config.ts');
Expand Down
1 change: 1 addition & 0 deletions drizzle-kit/tests/cli/.env.test
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
DATABASE_URL=postgresql://test:[email protected]:5432/db
13 changes: 13 additions & 0 deletions drizzle-kit/tests/cli/env.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { defineConfig } from "../../src";

if (!process.env.DATABASE_URL) {
throw new Error("DATABASE_URL environment variable is not set.");
}

export default defineConfig({
schema: "./schema.ts",
dialect: "postgresql",
dbCredentials: {
url: process.env.DATABASE_URL,
},
});