@asafarim/envage 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/README.md +367 -0
  2. package/bin/envage.js +11 -0
  3. package/dist/cli/commands/decrypt.d.ts +12 -0
  4. package/dist/cli/commands/decrypt.d.ts.map +1 -0
  5. package/dist/cli/commands/decrypt.js +91 -0
  6. package/dist/cli/commands/decrypt.js.map +1 -0
  7. package/dist/cli/commands/encrypt.d.ts +11 -0
  8. package/dist/cli/commands/encrypt.d.ts.map +1 -0
  9. package/dist/cli/commands/encrypt.js +95 -0
  10. package/dist/cli/commands/encrypt.js.map +1 -0
  11. package/dist/cli/commands/init-key.d.ts +13 -0
  12. package/dist/cli/commands/init-key.d.ts.map +1 -0
  13. package/dist/cli/commands/init-key.js +32 -0
  14. package/dist/cli/commands/init-key.js.map +1 -0
  15. package/dist/cli/commands/status.d.ts +11 -0
  16. package/dist/cli/commands/status.d.ts.map +1 -0
  17. package/dist/cli/commands/status.js +73 -0
  18. package/dist/cli/commands/status.js.map +1 -0
  19. package/dist/cli/index.d.ts +13 -0
  20. package/dist/cli/index.d.ts.map +1 -0
  21. package/dist/cli/index.js +44 -0
  22. package/dist/cli/index.js.map +1 -0
  23. package/dist/cli/prompt.d.ts +9 -0
  24. package/dist/cli/prompt.d.ts.map +1 -0
  25. package/dist/cli/prompt.js +62 -0
  26. package/dist/cli/prompt.js.map +1 -0
  27. package/dist/index.d.ts +16 -0
  28. package/dist/index.d.ts.map +1 -0
  29. package/dist/index.js +15 -0
  30. package/dist/index.js.map +1 -0
  31. package/dist/lib/config.d.ts +16 -0
  32. package/dist/lib/config.d.ts.map +1 -0
  33. package/dist/lib/config.js +41 -0
  34. package/dist/lib/config.js.map +1 -0
  35. package/dist/lib/decrypt.d.ts +10 -0
  36. package/dist/lib/decrypt.d.ts.map +1 -0
  37. package/dist/lib/decrypt.js +60 -0
  38. package/dist/lib/decrypt.js.map +1 -0
  39. package/dist/lib/encrypt.d.ts +20 -0
  40. package/dist/lib/encrypt.d.ts.map +1 -0
  41. package/dist/lib/encrypt.js +75 -0
  42. package/dist/lib/encrypt.js.map +1 -0
  43. package/dist/lib/gitignore.d.ts +19 -0
  44. package/dist/lib/gitignore.d.ts.map +1 -0
  45. package/dist/lib/gitignore.js +82 -0
  46. package/dist/lib/gitignore.js.map +1 -0
  47. package/dist/lib/keygen.d.ts +30 -0
  48. package/dist/lib/keygen.d.ts.map +1 -0
  49. package/dist/lib/keygen.js +66 -0
  50. package/dist/lib/keygen.js.map +1 -0
  51. package/dist/lib/logger.d.ts +21 -0
  52. package/dist/lib/logger.d.ts.map +1 -0
  53. package/dist/lib/logger.js +52 -0
  54. package/dist/lib/logger.js.map +1 -0
  55. package/dist/lib/status.d.ts +14 -0
  56. package/dist/lib/status.d.ts.map +1 -0
  57. package/dist/lib/status.js +50 -0
  58. package/dist/lib/status.js.map +1 -0
  59. package/dist/types.d.ts +40 -0
  60. package/dist/types.d.ts.map +1 -0
  61. package/dist/types.js +8 -0
  62. package/dist/types.js.map +1 -0
  63. package/package.json +65 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/status.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAYzC,wBAAgB,cAAc,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAgErD"}
