Initial Commit
This commit is contained in:
@@ -0,0 +1,78 @@
|
||||
import { homedir } from 'node:os';
|
||||
import { join, resolve } from 'node:path';
|
||||
import { readFile } from 'node:fs/promises';
|
||||
import { parseEnv } from 'node:util';
|
||||
import { ADMIN_KEY, applicationEnvironment, fetchSecrets, localDeploymentAdmin, readCredentials, REQUIRED_KEYS, validateProject } from './lib/infisical';
|
||||
|
||||
const HELP = `Infisical-backed development deployment (secret values are never printed by this helper)
|
||||
|
||||
bun run secrets:status
|
||||
bun run secrets:run -- <trusted command> [arguments...]
|
||||
bun run secrets:run --deployment -- <trusted deployment command> [arguments...]
|
||||
|
||||
Optional: --credentials-file PATH (default ~/.config/evebot/infisical.env)
|
||||
Project metadata: .infisical.json. Public deployment configuration: config/dev.env.
|
||||
Only allowlisted app secrets are injected. --deployment additionally permits the Convex admin key
|
||||
and, when absent from the vault, derives it from the local persistent backend without printing it.
|
||||
Never use this wrapper with printenv, env, shell tracing, or docker compose config without --quiet.
|
||||
`;
|
||||
|
||||
export function parseArguments(args: string[]) {
|
||||
const [action, ...rest] = args;
|
||||
if (action !== 'status' && action !== 'run') throw new Error('Use status or run');
|
||||
let command: string[] = [];
|
||||
let credentialsFile = join(homedir(), '.config/evebot/infisical.env');
|
||||
let deployment = false;
|
||||
for (let i = 0; i < rest.length; i++) {
|
||||
if (rest[i] === '--') { command = rest.slice(i + 1); break; }
|
||||
if (rest[i] === '--credentials-file' && rest[i + 1] && !rest[i + 1].startsWith('--')) credentialsFile = resolve(rest[++i]);
|
||||
else if (rest[i] === '--deployment') deployment = true;
|
||||
// `bun run` consumes its own -- separator before forwarding script arguments.
|
||||
else if (action === 'run' && !rest[i].startsWith('-')) { command = rest.slice(i); break; }
|
||||
else throw new Error('Unknown or incomplete secrets helper option');
|
||||
}
|
||||
if (action === 'run' && !command.length) throw new Error('Supply a trusted command after --');
|
||||
if (action === 'status' && command.length) throw new Error('status does not execute a command');
|
||||
return { action, command, credentialsFile, deployment };
|
||||
}
|
||||
|
||||
async function main(args: string[]): Promise<void> {
|
||||
if (!args.length || args[0] === '--help') { console.log(HELP); return; }
|
||||
const { action, command, credentialsFile, deployment } = parseArguments(args);
|
||||
const project = validateProject(JSON.parse(await readFile(new URL('../.infisical.json', import.meta.url), 'utf8')));
|
||||
const credentials = await readCredentials(credentialsFile);
|
||||
const secrets = await fetchSecrets(project, credentials);
|
||||
if (action === 'status') {
|
||||
console.log(JSON.stringify({
|
||||
authenticated: true,
|
||||
domain: project.domain, projectId: project.workspaceId,
|
||||
environment: project.defaultEnvironment, secretPath: project.secretPath,
|
||||
secretNames: Object.keys(secrets).sort(),
|
||||
missingRequiredKeys: REQUIRED_KEYS.filter((key) => !secrets[key]?.trim()),
|
||||
values: 'not displayed',
|
||||
}, null, 2));
|
||||
return;
|
||||
}
|
||||
const publicConfig = parseEnv(await readFile(new URL('../config/dev.env', import.meta.url), 'utf8'));
|
||||
const env = applicationEnvironment(process.env, secrets, publicConfig, deployment);
|
||||
if (deployment && !env[ADMIN_KEY]) env[ADMIN_KEY] = await localDeploymentAdmin(env);
|
||||
const child = Bun.spawn(command, { env, stdin: 'inherit', stdout: 'inherit', stderr: 'inherit' });
|
||||
const interrupt = () => child.kill('SIGINT');
|
||||
const terminate = () => child.kill('SIGTERM');
|
||||
process.on('SIGINT', interrupt);
|
||||
process.on('SIGTERM', terminate);
|
||||
try { process.exitCode = await child.exited; }
|
||||
finally {
|
||||
process.off('SIGINT', interrupt);
|
||||
process.off('SIGTERM', terminate);
|
||||
}
|
||||
}
|
||||
|
||||
if (import.meta.main) {
|
||||
try { await main(process.argv.slice(2)); }
|
||||
catch (error) {
|
||||
// Never print stack/source excerpts or raw CLI errors around the secret boundary.
|
||||
console.error(error instanceof Error ? error.message : 'Secret configuration failed');
|
||||
process.exitCode = 1;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user