@envctrl/cli 1.2.0 → 1.4.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.
package/dist/index.js CHANGED
@@ -14,7 +14,7 @@ var BaseCommand = class {
14
14
  * Executes a subcommand and handles the result.
15
15
  *
16
16
  * Prints the error message to stderr and exits with code 1 on failure.
17
- * Prints data as JSON to stdout on success when `context.quiet` is false.
17
+ * Prints `result.message` to stdout on success when `context.quiet` is false.
18
18
  *
19
19
  * @param cmd - The subcommand to execute
20
20
  * @param options - Parsed options from Commander
@@ -27,8 +27,8 @@ var BaseCommand = class {
27
27
  `);
28
28
  process.exit(1);
29
29
  }
30
- if (!context.quiet && result.data !== void 0) {
31
- process.stdout.write(JSON.stringify(result.data, null, 2) + "\n");
30
+ if (!context.quiet && result.message !== void 0) {
31
+ process.stdout.write(result.message + "\n");
32
32
  }
33
33
  }
34
34
  /**
@@ -47,20 +47,24 @@ var BaseCommand = class {
47
47
 
48
48
  // src/utils/env-files.ts
49
49
  import path from "path";
50
+ var RESERVED_ENV_NAMES = /* @__PURE__ */ new Set(["keys", "example"]);
50
51
  function parseEnvironmentFromFilename(filename) {
51
52
  const base = path.basename(filename);
52
53
  if (!base.startsWith(".env.")) {
53
54
  return void 0;
54
55
  }
55
56
  const withoutPrefix = base.slice(".env.".length);
56
- if (!withoutPrefix || withoutPrefix === "keys") {
57
+ if (!withoutPrefix || RESERVED_ENV_NAMES.has(withoutPrefix)) {
57
58
  return void 0;
58
59
  }
59
- const withoutUnencrypted = withoutPrefix.endsWith(".unencrypted") ? withoutPrefix.slice(0, -".unencrypted".length) : withoutPrefix;
60
- if (!withoutUnencrypted) {
60
+ let name = withoutPrefix.endsWith(".unencrypted") ? withoutPrefix.slice(0, -".unencrypted".length) : withoutPrefix;
61
+ if (name.endsWith(".local")) {
62
+ name = name.slice(0, -".local".length);
63
+ }
64
+ if (!name || RESERVED_ENV_NAMES.has(name)) {
61
65
  return void 0;
62
66
  }
63
- return withoutUnencrypted;
67
+ return name;
64
68
  }
65
69
  function buildEnvFilePair(environment, cwd) {
66
70
  return {
@@ -195,9 +199,14 @@ var SwitchCommand = class {
195
199
  } else {
196
200
  await fs2.symlink(pair.encrypted, activePath);
197
201
  }
202
+ const lines = [`Switched to: ${environment}`];
203
+ if (created.length > 0) {
204
+ lines.push("Created:", ...created.map((f) => ` ${f}`));
205
+ }
198
206
  return {
199
207
  success: true,
200
- data: { environment, created, activePath }
208
+ data: { environment, created, activePath },
209
+ message: lines.join("\n")
201
210
  };
202
211
  } catch (err) {
203
212
  return {
@@ -242,6 +251,9 @@ function resolveDefaultKeystorePath() {
242
251
  function resolveKeystoresRegistryPath() {
243
252
  return path4.join(resolveAppDataRoot(), "keystores.json");
244
253
  }
254
+ function resolveBlacklistPath() {
255
+ return path4.join(resolveAppDataRoot(), "blacklist.json");
256
+ }
245
257
 
246
258
  // src/commands/keystore/registry.ts
247
259
  import fs3 from "fs/promises";
@@ -279,7 +291,11 @@ var KeystoreCreateSubCommand = class {
279
291
  registry.push({ name, path: keystorePath });
280
292
  await writeRegistry(registryPath, registry);
281
293
  }
282
- return { success: true, data: { path: keystorePath } };
294
+ return {
295
+ success: true,
296
+ data: { path: keystorePath },
297
+ message: `Keystore created at: ${keystorePath}`
298
+ };
283
299
  } catch (err) {
284
300
  return {
285
301
  success: false,
@@ -296,7 +312,14 @@ var KeystoreListSubCommand = class {
296
312
  try {
297
313
  const registryPath = resolveKeystoresRegistryPath();
298
314
  const entries = await readRegistry(registryPath);
299
- return { success: true, data: entries };
315
+ let message;
316
+ if (entries.length === 0) {
317
+ message = "No keystores registered.";
318
+ } else {
319
+ const maxLen = Math.max(...entries.map((e) => e.name.length));
320
+ message = entries.map((e) => `${e.name.padEnd(maxLen)} ${e.path}`).join("\n");
321
+ }
322
+ return { success: true, data: entries, message };
300
323
  } catch (err) {
301
324
  return {
302
325
  success: false,
@@ -331,7 +354,11 @@ var KeystoreDeleteSubCommand = class {
331
354
  const updated = registry.filter((e) => e.name !== name);
332
355
  await writeRegistry(registryPath, updated);
333
356
  await fs5.rm(entry.path, { recursive: true, force: true });
334
- return { success: true, data: entry };
357
+ return {
358
+ success: true,
359
+ data: entry,
360
+ message: `Deleted keystore: ${entry.name} (${entry.path})`
361
+ };
335
362
  } catch (err) {
336
363
  return {
337
364
  success: false,
@@ -385,7 +412,8 @@ var SetCommand = class {
385
412
  const changed = result.changedFilepaths.length > 0;
386
413
  return {
387
414
  success: true,
388
- data: { environment, key, changed }
415
+ data: { environment, key, changed },
416
+ message: `Set ${key} in ${environment}`
389
417
  };
390
418
  } catch (err) {
391
419
  return {
@@ -443,9 +471,14 @@ var EncryptCommand = class {
443
471
  unchangedFiles.push(filePath);
444
472
  }
445
473
  }
474
+ const lines = [
475
+ ...changedFiles.map((f) => `Encrypted: ${f}`),
476
+ ...unchangedFiles.map((f) => `Unchanged: ${f}`)
477
+ ];
446
478
  return {
447
479
  success: true,
448
- data: { changedFiles, unchangedFiles }
480
+ data: { changedFiles, unchangedFiles },
481
+ message: lines.length > 0 ? lines.join("\n") : "No files processed."
449
482
  };
450
483
  } catch (err) {
451
484
  return {
@@ -604,14 +637,19 @@ var InitCommand = class {
604
637
  keysLinked.push(linkPath);
605
638
  }
606
639
  }
607
- await fs9.writeFile(
608
- path8.join(workspaceRoot, ".envctrl.json"),
609
- JSON.stringify({ workspaceRoot, keystorePath, packages }, null, 2) + "\n",
610
- "utf8"
611
- );
640
+ const lines = [`Workspace: ${workspaceRoot}`, `Keystore: ${keystorePath}`];
641
+ if (packages.length > 0) {
642
+ lines.push("Packages:");
643
+ for (const p of packages) lines.push(` ${p.name} (${p.path})`);
644
+ }
645
+ if (keysLinked.length > 0) {
646
+ lines.push("Linked .env.keys:");
647
+ for (const l of keysLinked) lines.push(` ${l}`);
648
+ }
612
649
  return {
613
650
  success: true,
614
- data: { workspaceRoot, packages, keystorePath, keysLinked }
651
+ data: { workspaceRoot, packages, keystorePath, keysLinked },
652
+ message: lines.join("\n")
615
653
  };
616
654
  } catch (err) {
617
655
  return {
@@ -625,7 +663,7 @@ var InitBaseCommand = class extends BaseCommand {
625
663
  /** @inheritdoc */
626
664
  register(program2) {
627
665
  program2.command("init [workspace]").description(
628
- "Scan the workspace or monorepo, link packages to a shared keystore, and write .envctrl.json"
666
+ "Scan the workspace or monorepo and link packages to a shared keystore"
629
667
  ).option("-n, --name <name>", "name for the keystore entry in the registry").option("-k, --keystore <path>", "custom keystore directory path").action(
630
668
  async (workspace, opts) => {
631
669
  await this.dispatch(
@@ -638,13 +676,218 @@ var InitBaseCommand = class extends BaseCommand {
638
676
  }
639
677
  };
640
678
 
679
+ // src/commands/list/index.ts
680
+ import path10 from "path";
681
+
682
+ // src/utils/blacklist.ts
683
+ import fs10 from "fs/promises";
684
+ import path9 from "path";
685
+ async function readBlacklist() {
686
+ try {
687
+ const raw = await fs10.readFile(resolveBlacklistPath(), "utf8");
688
+ return JSON.parse(raw);
689
+ } catch {
690
+ return [];
691
+ }
692
+ }
693
+ async function writeBlacklist(entries) {
694
+ const filePath = resolveBlacklistPath();
695
+ await fs10.mkdir(path9.dirname(filePath), { recursive: true });
696
+ await fs10.writeFile(filePath, JSON.stringify(entries, null, 2) + "\n", "utf8");
697
+ }
698
+
699
+ // src/commands/list/index.ts
700
+ var ListCommand = class {
701
+ /** @inheritdoc */
702
+ async execute(_options, context) {
703
+ try {
704
+ const [files, blacklist] = await Promise.all([
705
+ Promise.resolve(listEnvFiles(context.cwd, ".env.*", [".env.keys"])),
706
+ readBlacklist()
707
+ ]);
708
+ const blacklisted = new Set(blacklist);
709
+ const names = files.map((f) => parseEnvironmentFromFilename(path10.basename(f))).filter((e) => e !== void 0 && !blacklisted.has(e));
710
+ const environments = [...new Set(names)].sort();
711
+ const message = environments.length > 0 ? environments.join("\n") : "No environments found.";
712
+ return { success: true, data: { environments }, message };
713
+ } catch (err) {
714
+ return {
715
+ success: false,
716
+ error: err instanceof Error ? err.message : String(err)
717
+ };
718
+ }
719
+ }
720
+ };
721
+ var ListBaseCommand = class extends BaseCommand {
722
+ /** @inheritdoc */
723
+ register(program2) {
724
+ program2.command("list").description("List all environment names discovered in the working directory").action(async () => {
725
+ await this.dispatch(new ListCommand(), {}, this.buildContext(program2));
726
+ });
727
+ }
728
+ };
729
+
730
+ // src/commands/rm/index.ts
731
+ import fs11 from "fs/promises";
732
+ import path11 from "path";
733
+ var RmCommand = class {
734
+ /** @inheritdoc */
735
+ async execute(options, context) {
736
+ const { environment, all, force } = options;
737
+ try {
738
+ const allFiles = listEnvFiles(context.cwd, ".env.*", [".env.keys"]);
739
+ const candidates = allFiles.filter((f) => {
740
+ const name = parseEnvironmentFromFilename(path11.basename(f));
741
+ return name === environment;
742
+ });
743
+ if (!all) {
744
+ const primary = path11.resolve(context.cwd, `.env.${environment}`);
745
+ const subset = candidates.filter((f) => f === primary);
746
+ return buildResult(environment, subset, force);
747
+ }
748
+ return buildResult(environment, candidates, force);
749
+ } catch (err) {
750
+ return {
751
+ success: false,
752
+ error: err instanceof Error ? err.message : String(err)
753
+ };
754
+ }
755
+ }
756
+ };
757
+ async function buildResult(environment, files, force) {
758
+ if (!force) {
759
+ const preview = files.length > 0 ? `Would delete:
760
+ ${files.map((f) => ` ${f}`).join("\n")}
761
+
762
+ Pass --force to confirm deletion.` : `No files found for environment: ${environment}`;
763
+ return {
764
+ success: true,
765
+ data: { environment, deletedFiles: [] },
766
+ message: preview
767
+ };
768
+ }
769
+ const deleted = [];
770
+ for (const f of files) {
771
+ try {
772
+ await fs11.rm(f, { force: true });
773
+ deleted.push(f);
774
+ } catch {
775
+ }
776
+ }
777
+ const message = deleted.length > 0 ? `Deleted:
778
+ ${deleted.map((f) => ` ${f}`).join("\n")}` : `No files found for environment: ${environment}`;
779
+ return {
780
+ success: true,
781
+ data: { environment, deletedFiles: deleted },
782
+ message
783
+ };
784
+ }
785
+ var RmBaseCommand = class extends BaseCommand {
786
+ /** @inheritdoc */
787
+ register(program2) {
788
+ program2.command("rm <environment>").description("Remove environment files from the working directory").option("-a, --all", "include all associated file variants (.local, .unencrypted)", false).option("--force", "actually delete files (default is dry run)", false).action(async (environment, opts) => {
789
+ await this.dispatch(
790
+ new RmCommand(),
791
+ { environment, all: opts.all, force: opts.force },
792
+ this.buildContext(program2)
793
+ );
794
+ });
795
+ }
796
+ };
797
+
798
+ // src/commands/blacklist/index.ts
799
+ var BlacklistAddSubCommand = class {
800
+ /** @inheritdoc */
801
+ async execute(options, _context) {
802
+ const { environment } = options;
803
+ try {
804
+ const current = await readBlacklist();
805
+ if (current.includes(environment)) {
806
+ return {
807
+ success: true,
808
+ data: { environment, blacklisted: current },
809
+ message: `Environment "${environment}" is already blacklisted.`
810
+ };
811
+ }
812
+ const updated = [...current, environment];
813
+ await writeBlacklist(updated);
814
+ return {
815
+ success: true,
816
+ data: { environment, blacklisted: updated },
817
+ message: `Added "${environment}" to the blacklist.
818
+ Blacklisted environments:
819
+ ${updated.map((e) => ` ${e}`).join("\n")}`
820
+ };
821
+ } catch (err) {
822
+ return {
823
+ success: false,
824
+ error: err instanceof Error ? err.message : String(err)
825
+ };
826
+ }
827
+ }
828
+ };
829
+ var BlacklistRmSubCommand = class {
830
+ /** @inheritdoc */
831
+ async execute(options, _context) {
832
+ const { environment } = options;
833
+ try {
834
+ const current = await readBlacklist();
835
+ if (!current.includes(environment)) {
836
+ return {
837
+ success: false,
838
+ error: `Environment "${environment}" is not in the blacklist.`
839
+ };
840
+ }
841
+ const updated = current.filter((e) => e !== environment);
842
+ await writeBlacklist(updated);
843
+ const message = updated.length > 0 ? `Removed "${environment}" from the blacklist.
844
+ Blacklisted environments:
845
+ ${updated.map((e) => ` ${e}`).join("\n")}` : `Removed "${environment}" from the blacklist.
846
+ Blacklist is now empty.`;
847
+ return {
848
+ success: true,
849
+ data: { environment, blacklisted: updated },
850
+ message
851
+ };
852
+ } catch (err) {
853
+ return {
854
+ success: false,
855
+ error: err instanceof Error ? err.message : String(err)
856
+ };
857
+ }
858
+ }
859
+ };
860
+ var BlacklistBaseCommand = class extends BaseCommand {
861
+ /** @inheritdoc */
862
+ register(program2) {
863
+ const blacklist = program2.command("blacklist").description("Manage the environment auto-detection blacklist");
864
+ blacklist.command("add <environment>").description("Exclude an environment from auto-detection").action(async (environment) => {
865
+ await this.dispatch(
866
+ new BlacklistAddSubCommand(),
867
+ { environment },
868
+ this.buildContext(program2)
869
+ );
870
+ });
871
+ blacklist.command("rm <environment>").description("Re-include an environment in auto-detection").action(async (environment) => {
872
+ await this.dispatch(
873
+ new BlacklistRmSubCommand(),
874
+ { environment },
875
+ this.buildContext(program2)
876
+ );
877
+ });
878
+ }
879
+ };
880
+
641
881
  // src/index.ts
642
882
  var program = new Command();
643
883
  program.name("envctrl").description("Encrypted environment file manager built on dotenvx").version("0.1.0").option("-q, --quiet", "suppress output", false);
644
884
  new InitBaseCommand().register(program);
885
+ new ListBaseCommand().register(program);
645
886
  new SwitchBaseCommand().register(program);
646
887
  new KeystoreBaseCommand().register(program);
647
888
  new SetBaseCommand().register(program);
648
889
  new EncryptBaseCommand().register(program);
890
+ new RmBaseCommand().register(program);
891
+ new BlacklistBaseCommand().register(program);
649
892
  program.parseAsync(process.argv);
650
893
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/commands/switch/index.ts","../src/base-command.ts","../src/utils/env-files.ts","../src/utils/dotenvx.ts","../src/commands/switch/sync.ts","../src/commands/keystore/create.ts","../src/utils/platform.ts","../src/commands/keystore/registry.ts","../src/commands/keystore/list.ts","../src/commands/keystore/delete.ts","../src/commands/keystore/index.ts","../src/commands/set/index.ts","../src/commands/encrypt/index.ts","../src/commands/init/index.ts","../src/commands/init/workspace.ts"],"sourcesContent":["import { Command } from 'commander';\nimport { SwitchBaseCommand } from './commands/switch/index.js';\nimport { KeystoreBaseCommand } from './commands/keystore/index.js';\nimport { SetBaseCommand } from './commands/set/index.js';\nimport { EncryptBaseCommand } from './commands/encrypt/index.js';\nimport { InitBaseCommand } from './commands/init/index.js';\n\nconst program = new Command();\n\nprogram\n .name('envctrl')\n .description('Encrypted environment file manager built on dotenvx')\n .version('0.1.0')\n .option('-q, --quiet', 'suppress output', false);\n\nnew InitBaseCommand().register(program);\nnew SwitchBaseCommand().register(program);\nnew KeystoreBaseCommand().register(program);\nnew SetBaseCommand().register(program);\nnew EncryptBaseCommand().register(program);\n\nprogram.parseAsync(process.argv);\n","import fs from 'node:fs/promises';\nimport os from 'node:os';\nimport path from 'node:path';\nimport type { Command } from 'commander';\nimport type { CommandContext, CommandResult, ISubCommand, SwitchResult } from '@envctrl/types';\nimport { BaseCommand } from '../../base-command.js';\nimport { buildEnvFilePair } from '../../utils/env-files.js';\nimport { setKeyValue } from '../../utils/dotenvx.js';\nimport { syncUnencryptedToEncrypted } from './sync.js';\n\n/** Options parsed by Commander for the `switch` subcommand. */\ninterface SwitchOptions {\n environment: string;\n}\n\n/**\n * Switches the active environment.\n *\n * Creates `.env.[env].unencrypted` and `.env.[env]` if they do not exist,\n * syncs keys from the unencrypted source into the encrypted file, and\n * points `.env` at the encrypted file via symlink (POSIX) or copy (Windows).\n */\nexport class SwitchCommand implements ISubCommand<SwitchOptions, SwitchResult> {\n /** @inheritdoc */\n async execute(\n options: SwitchOptions,\n context: CommandContext,\n ): Promise<CommandResult<SwitchResult>> {\n const { environment, cwd } = { ...options, cwd: context.cwd };\n const pair = buildEnvFilePair(environment, cwd);\n const created: string[] = [];\n\n try {\n try {\n await fs.access(pair.unencrypted);\n } catch {\n await fs.writeFile(pair.unencrypted, '', 'utf8');\n created.push(pair.unencrypted);\n }\n\n const encryptedExists = await fs\n .access(pair.encrypted)\n .then(() => true)\n .catch(() => false);\n\n await syncUnencryptedToEncrypted(environment, cwd);\n\n if (!encryptedExists) {\n const stillMissing = await fs\n .access(pair.encrypted)\n .then(() => false)\n .catch(() => true);\n\n if (stillMissing) {\n setKeyValue('_ENVCTRL_INIT', '1', pair.encrypted);\n created.push(pair.encrypted);\n } else {\n created.push(pair.encrypted);\n }\n }\n\n const activePath = path.resolve(cwd, '.env');\n\n try {\n await fs.unlink(activePath);\n } catch {}\n\n if (os.platform() === 'win32') {\n await fs.copyFile(pair.encrypted, activePath);\n } else {\n await fs.symlink(pair.encrypted, activePath);\n }\n\n return {\n success: true,\n data: { environment, created, activePath },\n };\n } catch (err) {\n return {\n success: false,\n error: err instanceof Error ? err.message : String(err),\n };\n }\n }\n}\n\n/** Registers the `switch [environment]` command onto the Commander program. */\nexport class SwitchBaseCommand extends BaseCommand {\n /** @inheritdoc */\n register(program: Command): void {\n program\n .command('switch <environment>')\n .description('Create, sync, and activate a named environment (.env.[env])')\n .action(async (environment: string) => {\n await this.dispatch(new SwitchCommand(), { environment }, this.buildContext(program));\n });\n }\n}\n","import type { Command } from 'commander';\nimport type { CommandContext, CommandResult, ISubCommand } from '@envctrl/types';\n\n/**\n * Abstract base for all top-level CLI commands.\n *\n * Subclasses implement {@link register} to attach their Commander subcommand\n * to the program. The {@link dispatch} helper handles uniform result\n * printing and process exit on failure.\n */\nexport abstract class BaseCommand {\n /**\n * Attaches this command's Commander definition onto `program`.\n *\n * @param program - The root Commander program instance\n */\n abstract register(program: Command): void;\n\n /**\n * Executes a subcommand and handles the result.\n *\n * Prints the error message to stderr and exits with code 1 on failure.\n * Prints data as JSON to stdout on success when `context.quiet` is false.\n *\n * @param cmd - The subcommand to execute\n * @param options - Parsed options from Commander\n * @param context - Injected runtime context\n */\n protected async dispatch<TOptions, TResult>(\n cmd: ISubCommand<TOptions, TResult>,\n options: TOptions,\n context: CommandContext,\n ): Promise<void> {\n const result: CommandResult<TResult> = await cmd.execute(options, context);\n\n if (!result.success) {\n process.stderr.write(`error: ${result.error ?? 'unknown error'}\\n`);\n process.exit(1);\n }\n\n if (!context.quiet && result.data !== undefined) {\n process.stdout.write(JSON.stringify(result.data, null, 2) + '\\n');\n }\n }\n\n /**\n * Builds a {@link CommandContext} from the Commander program's global options.\n *\n * @param program - The root Commander program instance\n */\n protected buildContext(program: Command): CommandContext {\n const opts = program.opts<{ quiet?: boolean }>();\n return {\n cwd: process.cwd(),\n quiet: opts.quiet ?? false,\n };\n }\n}\n","import path from 'node:path';\nimport type { EnvFilePair, EnvironmentName } from '@envctrl/types';\n\n/**\n * Derives the environment name from a `.env.*` filename.\n *\n * Rules:\n * - `.env.production` → `\"production\"`\n * - `.env.development.unencrypted` → `\"development\"` (strips `.unencrypted`)\n * - `.env` or `.env.keys` → `undefined`\n *\n * @param filename - The basename of the env file (not a full path)\n */\nexport function parseEnvironmentFromFilename(filename: string): EnvironmentName | undefined {\n const base = path.basename(filename);\n\n if (!base.startsWith('.env.')) {\n return undefined;\n }\n\n const withoutPrefix = base.slice('.env.'.length);\n\n if (!withoutPrefix || withoutPrefix === 'keys') {\n return undefined;\n }\n\n const withoutUnencrypted = withoutPrefix.endsWith('.unencrypted')\n ? withoutPrefix.slice(0, -'.unencrypted'.length)\n : withoutPrefix;\n\n if (!withoutUnencrypted) {\n return undefined;\n }\n\n return withoutUnencrypted;\n}\n\n/**\n * Builds the {@link EnvFilePair} for a given environment name and working directory.\n *\n * @param environment - The environment name, e.g. `\"production\"`\n * @param cwd - The working directory that will contain the env files\n */\nexport function buildEnvFilePair(environment: EnvironmentName, cwd: string): EnvFilePair {\n return {\n environment,\n unencrypted: path.resolve(cwd, `.env.${environment}.unencrypted`),\n encrypted: path.resolve(cwd, `.env.${environment}`),\n };\n}\n\n/**\n * Parses KEY=VALUE pairs from a plaintext `.env` file string.\n *\n * Skips blank lines and comment lines starting with `#`.\n *\n * @param content - Raw file content\n */\nexport function parseEnvContent(content: string): Record<string, string> {\n const result: Record<string, string> = {};\n\n for (const line of content.split('\\n')) {\n const trimmed = line.trim();\n\n if (!trimmed || trimmed.startsWith('#')) {\n continue;\n }\n\n const eqIdx = trimmed.indexOf('=');\n if (eqIdx === -1) {\n continue;\n }\n\n const key = trimmed.slice(0, eqIdx).trim();\n const value = trimmed.slice(eqIdx + 1).trim();\n result[key] = value;\n }\n\n return result;\n}\n\n/**\n * Writes or updates a single `KEY=VALUE` line in a plaintext env file string.\n *\n * If the key already exists its line is replaced in-place; otherwise the\n * entry is appended.\n *\n * @param content - Current raw file content\n * @param key - The environment variable name\n * @param value - The new value\n */\nexport function upsertEnvLine(content: string, key: string, value: string): string {\n const lines = content.split('\\n');\n const pattern = new RegExp(`^${key}\\\\s*=`);\n let replaced = false;\n\n const updated = lines.map((line) => {\n if (pattern.test(line)) {\n replaced = true;\n return `${key}=${value}`;\n }\n return line;\n });\n\n if (!replaced) {\n if (updated[updated.length - 1] !== '') {\n updated.push(`${key}=${value}`);\n } else {\n updated.splice(updated.length - 1, 0, `${key}=${value}`);\n }\n }\n\n return updated.join('\\n');\n}\n","import { createRequire } from 'node:module';\nimport path from 'node:path';\nimport { execFile } from 'node:child_process';\nimport { promisify } from 'node:util';\nimport * as dotenvx from '@dotenvx/dotenvx';\nimport type { DotenvxEncryptResult, DotenvxSetResult } from '@envctrl/types';\n\nconst execFileAsync = promisify(execFile);\n\n/**\n * Resolves the path to the dotenvx CLI entry script from the installed package.\n *\n * Reads the `bin` field from the package's own `package.json` to avoid\n * hardcoding the path, which can change across dotenvx versions.\n */\nfunction resolveDotenvxBin(): string {\n const require = createRequire(import.meta.url);\n const pkgJsonPath = require.resolve('@dotenvx/dotenvx/package.json');\n const pkgJson = require('@dotenvx/dotenvx/package.json') as {\n bin: Record<string, string>;\n };\n const binRelPath = pkgJson.bin['dotenvx'];\n return path.resolve(path.dirname(pkgJsonPath), binRelPath);\n}\n\n/**\n * Sets a single key-value pair in an encrypted env file using the dotenvx JS API.\n *\n * @param key - The environment variable name\n * @param value - The value to set\n * @param encryptedFilePath - Absolute path to the target `.env.[env]` file\n * @param keysFilePath - Optional path to the `.env.keys` file\n */\nexport function setKeyValue(\n key: string,\n value: string,\n encryptedFilePath: string,\n keysFilePath?: string,\n): DotenvxSetResult {\n const output = dotenvx.set(key, value, {\n path: encryptedFilePath,\n encrypt: true,\n ...(keysFilePath ? { envKeysFile: keysFilePath } : {}),\n });\n\n return {\n changedFilepaths: output.changedFilepaths,\n unchangedFilepaths: output.unchangedFilepaths,\n };\n}\n\n/**\n * Encrypts an existing plaintext env file in-place using the dotenvx CLI.\n *\n * Spawns the bundled `dotenvx encrypt -f <filePath>` process. The CLI\n * handles creating or updating the corresponding `.env.keys` entry.\n *\n * @param filePath - Absolute path to the plaintext `.env.*` file to encrypt\n */\nexport async function encryptFile(filePath: string): Promise<DotenvxEncryptResult> {\n const bin = resolveDotenvxBin();\n await execFileAsync(process.execPath, [bin, 'encrypt', '-f', filePath]);\n\n return {\n changedFilepaths: [filePath],\n unchangedFilepaths: [],\n };\n}\n\n/**\n * Lists env files in a directory using the dotenvx JS API.\n *\n * @param directory - Directory to scan\n * @param include - Glob pattern(s) to include\n * @param exclude - Glob pattern(s) to exclude\n */\nexport function listEnvFiles(\n directory: string,\n include: string | string[],\n exclude: string | string[],\n): string[] {\n return dotenvx.ls(directory, include, exclude);\n}\n","import fs from 'node:fs/promises';\nimport { parseEnvContent, buildEnvFilePair } from '../../utils/env-files.js';\nimport { setKeyValue } from '../../utils/dotenvx.js';\nimport type { EnvironmentName } from '@envctrl/types';\n\n/**\n * Syncs all keys from the unencrypted file into the encrypted file.\n *\n * Reads the plaintext `.env.[env].unencrypted` file and calls dotenvx `set`\n * for each key so the encrypted counterpart stays in sync.\n *\n * @param environment - The environment name\n * @param cwd - Working directory containing the env files\n */\nexport async function syncUnencryptedToEncrypted(\n environment: EnvironmentName,\n cwd: string,\n): Promise<void> {\n const pair = buildEnvFilePair(environment, cwd);\n\n let content: string;\n try {\n content = await fs.readFile(pair.unencrypted, 'utf8');\n } catch {\n return;\n }\n\n const entries = parseEnvContent(content);\n\n for (const [key, value] of Object.entries(entries)) {\n setKeyValue(key, value, pair.encrypted);\n }\n}\n","import fs from 'node:fs/promises';\nimport path from 'node:path';\nimport type { CommandContext, CommandResult, ISubCommand, KeystoreConfig } from '@envctrl/types';\nimport { resolveDefaultKeystorePath, resolveKeystoresRegistryPath } from '../../utils/platform.js';\nimport { readRegistry, writeRegistry } from './registry.js';\n\n/** Options parsed by Commander for `keystore create`. */\nexport interface KeystoreCreateOptions {\n keystorePath: string | undefined;\n name: string | undefined;\n}\n\n/**\n * Creates a new keystore directory and registers it in the global registry.\n *\n * If no path is provided, defaults to the platform-appropriate application\n * data directory. An empty `.env.keys` stub is written on first creation.\n */\nexport class KeystoreCreateSubCommand implements ISubCommand<\n KeystoreCreateOptions,\n KeystoreConfig\n> {\n /** @inheritdoc */\n async execute(\n options: KeystoreCreateOptions,\n _context: CommandContext,\n ): Promise<CommandResult<KeystoreConfig>> {\n const keystorePath = options.keystorePath ?? resolveDefaultKeystorePath();\n const name = options.name ?? path.basename(keystorePath);\n\n try {\n await fs.mkdir(keystorePath, { recursive: true });\n\n const keysFile = path.join(keystorePath, '.env.keys');\n try {\n await fs.access(keysFile);\n } catch {\n await fs.writeFile(keysFile, '# dotenvx keys\\n', 'utf8');\n }\n\n const registryPath = resolveKeystoresRegistryPath();\n await fs.mkdir(path.dirname(registryPath), { recursive: true });\n\n const registry = await readRegistry(registryPath);\n const exists = registry.some((e) => e.name === name || e.path === keystorePath);\n\n if (!exists) {\n registry.push({ name, path: keystorePath });\n await writeRegistry(registryPath, registry);\n }\n\n return { success: true, data: { path: keystorePath } };\n } catch (err) {\n return {\n success: false,\n error: err instanceof Error ? err.message : String(err),\n };\n }\n }\n}\n","import os from 'node:os';\nimport path from 'node:path';\n\n/**\n * Resolves the envctrl application data root directory for the current platform.\n *\n * - Linux: `$XDG_DATA_HOME/envctrl` or `~/.local/share/envctrl`\n * - macOS: `~/Library/Application Support/envctrl`\n * - Windows: `%APPDATA%\\envctrl`\n */\nfunction resolveAppDataRoot(): string {\n const platform = os.platform();\n const home = os.homedir();\n\n if (platform === 'win32') {\n const appData = process.env['APPDATA'] ?? path.join(home, 'AppData', 'Roaming');\n return path.join(appData, 'envctrl');\n }\n\n if (platform === 'darwin') {\n return path.join(home, 'Library', 'Application Support', 'envctrl');\n }\n\n const xdgDataHome = process.env['XDG_DATA_HOME'] ?? path.join(home, '.local', 'share');\n return path.join(xdgDataHome, 'envctrl');\n}\n\n/**\n * Resolves the default keystore directory.\n *\n * Placed inside a `keystores/default` subdirectory of the app data root\n * so that the registry JSON file at the root level is never deleted when\n * the default keystore is removed.\n */\nexport function resolveDefaultKeystorePath(): string {\n return path.join(resolveAppDataRoot(), 'keystores', 'default');\n}\n\n/**\n * Returns the path to the global keystores registry JSON file.\n *\n * Stored at the app data root, outside any individual keystore directory,\n * so it survives keystore deletion.\n */\nexport function resolveKeystoresRegistryPath(): string {\n return path.join(resolveAppDataRoot(), 'keystores.json');\n}\n","import fs from 'node:fs/promises';\nimport type { KeystoreEntry } from '@envctrl/types';\n\n/**\n * Reads the keystores registry JSON file.\n *\n * Returns an empty array if the file does not exist yet.\n *\n * @param registryPath - Absolute path to the `keystores.json` file\n */\nexport async function readRegistry(registryPath: string): Promise<KeystoreEntry[]> {\n try {\n const raw = await fs.readFile(registryPath, 'utf8');\n return JSON.parse(raw) as KeystoreEntry[];\n } catch {\n return [];\n }\n}\n\n/**\n * Writes the keystores registry to disk.\n *\n * @param registryPath - Absolute path to the `keystores.json` file\n * @param entries - Array of keystore entries to persist\n */\nexport async function writeRegistry(registryPath: string, entries: KeystoreEntry[]): Promise<void> {\n await fs.writeFile(registryPath, JSON.stringify(entries, null, 2) + '\\n', 'utf8');\n}\n","import type { CommandContext, CommandResult, ISubCommand, KeystoreEntry } from '@envctrl/types';\nimport { resolveKeystoresRegistryPath } from '../../utils/platform.js';\nimport { readRegistry } from './registry.js';\n\n/**\n * Lists all registered keystores from the global registry.\n *\n * Returns an empty array if no keystores have been created yet.\n */\nexport class KeystoreListSubCommand implements ISubCommand<Record<string, never>, KeystoreEntry[]> {\n /** @inheritdoc */\n async execute(\n _options: Record<string, never>,\n _context: CommandContext,\n ): Promise<CommandResult<KeystoreEntry[]>> {\n try {\n const registryPath = resolveKeystoresRegistryPath();\n const entries = await readRegistry(registryPath);\n return { success: true, data: entries };\n } catch (err) {\n return {\n success: false,\n error: err instanceof Error ? err.message : String(err),\n };\n }\n }\n}\n","import fs from 'node:fs/promises';\nimport type { CommandContext, CommandResult, ISubCommand, KeystoreEntry } from '@envctrl/types';\nimport { resolveKeystoresRegistryPath } from '../../utils/platform.js';\nimport { readRegistry, writeRegistry } from './registry.js';\n\n/** Options parsed by Commander for `keystore delete`. */\nexport interface KeystoreDeleteOptions {\n name: string;\n force: boolean;\n}\n\n/**\n * Deletes a named keystore directory and removes its entry from the registry.\n *\n * Requires `--force` flag to skip the confirmation guard. Without it,\n * the command exits early with a descriptive error.\n */\nexport class KeystoreDeleteSubCommand implements ISubCommand<KeystoreDeleteOptions, KeystoreEntry> {\n /** @inheritdoc */\n async execute(\n options: KeystoreDeleteOptions,\n _context: CommandContext,\n ): Promise<CommandResult<KeystoreEntry>> {\n const { name, force } = options;\n\n try {\n const registryPath = resolveKeystoresRegistryPath();\n const registry = await readRegistry(registryPath);\n const entry = registry.find((e) => e.name === name);\n\n if (!entry) {\n return {\n success: false,\n error: `keystore \"${name}\" not found in registry`,\n };\n }\n\n if (!force) {\n return {\n success: false,\n error: `pass --force to confirm deletion of keystore \"${name}\" at ${entry.path}`,\n };\n }\n\n const updated = registry.filter((e) => e.name !== name);\n await writeRegistry(registryPath, updated);\n\n await fs.rm(entry.path, { recursive: true, force: true });\n\n return { success: true, data: entry };\n } catch (err) {\n return {\n success: false,\n error: err instanceof Error ? err.message : String(err),\n };\n }\n }\n}\n","import type { Command } from 'commander';\nimport { BaseCommand } from '../../base-command.js';\nimport { KeystoreCreateSubCommand } from './create.js';\nimport { KeystoreListSubCommand } from './list.js';\nimport { KeystoreDeleteSubCommand } from './delete.js';\n\n/** Registers the `keystore` command group onto the Commander program. */\nexport class KeystoreBaseCommand extends BaseCommand {\n /** @inheritdoc */\n register(program: Command): void {\n const keystoreCmd = program\n .command('keystore')\n .description('Manage keystore directories for dotenvx private keys');\n\n keystoreCmd\n .command('create [path]')\n .description('Create a new keystore (defaults to platform app data folder)')\n .option('-n, --name <name>', 'name for the keystore entry in the registry')\n .action(async (keystorePath: string | undefined, opts: { name?: string }) => {\n await this.dispatch(\n new KeystoreCreateSubCommand(),\n { keystorePath, name: opts.name },\n this.buildContext(program),\n );\n });\n\n keystoreCmd\n .command('list')\n .description('List all registered keystores')\n .action(async () => {\n await this.dispatch(new KeystoreListSubCommand(), {}, this.buildContext(program));\n });\n\n keystoreCmd\n .command('delete <name>')\n .description('Delete a named keystore and remove it from the registry')\n .option('--force', 'skip confirmation and delete immediately', false)\n .action(async (name: string, opts: { force: boolean }) => {\n await this.dispatch(\n new KeystoreDeleteSubCommand(),\n { name, force: opts.force },\n this.buildContext(program),\n );\n });\n }\n}\n","import fs from 'node:fs/promises';\nimport type { Command } from 'commander';\nimport type { CommandContext, CommandResult, ISubCommand, SetResult } from '@envctrl/types';\nimport { BaseCommand } from '../../base-command.js';\nimport { buildEnvFilePair, upsertEnvLine } from '../../utils/env-files.js';\nimport { setKeyValue } from '../../utils/dotenvx.js';\n\n/** Options parsed by Commander for the `set` subcommand. */\ninterface SetOptions {\n environment: string;\n key: string;\n value: string;\n}\n\n/**\n * Sets a single environment variable in both the unencrypted and encrypted files.\n *\n * Writes the key to the plaintext `.env.[env].unencrypted` file first to\n * maintain human-readable source of truth, then calls dotenvx `set` to\n * write and encrypt the value in `.env.[env]`.\n */\nexport class SetCommand implements ISubCommand<SetOptions, SetResult> {\n /** @inheritdoc */\n async execute(options: SetOptions, context: CommandContext): Promise<CommandResult<SetResult>> {\n const { environment, key, value } = options;\n const pair = buildEnvFilePair(environment, context.cwd);\n\n try {\n let existingContent = '';\n try {\n existingContent = await fs.readFile(pair.unencrypted, 'utf8');\n } catch {}\n\n const updatedContent = upsertEnvLine(existingContent, key, value);\n await fs.writeFile(pair.unencrypted, updatedContent, 'utf8');\n\n const result = setKeyValue(key, value, pair.encrypted);\n const changed = result.changedFilepaths.length > 0;\n\n return {\n success: true,\n data: { environment, key, changed },\n };\n } catch (err) {\n return {\n success: false,\n error: err instanceof Error ? err.message : String(err),\n };\n }\n }\n}\n\n/** Registers the `set <environment> <key> <value>` command onto the Commander program. */\nexport class SetBaseCommand extends BaseCommand {\n /** @inheritdoc */\n register(program: Command): void {\n program\n .command('set <environment> <key> <value>')\n .description('Set an environment variable and sync it to the encrypted file')\n .action(async (environment: string, key: string, value: string) => {\n await this.dispatch(\n new SetCommand(),\n { environment, key, value },\n this.buildContext(program),\n );\n });\n }\n}\n","import fs from 'node:fs/promises';\nimport path from 'node:path';\nimport type { Command } from 'commander';\nimport type { CommandContext, CommandResult, EncryptResult, ISubCommand } from '@envctrl/types';\nimport { BaseCommand } from '../../base-command.js';\nimport { parseEnvironmentFromFilename } from '../../utils/env-files.js';\nimport { listEnvFiles, encryptFile } from '../../utils/dotenvx.js';\n\n/** Options parsed by Commander for the `encrypt` subcommand. */\ninterface EncryptOptions {\n files: string[];\n}\n\n/**\n * Encrypts existing plaintext `.env.*` files using dotenvx.\n *\n * For each target file:\n * 1. Detects the environment name from the filename.\n * 2. Copies the plaintext content to `.env.[env].unencrypted` as a backup.\n * 3. Encrypts the file in-place via `dotenvx encrypt`.\n *\n * If no files are specified, all `.env.*` files in the working directory\n * are encrypted (excluding `.env.keys` and `*.unencrypted` files).\n */\nexport class EncryptCommand implements ISubCommand<EncryptOptions, EncryptResult> {\n /** @inheritdoc */\n async execute(\n options: EncryptOptions,\n context: CommandContext,\n ): Promise<CommandResult<EncryptResult>> {\n const { cwd } = context;\n\n try {\n const targets =\n options.files.length > 0\n ? options.files.map((f) => path.resolve(cwd, f))\n : listEnvFiles(cwd, '.env.*', ['.env.keys', '*.unencrypted']);\n\n const changedFiles: string[] = [];\n const unchangedFiles: string[] = [];\n\n for (const filePath of targets) {\n const basename = path.basename(filePath);\n const environment = parseEnvironmentFromFilename(basename);\n\n if (!environment) {\n unchangedFiles.push(filePath);\n continue;\n }\n\n const unencryptedPath = path.resolve(\n path.dirname(filePath),\n `.env.${environment}.unencrypted`,\n );\n\n try {\n const content = await fs.readFile(filePath, 'utf8');\n\n try {\n await fs.access(unencryptedPath);\n } catch {\n await fs.writeFile(unencryptedPath, content, 'utf8');\n }\n\n const result = await encryptFile(filePath);\n changedFiles.push(...result.changedFilepaths);\n } catch {\n unchangedFiles.push(filePath);\n }\n }\n\n return {\n success: true,\n data: { changedFiles, unchangedFiles },\n };\n } catch (err) {\n return {\n success: false,\n error: err instanceof Error ? err.message : String(err),\n };\n }\n }\n}\n\n/** Registers the `encrypt [files...]` command onto the Commander program. */\nexport class EncryptBaseCommand extends BaseCommand {\n /** @inheritdoc */\n register(program: Command): void {\n program\n .command('encrypt [files...]')\n .description('Encrypt plaintext .env.* files and create .unencrypted backups')\n .action(async (files: string[]) => {\n await this.dispatch(new EncryptCommand(), { files }, this.buildContext(program));\n });\n }\n}\n","import fs from 'node:fs/promises';\nimport os from 'node:os';\nimport path from 'node:path';\nimport type { Command } from 'commander';\nimport type { CommandContext, CommandResult, InitResult, ISubCommand } from '@envctrl/types';\nimport { BaseCommand } from '../../base-command.js';\nimport { resolveDefaultKeystorePath, resolveKeystoresRegistryPath } from '../../utils/platform.js';\nimport { readRegistry, writeRegistry } from '../keystore/registry.js';\nimport { findWorkspaceRoot, scanWorkspacePackages } from './workspace.js';\n\n/** Options parsed by Commander for the `init` subcommand. */\ninterface InitOptions {\n workspace: string | undefined;\n name: string | undefined;\n keystore: string | undefined;\n}\n\n/**\n * Initialises envctrl for an existing workspace or monorepo.\n *\n * 1. Locates the workspace root by traversing up from the working directory.\n * 2. Scans all workspace packages.\n * 3. Creates a keystore and registers it.\n * 4. Symlinks the keystore `.env.keys` into each package directory so that\n * dotenvx resolves the same key material across all packages when\n * environments are switched.\n * 5. Writes `.envctrl.json` at the workspace root recording the setup.\n */\nexport class InitCommand implements ISubCommand<InitOptions, InitResult> {\n /** @inheritdoc */\n async execute(options: InitOptions, context: CommandContext): Promise<CommandResult<InitResult>> {\n const startDir = options.workspace\n ? path.resolve(context.cwd, options.workspace)\n : context.cwd;\n\n try {\n const workspaceRoot = await findWorkspaceRoot(startDir);\n const packages = await scanWorkspacePackages(workspaceRoot);\n\n const keystorePath = options.keystore ?? resolveDefaultKeystorePath();\n const keystoreName = options.name ?? path.basename(workspaceRoot);\n\n await fs.mkdir(keystorePath, { recursive: true });\n\n const keysFile = path.join(keystorePath, '.env.keys');\n try {\n await fs.access(keysFile);\n } catch {\n await fs.writeFile(keysFile, '# dotenvx keys\\n', 'utf8');\n }\n\n const registryPath = resolveKeystoresRegistryPath();\n await fs.mkdir(path.dirname(registryPath), { recursive: true });\n const registry = await readRegistry(registryPath);\n const alreadyRegistered = registry.some(\n (e) => e.name === keystoreName || e.path === keystorePath,\n );\n if (!alreadyRegistered) {\n registry.push({ name: keystoreName, path: keystorePath });\n await writeRegistry(registryPath, registry);\n }\n\n const linkTargets = [\n workspaceRoot,\n ...packages.map((p) => path.resolve(workspaceRoot, p.path)),\n ];\n\n const keysLinked: string[] = [];\n\n for (const dir of linkTargets) {\n const linkPath = path.join(dir, '.env.keys');\n try {\n await fs.access(linkPath);\n } catch {\n if (os.platform() === 'win32') {\n await fs.copyFile(keysFile, linkPath);\n } else {\n await fs.symlink(keysFile, linkPath);\n }\n keysLinked.push(linkPath);\n }\n }\n\n await fs.writeFile(\n path.join(workspaceRoot, '.envctrl.json'),\n JSON.stringify({ workspaceRoot, keystorePath, packages }, null, 2) + '\\n',\n 'utf8',\n );\n\n return {\n success: true,\n data: { workspaceRoot, packages, keystorePath, keysLinked },\n };\n } catch (err) {\n return {\n success: false,\n error: err instanceof Error ? err.message : String(err),\n };\n }\n }\n}\n\n/** Registers the `init [workspace]` command onto the Commander program. */\nexport class InitBaseCommand extends BaseCommand {\n /** @inheritdoc */\n register(program: Command): void {\n program\n .command('init [workspace]')\n .description(\n 'Scan the workspace or monorepo, link packages to a shared keystore, and write .envctrl.json',\n )\n .option('-n, --name <name>', 'name for the keystore entry in the registry')\n .option('-k, --keystore <path>', 'custom keystore directory path')\n .action(\n async (workspace: string | undefined, opts: { name?: string; keystore?: string }) => {\n await this.dispatch(\n new InitCommand(),\n { workspace, name: opts.name, keystore: opts.keystore },\n this.buildContext(program),\n );\n },\n );\n }\n}\n","import fs from 'node:fs/promises';\nimport path from 'node:path';\nimport type { WorkspacePackage } from '@envctrl/types';\n\n/**\n * Traverses up from `startDir` to find the workspace root.\n *\n * A directory is considered the workspace root if it contains\n * `pnpm-workspace.yaml` or a `package.json` with a `workspaces` field.\n * Falls back to `startDir` if no root is found.\n */\nexport async function findWorkspaceRoot(startDir: string): Promise<string> {\n let dir = startDir;\n\n while (true) {\n try {\n await fs.access(path.join(dir, 'pnpm-workspace.yaml'));\n return dir;\n } catch {}\n\n try {\n const raw = await fs.readFile(path.join(dir, 'package.json'), 'utf8');\n const pkg = JSON.parse(raw) as { workspaces?: unknown };\n if (pkg.workspaces) return dir;\n } catch {}\n\n const parent = path.dirname(dir);\n if (parent === dir) return startDir;\n dir = parent;\n }\n}\n\n/**\n * Reads workspace package glob patterns from `pnpm-workspace.yaml` or\n * the `workspaces` field in `package.json`.\n */\nasync function readWorkspacePatterns(workspaceRoot: string): Promise<string[]> {\n try {\n const raw = await fs.readFile(path.join(workspaceRoot, 'pnpm-workspace.yaml'), 'utf8');\n const patterns: string[] = [];\n let inPackages = false;\n\n for (const line of raw.split('\\n')) {\n if (/^packages\\s*:/.test(line)) {\n inPackages = true;\n continue;\n }\n if (inPackages) {\n const match = line.match(/^\\s+-\\s+['\"]?([^'\"]+)['\"]?/);\n if (match) {\n patterns.push(match[1].trim());\n } else if (line.trim() && !line.startsWith('#')) {\n break;\n }\n }\n }\n\n if (patterns.length > 0) return patterns;\n } catch {}\n\n try {\n const raw = await fs.readFile(path.join(workspaceRoot, 'package.json'), 'utf8');\n const pkg = JSON.parse(raw) as {\n workspaces?: string[] | { packages: string[] };\n };\n if (Array.isArray(pkg.workspaces)) return pkg.workspaces;\n if (pkg.workspaces?.packages) return pkg.workspaces.packages;\n } catch {}\n\n return [];\n}\n\n/**\n * Expands a single workspace glob pattern (supporting one trailing `*`) into\n * matching subdirectory paths under `workspaceRoot`.\n */\nasync function expandPattern(workspaceRoot: string, pattern: string): Promise<string[]> {\n if (!pattern.includes('*')) {\n return [path.resolve(workspaceRoot, pattern)];\n }\n\n const starIndex = pattern.indexOf('*');\n const baseRelative = pattern.slice(0, starIndex).replace(/\\/$/, '');\n const suffix = pattern.slice(starIndex + 1);\n const baseDir = path.resolve(workspaceRoot, baseRelative);\n\n try {\n const entries = await fs.readdir(baseDir, { withFileTypes: true });\n return entries\n .filter((e) => e.isDirectory() && !e.name.startsWith('.'))\n .map((e) => path.join(baseDir, e.name + suffix));\n } catch {\n return [];\n }\n}\n\n/**\n * Scans `workspaceRoot` for all packages by expanding workspace glob patterns\n * and filtering directories that contain a `package.json`.\n */\nexport async function scanWorkspacePackages(workspaceRoot: string): Promise<WorkspacePackage[]> {\n const patterns = await readWorkspacePatterns(workspaceRoot);\n const packages: WorkspacePackage[] = [];\n\n for (const pattern of patterns) {\n const dirs = await expandPattern(workspaceRoot, pattern);\n\n for (const dir of dirs) {\n try {\n const raw = await fs.readFile(path.join(dir, 'package.json'), 'utf8');\n const pkg = JSON.parse(raw) as { name?: string };\n if (pkg.name) {\n packages.push({\n name: pkg.name,\n path: path.relative(workspaceRoot, dir),\n });\n }\n } catch {}\n }\n }\n\n return packages;\n}\n"],"mappings":"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","names":["fs","path","program","path","require","fs","path","program","fs","path","os","path","fs","path","fs","fs","fs","program","fs","fs","program","fs","path","path","fs","program","fs","os","path","fs","path","path","fs","os","program"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/commands/switch/index.ts","../src/base-command.ts","../src/utils/env-files.ts","../src/utils/dotenvx.ts","../src/commands/switch/sync.ts","../src/commands/keystore/create.ts","../src/utils/platform.ts","../src/commands/keystore/registry.ts","../src/commands/keystore/list.ts","../src/commands/keystore/delete.ts","../src/commands/keystore/index.ts","../src/commands/set/index.ts","../src/commands/encrypt/index.ts","../src/commands/init/index.ts","../src/commands/init/workspace.ts","../src/commands/list/index.ts","../src/utils/blacklist.ts","../src/commands/rm/index.ts","../src/commands/blacklist/index.ts"],"sourcesContent":["import { Command } from 'commander';\nimport { SwitchBaseCommand } from './commands/switch/index.js';\nimport { KeystoreBaseCommand } from './commands/keystore/index.js';\nimport { SetBaseCommand } from './commands/set/index.js';\nimport { EncryptBaseCommand } from './commands/encrypt/index.js';\nimport { InitBaseCommand } from './commands/init/index.js';\nimport { ListBaseCommand } from './commands/list/index.js';\nimport { RmBaseCommand } from './commands/rm/index.js';\nimport { BlacklistBaseCommand } from './commands/blacklist/index.js';\n\nconst program = new Command();\n\nprogram\n .name('envctrl')\n .description('Encrypted environment file manager built on dotenvx')\n .version('0.1.0')\n .option('-q, --quiet', 'suppress output', false);\n\nnew InitBaseCommand().register(program);\nnew ListBaseCommand().register(program);\nnew SwitchBaseCommand().register(program);\nnew KeystoreBaseCommand().register(program);\nnew SetBaseCommand().register(program);\nnew EncryptBaseCommand().register(program);\nnew RmBaseCommand().register(program);\nnew BlacklistBaseCommand().register(program);\n\nprogram.parseAsync(process.argv);\n","import fs from 'node:fs/promises';\nimport os from 'node:os';\nimport path from 'node:path';\nimport type { Command } from 'commander';\nimport type { CommandContext, CommandResult, ISubCommand, SwitchResult } from '@envctrl/types';\nimport { BaseCommand } from '../../base-command.js';\nimport { buildEnvFilePair } from '../../utils/env-files.js';\nimport { setKeyValue } from '../../utils/dotenvx.js';\nimport { syncUnencryptedToEncrypted } from './sync.js';\n\n/** Options parsed by Commander for the `switch` subcommand. */\ninterface SwitchOptions {\n environment: string;\n}\n\n/**\n * Switches the active environment.\n *\n * Creates `.env.[env].unencrypted` and `.env.[env]` if they do not exist,\n * syncs keys from the unencrypted source into the encrypted file, and\n * points `.env` at the encrypted file via symlink (POSIX) or copy (Windows).\n */\nexport class SwitchCommand implements ISubCommand<SwitchOptions, SwitchResult> {\n /** @inheritdoc */\n async execute(\n options: SwitchOptions,\n context: CommandContext,\n ): Promise<CommandResult<SwitchResult>> {\n const { environment, cwd } = { ...options, cwd: context.cwd };\n const pair = buildEnvFilePair(environment, cwd);\n const created: string[] = [];\n\n try {\n try {\n await fs.access(pair.unencrypted);\n } catch {\n await fs.writeFile(pair.unencrypted, '', 'utf8');\n created.push(pair.unencrypted);\n }\n\n const encryptedExists = await fs\n .access(pair.encrypted)\n .then(() => true)\n .catch(() => false);\n\n await syncUnencryptedToEncrypted(environment, cwd);\n\n if (!encryptedExists) {\n const stillMissing = await fs\n .access(pair.encrypted)\n .then(() => false)\n .catch(() => true);\n\n if (stillMissing) {\n setKeyValue('_ENVCTRL_INIT', '1', pair.encrypted);\n created.push(pair.encrypted);\n } else {\n created.push(pair.encrypted);\n }\n }\n\n const activePath = path.resolve(cwd, '.env');\n\n try {\n await fs.unlink(activePath);\n } catch {}\n\n if (os.platform() === 'win32') {\n await fs.copyFile(pair.encrypted, activePath);\n } else {\n await fs.symlink(pair.encrypted, activePath);\n }\n\n const lines = [`Switched to: ${environment}`];\n if (created.length > 0) {\n lines.push('Created:', ...created.map((f) => ` ${f}`));\n }\n\n return {\n success: true,\n data: { environment, created, activePath },\n message: lines.join('\\n'),\n };\n } catch (err) {\n return {\n success: false,\n error: err instanceof Error ? err.message : String(err),\n };\n }\n }\n}\n\n/** Registers the `switch [environment]` command onto the Commander program. */\nexport class SwitchBaseCommand extends BaseCommand {\n /** @inheritdoc */\n register(program: Command): void {\n program\n .command('switch <environment>')\n .description('Create, sync, and activate a named environment (.env.[env])')\n .action(async (environment: string) => {\n await this.dispatch(new SwitchCommand(), { environment }, this.buildContext(program));\n });\n }\n}\n","import type { Command } from 'commander';\nimport type { CommandContext, CommandResult, ISubCommand } from '@envctrl/types';\n\n/**\n * Abstract base for all top-level CLI commands.\n *\n * Subclasses implement {@link register} to attach their Commander subcommand\n * to the program. The {@link dispatch} helper handles uniform result\n * printing and process exit on failure.\n */\nexport abstract class BaseCommand {\n /**\n * Attaches this command's Commander definition onto `program`.\n *\n * @param program - The root Commander program instance\n */\n abstract register(program: Command): void;\n\n /**\n * Executes a subcommand and handles the result.\n *\n * Prints the error message to stderr and exits with code 1 on failure.\n * Prints `result.message` to stdout on success when `context.quiet` is false.\n *\n * @param cmd - The subcommand to execute\n * @param options - Parsed options from Commander\n * @param context - Injected runtime context\n */\n protected async dispatch<TOptions, TResult>(\n cmd: ISubCommand<TOptions, TResult>,\n options: TOptions,\n context: CommandContext,\n ): Promise<void> {\n const result: CommandResult<TResult> = await cmd.execute(options, context);\n\n if (!result.success) {\n process.stderr.write(`error: ${result.error ?? 'unknown error'}\\n`);\n process.exit(1);\n }\n\n if (!context.quiet && result.message !== undefined) {\n process.stdout.write(result.message + '\\n');\n }\n }\n\n /**\n * Builds a {@link CommandContext} from the Commander program's global options.\n *\n * @param program - The root Commander program instance\n */\n protected buildContext(program: Command): CommandContext {\n const opts = program.opts<{ quiet?: boolean }>();\n return {\n cwd: process.cwd(),\n quiet: opts.quiet ?? false,\n };\n }\n}\n","import path from 'node:path';\nimport type { EnvFilePair, EnvironmentName } from '@envctrl/types';\n\nconst RESERVED_ENV_NAMES = new Set(['keys', 'example']);\n\n/**\n * Derives the environment name from a `.env.*` filename, stripping `.unencrypted`\n * and `.local` suffixes. Returns `undefined` for reserved filenames like\n * `.env.keys` and `.env.example`.\n */\nexport function parseEnvironmentFromFilename(filename: string): EnvironmentName | undefined {\n const base = path.basename(filename);\n\n if (!base.startsWith('.env.')) {\n return undefined;\n }\n\n const withoutPrefix = base.slice('.env.'.length);\n\n if (!withoutPrefix || RESERVED_ENV_NAMES.has(withoutPrefix)) {\n return undefined;\n }\n\n let name = withoutPrefix.endsWith('.unencrypted')\n ? withoutPrefix.slice(0, -'.unencrypted'.length)\n : withoutPrefix;\n\n if (name.endsWith('.local')) {\n name = name.slice(0, -'.local'.length);\n }\n\n if (!name || RESERVED_ENV_NAMES.has(name)) {\n return undefined;\n }\n\n return name;\n}\n\n/**\n * Builds the {@link EnvFilePair} for a given environment name and working directory.\n *\n * @param environment - The environment name, e.g. `\"production\"`\n * @param cwd - The working directory that will contain the env files\n */\nexport function buildEnvFilePair(environment: EnvironmentName, cwd: string): EnvFilePair {\n return {\n environment,\n unencrypted: path.resolve(cwd, `.env.${environment}.unencrypted`),\n encrypted: path.resolve(cwd, `.env.${environment}`),\n };\n}\n\n/**\n * Parses KEY=VALUE pairs from a plaintext `.env` file string.\n *\n * Skips blank lines and comment lines starting with `#`.\n *\n * @param content - Raw file content\n */\nexport function parseEnvContent(content: string): Record<string, string> {\n const result: Record<string, string> = {};\n\n for (const line of content.split('\\n')) {\n const trimmed = line.trim();\n\n if (!trimmed || trimmed.startsWith('#')) {\n continue;\n }\n\n const eqIdx = trimmed.indexOf('=');\n if (eqIdx === -1) {\n continue;\n }\n\n const key = trimmed.slice(0, eqIdx).trim();\n const value = trimmed.slice(eqIdx + 1).trim();\n result[key] = value;\n }\n\n return result;\n}\n\n/**\n * Writes or updates a single `KEY=VALUE` line in a plaintext env file string.\n *\n * If the key already exists its line is replaced in-place; otherwise the\n * entry is appended.\n *\n * @param content - Current raw file content\n * @param key - The environment variable name\n * @param value - The new value\n */\nexport function upsertEnvLine(content: string, key: string, value: string): string {\n const lines = content.split('\\n');\n const pattern = new RegExp(`^${key}\\\\s*=`);\n let replaced = false;\n\n const updated = lines.map((line) => {\n if (pattern.test(line)) {\n replaced = true;\n return `${key}=${value}`;\n }\n return line;\n });\n\n if (!replaced) {\n if (updated[updated.length - 1] !== '') {\n updated.push(`${key}=${value}`);\n } else {\n updated.splice(updated.length - 1, 0, `${key}=${value}`);\n }\n }\n\n return updated.join('\\n');\n}\n","import { createRequire } from 'node:module';\nimport path from 'node:path';\nimport { execFile } from 'node:child_process';\nimport { promisify } from 'node:util';\nimport * as dotenvx from '@dotenvx/dotenvx';\nimport type { DotenvxEncryptResult, DotenvxSetResult } from '@envctrl/types';\n\nconst execFileAsync = promisify(execFile);\n\n/**\n * Resolves the path to the dotenvx CLI entry script from the installed package.\n *\n * Reads the `bin` field from the package's own `package.json` to avoid\n * hardcoding the path, which can change across dotenvx versions.\n */\nfunction resolveDotenvxBin(): string {\n const require = createRequire(import.meta.url);\n const pkgJsonPath = require.resolve('@dotenvx/dotenvx/package.json');\n const pkgJson = require('@dotenvx/dotenvx/package.json') as {\n bin: Record<string, string>;\n };\n const binRelPath = pkgJson.bin['dotenvx'];\n return path.resolve(path.dirname(pkgJsonPath), binRelPath);\n}\n\n/**\n * Sets a single key-value pair in an encrypted env file using the dotenvx JS API.\n *\n * @param key - The environment variable name\n * @param value - The value to set\n * @param encryptedFilePath - Absolute path to the target `.env.[env]` file\n * @param keysFilePath - Optional path to the `.env.keys` file\n */\nexport function setKeyValue(\n key: string,\n value: string,\n encryptedFilePath: string,\n keysFilePath?: string,\n): DotenvxSetResult {\n const output = dotenvx.set(key, value, {\n path: encryptedFilePath,\n encrypt: true,\n ...(keysFilePath ? { envKeysFile: keysFilePath } : {}),\n });\n\n return {\n changedFilepaths: output.changedFilepaths,\n unchangedFilepaths: output.unchangedFilepaths,\n };\n}\n\n/**\n * Encrypts an existing plaintext env file in-place using the dotenvx CLI.\n *\n * Spawns the bundled `dotenvx encrypt -f <filePath>` process. The CLI\n * handles creating or updating the corresponding `.env.keys` entry.\n *\n * @param filePath - Absolute path to the plaintext `.env.*` file to encrypt\n */\nexport async function encryptFile(filePath: string): Promise<DotenvxEncryptResult> {\n const bin = resolveDotenvxBin();\n await execFileAsync(process.execPath, [bin, 'encrypt', '-f', filePath]);\n\n return {\n changedFilepaths: [filePath],\n unchangedFilepaths: [],\n };\n}\n\n/**\n * Lists env files in a directory using the dotenvx JS API.\n *\n * @param directory - Directory to scan\n * @param include - Glob pattern(s) to include\n * @param exclude - Glob pattern(s) to exclude\n */\nexport function listEnvFiles(\n directory: string,\n include: string | string[],\n exclude: string | string[],\n): string[] {\n return dotenvx.ls(directory, include, exclude);\n}\n","import fs from 'node:fs/promises';\nimport { parseEnvContent, buildEnvFilePair } from '../../utils/env-files.js';\nimport { setKeyValue } from '../../utils/dotenvx.js';\nimport type { EnvironmentName } from '@envctrl/types';\n\n/**\n * Syncs all keys from the unencrypted file into the encrypted file.\n *\n * Reads the plaintext `.env.[env].unencrypted` file and calls dotenvx `set`\n * for each key so the encrypted counterpart stays in sync.\n *\n * @param environment - The environment name\n * @param cwd - Working directory containing the env files\n */\nexport async function syncUnencryptedToEncrypted(\n environment: EnvironmentName,\n cwd: string,\n): Promise<void> {\n const pair = buildEnvFilePair(environment, cwd);\n\n let content: string;\n try {\n content = await fs.readFile(pair.unencrypted, 'utf8');\n } catch {\n return;\n }\n\n const entries = parseEnvContent(content);\n\n for (const [key, value] of Object.entries(entries)) {\n setKeyValue(key, value, pair.encrypted);\n }\n}\n","import fs from 'node:fs/promises';\nimport path from 'node:path';\nimport type { CommandContext, CommandResult, ISubCommand, KeystoreConfig } from '@envctrl/types';\nimport { resolveDefaultKeystorePath, resolveKeystoresRegistryPath } from '../../utils/platform.js';\nimport { readRegistry, writeRegistry } from './registry.js';\n\n/** Options parsed by Commander for `keystore create`. */\nexport interface KeystoreCreateOptions {\n keystorePath: string | undefined;\n name: string | undefined;\n}\n\n/**\n * Creates a new keystore directory and registers it in the global registry.\n *\n * If no path is provided, defaults to the platform-appropriate application\n * data directory. An empty `.env.keys` stub is written on first creation.\n */\nexport class KeystoreCreateSubCommand implements ISubCommand<\n KeystoreCreateOptions,\n KeystoreConfig\n> {\n /** @inheritdoc */\n async execute(\n options: KeystoreCreateOptions,\n _context: CommandContext,\n ): Promise<CommandResult<KeystoreConfig>> {\n const keystorePath = options.keystorePath ?? resolveDefaultKeystorePath();\n const name = options.name ?? path.basename(keystorePath);\n\n try {\n await fs.mkdir(keystorePath, { recursive: true });\n\n const keysFile = path.join(keystorePath, '.env.keys');\n try {\n await fs.access(keysFile);\n } catch {\n await fs.writeFile(keysFile, '# dotenvx keys\\n', 'utf8');\n }\n\n const registryPath = resolveKeystoresRegistryPath();\n await fs.mkdir(path.dirname(registryPath), { recursive: true });\n\n const registry = await readRegistry(registryPath);\n const exists = registry.some((e) => e.name === name || e.path === keystorePath);\n\n if (!exists) {\n registry.push({ name, path: keystorePath });\n await writeRegistry(registryPath, registry);\n }\n\n return {\n success: true,\n data: { path: keystorePath },\n message: `Keystore created at: ${keystorePath}`,\n };\n } catch (err) {\n return {\n success: false,\n error: err instanceof Error ? err.message : String(err),\n };\n }\n }\n}\n","import os from 'node:os';\nimport path from 'node:path';\n\n/**\n * Resolves the envctrl application data root directory for the current platform.\n *\n * - Linux: `$XDG_DATA_HOME/envctrl` or `~/.local/share/envctrl`\n * - macOS: `~/Library/Application Support/envctrl`\n * - Windows: `%APPDATA%\\envctrl`\n */\nfunction resolveAppDataRoot(): string {\n const platform = os.platform();\n const home = os.homedir();\n\n if (platform === 'win32') {\n const appData = process.env['APPDATA'] ?? path.join(home, 'AppData', 'Roaming');\n return path.join(appData, 'envctrl');\n }\n\n if (platform === 'darwin') {\n return path.join(home, 'Library', 'Application Support', 'envctrl');\n }\n\n const xdgDataHome = process.env['XDG_DATA_HOME'] ?? path.join(home, '.local', 'share');\n return path.join(xdgDataHome, 'envctrl');\n}\n\n/**\n * Resolves the default keystore directory.\n *\n * Placed inside a `keystores/default` subdirectory of the app data root\n * so that the registry JSON file at the root level is never deleted when\n * the default keystore is removed.\n */\nexport function resolveDefaultKeystorePath(): string {\n return path.join(resolveAppDataRoot(), 'keystores', 'default');\n}\n\n/**\n * Returns the path to the global keystores registry JSON file.\n *\n * Stored at the app data root, outside any individual keystore directory,\n * so it survives keystore deletion.\n */\nexport function resolveKeystoresRegistryPath(): string {\n return path.join(resolveAppDataRoot(), 'keystores.json');\n}\n\n/** Returns the path to the blacklist JSON file. */\nexport function resolveBlacklistPath(): string {\n return path.join(resolveAppDataRoot(), 'blacklist.json');\n}\n","import fs from 'node:fs/promises';\nimport type { KeystoreEntry } from '@envctrl/types';\n\n/**\n * Reads the keystores registry JSON file.\n *\n * Returns an empty array if the file does not exist yet.\n *\n * @param registryPath - Absolute path to the `keystores.json` file\n */\nexport async function readRegistry(registryPath: string): Promise<KeystoreEntry[]> {\n try {\n const raw = await fs.readFile(registryPath, 'utf8');\n return JSON.parse(raw) as KeystoreEntry[];\n } catch {\n return [];\n }\n}\n\n/**\n * Writes the keystores registry to disk.\n *\n * @param registryPath - Absolute path to the `keystores.json` file\n * @param entries - Array of keystore entries to persist\n */\nexport async function writeRegistry(registryPath: string, entries: KeystoreEntry[]): Promise<void> {\n await fs.writeFile(registryPath, JSON.stringify(entries, null, 2) + '\\n', 'utf8');\n}\n","import type { CommandContext, CommandResult, ISubCommand, KeystoreEntry } from '@envctrl/types';\nimport { resolveKeystoresRegistryPath } from '../../utils/platform.js';\nimport { readRegistry } from './registry.js';\n\n/**\n * Lists all registered keystores from the global registry.\n *\n * Returns an empty array if no keystores have been created yet.\n */\nexport class KeystoreListSubCommand implements ISubCommand<Record<string, never>, KeystoreEntry[]> {\n /** @inheritdoc */\n async execute(\n _options: Record<string, never>,\n _context: CommandContext,\n ): Promise<CommandResult<KeystoreEntry[]>> {\n try {\n const registryPath = resolveKeystoresRegistryPath();\n const entries = await readRegistry(registryPath);\n\n let message: string;\n if (entries.length === 0) {\n message = 'No keystores registered.';\n } else {\n const maxLen = Math.max(...entries.map((e) => e.name.length));\n message = entries.map((e) => `${e.name.padEnd(maxLen)} ${e.path}`).join('\\n');\n }\n\n return { success: true, data: entries, message };\n } catch (err) {\n return {\n success: false,\n error: err instanceof Error ? err.message : String(err),\n };\n }\n }\n}\n","import fs from 'node:fs/promises';\nimport type { CommandContext, CommandResult, ISubCommand, KeystoreEntry } from '@envctrl/types';\nimport { resolveKeystoresRegistryPath } from '../../utils/platform.js';\nimport { readRegistry, writeRegistry } from './registry.js';\n\n/** Options parsed by Commander for `keystore delete`. */\nexport interface KeystoreDeleteOptions {\n name: string;\n force: boolean;\n}\n\n/**\n * Deletes a named keystore directory and removes its entry from the registry.\n *\n * Requires `--force` flag to skip the confirmation guard. Without it,\n * the command exits early with a descriptive error.\n */\nexport class KeystoreDeleteSubCommand implements ISubCommand<KeystoreDeleteOptions, KeystoreEntry> {\n /** @inheritdoc */\n async execute(\n options: KeystoreDeleteOptions,\n _context: CommandContext,\n ): Promise<CommandResult<KeystoreEntry>> {\n const { name, force } = options;\n\n try {\n const registryPath = resolveKeystoresRegistryPath();\n const registry = await readRegistry(registryPath);\n const entry = registry.find((e) => e.name === name);\n\n if (!entry) {\n return {\n success: false,\n error: `keystore \"${name}\" not found in registry`,\n };\n }\n\n if (!force) {\n return {\n success: false,\n error: `pass --force to confirm deletion of keystore \"${name}\" at ${entry.path}`,\n };\n }\n\n const updated = registry.filter((e) => e.name !== name);\n await writeRegistry(registryPath, updated);\n\n await fs.rm(entry.path, { recursive: true, force: true });\n\n return {\n success: true,\n data: entry,\n message: `Deleted keystore: ${entry.name} (${entry.path})`,\n };\n } catch (err) {\n return {\n success: false,\n error: err instanceof Error ? err.message : String(err),\n };\n }\n }\n}\n","import type { Command } from 'commander';\nimport { BaseCommand } from '../../base-command.js';\nimport { KeystoreCreateSubCommand } from './create.js';\nimport { KeystoreListSubCommand } from './list.js';\nimport { KeystoreDeleteSubCommand } from './delete.js';\n\n/** Registers the `keystore` command group onto the Commander program. */\nexport class KeystoreBaseCommand extends BaseCommand {\n /** @inheritdoc */\n register(program: Command): void {\n const keystoreCmd = program\n .command('keystore')\n .description('Manage keystore directories for dotenvx private keys');\n\n keystoreCmd\n .command('create [path]')\n .description('Create a new keystore (defaults to platform app data folder)')\n .option('-n, --name <name>', 'name for the keystore entry in the registry')\n .action(async (keystorePath: string | undefined, opts: { name?: string }) => {\n await this.dispatch(\n new KeystoreCreateSubCommand(),\n { keystorePath, name: opts.name },\n this.buildContext(program),\n );\n });\n\n keystoreCmd\n .command('list')\n .description('List all registered keystores')\n .action(async () => {\n await this.dispatch(new KeystoreListSubCommand(), {}, this.buildContext(program));\n });\n\n keystoreCmd\n .command('delete <name>')\n .description('Delete a named keystore and remove it from the registry')\n .option('--force', 'skip confirmation and delete immediately', false)\n .action(async (name: string, opts: { force: boolean }) => {\n await this.dispatch(\n new KeystoreDeleteSubCommand(),\n { name, force: opts.force },\n this.buildContext(program),\n );\n });\n }\n}\n","import fs from 'node:fs/promises';\nimport type { Command } from 'commander';\nimport type { CommandContext, CommandResult, ISubCommand, SetResult } from '@envctrl/types';\nimport { BaseCommand } from '../../base-command.js';\nimport { buildEnvFilePair, upsertEnvLine } from '../../utils/env-files.js';\nimport { setKeyValue } from '../../utils/dotenvx.js';\n\n/** Options parsed by Commander for the `set` subcommand. */\ninterface SetOptions {\n environment: string;\n key: string;\n value: string;\n}\n\n/**\n * Sets a single environment variable in both the unencrypted and encrypted files.\n *\n * Writes the key to the plaintext `.env.[env].unencrypted` file first to\n * maintain human-readable source of truth, then calls dotenvx `set` to\n * write and encrypt the value in `.env.[env]`.\n */\nexport class SetCommand implements ISubCommand<SetOptions, SetResult> {\n /** @inheritdoc */\n async execute(options: SetOptions, context: CommandContext): Promise<CommandResult<SetResult>> {\n const { environment, key, value } = options;\n const pair = buildEnvFilePair(environment, context.cwd);\n\n try {\n let existingContent = '';\n try {\n existingContent = await fs.readFile(pair.unencrypted, 'utf8');\n } catch {}\n\n const updatedContent = upsertEnvLine(existingContent, key, value);\n await fs.writeFile(pair.unencrypted, updatedContent, 'utf8');\n\n const result = setKeyValue(key, value, pair.encrypted);\n const changed = result.changedFilepaths.length > 0;\n\n return {\n success: true,\n data: { environment, key, changed },\n message: `Set ${key} in ${environment}`,\n };\n } catch (err) {\n return {\n success: false,\n error: err instanceof Error ? err.message : String(err),\n };\n }\n }\n}\n\n/** Registers the `set <environment> <key> <value>` command onto the Commander program. */\nexport class SetBaseCommand extends BaseCommand {\n /** @inheritdoc */\n register(program: Command): void {\n program\n .command('set <environment> <key> <value>')\n .description('Set an environment variable and sync it to the encrypted file')\n .action(async (environment: string, key: string, value: string) => {\n await this.dispatch(\n new SetCommand(),\n { environment, key, value },\n this.buildContext(program),\n );\n });\n }\n}\n","import fs from 'node:fs/promises';\nimport path from 'node:path';\nimport type { Command } from 'commander';\nimport type { CommandContext, CommandResult, EncryptResult, ISubCommand } from '@envctrl/types';\nimport { BaseCommand } from '../../base-command.js';\nimport { parseEnvironmentFromFilename } from '../../utils/env-files.js';\nimport { listEnvFiles, encryptFile } from '../../utils/dotenvx.js';\n\n/** Options parsed by Commander for the `encrypt` subcommand. */\ninterface EncryptOptions {\n files: string[];\n}\n\n/**\n * Encrypts existing plaintext `.env.*` files using dotenvx, backing up each file's\n * content to `.env.[env].unencrypted` before encrypting in-place.\n * Defaults to all `.env.*` files in the working directory when no files are specified.\n */\nexport class EncryptCommand implements ISubCommand<EncryptOptions, EncryptResult> {\n /** @inheritdoc */\n async execute(\n options: EncryptOptions,\n context: CommandContext,\n ): Promise<CommandResult<EncryptResult>> {\n const { cwd } = context;\n\n try {\n const targets =\n options.files.length > 0\n ? options.files.map((f) => path.resolve(cwd, f))\n : listEnvFiles(cwd, '.env.*', ['.env.keys', '*.unencrypted']);\n\n const changedFiles: string[] = [];\n const unchangedFiles: string[] = [];\n\n for (const filePath of targets) {\n const basename = path.basename(filePath);\n const environment = parseEnvironmentFromFilename(basename);\n\n if (!environment) {\n unchangedFiles.push(filePath);\n continue;\n }\n\n const unencryptedPath = path.resolve(\n path.dirname(filePath),\n `.env.${environment}.unencrypted`,\n );\n\n try {\n const content = await fs.readFile(filePath, 'utf8');\n\n try {\n await fs.access(unencryptedPath);\n } catch {\n await fs.writeFile(unencryptedPath, content, 'utf8');\n }\n\n const result = await encryptFile(filePath);\n changedFiles.push(...result.changedFilepaths);\n } catch {\n unchangedFiles.push(filePath);\n }\n }\n\n const lines: string[] = [\n ...changedFiles.map((f) => `Encrypted: ${f}`),\n ...unchangedFiles.map((f) => `Unchanged: ${f}`),\n ];\n\n return {\n success: true,\n data: { changedFiles, unchangedFiles },\n message: lines.length > 0 ? lines.join('\\n') : 'No files processed.',\n };\n } catch (err) {\n return {\n success: false,\n error: err instanceof Error ? err.message : String(err),\n };\n }\n }\n}\n\n/** Registers the `encrypt [files...]` command onto the Commander program. */\nexport class EncryptBaseCommand extends BaseCommand {\n /** @inheritdoc */\n register(program: Command): void {\n program\n .command('encrypt [files...]')\n .description('Encrypt plaintext .env.* files and create .unencrypted backups')\n .action(async (files: string[]) => {\n await this.dispatch(new EncryptCommand(), { files }, this.buildContext(program));\n });\n }\n}\n","import fs from 'node:fs/promises';\nimport os from 'node:os';\nimport path from 'node:path';\nimport type { Command } from 'commander';\nimport type { CommandContext, CommandResult, InitResult, ISubCommand } from '@envctrl/types';\nimport { BaseCommand } from '../../base-command.js';\nimport { resolveDefaultKeystorePath, resolveKeystoresRegistryPath } from '../../utils/platform.js';\nimport { readRegistry, writeRegistry } from '../keystore/registry.js';\nimport { findWorkspaceRoot, scanWorkspacePackages } from './workspace.js';\n\n/** Options parsed by Commander for the `init` subcommand. */\ninterface InitOptions {\n workspace: string | undefined;\n name: string | undefined;\n keystore: string | undefined;\n}\n\n/**\n * Initialises envctrl for an existing workspace or monorepo by creating a shared keystore\n * and symlinking its `.env.keys` into every package directory so that dotenvx resolves\n * the same key material across all packages when environments are switched.\n */\nexport class InitCommand implements ISubCommand<InitOptions, InitResult> {\n /** @inheritdoc */\n async execute(options: InitOptions, context: CommandContext): Promise<CommandResult<InitResult>> {\n const startDir = options.workspace\n ? path.resolve(context.cwd, options.workspace)\n : context.cwd;\n\n try {\n const workspaceRoot = await findWorkspaceRoot(startDir);\n const packages = await scanWorkspacePackages(workspaceRoot);\n\n const keystorePath = options.keystore ?? resolveDefaultKeystorePath();\n const keystoreName = options.name ?? path.basename(workspaceRoot);\n\n await fs.mkdir(keystorePath, { recursive: true });\n\n const keysFile = path.join(keystorePath, '.env.keys');\n try {\n await fs.access(keysFile);\n } catch {\n await fs.writeFile(keysFile, '# dotenvx keys\\n', 'utf8');\n }\n\n const registryPath = resolveKeystoresRegistryPath();\n await fs.mkdir(path.dirname(registryPath), { recursive: true });\n const registry = await readRegistry(registryPath);\n const alreadyRegistered = registry.some(\n (e) => e.name === keystoreName || e.path === keystorePath,\n );\n if (!alreadyRegistered) {\n registry.push({ name: keystoreName, path: keystorePath });\n await writeRegistry(registryPath, registry);\n }\n\n const linkTargets = [\n workspaceRoot,\n ...packages.map((p) => path.resolve(workspaceRoot, p.path)),\n ];\n\n const keysLinked: string[] = [];\n\n for (const dir of linkTargets) {\n const linkPath = path.join(dir, '.env.keys');\n try {\n await fs.access(linkPath);\n } catch {\n if (os.platform() === 'win32') {\n await fs.copyFile(keysFile, linkPath);\n } else {\n await fs.symlink(keysFile, linkPath);\n }\n keysLinked.push(linkPath);\n }\n }\n\n const lines = [`Workspace: ${workspaceRoot}`, `Keystore: ${keystorePath}`];\n if (packages.length > 0) {\n lines.push('Packages:');\n for (const p of packages) lines.push(` ${p.name} (${p.path})`);\n }\n if (keysLinked.length > 0) {\n lines.push('Linked .env.keys:');\n for (const l of keysLinked) lines.push(` ${l}`);\n }\n\n return {\n success: true,\n data: { workspaceRoot, packages, keystorePath, keysLinked },\n message: lines.join('\\n'),\n };\n } catch (err) {\n return {\n success: false,\n error: err instanceof Error ? err.message : String(err),\n };\n }\n }\n}\n\n/** Registers the `init [workspace]` command onto the Commander program. */\nexport class InitBaseCommand extends BaseCommand {\n /** @inheritdoc */\n register(program: Command): void {\n program\n .command('init [workspace]')\n .description(\n 'Scan the workspace or monorepo and link packages to a shared keystore',\n )\n .option('-n, --name <name>', 'name for the keystore entry in the registry')\n .option('-k, --keystore <path>', 'custom keystore directory path')\n .action(\n async (workspace: string | undefined, opts: { name?: string; keystore?: string }) => {\n await this.dispatch(\n new InitCommand(),\n { workspace, name: opts.name, keystore: opts.keystore },\n this.buildContext(program),\n );\n },\n );\n }\n}\n","import fs from 'node:fs/promises';\nimport path from 'node:path';\nimport type { WorkspacePackage } from '@envctrl/types';\n\n/**\n * Traverses up from `startDir` to find the workspace root.\n *\n * A directory is considered the workspace root if it contains\n * `pnpm-workspace.yaml` or a `package.json` with a `workspaces` field.\n * Falls back to `startDir` if no root is found.\n */\nexport async function findWorkspaceRoot(startDir: string): Promise<string> {\n let dir = startDir;\n\n while (true) {\n try {\n await fs.access(path.join(dir, 'pnpm-workspace.yaml'));\n return dir;\n } catch {}\n\n try {\n const raw = await fs.readFile(path.join(dir, 'package.json'), 'utf8');\n const pkg = JSON.parse(raw) as { workspaces?: unknown };\n if (pkg.workspaces) return dir;\n } catch {}\n\n const parent = path.dirname(dir);\n if (parent === dir) return startDir;\n dir = parent;\n }\n}\n\n/**\n * Reads workspace package glob patterns from `pnpm-workspace.yaml` or\n * the `workspaces` field in `package.json`.\n */\nasync function readWorkspacePatterns(workspaceRoot: string): Promise<string[]> {\n try {\n const raw = await fs.readFile(path.join(workspaceRoot, 'pnpm-workspace.yaml'), 'utf8');\n const patterns: string[] = [];\n let inPackages = false;\n\n for (const line of raw.split('\\n')) {\n if (/^packages\\s*:/.test(line)) {\n inPackages = true;\n continue;\n }\n if (inPackages) {\n const match = line.match(/^\\s+-\\s+['\"]?([^'\"]+)['\"]?/);\n if (match) {\n patterns.push(match[1].trim());\n } else if (line.trim() && !line.startsWith('#')) {\n break;\n }\n }\n }\n\n if (patterns.length > 0) return patterns;\n } catch {}\n\n try {\n const raw = await fs.readFile(path.join(workspaceRoot, 'package.json'), 'utf8');\n const pkg = JSON.parse(raw) as {\n workspaces?: string[] | { packages: string[] };\n };\n if (Array.isArray(pkg.workspaces)) return pkg.workspaces;\n if (pkg.workspaces?.packages) return pkg.workspaces.packages;\n } catch {}\n\n return [];\n}\n\n/**\n * Expands a single workspace glob pattern (supporting one trailing `*`) into\n * matching subdirectory paths under `workspaceRoot`.\n */\nasync function expandPattern(workspaceRoot: string, pattern: string): Promise<string[]> {\n if (!pattern.includes('*')) {\n return [path.resolve(workspaceRoot, pattern)];\n }\n\n const starIndex = pattern.indexOf('*');\n const baseRelative = pattern.slice(0, starIndex).replace(/\\/$/, '');\n const suffix = pattern.slice(starIndex + 1);\n const baseDir = path.resolve(workspaceRoot, baseRelative);\n\n try {\n const entries = await fs.readdir(baseDir, { withFileTypes: true });\n return entries\n .filter((e) => e.isDirectory() && !e.name.startsWith('.'))\n .map((e) => path.join(baseDir, e.name + suffix));\n } catch {\n return [];\n }\n}\n\n/**\n * Scans `workspaceRoot` for all packages by expanding workspace glob patterns\n * and filtering directories that contain a `package.json`.\n */\nexport async function scanWorkspacePackages(workspaceRoot: string): Promise<WorkspacePackage[]> {\n const patterns = await readWorkspacePatterns(workspaceRoot);\n const packages: WorkspacePackage[] = [];\n\n for (const pattern of patterns) {\n const dirs = await expandPattern(workspaceRoot, pattern);\n\n for (const dir of dirs) {\n try {\n const raw = await fs.readFile(path.join(dir, 'package.json'), 'utf8');\n const pkg = JSON.parse(raw) as { name?: string };\n if (pkg.name) {\n packages.push({\n name: pkg.name,\n path: path.relative(workspaceRoot, dir),\n });\n }\n } catch {}\n }\n }\n\n return packages;\n}\n","import path from 'node:path';\nimport type { Command } from 'commander';\nimport type { CommandContext, CommandResult, EnvironmentName, ISubCommand, ListResult } from '@envctrl/types';\nimport { BaseCommand } from '../../base-command.js';\nimport { listEnvFiles } from '../../utils/dotenvx.js';\nimport { readBlacklist } from '../../utils/blacklist.js';\nimport { parseEnvironmentFromFilename } from '../../utils/env-files.js';\n\n/**\n * Lists all unique environment names discovered in the working directory by\n * scanning `.env.*` files, normalising `.local` and `.unencrypted` suffixes,\n * and filtering out blacklisted names.\n */\nexport class ListCommand implements ISubCommand<Record<string, never>, ListResult> {\n /** @inheritdoc */\n async execute(\n _options: Record<string, never>,\n context: CommandContext,\n ): Promise<CommandResult<ListResult>> {\n try {\n const [files, blacklist] = await Promise.all([\n Promise.resolve(listEnvFiles(context.cwd, '.env.*', ['.env.keys'])),\n readBlacklist(),\n ]);\n const blacklisted = new Set(blacklist);\n const names = files\n .map((f) => parseEnvironmentFromFilename(path.basename(f)))\n .filter((e): e is EnvironmentName => e !== undefined && !blacklisted.has(e));\n const environments = [...new Set(names)].sort();\n\n const message =\n environments.length > 0 ? environments.join('\\n') : 'No environments found.';\n\n return { success: true, data: { environments }, message };\n } catch (err) {\n return {\n success: false,\n error: err instanceof Error ? err.message : String(err),\n };\n }\n }\n}\n\n/** Registers the `list` command onto the Commander program. */\nexport class ListBaseCommand extends BaseCommand {\n /** @inheritdoc */\n register(program: Command): void {\n program\n .command('list')\n .description('List all environment names discovered in the working directory')\n .action(async () => {\n await this.dispatch(new ListCommand(), {}, this.buildContext(program));\n });\n }\n}\n","import fs from 'node:fs/promises';\nimport path from 'node:path';\nimport type { EnvironmentName } from '@envctrl/types';\nimport { resolveBlacklistPath } from './platform.js';\n\n/** Reads the blacklist from disk, returning an empty array if it does not exist. */\nexport async function readBlacklist(): Promise<EnvironmentName[]> {\n try {\n const raw = await fs.readFile(resolveBlacklistPath(), 'utf8');\n return JSON.parse(raw) as EnvironmentName[];\n } catch {\n return [];\n }\n}\n\n/** Writes the blacklist to disk. */\nexport async function writeBlacklist(entries: EnvironmentName[]): Promise<void> {\n const filePath = resolveBlacklistPath();\n await fs.mkdir(path.dirname(filePath), { recursive: true });\n await fs.writeFile(filePath, JSON.stringify(entries, null, 2) + '\\n', 'utf8');\n}\n","import fs from 'node:fs/promises';\nimport path from 'node:path';\nimport type { Command } from 'commander';\nimport type { CommandContext, CommandResult, EnvironmentName, ISubCommand, RemoveResult } from '@envctrl/types';\nimport { BaseCommand } from '../../base-command.js';\nimport { listEnvFiles } from '../../utils/dotenvx.js';\nimport { parseEnvironmentFromFilename } from '../../utils/env-files.js';\n\n/** Options parsed by Commander for the `rm` subcommand. */\ninterface RmOptions {\n environment: EnvironmentName;\n all: boolean;\n force: boolean;\n}\n\n/**\n * Removes environment files from the working directory.\n *\n * Without `--force`, performs a dry run listing files that would be deleted.\n * With `-a`, includes all associated file variants (`.local`, `.unencrypted`).\n * With `--force`, permanently deletes the resolved files.\n */\nexport class RmCommand implements ISubCommand<RmOptions, RemoveResult> {\n /** @inheritdoc */\n async execute(options: RmOptions, context: CommandContext): Promise<CommandResult<RemoveResult>> {\n const { environment, all, force } = options;\n\n try {\n const allFiles = listEnvFiles(context.cwd, '.env.*', ['.env.keys']);\n const candidates = allFiles.filter((f) => {\n const name = parseEnvironmentFromFilename(path.basename(f));\n return name === environment;\n });\n\n if (!all) {\n const primary = path.resolve(context.cwd, `.env.${environment}`);\n const subset = candidates.filter((f) => f === primary);\n return buildResult(environment, subset, force);\n }\n\n return buildResult(environment, candidates, force);\n } catch (err) {\n return {\n success: false,\n error: err instanceof Error ? err.message : String(err),\n };\n }\n }\n}\n\nasync function buildResult(\n environment: EnvironmentName,\n files: string[],\n force: boolean,\n): Promise<CommandResult<RemoveResult>> {\n if (!force) {\n const preview = files.length > 0\n ? `Would delete:\\n${files.map((f) => ` ${f}`).join('\\n')}\\n\\nPass --force to confirm deletion.`\n : `No files found for environment: ${environment}`;\n\n return {\n success: true,\n data: { environment, deletedFiles: [] },\n message: preview,\n };\n }\n\n const deleted: string[] = [];\n for (const f of files) {\n try {\n await fs.rm(f, { force: true });\n deleted.push(f);\n } catch {}\n }\n\n const message =\n deleted.length > 0\n ? `Deleted:\\n${deleted.map((f) => ` ${f}`).join('\\n')}`\n : `No files found for environment: ${environment}`;\n\n return {\n success: true,\n data: { environment, deletedFiles: deleted },\n message,\n };\n}\n\n/** Registers the `rm <environment>` command onto the Commander program. */\nexport class RmBaseCommand extends BaseCommand {\n /** @inheritdoc */\n register(program: Command): void {\n program\n .command('rm <environment>')\n .description('Remove environment files from the working directory')\n .option('-a, --all', 'include all associated file variants (.local, .unencrypted)', false)\n .option('--force', 'actually delete files (default is dry run)', false)\n .action(async (environment: string, opts: { all: boolean; force: boolean }) => {\n await this.dispatch(\n new RmCommand(),\n { environment, all: opts.all, force: opts.force },\n this.buildContext(program),\n );\n });\n }\n}\n","import type { Command } from 'commander';\nimport type { BlacklistResult, CommandContext, CommandResult, EnvironmentName, ISubCommand } from '@envctrl/types';\nimport { BaseCommand } from '../../base-command.js';\nimport { readBlacklist, writeBlacklist } from '../../utils/blacklist.js';\n\n/** Options parsed by Commander for `blacklist add`. */\ninterface BlacklistAddOptions {\n environment: EnvironmentName;\n}\n\n/** Options parsed by Commander for `blacklist rm`. */\ninterface BlacklistRmOptions {\n environment: EnvironmentName;\n}\n\n/**\n * Adds an environment name to the global blacklist, preventing it from\n * appearing in auto-detection results such as `envctrl list`.\n */\nexport class BlacklistAddSubCommand implements ISubCommand<BlacklistAddOptions, BlacklistResult> {\n /** @inheritdoc */\n async execute(\n options: BlacklistAddOptions,\n _context: CommandContext,\n ): Promise<CommandResult<BlacklistResult>> {\n const { environment } = options;\n\n try {\n const current = await readBlacklist();\n\n if (current.includes(environment)) {\n return {\n success: true,\n data: { environment, blacklisted: current },\n message: `Environment \"${environment}\" is already blacklisted.`,\n };\n }\n\n const updated = [...current, environment];\n await writeBlacklist(updated);\n\n return {\n success: true,\n data: { environment, blacklisted: updated },\n message: `Added \"${environment}\" to the blacklist.\\nBlacklisted environments:\\n${updated.map((e) => ` ${e}`).join('\\n')}`,\n };\n } catch (err) {\n return {\n success: false,\n error: err instanceof Error ? err.message : String(err),\n };\n }\n }\n}\n\n/**\n * Removes an environment name from the global blacklist, restoring it to\n * auto-detection results such as `envctrl list`.\n */\nexport class BlacklistRmSubCommand implements ISubCommand<BlacklistRmOptions, BlacklistResult> {\n /** @inheritdoc */\n async execute(\n options: BlacklistRmOptions,\n _context: CommandContext,\n ): Promise<CommandResult<BlacklistResult>> {\n const { environment } = options;\n\n try {\n const current = await readBlacklist();\n\n if (!current.includes(environment)) {\n return {\n success: false,\n error: `Environment \"${environment}\" is not in the blacklist.`,\n };\n }\n\n const updated = current.filter((e) => e !== environment);\n await writeBlacklist(updated);\n\n const message =\n updated.length > 0\n ? `Removed \"${environment}\" from the blacklist.\\nBlacklisted environments:\\n${updated.map((e) => ` ${e}`).join('\\n')}`\n : `Removed \"${environment}\" from the blacklist.\\nBlacklist is now empty.`;\n\n return {\n success: true,\n data: { environment, blacklisted: updated },\n message,\n };\n } catch (err) {\n return {\n success: false,\n error: err instanceof Error ? err.message : String(err),\n };\n }\n }\n}\n\n/** Registers the `blacklist` command group onto the Commander program. */\nexport class BlacklistBaseCommand extends BaseCommand {\n /** @inheritdoc */\n register(program: Command): void {\n const blacklist = program\n .command('blacklist')\n .description('Manage the environment auto-detection blacklist');\n\n blacklist\n .command('add <environment>')\n .description('Exclude an environment from auto-detection')\n .action(async (environment: string) => {\n await this.dispatch(\n new BlacklistAddSubCommand(),\n { environment },\n this.buildContext(program),\n );\n });\n\n blacklist\n .command('rm <environment>')\n .description('Re-include an environment in auto-detection')\n .action(async (environment: string) => {\n await this.dispatch(\n new BlacklistRmSubCommand(),\n { environment },\n this.buildContext(program),\n );\n });\n }\n}\n"],"mappings":"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","names":["fs","path","program","path","require","fs","path","program","fs","path","os","path","fs","path","fs","fs","fs","program","fs","fs","program","fs","path","path","fs","program","fs","os","path","fs","path","path","fs","os","program","path","fs","path","fs","path","path","program","fs","path","path","fs","program","program"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@envctrl/cli",
3
- "version": "1.2.0",
3
+ "version": "1.4.0",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "dist"
@@ -21,7 +21,7 @@
21
21
  "tsup": "^8.5.0",
22
22
  "typescript": "^5.8.0",
23
23
  "vitest": "^3.0.0",
24
- "@envctrl/types": "1.1.0"
24
+ "@envctrl/types": "1.3.0"
25
25
  },
26
26
  "scripts": {
27
27
  "build": "tsup",