@@ -0,0 +1,73 @@
1
+ import path from "path";
2
+ import chalk from "chalk";
3
+ import { loadConfig } from "../../lib/config.js";
4
+ import { getEnvStatus, getFolderStatus } from "../../lib/status.js";
5
+ import { logger } from "../../lib/logger.js";
6
+ export function registerStatus(program) {
7
+ program
8
+ .command("status [path]")
9
+ .description("Show encryption status of env files")
10
+ .option("-e, --env <env>", "filter by environment name")
11
+ .action(async (folderArg, opts) => {
12
+ const config = await loadConfig();
13
+ let statuses;
14
+ if (folderArg) {
15
+ const folder = path.resolve(process.cwd(), folderArg);
16
+ statuses = await getFolderStatus(folder, config);
17
+ }
18
+ else if (config.apps.length > 0) {
19
+ statuses = await getEnvStatus(config);
20
+ }
21
+ else {
22
+ // Fallback: scan current directory with all configured envs
23
+ statuses = await getFolderStatus(process.cwd(), config);
24
+ }
25
+ // Filter by env if requested
26
+ if (opts.env) {
27
+ statuses = statuses.filter((s) => s.env === opts.env);
28
+ }
29
+ if (statuses.length === 0) {
30
+ logger.warn("No env files found. Check your envage.config.json.");
31
+ return;
32
+ }
33
+ // Group by folder
34
+ const byFolder = new Map();
35
+ for (const s of statuses) {
36
+ const existing = byFolder.get(s.folder) ?? [];
37
+ existing.push(s);
38
+ byFolder.set(s.folder, existing);
39
+ }
40
+ for (const [folder, envStatuses] of byFolder) {
41
+ // Print relative folder name
42
+ const rel = path.relative(process.cwd(), folder) || ".";
43
+ logger.header(rel);
44
+ for (const s of envStatuses) {
45
+ const tag = renderStatusTag(s);
46
+ console.log(` ${chalk.bold(s.env.padEnd(10))} ${tag}`);
47
+ }
48
+ }
49
+ // Summary
50
+ const encrypted = statuses.filter((s) => s.encrypted && !s.decrypted).length;
51
+ const decrypted = statuses.filter((s) => s.decrypted).length;
52
+ const missing = statuses.filter((s) => !s.encrypted && !s.decrypted).length;
53
+ logger.raw("");
54
+ logger.raw(chalk.dim(`${statuses.length} entries — ` +
55
+ `${chalk.green(encrypted + " encrypted")} ` +
56
+ `${chalk.yellow(decrypted + " decrypted")} ` +
57
+ (missing > 0 ? chalk.red(missing + " missing") : "")));
58
+ });
59
+ }
60
+ function renderStatusTag(s) {
61
+ if (s.decrypted && s.encrypted) {
62
+ return (chalk.yellow("⚠ both exist") +
63
+ chalk.dim(" (decrypted + encrypted — consider removing the plaintext file)"));
64
+ }
65
+ if (s.encrypted) {
66
+ return chalk.green("🔒 encrypted");
67
+ }
68
+ if (s.decrypted) {
69
+ return chalk.yellow("🔓 decrypted") + chalk.dim(" (not yet encrypted)");
70
+ }
71
+ return chalk.red("✗ missing");
72
+ }
73
+ //# sourceMappingURL=status.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"status.js","sourceRoot":"","sources":["../../../src/cli/commands/status.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * @asafarim/envage CLI entry point.
3
+ *
4
+ * Registered commands:
5
+ * encrypt — Encrypt .env.<env> → .env.<env>.age
6
+ * decrypt — Decrypt .env.<env>.age → .env.<env>
7
+ * init-key — Generate an age keypair
8
+ * status — Show encryption status
9
+ */
10
+ import { Command } from "commander";
11
+ export declare function createCLI(): Command;
12
+ export declare function runCLI(): Promise<void>;
13
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAWpC,wBAAgB,SAAS,IAAI,OAAO,CA0BnC;AAED,wBAAsB,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC,CAG5C"}
@@ -0,0 +1,44 @@
1
+ /**
2
+ * @asafarim/envage CLI entry point.
3
+ *
4
+ * Registered commands:
5
+ * encrypt — Encrypt .env.<env> → .env.<env>.age
6
+ * decrypt — Decrypt .env.<env>.age → .env.<env>
7
+ * init-key — Generate an age keypair
8
+ * status — Show encryption status
9
+ */
10
+ import { Command } from "commander";
11
+ import { createRequire } from "module";
12
+ import { registerEncrypt } from "./commands/encrypt.js";
13
+ import { registerDecrypt } from "./commands/decrypt.js";
14
+ import { registerInitKey } from "./commands/init-key.js";
15
+ import { registerStatus } from "./commands/status.js";
16
+ const require = createRequire(import.meta.url);
17
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
18
+ const pkg = require("../../package.json");
19
+ export function createCLI() {
20
+ const program = new Command();
21
+ program
22
+ .name("envage")
23
+ .version(pkg.version)
24
+ .description(pkg.description)
25
+ .addHelpText("after", `
26
+ Examples:
27
+ $ envage init-key
28
+ $ envage encrypt apps/web --env dev
29
+ $ envage encrypt --all --env prod
30
+ $ envage decrypt apps/web --env dev
31
+ $ envage decrypt --all --env prod
32
+ $ envage status
33
+ $ envage status apps/web`);
34
+ registerEncrypt(program);
35
+ registerDecrypt(program);
36
+ registerInitKey(program);
37
+ registerStatus(program);
38
+ return program;
39
+ }
40
+ export async function runCLI() {
41
+ const program = createCLI();
42
+ await program.parseAsync(process.argv);
43
+ }
44
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,aAAa,EAAE,MAAM,QAAQ,CAAC;AACvC,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACzD,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAEtD,MAAM,OAAO,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAC/C,8DAA8D;AAC9D,MAAM,GAAG,GAAG,OAAO,CAAC,oBAAoB,CAA6C,CAAC;AAEtF,MAAM,UAAU,SAAS;IACvB,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;IAE9B,OAAO;SACJ,IAAI,CAAC,QAAQ,CAAC;SACd,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC;SACpB,WAAW,CAAC,GAAG,CAAC,WAAW,CAAC;SAC5B,WAAW,CACV,OAAO,EACP;;;;;;;;2BAQqB,CACtB,CAAC;IAEJ,eAAe,CAAC,OAAO,CAAC,CAAC;IACzB,eAAe,CAAC,OAAO,CAAC,CAAC;IACzB,eAAe,CAAC,OAAO,CAAC,CAAC;IACzB,cAAc,CAAC,OAAO,CAAC,CAAC;IAExB,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,MAAM;IAC1B,MAAM,OAAO,GAAG,SAAS,EAAE,CAAC;IAC5B,MAAM,OAAO,CAAC,UAAU,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;AACzC,CAAC"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Ask a yes/no question. Returns true if the user confirms.
3
+ */
4
+ export declare function confirm(question: string): Promise<boolean>;
5
+ /**
6
+ * Prompt for a passphrase (hidden input).
7
+ */
8
+ export declare function promptPassphrase(prompt?: string): Promise<string>;
9
+ //# sourceMappingURL=prompt.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prompt.d.ts","sourceRoot":"","sources":["../../src/cli/prompt.ts"],"names":[],"mappings":"AAKA;;GAEG;AACH,wBAAgB,OAAO,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAW1D;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,SAAiB,GAAG,OAAO,CAAC,MAAM,CAAC,CAqCzE"}
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Simple interactive prompt helpers using readline (no extra deps).
3
+ */
4
+ import readline from "readline";
5
+ /**
6
+ * Ask a yes/no question. Returns true if the user confirms.
7
+ */
8
+ export function confirm(question) {
9
+ return new Promise((resolve) => {
10
+ const rl = readline.createInterface({
11
+ input: process.stdin,
12
+ output: process.stdout,
13
+ });
14
+ rl.question(`${question} [y/N] `, (answer) => {
15
+ rl.close();
16
+ resolve(answer.toLowerCase() === "y" || answer.toLowerCase() === "yes");
17
+ });
18
+ });
19
+ }
20
+ /**
21
+ * Prompt for a passphrase (hidden input).
22
+ */
23
+ export function promptPassphrase(prompt = "Passphrase: ") {
24
+ return new Promise((resolve) => {
25
+ const rl = readline.createInterface({
26
+ input: process.stdin,
27
+ output: process.stdout,
28
+ });
29
+ // Hide input
30
+ if (process.stdin.isTTY) {
31
+ process.stdout.write(prompt);
32
+ process.stdin.setRawMode(true);
33
+ process.stdin.resume();
34
+ process.stdin.setEncoding("utf-8");
35
+ let passphrase = "";
36
+ const handler = (ch) => {
37
+ if (ch === "\n" || ch === "\r" || ch === "") {
38
+ process.stdin.setRawMode(false);
39
+ process.stdin.removeListener("data", handler);
40
+ process.stdout.write("\n");
41
+ rl.close();
42
+ resolve(passphrase);
43
+ }
44
+ else if (ch === "") {
45
+ passphrase = passphrase.slice(0, -1);
46
+ }
47
+ else {
48
+ passphrase += ch;
49
+ }
50
+ };
51
+ process.stdin.on("data", handler);
52
+ }
53
+ else {
54
+ // Non-TTY (piped input) — just read normally
55
+ rl.question(prompt, (answer) => {
56
+ rl.close();
57
+ resolve(answer);
58
+ });
59
+ }
60
+ });
61
+ }
62
+ //# sourceMappingURL=prompt.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prompt.js","sourceRoot":"","sources":["../../src/cli/prompt.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,QAAQ,MAAM,UAAU,CAAC;AAEhC;;GAEG;AACH,MAAM,UAAU,OAAO,CAAC,QAAgB;IACtC,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;QAC7B,MAAM,EAAE,GAAG,QAAQ,CAAC,eAAe,CAAC;YAClC,KAAK,EAAE,OAAO,CAAC,KAAK;YACpB,MAAM,EAAE,OAAO,CAAC,MAAM;SACvB,CAAC,CAAC;QACH,EAAE,CAAC,QAAQ,CAAC,GAAG,QAAQ,SAAS,EAAE,CAAC,MAAM,EAAE,EAAE;YAC3C,EAAE,CAAC,KAAK,EAAE,CAAC;YACX,OAAO,CAAC,MAAM,CAAC,WAAW,EAAE,KAAK,GAAG,IAAI,MAAM,CAAC,WAAW,EAAE,KAAK,KAAK,CAAC,CAAC;QAC1E,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,gBAAgB,CAAC,MAAM,GAAG,cAAc;IACtD,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;QAC7B,MAAM,EAAE,GAAG,QAAQ,CAAC,eAAe,CAAC;YAClC,KAAK,EAAE,OAAO,CAAC,KAAK;YACpB,MAAM,EAAE,OAAO,CAAC,MAAM;SACvB,CAAC,CAAC;QAEH,aAAa;QACb,IAAI,OAAO,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC;YACxB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;YAC7B,OAAO,CAAC,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;YAC/B,OAAO,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC;YACvB,OAAO,CAAC,KAAK,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC;YAEnC,IAAI,UAAU,GAAG,EAAE,CAAC;YACpB,MAAM,OAAO,GAAG,CAAC,EAAU,EAAE,EAAE;gBAC7B,IAAI,EAAE,KAAK,IAAI,IAAI,EAAE,KAAK,IAAI,IAAI,EAAE,KAAK,GAAG,EAAE,CAAC;oBAC7C,OAAO,CAAC,KAAK,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC;oBAChC,OAAO,CAAC,KAAK,CAAC,cAAc,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;oBAC9C,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;oBAC3B,EAAE,CAAC,KAAK,EAAE,CAAC;oBACX,OAAO,CAAC,UAAU,CAAC,CAAC;gBACtB,CAAC;qBAAM,IAAI,EAAE,KAAK,GAAG,EAAE,CAAC;oBACtB,UAAU,GAAG,UAAU,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;gBACvC,CAAC;qBAAM,CAAC;oBACN,UAAU,IAAI,EAAE,CAAC;gBACnB,CAAC;YACH,CAAC,CAAC;YACF,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QACpC,CAAC;aAAM,CAAC;YACN,6CAA6C;YAC7C,EAAE,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC,MAAM,EAAE,EAAE;gBAC7B,EAAE,CAAC,KAAK,EAAE,CAAC;gBACX,OAAO,CAAC,MAAM,CAAC,CAAC;YAClB,CAAC,CAAC,CAAC;QACL,CAAC;IACH,CAAC,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,16 @@
1
+ /**
2
+ * @asafarim/envage — Public programmatic API
3
+ *
4
+ * @example
5
+ * ```ts
6
+ * import { encryptEnv, decryptEnv, generateKeyPair, getEnvStatus } from "@asafarim/envage";
7
+ * ```
8
+ */
9
+ export { encryptEnv } from "./lib/encrypt.js";
10
+ export { decryptEnv } from "./lib/decrypt.js";
11
+ export { generateKeyPair, generateKeyPairToFolder, readIdentityFromFile, readRecipientFromFile, } from "./lib/keygen.js";
12
+ export { getEnvStatus, getFolderStatus, checkEnvStatus } from "./lib/status.js";
13
+ export { loadConfig, saveConfig, resolveAppPaths, CONFIG_FILENAME } from "./lib/config.js";
14
+ export { ensureGitignore, checkStagedEnvFiles, getGitignoreRules } from "./lib/gitignore.js";
15
+ export type { EnvStatus, EnvageConfig, EnvOptions } from "./types.js";
16
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EACL,eAAe,EACf,uBAAuB,EACvB,oBAAoB,EACpB,qBAAqB,GACtB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAChF,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAC3F,OAAO,EAAE,eAAe,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAC7F,YAAY,EAAE,SAAS,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,15 @@
1
+ /**
2
+ * @asafarim/envage — Public programmatic API
3
+ *
4
+ * @example
5
+ * ```ts
6
+ * import { encryptEnv, decryptEnv, generateKeyPair, getEnvStatus } from "@asafarim/envage";
7
+ * ```
8
+ */
9
+ export { encryptEnv } from "./lib/encrypt.js";
10
+ export { decryptEnv } from "./lib/decrypt.js";
11
+ export { generateKeyPair, generateKeyPairToFolder, readIdentityFromFile, readRecipientFromFile, } from "./lib/keygen.js";
12
+ export { getEnvStatus, getFolderStatus, checkEnvStatus } from "./lib/status.js";
13
+ export { loadConfig, saveConfig, resolveAppPaths, CONFIG_FILENAME } from "./lib/config.js";
14
+ export { ensureGitignore, checkStagedEnvFiles, getGitignoreRules } from "./lib/gitignore.js";
15
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EACL,eAAe,EACf,uBAAuB,EACvB,oBAAoB,EACpB,qBAAqB,GACtB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAChF,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAC3F,OAAO,EAAE,eAAe,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC"}
@@ -0,0 +1,16 @@
1
+ import type { EnvageConfig } from "../types.js";
2
+ export declare const CONFIG_FILENAME = "envage.config.json";
3
+ /**
4
+ * Load config from a given root directory.
5
+ * Falls back to DEFAULT_CONFIG if the file does not exist.
6
+ */
7
+ export declare function loadConfig(cwd?: string): Promise<EnvageConfig>;
8
+ /**
9
+ * Save config to the given root directory.
10
+ */
11
+ export declare function saveConfig(config: EnvageConfig, cwd?: string): Promise<void>;
12
+ /**
13
+ * Resolve all app folder paths as absolute paths.
14
+ */
15
+ export declare function resolveAppPaths(config: EnvageConfig, cwd?: string): string[];
16
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/lib/config.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAGhD,eAAO,MAAM,eAAe,uBAAuB,CAAC;AAEpD;;;GAGG;AACH,wBAAsB,UAAU,CAAC,GAAG,SAAgB,GAAG,OAAO,CAAC,YAAY,CAAC,CAc3E;AAED;;GAEG;AACH,wBAAsB,UAAU,CAC9B,MAAM,EAAE,YAAY,EACpB,GAAG,SAAgB,GAClB,OAAO,CAAC,IAAI,CAAC,CAGf;AAED;;GAEG;AACH,wBAAgB,eAAe,CAC7B,MAAM,EAAE,YAAY,EACpB,GAAG,SAAgB,GAClB,MAAM,EAAE,CAIV"}
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Loads, saves, and validates envage.config.json.
3
+ */
4
+ import fs from "fs/promises";
5
+ import path from "path";
6
+ import { DEFAULT_CONFIG } from "../types.js";
7
+ export const CONFIG_FILENAME = "envage.config.json";
8
+ /**
9
+ * Load config from a given root directory.
10
+ * Falls back to DEFAULT_CONFIG if the file does not exist.
11
+ */
12
+ export async function loadConfig(cwd = process.cwd()) {
13
+ const configPath = path.join(cwd, CONFIG_FILENAME);
14
+ try {
15
+ const raw = await fs.readFile(configPath, "utf-8");
16
+ const parsed = JSON.parse(raw);
17
+ return {
18
+ apps: parsed.apps ?? DEFAULT_CONFIG.apps,
19
+ envs: parsed.envs ?? DEFAULT_CONFIG.envs,
20
+ keyFile: parsed.keyFile ?? DEFAULT_CONFIG.keyFile,
21
+ keyPubFile: parsed.keyPubFile ?? DEFAULT_CONFIG.keyPubFile,
22
+ };
23
+ }
24
+ catch {
25
+ return { ...DEFAULT_CONFIG };
26
+ }
27
+ }
28
+ /**
29
+ * Save config to the given root directory.
30
+ */
31
+ export async function saveConfig(config, cwd = process.cwd()) {
32
+ const configPath = path.join(cwd, CONFIG_FILENAME);
33
+ await fs.writeFile(configPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
34
+ }
35
+ /**
36
+ * Resolve all app folder paths as absolute paths.
37
+ */
38
+ export function resolveAppPaths(config, cwd = process.cwd()) {
39
+ return config.apps.map((app) => path.isAbsolute(app) ? app : path.resolve(cwd, app));
40
+ }
41
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/lib/config.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,EAAE,MAAM,aAAa,CAAC;AAC7B,OAAO,IAAI,MAAM,MAAM,CAAC;AAExB,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAE7C,MAAM,CAAC,MAAM,eAAe,GAAG,oBAAoB,CAAC;AAEpD;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,GAAG,GAAG,OAAO,CAAC,GAAG,EAAE;IAClD,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,eAAe,CAAC,CAAC;IACnD,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;QACnD,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAA0B,CAAC;QACxD,OAAO;YACL,IAAI,EAAE,MAAM,CAAC,IAAI,IAAI,cAAc,CAAC,IAAI;YACxC,IAAI,EAAE,MAAM,CAAC,IAAI,IAAI,cAAc,CAAC,IAAI;YACxC,OAAO,EAAE,MAAM,CAAC,OAAO,IAAI,cAAc,CAAC,OAAO;YACjD,UAAU,EAAE,MAAM,CAAC,UAAU,IAAI,cAAc,CAAC,UAAU;SAC3D,CAAC;IACJ,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,GAAG,cAAc,EAAE,CAAC;IAC/B,CAAC;AACH,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAC9B,MAAoB,EACpB,GAAG,GAAG,OAAO,CAAC,GAAG,EAAE;IAEnB,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,eAAe,CAAC,CAAC;IACnD,MAAM,EAAE,CAAC,SAAS,CAAC,UAAU,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,EAAE,OAAO,CAAC,CAAC;AAClF,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,eAAe,CAC7B,MAAoB,EACpB,GAAG,GAAG,OAAO,CAAC,GAAG,EAAE;IAEnB,OAAO,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAC7B,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,GAAG,CAAC,CACpD,CAAC;AACJ,CAAC"}
@@ -0,0 +1,10 @@
1
+ import type { EnvOptions } from "../types.js";
2
+ /**
3
+ * Decrypt a .env.<env>.age file into .env.<env>.
4
+ *
5
+ * Either `keyFile` (path to private key .age/key.txt) or `passphrase` must be supplied.
6
+ *
7
+ * @returns The path to the written plaintext file
8
+ */
9
+ export declare function decryptEnv(options: EnvOptions): Promise<string>;
10
+ //# sourceMappingURL=decrypt.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"decrypt.d.ts","sourceRoot":"","sources":["../../src/lib/decrypt.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAE9C;;;;;;GAMG;AACH,wBAAsB,UAAU,CAAC,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CA+CrE"}
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Decrypts a .env.<env>.age file into .env.<env> using age encryption.
3
+ *
4
+ * Supports both:
5
+ * - Private key decryption (via keyFile)
6
+ * - Passphrase-based decryption
7
+ *
8
+ * SECURITY: decrypted content is never logged.
9
+ */
10
+ import * as age from "age-encryption";
11
+ import fs from "fs/promises";
12
+ import path from "path";
13
+ import { readIdentityFromFile } from "./keygen.js";
14
+ /**
15
+ * Decrypt a .env.<env>.age file into .env.<env>.
16
+ *
17
+ * Either `keyFile` (path to private key .age/key.txt) or `passphrase` must be supplied.
18
+ *
19
+ * @returns The path to the written plaintext file
20
+ */
21
+ export async function decryptEnv(options) {
22
+ const { folder, env, keyFile, passphrase } = options;
23
+ const cipherPath = path.join(folder, `.env.${env}.age`);
24
+ const plainPath = path.join(folder, `.env.${env}`);
25
+ // Read the ciphertext
26
+ let ciphertext;
27
+ try {
28
+ const buf = await fs.readFile(cipherPath);
29
+ ciphertext = new Uint8Array(buf);
30
+ }
31
+ catch (err) {
32
+ const msg = err instanceof Error ? err.message : String(err);
33
+ throw new Error(`Cannot read ${cipherPath}: ${msg}\n` +
34
+ `Make sure the encrypted file exists before decrypting.`);
35
+ }
36
+ const decrypter = new age.Decrypter();
37
+ if (passphrase) {
38
+ decrypter.addPassphrase(passphrase);
39
+ }
40
+ else if (keyFile) {
41
+ const identity = await readIdentityFromFile(keyFile);
42
+ decrypter.addIdentity(identity);
43
+ }
44
+ else {
45
+ throw new Error("Either --key (key file path) or --passphrase must be provided for decryption.");
46
+ }
47
+ let plaintext;
48
+ try {
49
+ plaintext = await decrypter.decrypt(ciphertext, "uint8array");
50
+ }
51
+ catch (err) {
52
+ const msg = err instanceof Error ? err.message : String(err);
53
+ throw new Error(`Decryption failed for ${cipherPath}: ${msg}\n` +
54
+ `Check that you are using the correct key or passphrase.`);
55
+ }
56
+ // Write decrypted content — never log it
57
+ await fs.writeFile(plainPath, plaintext);
58
+ return plainPath;
59
+ }
60
+ //# sourceMappingURL=decrypt.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"decrypt.js","sourceRoot":"","sources":["../../src/lib/decrypt.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,KAAK,GAAG,MAAM,gBAAgB,CAAC;AACtC,OAAO,EAAE,MAAM,aAAa,CAAC;AAC7B,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAGnD;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,OAAmB;IAClD,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,EAAE,UAAU,EAAE,GAAG,OAAO,CAAC;IAErD,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,QAAQ,GAAG,MAAM,CAAC,CAAC;IACxD,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,QAAQ,GAAG,EAAE,CAAC,CAAC;IAEnD,sBAAsB;IACtB,IAAI,UAAsB,CAAC;IAC3B,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC;QAC1C,UAAU,GAAG,IAAI,UAAU,CAAC,GAAG,CAAC,CAAC;IACnC,CAAC;IAAC,OAAO,GAAY,EAAE,CAAC;QACtB,MAAM,GAAG,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAC7D,MAAM,IAAI,KAAK,CACb,eAAe,UAAU,KAAK,GAAG,IAAI;YACnC,wDAAwD,CAC3D,CAAC;IACJ,CAAC;IAED,MAAM,SAAS,GAAG,IAAI,GAAG,CAAC,SAAS,EAAE,CAAC;IAEtC,IAAI,UAAU,EAAE,CAAC;QACf,SAAS,CAAC,aAAa,CAAC,UAAU,CAAC,CAAC;IACtC,CAAC;SAAM,IAAI,OAAO,EAAE,CAAC;QACnB,MAAM,QAAQ,GAAG,MAAM,oBAAoB,CAAC,OAAO,CAAC,CAAC;QACrD,SAAS,CAAC,WAAW,CAAC,QAAQ,CAAC,CAAC;IAClC,CAAC;SAAM,CAAC;QACN,MAAM,IAAI,KAAK,CACb,+EAA+E,CAChF,CAAC;IACJ,CAAC;IAED,IAAI,SAAqB,CAAC;IAC1B,IAAI,CAAC;QACH,SAAS,GAAG,MAAM,SAAS,CAAC,OAAO,CAAC,UAAU,EAAE,YAAY,CAAC,CAAC;IAChE,CAAC;IAAC,OAAO,GAAY,EAAE,CAAC;QACtB,MAAM,GAAG,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAC7D,MAAM,IAAI,KAAK,CACb,yBAAyB,UAAU,KAAK,GAAG,IAAI;YAC7C,yDAAyD,CAC5D,CAAC;IACJ,CAAC;IAED,yCAAyC;IACzC,MAAM,EAAE,CAAC,SAAS,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC;IAEzC,OAAO,SAAS,CAAC;AACnB,CAAC"}
@@ -0,0 +1,20 @@
1
+ import type { EnvOptions } from "../types.js";
2
+ /**
3
+ * Encrypt a .env.<env> file into .env.<env>.age.
4
+ *
5
+ * Either `keyFile` (path to a .age/key.pub) or `passphrase` must be supplied.
6
+ * If `keyFile` points to a private key (key.txt), the corresponding public key
7
+ * is derived automatically if key.pub lives alongside it.
8
+ *
9
+ * @returns The path to the written .age file
10
+ */
11
+ export declare function encryptEnv(options: EnvOptions): Promise<string>;
12
+ /**
13
+ * Build the encrypted file path for a given folder + env.
14
+ */
15
+ export declare function encryptedPath(folder: string, env: string): string;
16
+ /**
17
+ * Build the plaintext file path for a given folder + env.
18
+ */
19
+ export declare function plaintextPath(folder: string, env: string): string;
20
+ //# sourceMappingURL=encrypt.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"encrypt.d.ts","sourceRoot":"","sources":["../../src/lib/encrypt.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAE9C;;;;;;;;GAQG;AACH,wBAAsB,UAAU,CAAC,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAgDrE;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAEjE;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAEjE"}
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Encrypts a .env.<env> file into .env.<env>.age using age encryption.
3
+ *
4
+ * Supports both:
5
+ * - Public key encryption (via keyFile / pubFile)
6
+ * - Passphrase-based encryption
7
+ */
8
+ import * as age from "age-encryption";
9
+ import fs from "fs/promises";
10
+ import path from "path";
11
+ import { readRecipientFromFile } from "./keygen.js";
12
+ /**
13
+ * Encrypt a .env.<env> file into .env.<env>.age.
14
+ *
15
+ * Either `keyFile` (path to a .age/key.pub) or `passphrase` must be supplied.
16
+ * If `keyFile` points to a private key (key.txt), the corresponding public key
17
+ * is derived automatically if key.pub lives alongside it.
18
+ *
19
+ * @returns The path to the written .age file
20
+ */
21
+ export async function encryptEnv(options) {
22
+ const { folder, env, keyFile, passphrase } = options;
23
+ const plainPath = path.join(folder, `.env.${env}`);
24
+ const cipherPath = path.join(folder, `.env.${env}.age`);
25
+ // Read the plaintext env file
26
+ let plaintext;
27
+ try {
28
+ const buf = await fs.readFile(plainPath);
29
+ plaintext = new Uint8Array(buf);
30
+ }
31
+ catch (err) {
32
+ const msg = err instanceof Error ? err.message : String(err);
33
+ throw new Error(`Cannot read ${plainPath}: ${msg}\n` +
34
+ `Make sure the file exists before encrypting.`);
35
+ }
36
+ const encrypter = new age.Encrypter();
37
+ if (passphrase) {
38
+ encrypter.setPassphrase(passphrase);
39
+ }
40
+ else if (keyFile) {
41
+ // Determine the pub file: prefer key.pub alongside the key file
42
+ const pubFile = keyFile.replace(/key\.txt$/, "key.pub");
43
+ let recipient;
44
+ try {
45
+ recipient = await readRecipientFromFile(pubFile);
46
+ }
47
+ catch {
48
+ // If no .pub, try reading the keyFile as a private key and deriving the recipient
49
+ const { readIdentityFromFile } = await import("./keygen.js");
50
+ const { identityToRecipient } = await import("age-encryption");
51
+ const identity = await readIdentityFromFile(keyFile);
52
+ recipient = await identityToRecipient(identity);
53
+ }
54
+ encrypter.addRecipient(recipient);
55
+ }
56
+ else {
57
+ throw new Error("Either --key (key file path) or --passphrase must be provided for encryption.");
58
+ }
59
+ const ciphertext = await encrypter.encrypt(plaintext);
60
+ await fs.writeFile(cipherPath, ciphertext);
61
+ return cipherPath;
62
+ }
63
+ /**
64
+ * Build the encrypted file path for a given folder + env.
65
+ */
66
+ export function encryptedPath(folder, env) {
67
+ return path.join(folder, `.env.${env}.age`);
68
+ }
69
+ /**
70
+ * Build the plaintext file path for a given folder + env.
71
+ */
72
+ export function plaintextPath(folder, env) {
73
+ return path.join(folder, `.env.${env}`);
74
+ }
75
+ //# sourceMappingURL=encrypt.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"encrypt.js","sourceRoot":"","sources":["../../src/lib/encrypt.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,KAAK,GAAG,MAAM,gBAAgB,CAAC;AACtC,OAAO,EAAE,MAAM,aAAa,CAAC;AAC7B,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAGpD;;;;;;;;GAQG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,OAAmB;IAClD,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,EAAE,UAAU,EAAE,GAAG,OAAO,CAAC;IAErD,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,QAAQ,GAAG,EAAE,CAAC,CAAC;IACnD,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,QAAQ,GAAG,MAAM,CAAC,CAAC;IAExD,8BAA8B;IAC9B,IAAI,SAAqB,CAAC;IAC1B,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC;QACzC,SAAS,GAAG,IAAI,UAAU,CAAC,GAAG,CAAC,CAAC;IAClC,CAAC;IAAC,OAAO,GAAY,EAAE,CAAC;QACtB,MAAM,GAAG,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAC7D,MAAM,IAAI,KAAK,CACb,eAAe,SAAS,KAAK,GAAG,IAAI;YAClC,8CAA8C,CACjD,CAAC;IACJ,CAAC;IAED,MAAM,SAAS,GAAG,IAAI,GAAG,CAAC,SAAS,EAAE,CAAC;IAEtC,IAAI,UAAU,EAAE,CAAC;QACf,SAAS,CAAC,aAAa,CAAC,UAAU,CAAC,CAAC;IACtC,CAAC;SAAM,IAAI,OAAO,EAAE,CAAC;QACnB,gEAAgE;QAChE,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,WAAW,EAAE,SAAS,CAAC,CAAC;QACxD,IAAI,SAAiB,CAAC;QACtB,IAAI,CAAC;YACH,SAAS,GAAG,MAAM,qBAAqB,CAAC,OAAO,CAAC,CAAC;QACnD,CAAC;QAAC,MAAM,CAAC;YACP,kFAAkF;YAClF,MAAM,EAAE,oBAAoB,EAAE,GAAG,MAAM,MAAM,CAAC,aAAa,CAAC,CAAC;YAC7D,MAAM,EAAE,mBAAmB,EAAE,GAAG,MAAM,MAAM,CAAC,gBAAgB,CAAC,CAAC;YAC/D,MAAM,QAAQ,GAAG,MAAM,oBAAoB,CAAC,OAAO,CAAC,CAAC;YACrD,SAAS,GAAG,MAAM,mBAAmB,CAAC,QAAQ,CAAC,CAAC;QAClD,CAAC;QACD,SAAS,CAAC,YAAY,CAAC,SAAS,CAAC,CAAC;IACpC,CAAC;SAAM,CAAC;QACN,MAAM,IAAI,KAAK,CACb,+EAA+E,CAChF,CAAC;IACJ,CAAC;IAED,MAAM,UAAU,GAAG,MAAM,SAAS,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAEtD,MAAM,EAAE,CAAC,SAAS,CAAC,UAAU,EAAE,UAAU,CAAC,CAAC;IAE3C,OAAO,UAAU,CAAC;AACpB,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,aAAa,CAAC,MAAc,EAAE,GAAW;IACvD,OAAO,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,QAAQ,GAAG,MAAM,CAAC,CAAC;AAC9C,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,aAAa,CAAC,MAAc,EAAE,GAAW;IACvD,OAAO,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,QAAQ,GAAG,EAAE,CAAC,CAAC;AAC1C,CAAC"}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Ensures the envage gitignore rules are present in <cwd>/.gitignore.
3
+ * If the file doesn't exist, it is created. If the block already exists,
4
+ * it is updated in place.
5
+ */
6
+ export declare function ensureGitignore(cwd?: string): Promise<void>;
7
+ /**
8
+ * Checks whether any staged files in the current git repo are decrypted
9
+ * .env files (i.e. match .env* but NOT .env*.age).
10
+ *
11
+ * Returns an array of offending file paths.
12
+ * Returns empty array if git is not available or not in a repo.
13
+ */
14
+ export declare function checkStagedEnvFiles(cwd?: string): string[];
15
+ /**
16
+ * Returns the recommended gitignore content block as a string (for display).
17
+ */
18
+ export declare function getGitignoreRules(): string;
19
+ //# sourceMappingURL=gitignore.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gitignore.d.ts","sourceRoot":"","sources":["../../src/lib/gitignore.ts"],"names":[],"mappings":"AAuBA;;;;GAIG;AACH,wBAAsB,eAAe,CAAC,GAAG,SAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,CA8BxE;AAED;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,SAAgB,GAAG,MAAM,EAAE,CAgBjE;AAED;;GAEG;AACH,wBAAgB,iBAAiB,IAAI,MAAM,CAE1C"}
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Git integration helpers:
3
+ * - generate .gitignore rules for env files
4
+ * - warn if decrypted env files are staged
5
+ */
6
+ import fs from "fs/promises";
7
+ import path from "path";
8
+ import { execSync } from "child_process";
9
+ /** The gitignore block managed by envage */
10
+ const ENVAGE_BLOCK_START = "# --- envage managed ---";
11
+ const ENVAGE_BLOCK_END = "# --- end envage ---";
12
+ const ENVAGE_RULES = `${ENVAGE_BLOCK_START}
13
+ # Decrypted env files — never commit
14
+ .env*
15
+ !.env*.age
16
+ !.env.example
17
+ !.env.template
18
+ # Age private key
19
+ .age/key.txt
20
+ ${ENVAGE_BLOCK_END}`;
21
+ /**
22
+ * Ensures the envage gitignore rules are present in <cwd>/.gitignore.
23
+ * If the file doesn't exist, it is created. If the block already exists,
24
+ * it is updated in place.
25
+ */
26
+ export async function ensureGitignore(cwd = process.cwd()) {
27
+ const gitignorePath = path.join(cwd, ".gitignore");
28
+ let existing = "";
29
+ try {
30
+ existing = await fs.readFile(gitignorePath, "utf-8");
31
+ }
32
+ catch {
33
+ // file doesn't exist — we'll create it
34
+ }
35
+ if (existing.includes(ENVAGE_BLOCK_START)) {
36
+ // Update the block in place
37
+ const startIdx = existing.indexOf(ENVAGE_BLOCK_START);
38
+ const endIdx = existing.indexOf(ENVAGE_BLOCK_END);
39
+ if (endIdx !== -1) {
40
+ const updated = existing.slice(0, startIdx) +
41
+ ENVAGE_RULES +
42
+ existing.slice(endIdx + ENVAGE_BLOCK_END.length);
43
+ await fs.writeFile(gitignorePath, updated, "utf-8");
44
+ }
45
+ }
46
+ else {
47
+ // Append the block
48
+ const separator = existing.endsWith("\n") || existing === "" ? "" : "\n";
49
+ await fs.writeFile(gitignorePath, existing + separator + "\n" + ENVAGE_RULES + "\n", "utf-8");
50
+ }
51
+ }
52
+ /**
53
+ * Checks whether any staged files in the current git repo are decrypted
54
+ * .env files (i.e. match .env* but NOT .env*.age).
55
+ *
56
+ * Returns an array of offending file paths.
57
+ * Returns empty array if git is not available or not in a repo.
58
+ */
59
+ export function checkStagedEnvFiles(cwd = process.cwd()) {
60
+ try {
61
+ const output = execSync("git diff --cached --name-only", {
62
+ cwd,
63
+ encoding: "utf-8",
64
+ stdio: ["ignore", "pipe", "ignore"],
65
+ });
66
+ const staged = output.split("\n").filter(Boolean);
67
+ return staged.filter((f) => {
68
+ const base = path.basename(f);
69
+ return base.match(/^\.env/) && !base.endsWith(".age");
70
+ });
71
+ }
72
+ catch {
73
+ return [];
74
+ }
75
+ }
76
+ /**
77
+ * Returns the recommended gitignore content block as a string (for display).
78
+ */
79
+ export function getGitignoreRules() {
80
+ return ENVAGE_RULES;
81
+ }
82
+ //# sourceMappingURL=gitignore.js.map