@envctrl/cli 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +475 -0
- package/dist/index.js.map +1 -0
- package/package.json +35 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,475 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/index.ts
|
|
4
|
+
import { Command } from "commander";
|
|
5
|
+
|
|
6
|
+
// src/commands/switch/index.ts
|
|
7
|
+
import fs2 from "fs/promises";
|
|
8
|
+
import os from "os";
|
|
9
|
+
import path3 from "path";
|
|
10
|
+
|
|
11
|
+
// src/base-command.ts
|
|
12
|
+
var BaseCommand = class {
|
|
13
|
+
/**
|
|
14
|
+
* Executes a subcommand and handles the result.
|
|
15
|
+
*
|
|
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.
|
|
18
|
+
*
|
|
19
|
+
* @param cmd - The subcommand to execute
|
|
20
|
+
* @param options - Parsed options from Commander
|
|
21
|
+
* @param context - Injected runtime context
|
|
22
|
+
*/
|
|
23
|
+
async dispatch(cmd, options, context) {
|
|
24
|
+
const result = await cmd.execute(options, context);
|
|
25
|
+
if (!result.success) {
|
|
26
|
+
process.stderr.write(`error: ${result.error ?? "unknown error"}
|
|
27
|
+
`);
|
|
28
|
+
process.exit(1);
|
|
29
|
+
}
|
|
30
|
+
if (!context.quiet && result.data !== void 0) {
|
|
31
|
+
process.stdout.write(JSON.stringify(result.data, null, 2) + "\n");
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Builds a {@link CommandContext} from the Commander program's global options.
|
|
36
|
+
*
|
|
37
|
+
* @param program - The root Commander program instance
|
|
38
|
+
*/
|
|
39
|
+
buildContext(program2) {
|
|
40
|
+
const opts = program2.opts();
|
|
41
|
+
return {
|
|
42
|
+
cwd: process.cwd(),
|
|
43
|
+
quiet: opts.quiet ?? false
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
// src/utils/env-files.ts
|
|
49
|
+
import path from "path";
|
|
50
|
+
function parseEnvironmentFromFilename(filename) {
|
|
51
|
+
const base = path.basename(filename);
|
|
52
|
+
if (!base.startsWith(".env.")) {
|
|
53
|
+
return void 0;
|
|
54
|
+
}
|
|
55
|
+
const withoutPrefix = base.slice(".env.".length);
|
|
56
|
+
if (!withoutPrefix || withoutPrefix === "keys") {
|
|
57
|
+
return void 0;
|
|
58
|
+
}
|
|
59
|
+
const withoutUnencrypted = withoutPrefix.endsWith(".unencrypted") ? withoutPrefix.slice(0, -".unencrypted".length) : withoutPrefix;
|
|
60
|
+
if (!withoutUnencrypted) {
|
|
61
|
+
return void 0;
|
|
62
|
+
}
|
|
63
|
+
return withoutUnencrypted;
|
|
64
|
+
}
|
|
65
|
+
function buildEnvFilePair(environment, cwd) {
|
|
66
|
+
return {
|
|
67
|
+
environment,
|
|
68
|
+
unencrypted: path.resolve(cwd, `.env.${environment}.unencrypted`),
|
|
69
|
+
encrypted: path.resolve(cwd, `.env.${environment}`)
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
function parseEnvContent(content) {
|
|
73
|
+
const result = {};
|
|
74
|
+
for (const line of content.split("\n")) {
|
|
75
|
+
const trimmed = line.trim();
|
|
76
|
+
if (!trimmed || trimmed.startsWith("#")) {
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
const eqIdx = trimmed.indexOf("=");
|
|
80
|
+
if (eqIdx === -1) {
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
const key = trimmed.slice(0, eqIdx).trim();
|
|
84
|
+
const value = trimmed.slice(eqIdx + 1).trim();
|
|
85
|
+
result[key] = value;
|
|
86
|
+
}
|
|
87
|
+
return result;
|
|
88
|
+
}
|
|
89
|
+
function upsertEnvLine(content, key, value) {
|
|
90
|
+
const lines = content.split("\n");
|
|
91
|
+
const pattern = new RegExp(`^${key}\\s*=`);
|
|
92
|
+
let replaced = false;
|
|
93
|
+
const updated = lines.map((line) => {
|
|
94
|
+
if (pattern.test(line)) {
|
|
95
|
+
replaced = true;
|
|
96
|
+
return `${key}=${value}`;
|
|
97
|
+
}
|
|
98
|
+
return line;
|
|
99
|
+
});
|
|
100
|
+
if (!replaced) {
|
|
101
|
+
if (updated[updated.length - 1] !== "") {
|
|
102
|
+
updated.push(`${key}=${value}`);
|
|
103
|
+
} else {
|
|
104
|
+
updated.splice(updated.length - 1, 0, `${key}=${value}`);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return updated.join("\n");
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// src/utils/dotenvx.ts
|
|
111
|
+
import { createRequire } from "module";
|
|
112
|
+
import path2 from "path";
|
|
113
|
+
import { execFile } from "child_process";
|
|
114
|
+
import { promisify } from "util";
|
|
115
|
+
import * as dotenvx from "@dotenvx/dotenvx";
|
|
116
|
+
var execFileAsync = promisify(execFile);
|
|
117
|
+
function resolveDotenvxBin() {
|
|
118
|
+
const require2 = createRequire(import.meta.url);
|
|
119
|
+
const pkgJsonPath = require2.resolve("@dotenvx/dotenvx/package.json");
|
|
120
|
+
const pkgJson = require2("@dotenvx/dotenvx/package.json");
|
|
121
|
+
const binRelPath = pkgJson.bin["dotenvx"];
|
|
122
|
+
return path2.resolve(path2.dirname(pkgJsonPath), binRelPath);
|
|
123
|
+
}
|
|
124
|
+
function setKeyValue(key, value, encryptedFilePath, keysFilePath) {
|
|
125
|
+
const output = dotenvx.set(key, value, {
|
|
126
|
+
path: encryptedFilePath,
|
|
127
|
+
encrypt: true,
|
|
128
|
+
...keysFilePath ? { envKeysFile: keysFilePath } : {}
|
|
129
|
+
});
|
|
130
|
+
return {
|
|
131
|
+
changedFilepaths: output.changedFilepaths,
|
|
132
|
+
unchangedFilepaths: output.unchangedFilepaths
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
async function encryptFile(filePath) {
|
|
136
|
+
const bin = resolveDotenvxBin();
|
|
137
|
+
await execFileAsync(process.execPath, [bin, "encrypt", "-f", filePath]);
|
|
138
|
+
return {
|
|
139
|
+
changedFilepaths: [filePath],
|
|
140
|
+
unchangedFilepaths: []
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
function listEnvFiles(directory, include, exclude) {
|
|
144
|
+
return dotenvx.ls(directory, include, exclude);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// src/commands/switch/sync.ts
|
|
148
|
+
import fs from "fs/promises";
|
|
149
|
+
async function syncUnencryptedToEncrypted(environment, cwd) {
|
|
150
|
+
const pair = buildEnvFilePair(environment, cwd);
|
|
151
|
+
let content;
|
|
152
|
+
try {
|
|
153
|
+
content = await fs.readFile(pair.unencrypted, "utf8");
|
|
154
|
+
} catch {
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
const entries = parseEnvContent(content);
|
|
158
|
+
for (const [key, value] of Object.entries(entries)) {
|
|
159
|
+
setKeyValue(key, value, pair.encrypted);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// src/commands/switch/index.ts
|
|
164
|
+
var SwitchCommand = class {
|
|
165
|
+
/** @inheritdoc */
|
|
166
|
+
async execute(options, context) {
|
|
167
|
+
const { environment, cwd } = { ...options, cwd: context.cwd };
|
|
168
|
+
const pair = buildEnvFilePair(environment, cwd);
|
|
169
|
+
const created = [];
|
|
170
|
+
try {
|
|
171
|
+
try {
|
|
172
|
+
await fs2.access(pair.unencrypted);
|
|
173
|
+
} catch {
|
|
174
|
+
await fs2.writeFile(pair.unencrypted, "", "utf8");
|
|
175
|
+
created.push(pair.unencrypted);
|
|
176
|
+
}
|
|
177
|
+
const encryptedExists = await fs2.access(pair.encrypted).then(() => true).catch(() => false);
|
|
178
|
+
await syncUnencryptedToEncrypted(environment, cwd);
|
|
179
|
+
if (!encryptedExists) {
|
|
180
|
+
const stillMissing = await fs2.access(pair.encrypted).then(() => false).catch(() => true);
|
|
181
|
+
if (stillMissing) {
|
|
182
|
+
setKeyValue("_ENVCTRL_INIT", "1", pair.encrypted);
|
|
183
|
+
created.push(pair.encrypted);
|
|
184
|
+
} else {
|
|
185
|
+
created.push(pair.encrypted);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
const activePath = path3.resolve(cwd, ".env");
|
|
189
|
+
try {
|
|
190
|
+
await fs2.unlink(activePath);
|
|
191
|
+
} catch {
|
|
192
|
+
}
|
|
193
|
+
if (os.platform() === "win32") {
|
|
194
|
+
await fs2.copyFile(pair.encrypted, activePath);
|
|
195
|
+
} else {
|
|
196
|
+
await fs2.symlink(pair.encrypted, activePath);
|
|
197
|
+
}
|
|
198
|
+
return {
|
|
199
|
+
success: true,
|
|
200
|
+
data: { environment, created, activePath }
|
|
201
|
+
};
|
|
202
|
+
} catch (err) {
|
|
203
|
+
return {
|
|
204
|
+
success: false,
|
|
205
|
+
error: err instanceof Error ? err.message : String(err)
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
};
|
|
210
|
+
var SwitchBaseCommand = class extends BaseCommand {
|
|
211
|
+
/** @inheritdoc */
|
|
212
|
+
register(program2) {
|
|
213
|
+
program2.command("switch <environment>").description("Create, sync, and activate a named environment (.env.[env])").action(async (environment) => {
|
|
214
|
+
await this.dispatch(new SwitchCommand(), { environment }, this.buildContext(program2));
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
// src/commands/keystore/create.ts
|
|
220
|
+
import fs4 from "fs/promises";
|
|
221
|
+
import path5 from "path";
|
|
222
|
+
|
|
223
|
+
// src/utils/platform.ts
|
|
224
|
+
import os2 from "os";
|
|
225
|
+
import path4 from "path";
|
|
226
|
+
function resolveAppDataRoot() {
|
|
227
|
+
const platform = os2.platform();
|
|
228
|
+
const home = os2.homedir();
|
|
229
|
+
if (platform === "win32") {
|
|
230
|
+
const appData = process.env["APPDATA"] ?? path4.join(home, "AppData", "Roaming");
|
|
231
|
+
return path4.join(appData, "envctrl");
|
|
232
|
+
}
|
|
233
|
+
if (platform === "darwin") {
|
|
234
|
+
return path4.join(home, "Library", "Application Support", "envctrl");
|
|
235
|
+
}
|
|
236
|
+
const xdgDataHome = process.env["XDG_DATA_HOME"] ?? path4.join(home, ".local", "share");
|
|
237
|
+
return path4.join(xdgDataHome, "envctrl");
|
|
238
|
+
}
|
|
239
|
+
function resolveDefaultKeystorePath() {
|
|
240
|
+
return path4.join(resolveAppDataRoot(), "keystores", "default");
|
|
241
|
+
}
|
|
242
|
+
function resolveKeystoresRegistryPath() {
|
|
243
|
+
return path4.join(resolveAppDataRoot(), "keystores.json");
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// src/commands/keystore/registry.ts
|
|
247
|
+
import fs3 from "fs/promises";
|
|
248
|
+
async function readRegistry(registryPath) {
|
|
249
|
+
try {
|
|
250
|
+
const raw = await fs3.readFile(registryPath, "utf8");
|
|
251
|
+
return JSON.parse(raw);
|
|
252
|
+
} catch {
|
|
253
|
+
return [];
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
async function writeRegistry(registryPath, entries) {
|
|
257
|
+
await fs3.writeFile(registryPath, JSON.stringify(entries, null, 2) + "\n", "utf8");
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// src/commands/keystore/create.ts
|
|
261
|
+
var KeystoreCreateSubCommand = class {
|
|
262
|
+
/** @inheritdoc */
|
|
263
|
+
async execute(options, _context) {
|
|
264
|
+
const keystorePath = options.keystorePath ?? resolveDefaultKeystorePath();
|
|
265
|
+
const name = options.name ?? path5.basename(keystorePath);
|
|
266
|
+
try {
|
|
267
|
+
await fs4.mkdir(keystorePath, { recursive: true });
|
|
268
|
+
const keysFile = path5.join(keystorePath, ".env.keys");
|
|
269
|
+
try {
|
|
270
|
+
await fs4.access(keysFile);
|
|
271
|
+
} catch {
|
|
272
|
+
await fs4.writeFile(keysFile, "# dotenvx keys\n", "utf8");
|
|
273
|
+
}
|
|
274
|
+
const registryPath = resolveKeystoresRegistryPath();
|
|
275
|
+
await fs4.mkdir(path5.dirname(registryPath), { recursive: true });
|
|
276
|
+
const registry = await readRegistry(registryPath);
|
|
277
|
+
const exists = registry.some((e) => e.name === name || e.path === keystorePath);
|
|
278
|
+
if (!exists) {
|
|
279
|
+
registry.push({ name, path: keystorePath });
|
|
280
|
+
await writeRegistry(registryPath, registry);
|
|
281
|
+
}
|
|
282
|
+
return { success: true, data: { path: keystorePath } };
|
|
283
|
+
} catch (err) {
|
|
284
|
+
return {
|
|
285
|
+
success: false,
|
|
286
|
+
error: err instanceof Error ? err.message : String(err)
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
};
|
|
291
|
+
|
|
292
|
+
// src/commands/keystore/list.ts
|
|
293
|
+
var KeystoreListSubCommand = class {
|
|
294
|
+
/** @inheritdoc */
|
|
295
|
+
async execute(_options, _context) {
|
|
296
|
+
try {
|
|
297
|
+
const registryPath = resolveKeystoresRegistryPath();
|
|
298
|
+
const entries = await readRegistry(registryPath);
|
|
299
|
+
return { success: true, data: entries };
|
|
300
|
+
} catch (err) {
|
|
301
|
+
return {
|
|
302
|
+
success: false,
|
|
303
|
+
error: err instanceof Error ? err.message : String(err)
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
};
|
|
308
|
+
|
|
309
|
+
// src/commands/keystore/delete.ts
|
|
310
|
+
import fs5 from "fs/promises";
|
|
311
|
+
var KeystoreDeleteSubCommand = class {
|
|
312
|
+
/** @inheritdoc */
|
|
313
|
+
async execute(options, _context) {
|
|
314
|
+
const { name, force } = options;
|
|
315
|
+
try {
|
|
316
|
+
const registryPath = resolveKeystoresRegistryPath();
|
|
317
|
+
const registry = await readRegistry(registryPath);
|
|
318
|
+
const entry = registry.find((e) => e.name === name);
|
|
319
|
+
if (!entry) {
|
|
320
|
+
return {
|
|
321
|
+
success: false,
|
|
322
|
+
error: `keystore "${name}" not found in registry`
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
if (!force) {
|
|
326
|
+
return {
|
|
327
|
+
success: false,
|
|
328
|
+
error: `pass --force to confirm deletion of keystore "${name}" at ${entry.path}`
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
const updated = registry.filter((e) => e.name !== name);
|
|
332
|
+
await writeRegistry(registryPath, updated);
|
|
333
|
+
await fs5.rm(entry.path, { recursive: true, force: true });
|
|
334
|
+
return { success: true, data: entry };
|
|
335
|
+
} catch (err) {
|
|
336
|
+
return {
|
|
337
|
+
success: false,
|
|
338
|
+
error: err instanceof Error ? err.message : String(err)
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
};
|
|
343
|
+
|
|
344
|
+
// src/commands/keystore/index.ts
|
|
345
|
+
var KeystoreBaseCommand = class extends BaseCommand {
|
|
346
|
+
/** @inheritdoc */
|
|
347
|
+
register(program2) {
|
|
348
|
+
const keystoreCmd = program2.command("keystore").description("Manage keystore directories for dotenvx private keys");
|
|
349
|
+
keystoreCmd.command("create [path]").description("Create a new keystore (defaults to platform app data folder)").option("-n, --name <name>", "name for the keystore entry in the registry").action(async (keystorePath, opts) => {
|
|
350
|
+
await this.dispatch(
|
|
351
|
+
new KeystoreCreateSubCommand(),
|
|
352
|
+
{ keystorePath, name: opts.name },
|
|
353
|
+
this.buildContext(program2)
|
|
354
|
+
);
|
|
355
|
+
});
|
|
356
|
+
keystoreCmd.command("list").description("List all registered keystores").action(async () => {
|
|
357
|
+
await this.dispatch(new KeystoreListSubCommand(), {}, this.buildContext(program2));
|
|
358
|
+
});
|
|
359
|
+
keystoreCmd.command("delete <name>").description("Delete a named keystore and remove it from the registry").option("--force", "skip confirmation and delete immediately", false).action(async (name, opts) => {
|
|
360
|
+
await this.dispatch(
|
|
361
|
+
new KeystoreDeleteSubCommand(),
|
|
362
|
+
{ name, force: opts.force },
|
|
363
|
+
this.buildContext(program2)
|
|
364
|
+
);
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
};
|
|
368
|
+
|
|
369
|
+
// src/commands/set/index.ts
|
|
370
|
+
import fs6 from "fs/promises";
|
|
371
|
+
var SetCommand = class {
|
|
372
|
+
/** @inheritdoc */
|
|
373
|
+
async execute(options, context) {
|
|
374
|
+
const { environment, key, value } = options;
|
|
375
|
+
const pair = buildEnvFilePair(environment, context.cwd);
|
|
376
|
+
try {
|
|
377
|
+
let existingContent = "";
|
|
378
|
+
try {
|
|
379
|
+
existingContent = await fs6.readFile(pair.unencrypted, "utf8");
|
|
380
|
+
} catch {
|
|
381
|
+
}
|
|
382
|
+
const updatedContent = upsertEnvLine(existingContent, key, value);
|
|
383
|
+
await fs6.writeFile(pair.unencrypted, updatedContent, "utf8");
|
|
384
|
+
const result = setKeyValue(key, value, pair.encrypted);
|
|
385
|
+
const changed = result.changedFilepaths.length > 0;
|
|
386
|
+
return {
|
|
387
|
+
success: true,
|
|
388
|
+
data: { environment, key, changed }
|
|
389
|
+
};
|
|
390
|
+
} catch (err) {
|
|
391
|
+
return {
|
|
392
|
+
success: false,
|
|
393
|
+
error: err instanceof Error ? err.message : String(err)
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
};
|
|
398
|
+
var SetBaseCommand = class extends BaseCommand {
|
|
399
|
+
/** @inheritdoc */
|
|
400
|
+
register(program2) {
|
|
401
|
+
program2.command("set <environment> <key> <value>").description("Set an environment variable and sync it to the encrypted file").action(async (environment, key, value) => {
|
|
402
|
+
await this.dispatch(
|
|
403
|
+
new SetCommand(),
|
|
404
|
+
{ environment, key, value },
|
|
405
|
+
this.buildContext(program2)
|
|
406
|
+
);
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
};
|
|
410
|
+
|
|
411
|
+
// src/commands/encrypt/index.ts
|
|
412
|
+
import fs7 from "fs/promises";
|
|
413
|
+
import path6 from "path";
|
|
414
|
+
var EncryptCommand = class {
|
|
415
|
+
/** @inheritdoc */
|
|
416
|
+
async execute(options, context) {
|
|
417
|
+
const { cwd } = context;
|
|
418
|
+
try {
|
|
419
|
+
const targets = options.files.length > 0 ? options.files.map((f) => path6.resolve(cwd, f)) : listEnvFiles(cwd, ".env.*", [".env.keys", "*.unencrypted"]);
|
|
420
|
+
const changedFiles = [];
|
|
421
|
+
const unchangedFiles = [];
|
|
422
|
+
for (const filePath of targets) {
|
|
423
|
+
const basename = path6.basename(filePath);
|
|
424
|
+
const environment = parseEnvironmentFromFilename(basename);
|
|
425
|
+
if (!environment) {
|
|
426
|
+
unchangedFiles.push(filePath);
|
|
427
|
+
continue;
|
|
428
|
+
}
|
|
429
|
+
const unencryptedPath = path6.resolve(
|
|
430
|
+
path6.dirname(filePath),
|
|
431
|
+
`.env.${environment}.unencrypted`
|
|
432
|
+
);
|
|
433
|
+
try {
|
|
434
|
+
const content = await fs7.readFile(filePath, "utf8");
|
|
435
|
+
try {
|
|
436
|
+
await fs7.access(unencryptedPath);
|
|
437
|
+
} catch {
|
|
438
|
+
await fs7.writeFile(unencryptedPath, content, "utf8");
|
|
439
|
+
}
|
|
440
|
+
const result = await encryptFile(filePath);
|
|
441
|
+
changedFiles.push(...result.changedFilepaths);
|
|
442
|
+
} catch {
|
|
443
|
+
unchangedFiles.push(filePath);
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
return {
|
|
447
|
+
success: true,
|
|
448
|
+
data: { changedFiles, unchangedFiles }
|
|
449
|
+
};
|
|
450
|
+
} catch (err) {
|
|
451
|
+
return {
|
|
452
|
+
success: false,
|
|
453
|
+
error: err instanceof Error ? err.message : String(err)
|
|
454
|
+
};
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
};
|
|
458
|
+
var EncryptBaseCommand = class extends BaseCommand {
|
|
459
|
+
/** @inheritdoc */
|
|
460
|
+
register(program2) {
|
|
461
|
+
program2.command("encrypt [files...]").description("Encrypt plaintext .env.* files and create .unencrypted backups").action(async (files) => {
|
|
462
|
+
await this.dispatch(new EncryptCommand(), { files }, this.buildContext(program2));
|
|
463
|
+
});
|
|
464
|
+
}
|
|
465
|
+
};
|
|
466
|
+
|
|
467
|
+
// src/index.ts
|
|
468
|
+
var program = new Command();
|
|
469
|
+
program.name("envctrl").description("Encrypted environment file manager built on dotenvx").version("0.1.0").option("-q, --quiet", "suppress output", false);
|
|
470
|
+
new SwitchBaseCommand().register(program);
|
|
471
|
+
new KeystoreBaseCommand().register(program);
|
|
472
|
+
new SetBaseCommand().register(program);
|
|
473
|
+
new EncryptBaseCommand().register(program);
|
|
474
|
+
program.parseAsync(process.argv);
|
|
475
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +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"],"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';\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 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"],"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"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@envctrl/cli",
|
|
3
|
+
"version": "1.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"files": [
|
|
6
|
+
"dist"
|
|
7
|
+
],
|
|
8
|
+
"bin": {
|
|
9
|
+
"envctrl": "./dist/index.js"
|
|
10
|
+
},
|
|
11
|
+
"publishConfig": {
|
|
12
|
+
"access": "public"
|
|
13
|
+
},
|
|
14
|
+
"dependencies": {
|
|
15
|
+
"@dotenvx/dotenvx": "^1.75.1",
|
|
16
|
+
"commander": "^14.0.0"
|
|
17
|
+
},
|
|
18
|
+
"devDependencies": {
|
|
19
|
+
"@types/node": "^20.0.0",
|
|
20
|
+
"@vitest/coverage-v8": "^3.0.0",
|
|
21
|
+
"tsup": "^8.5.0",
|
|
22
|
+
"typescript": "^5.8.0",
|
|
23
|
+
"vitest": "^3.0.0",
|
|
24
|
+
"@envctrl/types": "0.1.0"
|
|
25
|
+
},
|
|
26
|
+
"scripts": {
|
|
27
|
+
"build": "tsup",
|
|
28
|
+
"dev": "tsup --watch",
|
|
29
|
+
"typecheck": "tsc --noEmit",
|
|
30
|
+
"lint": "tsc --noEmit",
|
|
31
|
+
"test": "vitest run",
|
|
32
|
+
"test:watch": "vitest",
|
|
33
|
+
"test:coverage": "vitest run --coverage"
|
|
34
|
+
}
|
|
35
|
+
}
|