Initial envsitter CLI and safe env matching

This commit is contained in:
David Ibia
2026-01-12 10:30:49 +01:00
commit 3993746843
17 changed files with 1001 additions and 0 deletions

32
src/sources/dotenvFile.ts Normal file
View File

@@ -0,0 +1,32 @@
import { readFile } from 'node:fs/promises';
import { parseDotenv } from '../dotenv/parse.js';
export type DotenvFileSourceOptions = {
allowErrors?: boolean;
};
type Snapshot = {
values: ReadonlyMap<string, string>;
};
export class DotenvFileSource {
readonly filePath: string;
readonly options: DotenvFileSourceOptions;
constructor(filePath: string, options: DotenvFileSourceOptions = {}) {
this.filePath = filePath;
this.options = options;
}
async load(): Promise<Snapshot> {
const contents = await readFile(this.filePath, 'utf8');
const parsed = parseDotenv(contents);
if (parsed.errors.length > 0 && !this.options.allowErrors) {
const message = parsed.errors.map((e) => `L${e.line}: ${e.message}`).join(', ');
throw new Error(`Invalid dotenv file: ${message}`);
}
return { values: parsed.values };
}
}

View File

@@ -0,0 +1,45 @@
import { execFile } from 'node:child_process';
import { promisify } from 'node:util';
import { parseDotenv } from '../dotenv/parse.js';
const execFileAsync = promisify(execFile);
type Snapshot = {
values: ReadonlyMap<string, string>;
};
export type ExternalCommandSourceOptions = {
cwd?: string;
env?: NodeJS.ProcessEnv;
timeoutMs?: number;
allowErrors?: boolean;
};
export class ExternalCommandSource {
readonly command: string;
readonly args: readonly string[];
readonly options: ExternalCommandSourceOptions;
constructor(command: string, args: readonly string[] = [], options: ExternalCommandSourceOptions = {}) {
this.command = command;
this.args = args;
this.options = options;
}
async load(): Promise<Snapshot> {
const { stdout } = await execFileAsync(this.command, [...this.args], {
cwd: this.options.cwd,
env: this.options.env,
timeout: this.options.timeoutMs
});
const parsed = parseDotenv(stdout);
if (parsed.errors.length > 0 && !this.options.allowErrors) {
const message = parsed.errors.map((e) => `L${e.line}: ${e.message}`).join(', ');
throw new Error(`Invalid dotenv content from external command: ${message}`);
}
return { values: parsed.values };
}
}