@kitsy/cnos-cli 1.1.2 → 1.3.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 +1276 -218
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -11,27 +11,66 @@ var COMMAND_OPTION_KEYS_WITH_VALUE = /* @__PURE__ */ new Set([
|
|
|
11
11
|
"--format",
|
|
12
12
|
"--framework",
|
|
13
13
|
"--prefix",
|
|
14
|
+
"--scan",
|
|
14
15
|
"--target",
|
|
15
16
|
"--to",
|
|
16
17
|
"--provider",
|
|
17
18
|
"--passphrase",
|
|
18
19
|
"--vault",
|
|
19
|
-
"--inherit"
|
|
20
|
+
"--inherit",
|
|
21
|
+
"--as",
|
|
22
|
+
"--set",
|
|
23
|
+
"--debounce"
|
|
24
|
+
]);
|
|
25
|
+
var COMMAND_FLAG_KEYS = /* @__PURE__ */ new Set([
|
|
26
|
+
"--flatten",
|
|
27
|
+
"--public",
|
|
28
|
+
"--local",
|
|
29
|
+
"--remote",
|
|
30
|
+
"--ref",
|
|
31
|
+
"--no-passphrase",
|
|
32
|
+
"--stdin",
|
|
33
|
+
"--reveal",
|
|
34
|
+
"--auth",
|
|
35
|
+
"--all",
|
|
36
|
+
"--store-keychain",
|
|
37
|
+
"--watch",
|
|
38
|
+
"--dry-run",
|
|
39
|
+
"--apply",
|
|
40
|
+
"--rewrite",
|
|
41
|
+
"--signal"
|
|
20
42
|
]);
|
|
21
|
-
var COMMAND_FLAG_KEYS = /* @__PURE__ */ new Set(["--flatten", "--public", "--local", "--remote", "--ref"]);
|
|
22
43
|
function normalizeCommand(argv) {
|
|
23
44
|
const [command = "doctor", ...rest] = argv;
|
|
24
45
|
const resource = rest[0];
|
|
25
46
|
const remaining = rest.slice(1);
|
|
47
|
+
if ((command === "set" || command === "get") && (resource === "value" || resource === "secret")) {
|
|
48
|
+
return [resource, command, ...remaining];
|
|
49
|
+
}
|
|
26
50
|
if ((command === "create" || command === "add") && resource === "profile") {
|
|
27
51
|
return ["profile", "create", ...remaining];
|
|
28
52
|
}
|
|
53
|
+
if ((command === "create" || command === "add") && resource === "vault") {
|
|
54
|
+
return ["vault", "create", ...remaining];
|
|
55
|
+
}
|
|
29
56
|
if ((command === "delete" || command === "remove") && resource === "profile") {
|
|
30
57
|
return ["profile", "delete", ...remaining];
|
|
31
58
|
}
|
|
59
|
+
if ((command === "delete" || command === "remove") && resource === "vault") {
|
|
60
|
+
return ["vault", "remove", ...remaining];
|
|
61
|
+
}
|
|
62
|
+
if (command === "auth" && resource === "vault") {
|
|
63
|
+
return ["vault", "auth", ...remaining];
|
|
64
|
+
}
|
|
65
|
+
if (command === "logout" && resource === "vault") {
|
|
66
|
+
return ["vault", "logout", ...remaining];
|
|
67
|
+
}
|
|
32
68
|
if (command === "list" && resource === "profile") {
|
|
33
69
|
return ["profile", "list", ...remaining];
|
|
34
70
|
}
|
|
71
|
+
if (command === "list" && resource === "vault") {
|
|
72
|
+
return ["vault", "list", ...remaining];
|
|
73
|
+
}
|
|
35
74
|
if ((command === "create" || command === "add") && resource === "secret") {
|
|
36
75
|
return ["secret", "set", ...remaining];
|
|
37
76
|
}
|
|
@@ -186,45 +225,27 @@ function printJson(value) {
|
|
|
186
225
|
}
|
|
187
226
|
|
|
188
227
|
// src/services/writes.ts
|
|
189
|
-
import { mkdir, readFile,
|
|
228
|
+
import { mkdir, readFile, writeFile } from "fs/promises";
|
|
190
229
|
import path from "path";
|
|
191
230
|
import {
|
|
192
|
-
|
|
231
|
+
createSecretVaultProvider,
|
|
193
232
|
parseYaml,
|
|
194
233
|
resolveConfigDocumentPath,
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
stringifyYaml,
|
|
198
|
-
writeLocalSecret
|
|
234
|
+
resolveVaultAuth,
|
|
235
|
+
stringifyYaml
|
|
199
236
|
} from "@kitsy/cnos/internal";
|
|
200
237
|
|
|
201
238
|
// src/services/runtime.ts
|
|
202
|
-
import { createCnos } from "@kitsy/cnos";
|
|
239
|
+
import { createCnos } from "@kitsy/cnos/configure";
|
|
203
240
|
async function createRuntimeService(options = {}) {
|
|
204
|
-
const createOptions = {
|
|
205
|
-
...options.root ? {
|
|
206
|
-
root: options.root
|
|
207
|
-
} : {},
|
|
208
|
-
...options.workspace ? {
|
|
209
|
-
workspace: options.workspace
|
|
210
|
-
} : {},
|
|
211
|
-
...options.profile ? {
|
|
212
|
-
profile: options.profile
|
|
213
|
-
} : {},
|
|
214
|
-
...options.globalRoot ? {
|
|
215
|
-
globalRoot: options.globalRoot
|
|
216
|
-
} : {},
|
|
217
|
-
...options.cliArgs && options.cliArgs.length > 0 ? {
|
|
218
|
-
cliArgs: options.cliArgs
|
|
219
|
-
} : {},
|
|
220
|
-
...options.processEnv ? {
|
|
221
|
-
processEnv: options.processEnv
|
|
222
|
-
} : {
|
|
223
|
-
processEnv: process.env
|
|
224
|
-
}
|
|
225
|
-
};
|
|
226
241
|
return createCnos({
|
|
227
|
-
...
|
|
242
|
+
...options.root ? { root: options.root } : {},
|
|
243
|
+
...options.workspace ? { workspace: options.workspace } : {},
|
|
244
|
+
...options.profile ? { profile: options.profile } : {},
|
|
245
|
+
...options.globalRoot ? { globalRoot: options.globalRoot } : {},
|
|
246
|
+
...options.cliArgs && options.cliArgs.length > 0 ? { cliArgs: options.cliArgs } : {},
|
|
247
|
+
...options.secretResolution ? { secretResolution: options.secretResolution } : {},
|
|
248
|
+
processEnv: options.processEnv ?? process.env
|
|
228
249
|
});
|
|
229
250
|
}
|
|
230
251
|
|
|
@@ -303,7 +324,8 @@ async function defineValue(namespace, configPath, rawValue, options = {}) {
|
|
|
303
324
|
filePath: secret.filePath,
|
|
304
325
|
value: {
|
|
305
326
|
provider: secret.provider,
|
|
306
|
-
ref: secret.ref
|
|
327
|
+
ref: secret.ref,
|
|
328
|
+
...secret.vault ? { vault: secret.vault } : {}
|
|
307
329
|
}
|
|
308
330
|
};
|
|
309
331
|
}
|
|
@@ -321,48 +343,34 @@ async function defineValue(namespace, configPath, rawValue, options = {}) {
|
|
|
321
343
|
value: parsedValue
|
|
322
344
|
};
|
|
323
345
|
}
|
|
324
|
-
async function createVault(vault, options = {}) {
|
|
325
|
-
const passphrase = options.passphrase ?? resolveSecretPassphrase(vault, options.processEnv ?? process.env);
|
|
326
|
-
if (!passphrase) {
|
|
327
|
-
throw new Error("Vault creation requires --passphrase or CNOS_SECRET_PASSPHRASE");
|
|
328
|
-
}
|
|
329
|
-
const normalizedVault = vault.trim() || "default";
|
|
330
|
-
const filePath = await createSecretVault(
|
|
331
|
-
resolveSecretStoreRoot(options.processEnv),
|
|
332
|
-
normalizedVault,
|
|
333
|
-
passphrase
|
|
334
|
-
);
|
|
335
|
-
return {
|
|
336
|
-
vault: normalizedVault,
|
|
337
|
-
filePath
|
|
338
|
-
};
|
|
339
|
-
}
|
|
340
346
|
async function setSecret(configPath, rawValue, options = {}) {
|
|
341
347
|
const runtime = await createRuntimeService(options);
|
|
342
348
|
const workspaceRoot = getSelectedWorkspaceRoot(options, runtime);
|
|
343
349
|
const profile = options.profile ?? runtime.graph.profile;
|
|
344
350
|
const filePath = resolveConfigDocumentPath(workspaceRoot, "secret", configPath, profile);
|
|
345
351
|
const document = await readYamlDocument(filePath);
|
|
346
|
-
const mode = options.mode ?? "local";
|
|
347
352
|
const vault = options.vault?.trim() || "default";
|
|
353
|
+
const vaultDefinition = runtime.manifest.vaults[vault];
|
|
354
|
+
if (!vaultDefinition) {
|
|
355
|
+
throw new Error(`Unknown vault "${vault}". Create it first with cnos vault create ${vault}.`);
|
|
356
|
+
}
|
|
357
|
+
const mode = options.mode ?? (vaultDefinition.provider === "local" ? "local" : vaultDefinition.provider === "github-secrets" ? "ref" : "remote");
|
|
348
358
|
let reference;
|
|
349
|
-
let storePath;
|
|
350
359
|
if (mode === "local") {
|
|
351
|
-
const
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
const ref = configPath;
|
|
356
|
-
storePath = await writeLocalSecret(resolveSecretStoreRoot(options.processEnv), ref, rawValue, passphrase, vault);
|
|
360
|
+
const auth = await resolveVaultAuth(vault, vaultDefinition, options.processEnv ?? process.env);
|
|
361
|
+
const provider = createSecretVaultProvider(vault, vaultDefinition, options.processEnv ?? process.env);
|
|
362
|
+
await provider.authenticate(auth);
|
|
363
|
+
await provider.set(configPath, rawValue);
|
|
357
364
|
reference = {
|
|
358
365
|
provider: "local",
|
|
359
|
-
ref,
|
|
366
|
+
ref: configPath,
|
|
360
367
|
vault
|
|
361
368
|
};
|
|
362
369
|
} else {
|
|
363
370
|
reference = {
|
|
364
|
-
provider: options.provider
|
|
365
|
-
ref: rawValue
|
|
371
|
+
provider: options.provider?.trim() || vaultDefinition.provider,
|
|
372
|
+
ref: rawValue || configPath.replace(/[^A-Za-z0-9]+/g, "_").toUpperCase(),
|
|
373
|
+
vault
|
|
366
374
|
};
|
|
367
375
|
}
|
|
368
376
|
setNestedValue(document, configPath.split("."), reference);
|
|
@@ -372,8 +380,7 @@ async function setSecret(configPath, rawValue, options = {}) {
|
|
|
372
380
|
filePath,
|
|
373
381
|
provider: reference.provider,
|
|
374
382
|
ref: reference.ref,
|
|
375
|
-
...reference.vault ? { vault: reference.vault } : {}
|
|
376
|
-
...storePath ? { storePath } : {}
|
|
383
|
+
...reference.vault ? { vault: reference.vault } : {}
|
|
377
384
|
};
|
|
378
385
|
}
|
|
379
386
|
async function deleteSecret(configPath, options = {}) {
|
|
@@ -391,23 +398,19 @@ async function deleteSecret(configPath, options = {}) {
|
|
|
391
398
|
};
|
|
392
399
|
}
|
|
393
400
|
await writeFile(filePath, stringifyYaml(document), "utf8");
|
|
394
|
-
let removedStore;
|
|
395
401
|
const secretRef = metadata?.secretRef;
|
|
396
402
|
if (isSecretReference(secretRef) && secretRef.provider === "local") {
|
|
397
|
-
const
|
|
398
|
-
|
|
399
|
-
"
|
|
400
|
-
secretRef.vault ?? "default",
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
await rm(storePath, { force: true });
|
|
405
|
-
removedStore = storePath;
|
|
403
|
+
const definition = runtime.manifest.vaults[secretRef.vault ?? "default"];
|
|
404
|
+
if (definition) {
|
|
405
|
+
const auth = await resolveVaultAuth(secretRef.vault ?? "default", definition, options.processEnv ?? process.env);
|
|
406
|
+
const provider = createSecretVaultProvider(secretRef.vault ?? "default", definition, options.processEnv ?? process.env);
|
|
407
|
+
await provider.authenticate(auth);
|
|
408
|
+
await provider.delete(secretRef.ref);
|
|
409
|
+
}
|
|
406
410
|
}
|
|
407
411
|
return {
|
|
408
412
|
filePath,
|
|
409
|
-
deleted: true
|
|
410
|
-
...removedStore ? { removedStore } : {}
|
|
413
|
+
deleted: true
|
|
411
414
|
};
|
|
412
415
|
}
|
|
413
416
|
async function deleteValue(namespace, configPath, options = {}) {
|
|
@@ -464,6 +467,17 @@ async function runDefine(namespace, configPath, rawValue, options = {}) {
|
|
|
464
467
|
return `defined ${namespace}.${configPath} in ${result.filePath}`;
|
|
465
468
|
}
|
|
466
469
|
|
|
470
|
+
// src/commands/drift.ts
|
|
471
|
+
import { compareSchemaToGraph, formatDriftReport } from "@kitsy/cnos/internal";
|
|
472
|
+
async function runDrift(options = {}) {
|
|
473
|
+
const runtime = await createRuntimeService(options);
|
|
474
|
+
const report = compareSchemaToGraph(runtime);
|
|
475
|
+
if (options.json) {
|
|
476
|
+
return printJson(report);
|
|
477
|
+
}
|
|
478
|
+
return formatDriftReport(report);
|
|
479
|
+
}
|
|
480
|
+
|
|
467
481
|
// src/commands/diff.ts
|
|
468
482
|
import { flattenObject } from "@kitsy/cnos/internal";
|
|
469
483
|
function flattenRuntime(runtime) {
|
|
@@ -502,8 +516,15 @@ async function runDiff(leftProfile, rightProfile, options = {}) {
|
|
|
502
516
|
}
|
|
503
517
|
|
|
504
518
|
// src/services/doctor.ts
|
|
505
|
-
import { readFile as readFile2 } from "fs/promises";
|
|
519
|
+
import { readdir, readFile as readFile2 } from "fs/promises";
|
|
506
520
|
import path2 from "path";
|
|
521
|
+
import {
|
|
522
|
+
detectLegacyVaultFormat,
|
|
523
|
+
isSecretReference as isSecretReference2,
|
|
524
|
+
parseYaml as parseYaml2,
|
|
525
|
+
readKeychain,
|
|
526
|
+
resolveSecretStoreRoot
|
|
527
|
+
} from "@kitsy/cnos/internal";
|
|
507
528
|
|
|
508
529
|
// src/services/validation.ts
|
|
509
530
|
import { validateRuntime } from "@kitsy/cnos/internal";
|
|
@@ -548,6 +569,70 @@ async function checkGitignore(root) {
|
|
|
548
569
|
function issueSummary(issues) {
|
|
549
570
|
return issues.length === 0 ? "no issues" : issues.map((issue) => issue.message).join("; ");
|
|
550
571
|
}
|
|
572
|
+
async function collectYamlFiles(root) {
|
|
573
|
+
try {
|
|
574
|
+
const entries = await readdir(root, { withFileTypes: true });
|
|
575
|
+
const results = [];
|
|
576
|
+
for (const entry of entries) {
|
|
577
|
+
const target = path2.join(root, entry.name);
|
|
578
|
+
if (entry.isDirectory()) {
|
|
579
|
+
results.push(...await collectYamlFiles(target));
|
|
580
|
+
continue;
|
|
581
|
+
}
|
|
582
|
+
if (entry.isFile() && [".yml", ".yaml"].includes(path2.extname(entry.name).toLowerCase())) {
|
|
583
|
+
results.push(target);
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
return results;
|
|
587
|
+
} catch {
|
|
588
|
+
return [];
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
function hasPlaintextSecret(value) {
|
|
592
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
593
|
+
return typeof value === "string" || typeof value === "number" || typeof value === "boolean";
|
|
594
|
+
}
|
|
595
|
+
if (isSecretReference2(value)) {
|
|
596
|
+
return false;
|
|
597
|
+
}
|
|
598
|
+
return Object.values(value).some((entry) => hasPlaintextSecret(entry));
|
|
599
|
+
}
|
|
600
|
+
async function checkSecretSecurity(options, runtime) {
|
|
601
|
+
const storeRoot = resolveSecretStoreRoot(options.processEnv);
|
|
602
|
+
const legacyPaths = await Promise.all(
|
|
603
|
+
Object.entries(runtime.manifest.vaults).filter(([, definition]) => definition.provider === "local").map(async ([vault]) => ({ vault, path: await detectLegacyVaultFormat(storeRoot, vault) }))
|
|
604
|
+
);
|
|
605
|
+
const legacyDetected = legacyPaths.filter((entry) => Boolean(entry.path));
|
|
606
|
+
const secretFiles = await Promise.all(
|
|
607
|
+
runtime.graph.workspace.workspaceRoots.filter((root) => root.scope === "local").map((root) => collectYamlFiles(path2.join(root.path, "secrets")))
|
|
608
|
+
);
|
|
609
|
+
const plaintextFiles = [];
|
|
610
|
+
for (const file of secretFiles.flat()) {
|
|
611
|
+
try {
|
|
612
|
+
const parsed = parseYaml2(await readFile2(file, "utf8"));
|
|
613
|
+
if (hasPlaintextSecret(parsed)) {
|
|
614
|
+
plaintextFiles.push(file);
|
|
615
|
+
}
|
|
616
|
+
} catch {
|
|
617
|
+
plaintextFiles.push(file);
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
const keychainWarnings = await Promise.all(
|
|
621
|
+
Object.entries(runtime.manifest.vaults).filter(([, definition]) => definition.provider === "local").flatMap(
|
|
622
|
+
([vault, definition]) => (definition.auth?.passphrase?.from ?? []).filter((source) => source.startsWith("keychain:")).map(async (source) => ({ vault, source, value: await readKeychain(source.slice("keychain:".length)) }))
|
|
623
|
+
)
|
|
624
|
+
);
|
|
625
|
+
const warnings = [
|
|
626
|
+
...legacyDetected.map((entry) => `legacy vault ${entry.vault}: ${entry.path}`),
|
|
627
|
+
...plaintextFiles.map((file) => `plaintext secret file: ${file}`),
|
|
628
|
+
...keychainWarnings.filter((entry) => !entry.value).map((entry) => `no keychain entry for vault ${entry.vault} (${entry.source})`)
|
|
629
|
+
];
|
|
630
|
+
return {
|
|
631
|
+
name: "security",
|
|
632
|
+
ok: warnings.length === 0,
|
|
633
|
+
details: warnings.length === 0 ? "no legacy vaults, plaintext secret files, or missing keychain entries" : warnings.join("; ")
|
|
634
|
+
};
|
|
635
|
+
}
|
|
551
636
|
async function evaluateDoctor(options = {}) {
|
|
552
637
|
const root = path2.resolve(options.root ?? process.cwd());
|
|
553
638
|
const { runtime, summary } = await createValidationSummary(options);
|
|
@@ -579,6 +664,7 @@ async function evaluateDoctor(options = {}) {
|
|
|
579
664
|
ok: !runtime.manifest.workspaces.global.enabled || Boolean(runtime.graph.workspace.globalRoot),
|
|
580
665
|
details: runtime.manifest.workspaces.global.enabled ? runtime.graph.workspace.globalRoot ? `enabled at ${runtime.graph.workspace.globalRoot}` : "enabled but no global root resolved" : "disabled"
|
|
581
666
|
},
|
|
667
|
+
await checkSecretSecurity(options, runtime),
|
|
582
668
|
await checkGitignore(root)
|
|
583
669
|
];
|
|
584
670
|
}
|
|
@@ -597,7 +683,7 @@ async function runDoctor(options = {}) {
|
|
|
597
683
|
}
|
|
598
684
|
|
|
599
685
|
// src/commands/dump.ts
|
|
600
|
-
import { writeDump } from "@kitsy/cnos";
|
|
686
|
+
import { writeDump } from "@kitsy/cnos/configure";
|
|
601
687
|
async function runDump(options = {}) {
|
|
602
688
|
const cliArgs = [...options.cliArgs ?? []];
|
|
603
689
|
const flatten = consumeFlag(cliArgs, "--flatten");
|
|
@@ -619,12 +705,60 @@ async function runDump(options = {}) {
|
|
|
619
705
|
return `dumped ${result.files.length} files to ${result.root}`;
|
|
620
706
|
}
|
|
621
707
|
|
|
708
|
+
// src/commands/codegen.ts
|
|
709
|
+
import { watchSchema, writeCodegenOutput } from "@kitsy/cnos/internal";
|
|
710
|
+
async function runCodegen(options = {}) {
|
|
711
|
+
const cliArgs = [...options.cliArgs ?? []];
|
|
712
|
+
const out = consumeOption(cliArgs, "--out");
|
|
713
|
+
const watch2 = consumeFlag(cliArgs, "--watch");
|
|
714
|
+
if (cliArgs.length > 0) {
|
|
715
|
+
throw new Error(`Unknown codegen options: ${cliArgs.join(" ")}`);
|
|
716
|
+
}
|
|
717
|
+
if (watch2) {
|
|
718
|
+
const watcher = await watchSchema({
|
|
719
|
+
...options.root ? {
|
|
720
|
+
root: options.root
|
|
721
|
+
} : {},
|
|
722
|
+
...out ? {
|
|
723
|
+
out
|
|
724
|
+
} : {},
|
|
725
|
+
onError(error) {
|
|
726
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
727
|
+
process.stderr.write(`${message}
|
|
728
|
+
`);
|
|
729
|
+
}
|
|
730
|
+
});
|
|
731
|
+
const closeWatcher = () => {
|
|
732
|
+
watcher.close();
|
|
733
|
+
};
|
|
734
|
+
process.once("SIGINT", closeWatcher);
|
|
735
|
+
process.once("SIGTERM", closeWatcher);
|
|
736
|
+
return `watching schema changes -> ${out ?? ".cnos/types/cnos.d.ts"}`;
|
|
737
|
+
}
|
|
738
|
+
const result = await writeCodegenOutput({
|
|
739
|
+
...options.root ? {
|
|
740
|
+
root: options.root
|
|
741
|
+
} : {},
|
|
742
|
+
...out ? {
|
|
743
|
+
out
|
|
744
|
+
} : {}
|
|
745
|
+
});
|
|
746
|
+
const summary = result.hasSchema ? `generated types from ${result.schemaEntryCount} schema entr${result.schemaEntryCount === 1 ? "y" : "ies"}` : "generated empty types (no schema section found)";
|
|
747
|
+
return `${summary} -> ${result.typesPath} and ${result.runtimePath}`;
|
|
748
|
+
}
|
|
749
|
+
|
|
622
750
|
// src/commands/exportEnv.ts
|
|
751
|
+
import { mkdir as mkdir2, writeFile as writeFile2 } from "fs/promises";
|
|
752
|
+
import path3 from "path";
|
|
753
|
+
function formatEnvOutput(env) {
|
|
754
|
+
return Object.entries(env).sort(([left], [right]) => left.localeCompare(right)).map(([key, value]) => `${key}=${value}`).join("\n");
|
|
755
|
+
}
|
|
623
756
|
async function runExportEnv(options = {}) {
|
|
624
757
|
const cliArgs = [...options.cliArgs ?? []];
|
|
625
758
|
const isPublic = consumeFlag(cliArgs, "--public");
|
|
626
759
|
const framework = consumeOption(cliArgs, "--framework");
|
|
627
760
|
const prefix = consumeOption(cliArgs, "--prefix");
|
|
761
|
+
const to = consumeOption(cliArgs, "--to");
|
|
628
762
|
const runtime = await createRuntimeService({
|
|
629
763
|
...options,
|
|
630
764
|
cliArgs
|
|
@@ -632,16 +766,26 @@ async function runExportEnv(options = {}) {
|
|
|
632
766
|
const env = isPublic ? runtime.toPublicEnv({
|
|
633
767
|
...framework ? { framework } : {},
|
|
634
768
|
...prefix ? { prefix } : {}
|
|
635
|
-
}) :
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
769
|
+
}) : runtime.toEnv();
|
|
770
|
+
const output = formatEnvOutput(env);
|
|
771
|
+
if (to) {
|
|
772
|
+
const targetPath = path3.resolve(options.root ?? process.cwd(), to);
|
|
773
|
+
await mkdir2(path3.dirname(targetPath), { recursive: true });
|
|
774
|
+
await writeFile2(targetPath, output, "utf8");
|
|
775
|
+
if (options.json) {
|
|
776
|
+
return printJson({
|
|
777
|
+
to: targetPath,
|
|
778
|
+
count: Object.keys(env).length,
|
|
779
|
+
public: isPublic,
|
|
780
|
+
...framework ? { framework } : {}
|
|
781
|
+
});
|
|
782
|
+
}
|
|
783
|
+
return `Wrote ${Object.keys(env).length} env vars to ${targetPath}`;
|
|
784
|
+
}
|
|
641
785
|
if (options.json) {
|
|
642
786
|
return printJson(env);
|
|
643
787
|
}
|
|
644
|
-
return
|
|
788
|
+
return output;
|
|
645
789
|
}
|
|
646
790
|
|
|
647
791
|
// src/commands/export.ts
|
|
@@ -704,6 +848,23 @@ var COMMANDS = [
|
|
|
704
848
|
],
|
|
705
849
|
examples: ["cnos onboard", "cnos onboard --workspace webapp", "cnos onboard --root ../my-app --workspace app --move"]
|
|
706
850
|
},
|
|
851
|
+
{
|
|
852
|
+
id: "codegen",
|
|
853
|
+
summary: "Generate typed CNOS access wrappers from schema.",
|
|
854
|
+
usage: "cnos codegen [--out <path>] [--watch] [--root <path>]",
|
|
855
|
+
description: "Reads schema from .cnos/cnos.yml and generates typed CNOS declaration output plus a typed createCnos wrapper.",
|
|
856
|
+
options: [
|
|
857
|
+
{
|
|
858
|
+
flag: "--out <path>",
|
|
859
|
+
description: "Custom path for the generated type declaration file. runtime.ts is emitted beside it."
|
|
860
|
+
},
|
|
861
|
+
{
|
|
862
|
+
flag: "--watch",
|
|
863
|
+
description: "Watch the manifest schema and regenerate output when it changes."
|
|
864
|
+
}
|
|
865
|
+
],
|
|
866
|
+
examples: ["cnos codegen", "cnos codegen --out src/cnos-config.d.ts", "cnos codegen --watch"]
|
|
867
|
+
},
|
|
707
868
|
{
|
|
708
869
|
id: "read",
|
|
709
870
|
summary: "Read any fully-qualified CNOS key.",
|
|
@@ -796,17 +957,90 @@ var COMMANDS = [
|
|
|
796
957
|
description: "Provider name for --remote or --ref secret writes."
|
|
797
958
|
},
|
|
798
959
|
{
|
|
799
|
-
flag: "--
|
|
800
|
-
description: "
|
|
960
|
+
flag: "--vault <name>",
|
|
961
|
+
description: "Use a manifest-defined vault. Provider behavior is inferred from the vault definition."
|
|
962
|
+
},
|
|
963
|
+
{
|
|
964
|
+
flag: "--reveal",
|
|
965
|
+
description: "Reveal the resolved secret value for get-style reads. Output is masked by default."
|
|
801
966
|
}
|
|
802
967
|
],
|
|
803
968
|
examples: [
|
|
804
969
|
"cnos secret app.token",
|
|
805
|
-
"cnos
|
|
806
|
-
"cnos
|
|
807
|
-
"cnos secret
|
|
970
|
+
"cnos vault create local-dev",
|
|
971
|
+
"cnos vault auth local-dev",
|
|
972
|
+
"cnos secret set app.token super-secret --vault local-dev",
|
|
973
|
+
"cnos vault create github-ci --provider github-secrets --no-passphrase",
|
|
974
|
+
"cnos secret set app.token APP_TOKEN --vault github-ci"
|
|
808
975
|
]
|
|
809
976
|
},
|
|
977
|
+
{
|
|
978
|
+
id: "vault",
|
|
979
|
+
summary: "Manage manifest-defined secret vaults.",
|
|
980
|
+
usage: "cnos vault [create <name> | list | remove <name>] [options] [global-options]",
|
|
981
|
+
description: "Creates, lists, and removes vault definitions in .cnos/cnos.yml. Local vaults use encrypted material under ~/.cnos/secrets, while github-secrets vaults resolve from process.env in CI.",
|
|
982
|
+
options: [
|
|
983
|
+
{
|
|
984
|
+
flag: "--provider <local|github-secrets>",
|
|
985
|
+
description: "Vault provider. Defaults to local."
|
|
986
|
+
},
|
|
987
|
+
{
|
|
988
|
+
flag: "--no-passphrase",
|
|
989
|
+
description: "Allowed for passwordless providers such as github-secrets."
|
|
990
|
+
}
|
|
991
|
+
],
|
|
992
|
+
examples: [
|
|
993
|
+
"cnos vault create local-dev",
|
|
994
|
+
"cnos vault auth local-dev",
|
|
995
|
+
"cnos vault create github-ci --provider github-secrets --no-passphrase",
|
|
996
|
+
"cnos vault list",
|
|
997
|
+
"cnos vault remove local-dev"
|
|
998
|
+
]
|
|
999
|
+
},
|
|
1000
|
+
{
|
|
1001
|
+
id: "vault create",
|
|
1002
|
+
summary: "Create a manifest-defined vault.",
|
|
1003
|
+
usage: "cnos vault create <name> [--provider <local|github-secrets>] [--no-passphrase] [global-options]",
|
|
1004
|
+
description: "Creates a vault definition in .cnos/cnos.yml and, for local vaults, initializes the encrypted store under ~/.cnos/secrets.",
|
|
1005
|
+
examples: [
|
|
1006
|
+
"cnos vault create local-dev",
|
|
1007
|
+
"cnos vault create github-ci --provider github-secrets --no-passphrase"
|
|
1008
|
+
]
|
|
1009
|
+
},
|
|
1010
|
+
{
|
|
1011
|
+
id: "vault auth",
|
|
1012
|
+
summary: "Authenticate a vault for the current shell session.",
|
|
1013
|
+
usage: "cnos vault auth <name> [--store-keychain] [global-options]",
|
|
1014
|
+
description: "Authenticates a local vault using env, keychain, or prompt-based auth and stores a derived session key for later CNOS commands in the same shell.",
|
|
1015
|
+
examples: ["cnos vault auth local-dev", "cnos vault auth local-dev --store-keychain"]
|
|
1016
|
+
},
|
|
1017
|
+
{
|
|
1018
|
+
id: "vault logout",
|
|
1019
|
+
summary: "Clear vault auth state for the current shell session.",
|
|
1020
|
+
usage: "cnos vault logout <name> [global-options]",
|
|
1021
|
+
description: "Removes active vault session auth for the selected vault or all vaults when used with --all.",
|
|
1022
|
+
options: [
|
|
1023
|
+
{
|
|
1024
|
+
flag: "--all",
|
|
1025
|
+
description: "Clear all active vault auth sessions for the current shell context."
|
|
1026
|
+
}
|
|
1027
|
+
],
|
|
1028
|
+
examples: ["cnos vault logout local-dev", "cnos vault logout --all"]
|
|
1029
|
+
},
|
|
1030
|
+
{
|
|
1031
|
+
id: "vault list",
|
|
1032
|
+
summary: "List manifest-defined vaults.",
|
|
1033
|
+
usage: "cnos vault list [global-options]",
|
|
1034
|
+
description: "Lists vault definitions together with provider and passphrase policy.",
|
|
1035
|
+
examples: ["cnos vault list"]
|
|
1036
|
+
},
|
|
1037
|
+
{
|
|
1038
|
+
id: "vault remove",
|
|
1039
|
+
summary: "Remove a vault definition.",
|
|
1040
|
+
usage: "cnos vault remove <name> [global-options]",
|
|
1041
|
+
description: "Removes the vault from .cnos/cnos.yml and deletes local vault store metadata when present.",
|
|
1042
|
+
examples: ["cnos vault remove local-dev"]
|
|
1043
|
+
},
|
|
810
1044
|
{
|
|
811
1045
|
id: "define",
|
|
812
1046
|
summary: "Write a value or secret into the selected workspace.",
|
|
@@ -850,7 +1084,7 @@ var COMMANDS = [
|
|
|
850
1084
|
{
|
|
851
1085
|
id: "list",
|
|
852
1086
|
summary: "List resolved config entries.",
|
|
853
|
-
usage: "cnos list [value|secret|meta|env|public|all] [--prefix <path>] [global-options]",
|
|
1087
|
+
usage: "cnos list [value|secret|meta|env|public|all] [--prefix <path>] [--framework <name>] [global-options]",
|
|
854
1088
|
description: "Lists stored config or derived projections across one namespace or the full effective graph, with optional prefix filtering.",
|
|
855
1089
|
options: [
|
|
856
1090
|
{
|
|
@@ -860,9 +1094,13 @@ var COMMANDS = [
|
|
|
860
1094
|
{
|
|
861
1095
|
flag: "--prefix <path>",
|
|
862
1096
|
description: "Filter list output to entries whose logical keys begin with this prefix."
|
|
1097
|
+
},
|
|
1098
|
+
{
|
|
1099
|
+
flag: "--framework <name>",
|
|
1100
|
+
description: "When listing public output, apply framework-specific prefixes such as vite or next."
|
|
863
1101
|
}
|
|
864
1102
|
],
|
|
865
|
-
examples: ["cnos list", "cnos list value --prefix app.", "cnos list env", "cnos list --
|
|
1103
|
+
examples: ["cnos list", "cnos list value --prefix app.", "cnos list env", "cnos list public --framework vite"]
|
|
866
1104
|
},
|
|
867
1105
|
{
|
|
868
1106
|
id: "profile",
|
|
@@ -909,29 +1147,52 @@ var COMMANDS = [
|
|
|
909
1147
|
description: "Deletes .cnos/profiles/<name>.yml.",
|
|
910
1148
|
examples: ["cnos profile delete stage"]
|
|
911
1149
|
},
|
|
1150
|
+
{
|
|
1151
|
+
id: "promote",
|
|
1152
|
+
summary: "Promote shareable config into public or env projection surfaces.",
|
|
1153
|
+
usage: "cnos promote <key...> --to <public|env> [--as <ENV_VAR>] [global-options]",
|
|
1154
|
+
description: "Adds keys to public.promote or envMapping.explicit in .cnos/cnos.yml. Sensitive or non-shareable namespaces are rejected.",
|
|
1155
|
+
options: [
|
|
1156
|
+
{
|
|
1157
|
+
flag: "--to <public|env>",
|
|
1158
|
+
description: "Choose whether the keys are promoted to the public surface or env export surface."
|
|
1159
|
+
},
|
|
1160
|
+
{
|
|
1161
|
+
flag: "--as <ENV_VAR>",
|
|
1162
|
+
description: "Required for --to env. Sets the exported env var name for the promoted key."
|
|
1163
|
+
}
|
|
1164
|
+
],
|
|
1165
|
+
examples: [
|
|
1166
|
+
"cnos promote value.flag.auth.upi_enabled --to public",
|
|
1167
|
+
"cnos promote value.server.port --to env --as PORT"
|
|
1168
|
+
]
|
|
1169
|
+
},
|
|
912
1170
|
{
|
|
913
1171
|
id: "secret set",
|
|
914
1172
|
summary: "Write a secret securely.",
|
|
915
|
-
usage: "cnos secret set <path> <value> [--local|--remote|--ref] [--vault <name>] [--provider <name>] [
|
|
916
|
-
description: "Writes a secret reference into the repo
|
|
1173
|
+
usage: "cnos secret set <path> <value> [--local|--remote|--ref] [--vault <name>] [--provider <name>] [global-options]",
|
|
1174
|
+
description: "Writes a secret reference into the repo. When a local vault is selected, CNOS stores encrypted secret material outside the repo under ~/.cnos/secrets/vaults/<vault>; when a github-secrets vault is selected, CNOS writes a CI env-backed ref.",
|
|
917
1175
|
examples: [
|
|
918
|
-
"cnos
|
|
919
|
-
"cnos
|
|
1176
|
+
"cnos vault create db",
|
|
1177
|
+
"cnos vault auth db",
|
|
1178
|
+
"cnos secret set app.token super-secret --vault db",
|
|
1179
|
+
"cnos vault create github-ci --provider github-secrets --no-passphrase",
|
|
1180
|
+
"cnos secret set app.token APP_TOKEN --vault github-ci"
|
|
920
1181
|
]
|
|
921
1182
|
},
|
|
922
1183
|
{
|
|
923
1184
|
id: "secret create vault",
|
|
924
1185
|
summary: "Create a local secret vault.",
|
|
925
|
-
usage: "cnos secret create vault <name>
|
|
926
|
-
description: "
|
|
927
|
-
examples: ["cnos secret create vault db
|
|
1186
|
+
usage: "cnos secret create vault <name> [global-options]",
|
|
1187
|
+
description: "Alias for cnos vault create <name>.",
|
|
1188
|
+
examples: ["cnos secret create vault db"]
|
|
928
1189
|
},
|
|
929
1190
|
{
|
|
930
1191
|
id: "secret list",
|
|
931
1192
|
summary: "List resolved secrets.",
|
|
932
|
-
usage: "cnos secret list [global-options]",
|
|
933
|
-
description: "Lists
|
|
934
|
-
examples: ["cnos secret list --workspace api"]
|
|
1193
|
+
usage: "cnos secret list [--vault <name>] [--provider <name>] [global-options]",
|
|
1194
|
+
description: "Lists stored secret entries for the selected workspace and profile, optionally filtered by vault or provider.",
|
|
1195
|
+
examples: ["cnos secret list --workspace api", "cnos secret list --vault github-ci"]
|
|
935
1196
|
},
|
|
936
1197
|
{
|
|
937
1198
|
id: "secret delete",
|
|
@@ -982,7 +1243,7 @@ var COMMANDS = [
|
|
|
982
1243
|
{
|
|
983
1244
|
id: "export env",
|
|
984
1245
|
summary: "Render environment variables for the selected workspace.",
|
|
985
|
-
usage: "cnos export env [--public] [--framework <name>] [--prefix <prefix>] [global-options]",
|
|
1246
|
+
usage: "cnos export env [--public] [--framework <name>] [--prefix <prefix>] [--to <path>] [global-options]",
|
|
986
1247
|
description: "Exports the effective environment as KEY=VALUE lines, or only promoted public values when --public is set.",
|
|
987
1248
|
options: [
|
|
988
1249
|
{
|
|
@@ -996,9 +1257,17 @@ var COMMANDS = [
|
|
|
996
1257
|
{
|
|
997
1258
|
flag: "--prefix <prefix>",
|
|
998
1259
|
description: "Override the generated public env prefix."
|
|
1260
|
+
},
|
|
1261
|
+
{
|
|
1262
|
+
flag: "--to <path>",
|
|
1263
|
+
description: "Write the rendered KEY=VALUE output to a file instead of stdout."
|
|
999
1264
|
}
|
|
1000
1265
|
],
|
|
1001
|
-
examples: [
|
|
1266
|
+
examples: [
|
|
1267
|
+
"cnos export env",
|
|
1268
|
+
"cnos export env --to .env.local",
|
|
1269
|
+
"cnos export env --public --framework vite --to .env.local --workspace api"
|
|
1270
|
+
]
|
|
1002
1271
|
},
|
|
1003
1272
|
{
|
|
1004
1273
|
id: "dump",
|
|
@@ -1020,9 +1289,32 @@ var COMMANDS = [
|
|
|
1020
1289
|
{
|
|
1021
1290
|
id: "run",
|
|
1022
1291
|
summary: "Run a child process with CNOS env injected.",
|
|
1023
|
-
usage: "cnos run [global-options] -- <command...>",
|
|
1024
|
-
description: "Resolves the active workspace and profile, injects runtime env variables, and executes the command after --.",
|
|
1025
|
-
|
|
1292
|
+
usage: "cnos run [--public] [--framework <name>] [--set <logical-key=value>] [global-options] -- <command...>",
|
|
1293
|
+
description: "Resolves the active workspace and profile, injects runtime env variables, bootstraps __CNOS_GRAPH__ for singleton runtime reads, and executes the command after --.",
|
|
1294
|
+
options: [
|
|
1295
|
+
{
|
|
1296
|
+
flag: "--set <logical-key=value>",
|
|
1297
|
+
description: "Apply inline logical-key overrides for this run without touching repo config files."
|
|
1298
|
+
},
|
|
1299
|
+
{
|
|
1300
|
+
flag: "--public",
|
|
1301
|
+
description: "Inject only promoted public env variables into the child process."
|
|
1302
|
+
},
|
|
1303
|
+
{
|
|
1304
|
+
flag: "--framework <name>",
|
|
1305
|
+
description: "When used with --public, apply framework-specific prefixes such as vite or next."
|
|
1306
|
+
},
|
|
1307
|
+
{
|
|
1308
|
+
flag: "--prefix <prefix>",
|
|
1309
|
+
description: "Override the generated public env prefix for --public runs."
|
|
1310
|
+
}
|
|
1311
|
+
],
|
|
1312
|
+
examples: [
|
|
1313
|
+
"cnos run -- node server.js",
|
|
1314
|
+
"cnos run --profile stage -- node server.js",
|
|
1315
|
+
"cnos run --set value.server.port=9999 -- node server.js",
|
|
1316
|
+
"cnos run --public --framework vite -- pnpm build"
|
|
1317
|
+
]
|
|
1026
1318
|
},
|
|
1027
1319
|
{
|
|
1028
1320
|
id: "diff",
|
|
@@ -1050,6 +1342,60 @@ var COMMANDS = [
|
|
|
1050
1342
|
description: "Checks manifest/workspace setup, gitignore coverage, and related diagnostics for the selected workspace.",
|
|
1051
1343
|
examples: ["cnos doctor", "cnos doctor --workspace api --json"]
|
|
1052
1344
|
},
|
|
1345
|
+
{
|
|
1346
|
+
id: "drift",
|
|
1347
|
+
summary: "Compare resolved config against schema and report drift.",
|
|
1348
|
+
usage: "cnos drift [--workspace <id>] [--profile <name>] [--json]",
|
|
1349
|
+
description: "Reports missing required keys, undeclared keys, type mismatches, and defaults applied for the selected workspace/profile.",
|
|
1350
|
+
examples: ["cnos drift", "cnos drift --workspace api --profile stage", "cnos drift --json"]
|
|
1351
|
+
},
|
|
1352
|
+
{
|
|
1353
|
+
id: "watch",
|
|
1354
|
+
summary: "Watch CNOS inputs and either restart a process or emit changed keys.",
|
|
1355
|
+
usage: "cnos watch [--signal] [--debounce <ms>] [global-options] -- <command...>",
|
|
1356
|
+
description: "Watches the active manifest, workspace roots, env files, and config documents. In restart mode it respawns the child command after changes; in signal mode it prints changed keys as JSON.",
|
|
1357
|
+
options: [
|
|
1358
|
+
{
|
|
1359
|
+
flag: "--signal",
|
|
1360
|
+
description: "Emit changed keys as JSON instead of restarting a child process."
|
|
1361
|
+
},
|
|
1362
|
+
{
|
|
1363
|
+
flag: "--debounce <ms>",
|
|
1364
|
+
description: "Debounce change handling before re-resolving the graph. Defaults to 300ms."
|
|
1365
|
+
}
|
|
1366
|
+
],
|
|
1367
|
+
examples: ["cnos watch -- node server.js", "cnos watch --signal", "cnos watch --debounce 100 -- node server.js"]
|
|
1368
|
+
},
|
|
1369
|
+
{
|
|
1370
|
+
id: "migrate",
|
|
1371
|
+
summary: "Scan env usage and propose CNOS manifest mappings.",
|
|
1372
|
+
usage: "cnos migrate [--scan <path>] [--dry-run] [--apply] [--rewrite] [global-options]",
|
|
1373
|
+
description: "Scans JS/TS source for process.env and import.meta.env usage, proposes logical CNOS mappings, updates envMapping/public promote entries, and can rewrite supported source files with backups.",
|
|
1374
|
+
options: [
|
|
1375
|
+
{
|
|
1376
|
+
flag: "--scan <path>",
|
|
1377
|
+
description: "Directory to scan. Defaults to ./src relative to the repo root."
|
|
1378
|
+
},
|
|
1379
|
+
{
|
|
1380
|
+
flag: "--dry-run",
|
|
1381
|
+
description: "Preview the proposed mappings without changing the manifest."
|
|
1382
|
+
},
|
|
1383
|
+
{
|
|
1384
|
+
flag: "--apply",
|
|
1385
|
+
description: "Write proposed env mappings and public promotions into .cnos/cnos.yml."
|
|
1386
|
+
},
|
|
1387
|
+
{
|
|
1388
|
+
flag: "--rewrite",
|
|
1389
|
+
description: "With --apply, rewrite supported process.env usages in source files and create .bak backups."
|
|
1390
|
+
}
|
|
1391
|
+
],
|
|
1392
|
+
examples: [
|
|
1393
|
+
"cnos migrate",
|
|
1394
|
+
"cnos migrate --scan src --dry-run",
|
|
1395
|
+
"cnos migrate --scan apps/api/src --apply",
|
|
1396
|
+
"cnos migrate --apply --rewrite"
|
|
1397
|
+
]
|
|
1398
|
+
},
|
|
1053
1399
|
{
|
|
1054
1400
|
id: "help",
|
|
1055
1401
|
summary: "Show human-readable CLI help.",
|
|
@@ -1095,17 +1441,25 @@ var INTEGRATIONS = [
|
|
|
1095
1441
|
id: "vite",
|
|
1096
1442
|
packageName: "@kitsy/cnos-vite",
|
|
1097
1443
|
entrypoint: "@kitsy/cnos-vite",
|
|
1098
|
-
summary: "Inject CNOS public env into Vite
|
|
1444
|
+
summary: "Inject CNOS public env into Vite and embed browser-readable CNOS public data.",
|
|
1099
1445
|
usage: 'import { createCnosVitePlugin } from "@kitsy/cnos-vite"',
|
|
1100
|
-
examples: [
|
|
1446
|
+
examples: [
|
|
1447
|
+
"cnos export env --public --framework vite",
|
|
1448
|
+
"vite.config.ts -> plugins: [createCnosVitePlugin()]",
|
|
1449
|
+
'browser code -> import cnos from "@kitsy/cnos/browser"'
|
|
1450
|
+
]
|
|
1101
1451
|
},
|
|
1102
1452
|
{
|
|
1103
1453
|
id: "next",
|
|
1104
1454
|
packageName: "@kitsy/cnos-next",
|
|
1105
1455
|
entrypoint: "@kitsy/cnos-next",
|
|
1106
|
-
summary: "Merge CNOS public env into
|
|
1456
|
+
summary: "Merge CNOS public env into Next and embed browser-readable CNOS public data.",
|
|
1107
1457
|
usage: 'import { withCnosNext } from "@kitsy/cnos-next"',
|
|
1108
|
-
examples: [
|
|
1458
|
+
examples: [
|
|
1459
|
+
"cnos export env --public --framework next",
|
|
1460
|
+
"next.config.mjs -> export default withCnosNext({})",
|
|
1461
|
+
'browser code -> import cnos from "@kitsy/cnos/browser"'
|
|
1462
|
+
]
|
|
1109
1463
|
}
|
|
1110
1464
|
];
|
|
1111
1465
|
var HELP_DOCUMENT = {
|
|
@@ -1232,11 +1586,11 @@ function runHelpAi(topic, cliArgs = []) {
|
|
|
1232
1586
|
}
|
|
1233
1587
|
|
|
1234
1588
|
// src/commands/init.ts
|
|
1235
|
-
import
|
|
1589
|
+
import path5 from "path";
|
|
1236
1590
|
|
|
1237
1591
|
// src/services/scaffold.ts
|
|
1238
|
-
import { mkdir as
|
|
1239
|
-
import
|
|
1592
|
+
import { mkdir as mkdir3, readFile as readFile3, writeFile as writeFile3 } from "fs/promises";
|
|
1593
|
+
import path4 from "path";
|
|
1240
1594
|
function scaffoldManifest(projectName, workspace) {
|
|
1241
1595
|
const lines = [
|
|
1242
1596
|
"version: 1",
|
|
@@ -1270,12 +1624,12 @@ async function ensureFile(filePath, content) {
|
|
|
1270
1624
|
await readFile3(filePath, "utf8");
|
|
1271
1625
|
return false;
|
|
1272
1626
|
} catch {
|
|
1273
|
-
await
|
|
1627
|
+
await writeFile3(filePath, content, "utf8");
|
|
1274
1628
|
return true;
|
|
1275
1629
|
}
|
|
1276
1630
|
}
|
|
1277
1631
|
async function ensureGitignore(root) {
|
|
1278
|
-
const gitignorePath =
|
|
1632
|
+
const gitignorePath = path4.join(root, ".gitignore");
|
|
1279
1633
|
const requiredEntries = [
|
|
1280
1634
|
".cnos/env/.env",
|
|
1281
1635
|
".cnos/env/.env.*",
|
|
@@ -1298,18 +1652,18 @@ async function ensureGitignore(root) {
|
|
|
1298
1652
|
}
|
|
1299
1653
|
const prefix = current.trim().length > 0 ? `${current.trimEnd()}
|
|
1300
1654
|
` : "";
|
|
1301
|
-
await
|
|
1655
|
+
await writeFile3(gitignorePath, `${prefix}${missingEntries.join("\n")}
|
|
1302
1656
|
`, "utf8");
|
|
1303
1657
|
return true;
|
|
1304
1658
|
}
|
|
1305
1659
|
async function scaffoldWorkspace(root, workspace) {
|
|
1306
|
-
const cnosRoot =
|
|
1307
|
-
const workspaceRoot = workspace ?
|
|
1660
|
+
const cnosRoot = path4.join(root, ".cnos");
|
|
1661
|
+
const workspaceRoot = workspace ? path4.join(cnosRoot, "workspaces", workspace) : cnosRoot;
|
|
1308
1662
|
const createdPaths = [];
|
|
1309
|
-
await
|
|
1310
|
-
await
|
|
1311
|
-
await
|
|
1312
|
-
await
|
|
1663
|
+
await mkdir3(path4.join(workspaceRoot, "profiles"), { recursive: true });
|
|
1664
|
+
await mkdir3(path4.join(workspaceRoot, "values"), { recursive: true });
|
|
1665
|
+
await mkdir3(path4.join(workspaceRoot, "secrets"), { recursive: true });
|
|
1666
|
+
await mkdir3(path4.join(workspaceRoot, "env"), { recursive: true });
|
|
1313
1667
|
const relativePaths = workspace ? [
|
|
1314
1668
|
["workspaces", workspace, "profiles", ".gitkeep"],
|
|
1315
1669
|
["workspaces", workspace, "values", ".gitkeep"],
|
|
@@ -1322,15 +1676,15 @@ async function scaffoldWorkspace(root, workspace) {
|
|
|
1322
1676
|
["env", ".gitkeep"]
|
|
1323
1677
|
];
|
|
1324
1678
|
for (const relativePath of relativePaths) {
|
|
1325
|
-
const filePath =
|
|
1679
|
+
const filePath = path4.join(cnosRoot, ...relativePath);
|
|
1326
1680
|
if (await ensureFile(filePath, "")) {
|
|
1327
|
-
createdPaths.push(
|
|
1681
|
+
createdPaths.push(path4.relative(root, filePath).replace(/\\/g, "/"));
|
|
1328
1682
|
}
|
|
1329
1683
|
}
|
|
1330
|
-
if (await ensureFile(
|
|
1684
|
+
if (await ensureFile(path4.join(cnosRoot, "cnos.yml"), scaffoldManifest(path4.basename(root), workspace))) {
|
|
1331
1685
|
createdPaths.push(".cnos/cnos.yml");
|
|
1332
1686
|
}
|
|
1333
|
-
if (workspace && await ensureFile(
|
|
1687
|
+
if (workspace && await ensureFile(path4.join(root, ".cnos-workspace.yml"), `workspace: ${workspace}
|
|
1334
1688
|
globalRoot: ~/.cnos
|
|
1335
1689
|
`)) {
|
|
1336
1690
|
createdPaths.push(".cnos-workspace.yml");
|
|
@@ -1347,7 +1701,7 @@ globalRoot: ~/.cnos
|
|
|
1347
1701
|
|
|
1348
1702
|
// src/commands/init.ts
|
|
1349
1703
|
async function runInit(options = {}) {
|
|
1350
|
-
const root =
|
|
1704
|
+
const root = path5.resolve(options.root ?? process.cwd());
|
|
1351
1705
|
const result = await scaffoldWorkspace(root, options.workspace);
|
|
1352
1706
|
if (options.json) {
|
|
1353
1707
|
return printJson(result);
|
|
@@ -1358,6 +1712,12 @@ async function runInit(options = {}) {
|
|
|
1358
1712
|
return `initialized CNOS project at ${root}`;
|
|
1359
1713
|
}
|
|
1360
1714
|
|
|
1715
|
+
// src/format/maskSecret.ts
|
|
1716
|
+
var MASKED_SECRET_VALUE = "****";
|
|
1717
|
+
function maskSecretValue(value) {
|
|
1718
|
+
return value === void 0 ? "" : MASKED_SECRET_VALUE;
|
|
1719
|
+
}
|
|
1720
|
+
|
|
1361
1721
|
// src/format/printInspect.ts
|
|
1362
1722
|
function printInspect(record) {
|
|
1363
1723
|
const lines = [
|
|
@@ -1382,12 +1742,22 @@ function printInspect(record) {
|
|
|
1382
1742
|
|
|
1383
1743
|
// src/commands/inspect.ts
|
|
1384
1744
|
async function runInspect(key, options = {}) {
|
|
1745
|
+
const reveal = options.cliArgs?.includes("--reveal") ?? false;
|
|
1385
1746
|
const runtime = await createRuntimeService(options);
|
|
1386
1747
|
const inspectResult = runtime.inspect(key);
|
|
1748
|
+
const value = key.startsWith("secret.") && !reveal ? maskSecretValue(inspectResult.value) : inspectResult.value;
|
|
1749
|
+
const printable = {
|
|
1750
|
+
...inspectResult,
|
|
1751
|
+
value,
|
|
1752
|
+
overridden: inspectResult.overridden.map((entry) => ({
|
|
1753
|
+
...entry,
|
|
1754
|
+
value: key.startsWith("secret.") && !reveal ? maskSecretValue(entry.value) : entry.value
|
|
1755
|
+
}))
|
|
1756
|
+
};
|
|
1387
1757
|
if (options.json) {
|
|
1388
|
-
return printJson(
|
|
1758
|
+
return printJson(printable);
|
|
1389
1759
|
}
|
|
1390
|
-
return printInspect(
|
|
1760
|
+
return printInspect(printable);
|
|
1391
1761
|
}
|
|
1392
1762
|
|
|
1393
1763
|
// src/format/printValue.ts
|
|
@@ -1406,34 +1776,52 @@ function printValue(value, json = false) {
|
|
|
1406
1776
|
|
|
1407
1777
|
// src/services/listing.ts
|
|
1408
1778
|
import { flattenObject as flattenObject2 } from "@kitsy/cnos/internal";
|
|
1779
|
+
function matchesSecretFilter(candidate, filter) {
|
|
1780
|
+
const secretRef = candidate.metadata?.secretRef;
|
|
1781
|
+
if (filter.vault && secretRef?.vault !== filter.vault) {
|
|
1782
|
+
return false;
|
|
1783
|
+
}
|
|
1784
|
+
if (filter.provider && secretRef?.provider !== filter.provider) {
|
|
1785
|
+
return false;
|
|
1786
|
+
}
|
|
1787
|
+
return true;
|
|
1788
|
+
}
|
|
1409
1789
|
function matchesPrefix(key, prefix) {
|
|
1410
1790
|
if (!prefix) {
|
|
1411
1791
|
return true;
|
|
1412
1792
|
}
|
|
1413
1793
|
return key.startsWith(prefix) || key.split(".").slice(1).join(".").startsWith(prefix);
|
|
1414
1794
|
}
|
|
1415
|
-
function toStoredEntry(namespace, entry) {
|
|
1795
|
+
function toStoredEntry(namespace, entry, filter = {}) {
|
|
1416
1796
|
const sourceId = namespace === "value" ? "filesystem-values" : "filesystem-secrets";
|
|
1417
1797
|
const candidates = [entry.winner, ...entry.overridden].filter((candidate) => candidate.sourceId === sourceId);
|
|
1418
1798
|
if (candidates.length === 0) {
|
|
1419
1799
|
return void 0;
|
|
1420
1800
|
}
|
|
1801
|
+
const selectedCandidate = namespace === "secret" ? candidates.find((candidate) => matchesSecretFilter(candidate, filter)) : candidates[0];
|
|
1802
|
+
if (!selectedCandidate) {
|
|
1803
|
+
return void 0;
|
|
1804
|
+
}
|
|
1421
1805
|
return {
|
|
1422
1806
|
key: entry.key,
|
|
1423
|
-
value:
|
|
1807
|
+
value: selectedCandidate.value
|
|
1424
1808
|
};
|
|
1425
1809
|
}
|
|
1426
1810
|
function listStoredNamespace(namespace, options) {
|
|
1427
1811
|
return createRuntimeService(options).then(
|
|
1428
|
-
(runtime) => Array.from(runtime.graph.entries.values()).filter((entry) => entry.namespace === namespace).map((entry) => toStoredEntry(namespace, entry)).filter((entry) => Boolean(entry)).filter((entry) => matchesPrefix(entry.key, options.prefix)).sort((left, right) => left.key.localeCompare(right.key))
|
|
1812
|
+
(runtime) => Array.from(runtime.graph.entries.values()).filter((entry) => entry.namespace === namespace).map((entry) => toStoredEntry(namespace, entry, options)).filter((entry) => Boolean(entry)).filter((entry) => matchesPrefix(entry.key, options.prefix)).sort((left, right) => left.key.localeCompare(right.key))
|
|
1429
1813
|
);
|
|
1430
1814
|
}
|
|
1431
1815
|
function listProjectedNamespace(namespace, options) {
|
|
1432
1816
|
return createRuntimeService(options).then((runtime) => {
|
|
1433
|
-
const projected = namespace === "meta" ? flattenObject2(runtime.toNamespace("meta")) : namespace === "env" ? runtime.
|
|
1434
|
-
|
|
1817
|
+
const projected = namespace === "meta" ? flattenObject2(runtime.toNamespace("meta")) : namespace === "env" ? runtime.toEnv() : runtime.toPublicEnv({
|
|
1818
|
+
...options.framework ? {
|
|
1819
|
+
framework: options.framework
|
|
1820
|
+
} : {}
|
|
1821
|
+
});
|
|
1822
|
+
const entries = namespace === "env" ? Object.entries(projected).map(([envVar, value]) => ({
|
|
1435
1823
|
key: envVar,
|
|
1436
|
-
value
|
|
1824
|
+
value
|
|
1437
1825
|
})) : Object.entries(projected).map(([key, value]) => ({
|
|
1438
1826
|
key: namespace === "meta" ? `meta.${key}` : key,
|
|
1439
1827
|
value
|
|
@@ -1482,10 +1870,12 @@ async function runList(args = [], options = {}) {
|
|
|
1482
1870
|
const cliArgs = [...options.cliArgs ?? []];
|
|
1483
1871
|
const namespace = normalizeNamespace(args[0] ?? consumeOption(cliArgs, "--namespace"));
|
|
1484
1872
|
const prefix = consumeOption(cliArgs, "--prefix");
|
|
1873
|
+
const framework = consumeOption(cliArgs, "--framework");
|
|
1485
1874
|
const entries = await listConfigEntries(namespace, {
|
|
1486
1875
|
...options,
|
|
1487
1876
|
cliArgs,
|
|
1488
|
-
...prefix ? { prefix } : {}
|
|
1877
|
+
...prefix ? { prefix } : {},
|
|
1878
|
+
...framework ? { framework } : {}
|
|
1489
1879
|
});
|
|
1490
1880
|
if (options.json) {
|
|
1491
1881
|
return printJson(entries);
|
|
@@ -1496,35 +1886,115 @@ async function runList(args = [], options = {}) {
|
|
|
1496
1886
|
return entries.map((entry) => `${entry.key}=${printValue(entry.value)}`).join("\n");
|
|
1497
1887
|
}
|
|
1498
1888
|
|
|
1889
|
+
// src/commands/migrate.ts
|
|
1890
|
+
import path6 from "path";
|
|
1891
|
+
import {
|
|
1892
|
+
applyManifestMappings,
|
|
1893
|
+
loadManifest,
|
|
1894
|
+
proposeMapping,
|
|
1895
|
+
rewriteSourceFiles,
|
|
1896
|
+
scanEnvUsage
|
|
1897
|
+
} from "@kitsy/cnos/internal";
|
|
1898
|
+
async function runMigrate(options = {}) {
|
|
1899
|
+
const cliArgs = [...options.cliArgs ?? []];
|
|
1900
|
+
const scan = consumeOption(cliArgs, "--scan");
|
|
1901
|
+
const apply = consumeFlag(cliArgs, "--apply");
|
|
1902
|
+
const dryRun = consumeFlag(cliArgs, "--dry-run") || !apply;
|
|
1903
|
+
const rewrite = consumeFlag(cliArgs, "--rewrite");
|
|
1904
|
+
if (cliArgs.length > 0) {
|
|
1905
|
+
throw new Error(`Unknown migrate options: ${cliArgs.join(" ")}`);
|
|
1906
|
+
}
|
|
1907
|
+
const manifest = await loadManifest(options.root ? { root: options.root } : {});
|
|
1908
|
+
const scanRoot = path6.resolve(manifest.repoRoot, scan ?? "src");
|
|
1909
|
+
const usages = await scanEnvUsage(scanRoot);
|
|
1910
|
+
const uniqueProposals = new Map(usages.map((usage) => [usage.envVar, proposeMapping(usage.envVar)]));
|
|
1911
|
+
const proposals = Array.from(uniqueProposals.values()).sort((left, right) => left.envVar.localeCompare(right.envVar));
|
|
1912
|
+
let manifestResult;
|
|
1913
|
+
let rewriteResult;
|
|
1914
|
+
if (apply) {
|
|
1915
|
+
manifestResult = await applyManifestMappings(proposals, options.root);
|
|
1916
|
+
if (rewrite) {
|
|
1917
|
+
rewriteResult = await rewriteSourceFiles(usages.filter((usage) => usage.kind === "process-env"), uniqueProposals);
|
|
1918
|
+
}
|
|
1919
|
+
}
|
|
1920
|
+
if (options.json) {
|
|
1921
|
+
return printJson({
|
|
1922
|
+
scanRoot,
|
|
1923
|
+
dryRun,
|
|
1924
|
+
apply,
|
|
1925
|
+
rewrite,
|
|
1926
|
+
usages,
|
|
1927
|
+
proposals,
|
|
1928
|
+
...manifestResult ? { manifest: manifestResult } : {},
|
|
1929
|
+
...rewriteResult ? { rewriteResult } : {}
|
|
1930
|
+
});
|
|
1931
|
+
}
|
|
1932
|
+
const lines = [
|
|
1933
|
+
`Scanned ${usages.length} env usage${usages.length === 1 ? "" : "s"} in ${scanRoot}`,
|
|
1934
|
+
"",
|
|
1935
|
+
"Proposed mappings:",
|
|
1936
|
+
...proposals.map(
|
|
1937
|
+
(proposal) => ` ${proposal.envVar} -> ${proposal.logicalKey}${proposal.public ? " (promote to public)" : ""}`
|
|
1938
|
+
)
|
|
1939
|
+
];
|
|
1940
|
+
if (proposals.length === 0) {
|
|
1941
|
+
lines.push(" none");
|
|
1942
|
+
}
|
|
1943
|
+
if (dryRun) {
|
|
1944
|
+
lines.push("", "Dry run only. Re-run with --apply to update the manifest.");
|
|
1945
|
+
}
|
|
1946
|
+
if (manifestResult) {
|
|
1947
|
+
lines.push(
|
|
1948
|
+
"",
|
|
1949
|
+
`Updated ${manifestResult.manifestPath} with ${manifestResult.appliedMappings} env mapping${manifestResult.appliedMappings === 1 ? "" : "s"} and ${manifestResult.appliedPromotions} public promotion${manifestResult.appliedPromotions === 1 ? "" : "s"}.`
|
|
1950
|
+
);
|
|
1951
|
+
}
|
|
1952
|
+
if (rewrite) {
|
|
1953
|
+
if (!apply) {
|
|
1954
|
+
lines.push("", "Source rewrite requested but skipped because --apply was not set.");
|
|
1955
|
+
} else if (rewriteResult) {
|
|
1956
|
+
lines.push(
|
|
1957
|
+
"",
|
|
1958
|
+
`Rewrote ${rewriteResult.rewrittenFiles.length} file${rewriteResult.rewrittenFiles.length === 1 ? "" : "s"} and created ${rewriteResult.backupFiles.length} backup${rewriteResult.backupFiles.length === 1 ? "" : "s"}.`
|
|
1959
|
+
);
|
|
1960
|
+
if (rewriteResult.skippedUsages.length > 0) {
|
|
1961
|
+
lines.push("Skipped usages:");
|
|
1962
|
+
lines.push(...rewriteResult.skippedUsages.map((entry) => ` ${entry}`));
|
|
1963
|
+
}
|
|
1964
|
+
}
|
|
1965
|
+
}
|
|
1966
|
+
return lines.join("\n");
|
|
1967
|
+
}
|
|
1968
|
+
|
|
1499
1969
|
// src/commands/onboard.ts
|
|
1500
|
-
import { copyFile, readdir, rm
|
|
1501
|
-
import
|
|
1970
|
+
import { copyFile, readdir as readdir2, rm } from "fs/promises";
|
|
1971
|
+
import path7 from "path";
|
|
1502
1972
|
var ROOT_ENV_FILE_PATTERN = /^\.env(?:\.[A-Za-z0-9_-]+)*(?:\.example)?$/;
|
|
1503
1973
|
async function listRootEnvFiles(root) {
|
|
1504
|
-
const entries = await
|
|
1974
|
+
const entries = await readdir2(root, { withFileTypes: true });
|
|
1505
1975
|
return entries.filter((entry) => entry.isFile() && ROOT_ENV_FILE_PATTERN.test(entry.name)).map((entry) => entry.name).sort((left, right) => left.localeCompare(right));
|
|
1506
1976
|
}
|
|
1507
1977
|
async function runOnboard(options = {}) {
|
|
1508
|
-
const root =
|
|
1509
|
-
const workspace = options.workspace ??
|
|
1978
|
+
const root = path7.resolve(options.root ?? process.cwd());
|
|
1979
|
+
const workspace = options.workspace ?? path7.basename(root);
|
|
1510
1980
|
const cliArgs = [...options.cliArgs ?? []];
|
|
1511
1981
|
const move = consumeFlag(cliArgs, "--move");
|
|
1512
1982
|
if (cliArgs.length > 0) {
|
|
1513
1983
|
throw new Error(`Unsupported onboard arguments: ${cliArgs.join(" ")}`);
|
|
1514
1984
|
}
|
|
1515
1985
|
const scaffold = await scaffoldWorkspace(root, workspace);
|
|
1516
|
-
const envRoot =
|
|
1986
|
+
const envRoot = path7.join(root, ".cnos", "workspaces", workspace, "env");
|
|
1517
1987
|
const rootFiles = await listRootEnvFiles(root);
|
|
1518
1988
|
const imported = [];
|
|
1519
1989
|
const skipped = [];
|
|
1520
1990
|
for (const fileName of rootFiles) {
|
|
1521
|
-
const sourcePath =
|
|
1522
|
-
const targetPath =
|
|
1991
|
+
const sourcePath = path7.join(root, fileName);
|
|
1992
|
+
const targetPath = path7.join(envRoot, fileName);
|
|
1523
1993
|
try {
|
|
1524
1994
|
await copyFile(sourcePath, targetPath);
|
|
1525
|
-
imported.push(
|
|
1995
|
+
imported.push(path7.relative(root, targetPath).replace(/\\/g, "/"));
|
|
1526
1996
|
if (move) {
|
|
1527
|
-
await
|
|
1997
|
+
await rm(sourcePath);
|
|
1528
1998
|
}
|
|
1529
1999
|
} catch {
|
|
1530
2000
|
skipped.push(fileName);
|
|
@@ -1547,17 +2017,17 @@ async function runOnboard(options = {}) {
|
|
|
1547
2017
|
}
|
|
1548
2018
|
|
|
1549
2019
|
// src/commands/profile.ts
|
|
1550
|
-
import
|
|
2020
|
+
import path10 from "path";
|
|
1551
2021
|
|
|
1552
2022
|
// src/services/context.ts
|
|
1553
|
-
import { readFile as readFile4, writeFile as
|
|
1554
|
-
import
|
|
1555
|
-
import { parseYaml as
|
|
2023
|
+
import { readFile as readFile4, writeFile as writeFile4 } from "fs/promises";
|
|
2024
|
+
import path8 from "path";
|
|
2025
|
+
import { parseYaml as parseYaml3, stringifyYaml as stringifyYaml2 } from "@kitsy/cnos/internal";
|
|
1556
2026
|
async function loadCliContext(root = process.cwd()) {
|
|
1557
|
-
const filePath =
|
|
2027
|
+
const filePath = path8.join(path8.resolve(root), ".cnos-workspace.yml");
|
|
1558
2028
|
try {
|
|
1559
2029
|
const source = await readFile4(filePath, "utf8");
|
|
1560
|
-
const parsed =
|
|
2030
|
+
const parsed = parseYaml3(source);
|
|
1561
2031
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
1562
2032
|
return {};
|
|
1563
2033
|
}
|
|
@@ -1567,8 +2037,8 @@ async function loadCliContext(root = process.cwd()) {
|
|
|
1567
2037
|
}
|
|
1568
2038
|
}
|
|
1569
2039
|
async function saveCliContext(options = {}) {
|
|
1570
|
-
const root =
|
|
1571
|
-
const filePath =
|
|
2040
|
+
const root = path8.resolve(options.root ?? process.cwd());
|
|
2041
|
+
const filePath = path8.join(root, ".cnos-workspace.yml");
|
|
1572
2042
|
const current = await loadCliContext(root);
|
|
1573
2043
|
const next = {
|
|
1574
2044
|
...current.workspace ? { workspace: current.workspace } : {},
|
|
@@ -1578,7 +2048,7 @@ async function saveCliContext(options = {}) {
|
|
|
1578
2048
|
...options.profile ? { profile: options.profile } : {},
|
|
1579
2049
|
...options.globalRoot ? { globalRoot: options.globalRoot } : {}
|
|
1580
2050
|
};
|
|
1581
|
-
await
|
|
2051
|
+
await writeFile4(filePath, stringifyYaml2(next), "utf8");
|
|
1582
2052
|
return {
|
|
1583
2053
|
filePath,
|
|
1584
2054
|
context: next
|
|
@@ -1586,19 +2056,19 @@ async function saveCliContext(options = {}) {
|
|
|
1586
2056
|
}
|
|
1587
2057
|
|
|
1588
2058
|
// src/services/profiles.ts
|
|
1589
|
-
import { mkdir as
|
|
1590
|
-
import
|
|
1591
|
-
import { parseYaml as
|
|
2059
|
+
import { mkdir as mkdir4, readdir as readdir3, readFile as readFile5, rm as rm2, writeFile as writeFile5 } from "fs/promises";
|
|
2060
|
+
import path9 from "path";
|
|
2061
|
+
import { parseYaml as parseYaml4, stringifyYaml as stringifyYaml3 } from "@kitsy/cnos/internal";
|
|
1592
2062
|
async function createProfileDefinition(root = process.cwd(), profile, inherit) {
|
|
1593
|
-
const filePath =
|
|
1594
|
-
await
|
|
2063
|
+
const filePath = path9.join(path9.resolve(root), ".cnos", "profiles", `${profile}.yml`);
|
|
2064
|
+
await mkdir4(path9.dirname(filePath), { recursive: true });
|
|
1595
2065
|
const document = inherit && inherit !== "base" ? {
|
|
1596
2066
|
name: profile,
|
|
1597
2067
|
extends: [inherit]
|
|
1598
2068
|
} : {
|
|
1599
2069
|
name: profile
|
|
1600
2070
|
};
|
|
1601
|
-
await
|
|
2071
|
+
await writeFile5(filePath, stringifyYaml3(document), "utf8");
|
|
1602
2072
|
return {
|
|
1603
2073
|
filePath,
|
|
1604
2074
|
profile,
|
|
@@ -1606,9 +2076,9 @@ async function createProfileDefinition(root = process.cwd(), profile, inherit) {
|
|
|
1606
2076
|
};
|
|
1607
2077
|
}
|
|
1608
2078
|
async function listProfiles(root = process.cwd()) {
|
|
1609
|
-
const profilesRoot =
|
|
2079
|
+
const profilesRoot = path9.join(path9.resolve(root), ".cnos", "profiles");
|
|
1610
2080
|
try {
|
|
1611
|
-
const entries = await
|
|
2081
|
+
const entries = await readdir3(profilesRoot, { withFileTypes: true });
|
|
1612
2082
|
const discovered = /* @__PURE__ */ new Set(["base"]);
|
|
1613
2083
|
for (const entry of entries) {
|
|
1614
2084
|
if (entry.isFile() && entry.name.endsWith(".yml")) {
|
|
@@ -1621,9 +2091,9 @@ async function listProfiles(root = process.cwd()) {
|
|
|
1621
2091
|
}
|
|
1622
2092
|
}
|
|
1623
2093
|
async function deleteProfileDefinition(root = process.cwd(), profile) {
|
|
1624
|
-
const filePath =
|
|
2094
|
+
const filePath = path9.join(path9.resolve(root), ".cnos", "profiles", `${profile}.yml`);
|
|
1625
2095
|
try {
|
|
1626
|
-
await
|
|
2096
|
+
await rm2(filePath);
|
|
1627
2097
|
return {
|
|
1628
2098
|
filePath,
|
|
1629
2099
|
deleted: true
|
|
@@ -1641,9 +2111,9 @@ async function readProfileDefinition(root = process.cwd(), profile = "base") {
|
|
|
1641
2111
|
name: "base"
|
|
1642
2112
|
};
|
|
1643
2113
|
}
|
|
1644
|
-
const filePath =
|
|
2114
|
+
const filePath = path9.join(path9.resolve(root), ".cnos", "profiles", `${profile}.yml`);
|
|
1645
2115
|
try {
|
|
1646
|
-
return
|
|
2116
|
+
return parseYaml4(await readFile5(filePath, "utf8")) ?? void 0;
|
|
1647
2117
|
} catch {
|
|
1648
2118
|
return void 0;
|
|
1649
2119
|
}
|
|
@@ -1665,7 +2135,7 @@ function normalizeProfileAction(args) {
|
|
|
1665
2135
|
}
|
|
1666
2136
|
async function runProfile(args, options = {}) {
|
|
1667
2137
|
const { action, tail } = normalizeProfileAction(args);
|
|
1668
|
-
const root =
|
|
2138
|
+
const root = path10.resolve(options.root ?? process.cwd());
|
|
1669
2139
|
const cliArgs = [...options.cliArgs ?? []];
|
|
1670
2140
|
if (action === "create") {
|
|
1671
2141
|
const profile = tail[0] ?? "stage";
|
|
@@ -1708,6 +2178,73 @@ async function runProfile(args, options = {}) {
|
|
|
1708
2178
|
return profiles.join("\n");
|
|
1709
2179
|
}
|
|
1710
2180
|
|
|
2181
|
+
// src/commands/promote.ts
|
|
2182
|
+
import { writeFile as writeFile6 } from "fs/promises";
|
|
2183
|
+
import {
|
|
2184
|
+
ensureProjectionAllowed,
|
|
2185
|
+
loadManifest as loadManifest2,
|
|
2186
|
+
stringifyYaml as stringifyYaml4
|
|
2187
|
+
} from "@kitsy/cnos/internal";
|
|
2188
|
+
function normalizeTarget(value) {
|
|
2189
|
+
if (value === "public" || value === "env") {
|
|
2190
|
+
return value;
|
|
2191
|
+
}
|
|
2192
|
+
throw new Error("promote requires --to public|env");
|
|
2193
|
+
}
|
|
2194
|
+
function sortRecord(record) {
|
|
2195
|
+
return Object.fromEntries(Object.entries(record).sort(([left], [right]) => left.localeCompare(right)));
|
|
2196
|
+
}
|
|
2197
|
+
async function runPromote(args = [], options = {}) {
|
|
2198
|
+
const cliArgs = [...options.cliArgs ?? []];
|
|
2199
|
+
const target = normalizeTarget(consumeOption(cliArgs, "--to"));
|
|
2200
|
+
const alias = consumeOption(cliArgs, "--as");
|
|
2201
|
+
const keys = args.filter(Boolean);
|
|
2202
|
+
if (keys.length === 0) {
|
|
2203
|
+
throw new Error("promote requires at least one logical key");
|
|
2204
|
+
}
|
|
2205
|
+
if (target === "env") {
|
|
2206
|
+
if (keys.length !== 1) {
|
|
2207
|
+
throw new Error("promote --to env requires exactly one logical key");
|
|
2208
|
+
}
|
|
2209
|
+
if (!alias) {
|
|
2210
|
+
throw new Error("promote --to env requires --as <ENV_VAR>");
|
|
2211
|
+
}
|
|
2212
|
+
}
|
|
2213
|
+
const loadedManifest = await loadManifest2(options.root ? { root: options.root } : {});
|
|
2214
|
+
for (const key of keys) {
|
|
2215
|
+
ensureProjectionAllowed(loadedManifest.manifest, key, target);
|
|
2216
|
+
}
|
|
2217
|
+
const rawManifest = {
|
|
2218
|
+
...loadedManifest.rawManifest
|
|
2219
|
+
};
|
|
2220
|
+
if (target === "public") {
|
|
2221
|
+
rawManifest.public = {
|
|
2222
|
+
...rawManifest.public ?? {},
|
|
2223
|
+
promote: Array.from(/* @__PURE__ */ new Set([...rawManifest.public?.promote ?? [], ...keys])).sort(
|
|
2224
|
+
(left, right) => left.localeCompare(right)
|
|
2225
|
+
)
|
|
2226
|
+
};
|
|
2227
|
+
} else {
|
|
2228
|
+
rawManifest.envMapping = {
|
|
2229
|
+
...rawManifest.envMapping ?? {},
|
|
2230
|
+
explicit: sortRecord({
|
|
2231
|
+
...rawManifest.envMapping?.explicit ?? {},
|
|
2232
|
+
[alias]: keys[0]
|
|
2233
|
+
})
|
|
2234
|
+
};
|
|
2235
|
+
}
|
|
2236
|
+
await writeFile6(loadedManifest.manifestPath, stringifyYaml4(rawManifest), "utf8");
|
|
2237
|
+
if (options.json) {
|
|
2238
|
+
return printJson({
|
|
2239
|
+
target,
|
|
2240
|
+
keys,
|
|
2241
|
+
...target === "env" ? { envVar: alias } : {},
|
|
2242
|
+
manifestPath: loadedManifest.manifestPath
|
|
2243
|
+
});
|
|
2244
|
+
}
|
|
2245
|
+
return target === "public" ? `promoted ${keys.join(", ")} to public in ${loadedManifest.manifestPath}` : `promoted ${keys[0]} to env as ${alias} in ${loadedManifest.manifestPath}`;
|
|
2246
|
+
}
|
|
2247
|
+
|
|
1711
2248
|
// src/commands/read.ts
|
|
1712
2249
|
async function runRead(key, options = {}) {
|
|
1713
2250
|
const runtime = await createRuntimeService(options);
|
|
@@ -1715,22 +2252,82 @@ async function runRead(key, options = {}) {
|
|
|
1715
2252
|
if (value === void 0) {
|
|
1716
2253
|
throw new Error(`Missing CNOS config key: ${key}`);
|
|
1717
2254
|
}
|
|
2255
|
+
const isSecret = key.startsWith("secret.");
|
|
2256
|
+
const valueForOutput = isSecret ? maskSecretValue(value) : value;
|
|
1718
2257
|
if (options.json) {
|
|
1719
|
-
return printJson({ key, value });
|
|
2258
|
+
return printJson({ key, value: valueForOutput });
|
|
1720
2259
|
}
|
|
1721
|
-
return printValue(
|
|
2260
|
+
return printValue(valueForOutput);
|
|
1722
2261
|
}
|
|
1723
2262
|
|
|
1724
2263
|
// src/commands/run.ts
|
|
1725
2264
|
import { spawn } from "child_process";
|
|
2265
|
+
import {
|
|
2266
|
+
CNOS_GRAPH_ENV_VAR,
|
|
2267
|
+
CNOS_SECRET_PAYLOAD_ENV_VAR,
|
|
2268
|
+
CNOS_SESSION_KEY_ENV_VAR,
|
|
2269
|
+
serializeSecretPayload,
|
|
2270
|
+
serializeRuntimeGraph
|
|
2271
|
+
} from "@kitsy/cnos/internal";
|
|
2272
|
+
function consumeOptions(args, flag) {
|
|
2273
|
+
const values = [];
|
|
2274
|
+
for (let index = 0; index < args.length; ) {
|
|
2275
|
+
const token = args[index];
|
|
2276
|
+
if (token === flag) {
|
|
2277
|
+
const value = args[index + 1];
|
|
2278
|
+
if (!value) {
|
|
2279
|
+
throw new Error(`Missing value for ${flag}`);
|
|
2280
|
+
}
|
|
2281
|
+
values.push(value);
|
|
2282
|
+
args.splice(index, 2);
|
|
2283
|
+
continue;
|
|
2284
|
+
}
|
|
2285
|
+
if (token?.startsWith(`${flag}=`)) {
|
|
2286
|
+
values.push(token.slice(flag.length + 1));
|
|
2287
|
+
args.splice(index, 1);
|
|
2288
|
+
continue;
|
|
2289
|
+
}
|
|
2290
|
+
index += 1;
|
|
2291
|
+
}
|
|
2292
|
+
return values;
|
|
2293
|
+
}
|
|
2294
|
+
function normalizeSetOverrides(values) {
|
|
2295
|
+
return values.map((value) => {
|
|
2296
|
+
if (!value.includes("=")) {
|
|
2297
|
+
throw new Error("--set requires <logical-key>=<value>");
|
|
2298
|
+
}
|
|
2299
|
+
return value.startsWith("--") ? value : `--${value}`;
|
|
2300
|
+
});
|
|
2301
|
+
}
|
|
1726
2302
|
async function runCommand(command, options = {}) {
|
|
1727
2303
|
if (command.length === 0) {
|
|
1728
2304
|
throw new Error("run requires a command after --");
|
|
1729
2305
|
}
|
|
1730
|
-
const
|
|
2306
|
+
const cliArgs = [...options.cliArgs ?? []];
|
|
2307
|
+
const isPublic = consumeFlag(cliArgs, "--public");
|
|
2308
|
+
const isAuthenticated = consumeFlag(cliArgs, "--auth");
|
|
2309
|
+
const framework = consumeOption(cliArgs, "--framework");
|
|
2310
|
+
const prefix = consumeOption(cliArgs, "--prefix");
|
|
2311
|
+
const setOverrides = normalizeSetOverrides(consumeOptions(cliArgs, "--set"));
|
|
2312
|
+
const runtime = await createRuntimeService({
|
|
2313
|
+
...options,
|
|
2314
|
+
cliArgs: [...cliArgs, ...setOverrides]
|
|
2315
|
+
});
|
|
2316
|
+
const authenticatedSecrets = isAuthenticated ? Object.fromEntries(
|
|
2317
|
+
Array.from(runtime.graph.entries.values()).filter((entry) => entry.namespace === "secret").map((entry) => [entry.key, runtime.read(entry.key)])
|
|
2318
|
+
) : void 0;
|
|
2319
|
+
const secretPayload = authenticatedSecrets ? serializeSecretPayload(authenticatedSecrets) : void 0;
|
|
1731
2320
|
const env = {
|
|
1732
2321
|
...process.env,
|
|
1733
|
-
...runtime.
|
|
2322
|
+
...isPublic ? runtime.toPublicEnv({
|
|
2323
|
+
...framework ? { framework } : {},
|
|
2324
|
+
...prefix ? { prefix } : {}
|
|
2325
|
+
}) : runtime.toEnv(),
|
|
2326
|
+
[CNOS_GRAPH_ENV_VAR]: serializeRuntimeGraph(runtime.graph),
|
|
2327
|
+
...secretPayload ? {
|
|
2328
|
+
[CNOS_SECRET_PAYLOAD_ENV_VAR]: secretPayload.payload,
|
|
2329
|
+
[CNOS_SESSION_KEY_ENV_VAR]: secretPayload.sessionKey
|
|
2330
|
+
} : {}
|
|
1734
2331
|
};
|
|
1735
2332
|
return new Promise((resolve, reject) => {
|
|
1736
2333
|
const executable = command[0];
|
|
@@ -1765,12 +2362,254 @@ async function runCommand(command, options = {}) {
|
|
|
1765
2362
|
});
|
|
1766
2363
|
}
|
|
1767
2364
|
|
|
1768
|
-
// src/
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
2365
|
+
// src/services/vaults.ts
|
|
2366
|
+
import { rm as rm3, writeFile as writeFile7 } from "fs/promises";
|
|
2367
|
+
import path11 from "path";
|
|
2368
|
+
import {
|
|
2369
|
+
clearAllVaultSessionKeys,
|
|
2370
|
+
clearVaultSessionKey,
|
|
2371
|
+
createSecretVault,
|
|
2372
|
+
deriveVaultKey,
|
|
2373
|
+
loadManifest as loadManifest3,
|
|
2374
|
+
listSecretVaults,
|
|
2375
|
+
readVaultMetadata,
|
|
2376
|
+
resolveSecretStoreRoot as resolveSecretStoreRoot2,
|
|
2377
|
+
resolveVaultAuth as resolveVaultAuth2,
|
|
2378
|
+
resolveVaultDefinition,
|
|
2379
|
+
stringifyYaml as stringifyYaml5,
|
|
2380
|
+
writeKeychain,
|
|
2381
|
+
writeVaultSessionKey
|
|
2382
|
+
} from "@kitsy/cnos/internal";
|
|
2383
|
+
function sortVaults(vaults) {
|
|
2384
|
+
return Object.fromEntries(Object.entries(vaults).sort(([left], [right]) => left.localeCompare(right)));
|
|
2385
|
+
}
|
|
2386
|
+
function defaultLocalAuthSources(vault) {
|
|
2387
|
+
const token = vault.replace(/[^A-Za-z0-9]+/g, "_").toUpperCase();
|
|
2388
|
+
return [`env:CNOS_SECRET_PASSPHRASE_${token}`, "env:CNOS_SECRET_PASSPHRASE", `keychain:cnos/${vault}`, "prompt"];
|
|
2389
|
+
}
|
|
2390
|
+
async function createVaultDefinition(name, options = {}) {
|
|
2391
|
+
const vault = name.trim() || "default";
|
|
2392
|
+
const provider = options.provider?.trim() || "local";
|
|
2393
|
+
if (provider === "local" && (options.noPassphrase ?? false)) {
|
|
2394
|
+
throw new Error("Local vaults cannot be passwordless.");
|
|
2395
|
+
}
|
|
2396
|
+
const loadedManifest = await loadManifest3(options.root ? { root: options.root } : {});
|
|
2397
|
+
const rawManifest = {
|
|
2398
|
+
...loadedManifest.rawManifest,
|
|
2399
|
+
vaults: {
|
|
2400
|
+
...loadedManifest.rawManifest.vaults ?? {},
|
|
2401
|
+
[vault]: provider === "local" ? {
|
|
2402
|
+
provider: "local",
|
|
2403
|
+
auth: {
|
|
2404
|
+
passphrase: {
|
|
2405
|
+
from: defaultLocalAuthSources(vault)
|
|
2406
|
+
}
|
|
2407
|
+
}
|
|
2408
|
+
} : {
|
|
2409
|
+
provider,
|
|
2410
|
+
auth: {
|
|
2411
|
+
method: "environment"
|
|
2412
|
+
}
|
|
2413
|
+
}
|
|
2414
|
+
}
|
|
2415
|
+
};
|
|
2416
|
+
await writeFile7(loadedManifest.manifestPath, stringifyYaml5(rawManifest), "utf8");
|
|
2417
|
+
const definition = resolveVaultDefinition({ [vault]: rawManifest.vaults[vault] }, vault);
|
|
2418
|
+
return {
|
|
2419
|
+
...definition,
|
|
2420
|
+
authMethod: definition.auth?.method ?? (provider === "local" ? "passphrase" : "environment"),
|
|
2421
|
+
localStore: provider === "local",
|
|
2422
|
+
manifestPath: loadedManifest.manifestPath
|
|
2423
|
+
};
|
|
2424
|
+
}
|
|
2425
|
+
async function listVaultDefinitions(options = {}) {
|
|
2426
|
+
const loadedManifest = await loadManifest3(options.root ? { root: options.root } : {});
|
|
2427
|
+
const localStoreVaults = await listSecretVaults(resolveSecretStoreRoot2(options.processEnv));
|
|
2428
|
+
return Object.keys(loadedManifest.manifest.vaults).sort((left, right) => left.localeCompare(right)).map((name) => {
|
|
2429
|
+
const definition = resolveVaultDefinition(loadedManifest.manifest.vaults, name);
|
|
2430
|
+
return {
|
|
2431
|
+
...definition,
|
|
2432
|
+
authMethod: definition.auth?.method ?? (definition.provider === "local" ? "passphrase" : "environment"),
|
|
2433
|
+
localStore: localStoreVaults.includes(name)
|
|
2434
|
+
};
|
|
2435
|
+
});
|
|
2436
|
+
}
|
|
2437
|
+
async function removeVaultDefinition(name, options = {}) {
|
|
2438
|
+
const vault = name.trim() || "default";
|
|
2439
|
+
const loadedManifest = await loadManifest3(options.root ? { root: options.root } : {});
|
|
2440
|
+
if (!loadedManifest.rawManifest.vaults?.[vault]) {
|
|
2441
|
+
return {
|
|
2442
|
+
name: vault,
|
|
2443
|
+
deleted: false,
|
|
2444
|
+
manifestPath: loadedManifest.manifestPath
|
|
2445
|
+
};
|
|
2446
|
+
}
|
|
2447
|
+
const nextVaults = { ...loadedManifest.rawManifest.vaults ?? {} };
|
|
2448
|
+
delete nextVaults[vault];
|
|
2449
|
+
const rawManifest = {
|
|
2450
|
+
...loadedManifest.rawManifest,
|
|
2451
|
+
...Object.keys(nextVaults).length > 0 ? { vaults: sortVaults(nextVaults) } : {}
|
|
2452
|
+
};
|
|
2453
|
+
if (Object.keys(nextVaults).length === 0) {
|
|
2454
|
+
delete rawManifest.vaults;
|
|
2455
|
+
}
|
|
2456
|
+
await writeFile7(loadedManifest.manifestPath, stringifyYaml5(rawManifest), "utf8");
|
|
2457
|
+
const vaultRoot = path11.join(resolveSecretStoreRoot2(options.processEnv), "vaults", vault);
|
|
2458
|
+
let removedStore;
|
|
2459
|
+
try {
|
|
2460
|
+
await rm3(vaultRoot, { recursive: true, force: true });
|
|
2461
|
+
removedStore = vaultRoot;
|
|
2462
|
+
} catch {
|
|
2463
|
+
removedStore = void 0;
|
|
2464
|
+
}
|
|
2465
|
+
await clearVaultSessionKey(vault, options.processEnv);
|
|
2466
|
+
return {
|
|
2467
|
+
name: vault,
|
|
2468
|
+
deleted: true,
|
|
2469
|
+
manifestPath: loadedManifest.manifestPath,
|
|
2470
|
+
...removedStore ? { removedStore } : {}
|
|
2471
|
+
};
|
|
2472
|
+
}
|
|
2473
|
+
async function listLocalStoreVaults(options = {}) {
|
|
2474
|
+
return listSecretVaults(resolveSecretStoreRoot2(options.processEnv));
|
|
2475
|
+
}
|
|
2476
|
+
async function authenticateVault(name, options = {}) {
|
|
2477
|
+
const vault = name.trim() || "default";
|
|
2478
|
+
const loadedManifest = await loadManifest3(options.root ? { root: options.root } : {});
|
|
2479
|
+
const definition = loadedManifest.manifest.vaults[vault];
|
|
2480
|
+
if (!definition) {
|
|
2481
|
+
throw new Error(`Unknown vault "${vault}"`);
|
|
2482
|
+
}
|
|
2483
|
+
const auth = await resolveVaultAuth2(vault, definition, options.processEnv ?? process.env);
|
|
2484
|
+
const storeRoot = resolveSecretStoreRoot2(options.processEnv);
|
|
2485
|
+
if (definition.provider === "local") {
|
|
2486
|
+
if (!auth.passphrase) {
|
|
2487
|
+
throw new Error(`Vault "${vault}" requires passphrase-based authentication.`);
|
|
2488
|
+
}
|
|
2489
|
+
const existing = await readVaultMetadata(storeRoot, vault);
|
|
2490
|
+
if (!existing) {
|
|
2491
|
+
await createSecretVault(storeRoot, vault, auth.passphrase);
|
|
2492
|
+
}
|
|
2493
|
+
const metadata = await readVaultMetadata(storeRoot, vault);
|
|
2494
|
+
if (!metadata) {
|
|
2495
|
+
throw new Error(`Failed to initialize vault "${vault}"`);
|
|
2496
|
+
}
|
|
2497
|
+
const derivedKey = deriveVaultKey(auth.passphrase, Buffer.from(metadata.salt, "base64"), metadata.iterations);
|
|
2498
|
+
const sessionPath2 = await writeVaultSessionKey(vault, derivedKey, options.processEnv);
|
|
2499
|
+
if (options.storeKeychain) {
|
|
2500
|
+
await writeKeychain(`cnos/${vault}`, derivedKey.toString("hex"));
|
|
2501
|
+
}
|
|
2502
|
+
return {
|
|
2503
|
+
name: vault,
|
|
2504
|
+
method: auth.method,
|
|
2505
|
+
storedInKeychain: options.storeKeychain ?? false,
|
|
2506
|
+
sessionPath: sessionPath2
|
|
2507
|
+
};
|
|
2508
|
+
}
|
|
2509
|
+
const sessionPath = await writeVaultSessionKey(vault, Buffer.from(vault, "utf8"), options.processEnv);
|
|
2510
|
+
return {
|
|
2511
|
+
name: vault,
|
|
2512
|
+
method: auth.method,
|
|
2513
|
+
storedInKeychain: false,
|
|
2514
|
+
sessionPath
|
|
2515
|
+
};
|
|
2516
|
+
}
|
|
2517
|
+
async function logoutVault(name, options = {}) {
|
|
2518
|
+
if (options.all) {
|
|
2519
|
+
await clearAllVaultSessionKeys(options.processEnv);
|
|
2520
|
+
return { scope: "all" };
|
|
2521
|
+
}
|
|
2522
|
+
const vault = name?.trim() || "default";
|
|
2523
|
+
await clearVaultSessionKey(vault, options.processEnv);
|
|
2524
|
+
return { scope: vault };
|
|
2525
|
+
}
|
|
2526
|
+
|
|
2527
|
+
// src/commands/vault.ts
|
|
2528
|
+
function normalizeVaultAction(args) {
|
|
2529
|
+
const [action = "list", ...tail] = args;
|
|
2530
|
+
if (["create", "add", "list", "delete", "remove", "auth", "logout"].includes(action)) {
|
|
2531
|
+
return {
|
|
2532
|
+
action: action === "add" || action === "create" ? "create" : action === "delete" || action === "remove" ? "remove" : action === "auth" ? "auth" : action === "logout" ? "logout" : "list",
|
|
2533
|
+
tail
|
|
2534
|
+
};
|
|
2535
|
+
}
|
|
2536
|
+
return {
|
|
2537
|
+
action: "list",
|
|
2538
|
+
tail: args
|
|
2539
|
+
};
|
|
2540
|
+
}
|
|
2541
|
+
async function runVault(args = [], options = {}) {
|
|
2542
|
+
const { action, tail } = normalizeVaultAction(args);
|
|
2543
|
+
const cliArgs = [...options.cliArgs ?? []];
|
|
2544
|
+
if (consumeOption(cliArgs, "--passphrase")) {
|
|
2545
|
+
throw new Error("The --passphrase option is not supported in CNOS 1.4. Use env, keychain, or prompt-based auth.");
|
|
2546
|
+
}
|
|
2547
|
+
if (action === "create") {
|
|
2548
|
+
const name = tail[0] ?? "default";
|
|
2549
|
+
const provider = consumeOption(cliArgs, "--provider") ?? "local";
|
|
2550
|
+
const noPassphrase = consumeFlag(cliArgs, "--no-passphrase");
|
|
2551
|
+
const result = await createVaultDefinition(name, {
|
|
2552
|
+
...options,
|
|
2553
|
+
cliArgs,
|
|
2554
|
+
provider,
|
|
2555
|
+
...noPassphrase ? { noPassphrase: true } : {}
|
|
2556
|
+
});
|
|
2557
|
+
if (options.json) {
|
|
2558
|
+
return printJson(result);
|
|
2559
|
+
}
|
|
2560
|
+
return `created vault "${result.name}" with provider "${result.provider}" in ${result.manifestPath}`;
|
|
2561
|
+
}
|
|
2562
|
+
if (action === "auth") {
|
|
2563
|
+
const result = await authenticateVault(tail[0] ?? "default", {
|
|
2564
|
+
...options,
|
|
2565
|
+
cliArgs,
|
|
2566
|
+
storeKeychain: consumeFlag(cliArgs, "--store-keychain")
|
|
2567
|
+
});
|
|
2568
|
+
if (options.json) {
|
|
2569
|
+
return printJson(result);
|
|
2570
|
+
}
|
|
2571
|
+
return `authenticated vault "${result.name}" via ${result.method}`;
|
|
2572
|
+
}
|
|
2573
|
+
if (action === "logout") {
|
|
2574
|
+
const result = await logoutVault(tail[0], {
|
|
2575
|
+
...options,
|
|
2576
|
+
cliArgs,
|
|
2577
|
+
all: consumeFlag(cliArgs, "--all")
|
|
2578
|
+
});
|
|
2579
|
+
if (options.json) {
|
|
2580
|
+
return printJson(result);
|
|
2581
|
+
}
|
|
2582
|
+
return result.scope === "all" ? "logged out all vault sessions" : `logged out vault "${result.scope}"`;
|
|
2583
|
+
}
|
|
2584
|
+
if (action === "remove") {
|
|
2585
|
+
const name = tail[0] ?? "default";
|
|
2586
|
+
const result = await removeVaultDefinition(name, options);
|
|
2587
|
+
if (options.json) {
|
|
2588
|
+
return printJson(result);
|
|
2589
|
+
}
|
|
2590
|
+
return result.deleted ? `removed vault "${result.name}"` : `vault "${result.name}" was not found`;
|
|
2591
|
+
}
|
|
2592
|
+
const [manifestVaults, localStoreVaults] = await Promise.all([
|
|
2593
|
+
listVaultDefinitions(options),
|
|
2594
|
+
listLocalStoreVaults(options)
|
|
2595
|
+
]);
|
|
2596
|
+
if (options.json) {
|
|
2597
|
+
return printJson(
|
|
2598
|
+
manifestVaults.map((vault) => ({
|
|
2599
|
+
...vault,
|
|
2600
|
+
localStore: localStoreVaults.includes(vault.name)
|
|
2601
|
+
}))
|
|
2602
|
+
);
|
|
2603
|
+
}
|
|
2604
|
+
if (manifestVaults.length === 0) {
|
|
2605
|
+
return "";
|
|
2606
|
+
}
|
|
2607
|
+
return manifestVaults.map(
|
|
2608
|
+
(vault) => `${vault.name} provider=${vault.provider} auth=${vault.authMethod}${localStoreVaults.includes(vault.name) ? " local-store=true" : ""}`
|
|
2609
|
+
).join("\n");
|
|
1773
2610
|
}
|
|
2611
|
+
|
|
2612
|
+
// src/commands/secret.ts
|
|
1774
2613
|
function normalizeSecretCommand(args) {
|
|
1775
2614
|
const [actionOrPath, next, ...tail] = args;
|
|
1776
2615
|
if (!actionOrPath) {
|
|
@@ -1796,64 +2635,78 @@ function normalizeSecretCommand(args) {
|
|
|
1796
2635
|
tail: args
|
|
1797
2636
|
};
|
|
1798
2637
|
}
|
|
2638
|
+
async function readStdinValue() {
|
|
2639
|
+
const chunks = [];
|
|
2640
|
+
for await (const chunk of process.stdin) {
|
|
2641
|
+
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
2642
|
+
}
|
|
2643
|
+
return Buffer.concat(chunks).toString("utf8").trimEnd();
|
|
2644
|
+
}
|
|
1799
2645
|
async function runSecret(argsOrPath, options = {}) {
|
|
1800
2646
|
const args = Array.isArray(argsOrPath) ? argsOrPath : [argsOrPath];
|
|
1801
2647
|
const { action, tail } = normalizeSecretCommand(args);
|
|
1802
2648
|
const cliArgs = [...options.cliArgs ?? []];
|
|
2649
|
+
if (consumeOption(cliArgs, "--passphrase")) {
|
|
2650
|
+
throw new Error("The --passphrase option is not supported in CNOS 1.4. Use env, keychain, or prompt-based auth.");
|
|
2651
|
+
}
|
|
1803
2652
|
if (action === "create-vault") {
|
|
1804
|
-
|
|
1805
|
-
const passphrase = consumeOption(cliArgs, "--passphrase");
|
|
1806
|
-
const result = await createVault(vault, {
|
|
1807
|
-
...options,
|
|
1808
|
-
cliArgs,
|
|
1809
|
-
...passphrase ? { passphrase } : {}
|
|
1810
|
-
});
|
|
1811
|
-
if (options.json) {
|
|
1812
|
-
return printJson(result);
|
|
1813
|
-
}
|
|
1814
|
-
return `created secret vault "${result.vault}" in ${result.filePath}`;
|
|
2653
|
+
return runVault(["create", tail[0] ?? "default"], options);
|
|
1815
2654
|
}
|
|
1816
2655
|
if (action === "list") {
|
|
2656
|
+
const runtime2 = await createRuntimeService(options);
|
|
1817
2657
|
const prefix = consumeOption(cliArgs, "--prefix");
|
|
1818
|
-
const
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
2658
|
+
const vault = consumeOption(cliArgs, "--vault");
|
|
2659
|
+
const provider = consumeOption(cliArgs, "--provider");
|
|
2660
|
+
const entries = Array.from(runtime2.graph.entries.values()).filter((entry2) => entry2.namespace === "secret").filter((entry2) => !prefix || entry2.key.startsWith(`secret.${prefix}`) || entry2.key.startsWith(prefix)).filter((entry2) => {
|
|
2661
|
+
const secretRef2 = entry2.winner.metadata?.secretRef;
|
|
2662
|
+
if (vault && secretRef2?.vault !== vault) {
|
|
2663
|
+
return false;
|
|
2664
|
+
}
|
|
2665
|
+
if (provider && secretRef2?.provider !== provider) {
|
|
2666
|
+
return false;
|
|
2667
|
+
}
|
|
2668
|
+
return true;
|
|
2669
|
+
}).map((entry2) => {
|
|
2670
|
+
const secretRef2 = entry2.winner.metadata?.secretRef;
|
|
2671
|
+
return {
|
|
2672
|
+
key: entry2.key,
|
|
2673
|
+
vault: secretRef2?.vault ?? "default",
|
|
2674
|
+
provider: secretRef2?.provider ?? "local"
|
|
2675
|
+
};
|
|
2676
|
+
}).sort((left, right) => left.key.localeCompare(right.key));
|
|
1823
2677
|
if (options.json) {
|
|
1824
2678
|
return printJson(entries);
|
|
1825
2679
|
}
|
|
1826
|
-
return entries.map((
|
|
2680
|
+
return entries.map((entry2) => `${entry2.key} (vault: ${entry2.vault}, provider: ${entry2.provider})`).join("\n");
|
|
1827
2681
|
}
|
|
1828
2682
|
if (action === "set") {
|
|
1829
|
-
const
|
|
1830
|
-
const rawValue = tail[1] ?? "";
|
|
2683
|
+
const secretPath2 = tail[0];
|
|
1831
2684
|
const local = consumeFlag(cliArgs, "--local");
|
|
1832
2685
|
const remote = consumeFlag(cliArgs, "--remote");
|
|
1833
2686
|
const ref = consumeFlag(cliArgs, "--ref");
|
|
2687
|
+
const stdin = consumeFlag(cliArgs, "--stdin");
|
|
1834
2688
|
const target = consumeOption(cliArgs, "--target") ?? "local";
|
|
1835
2689
|
const provider = consumeOption(cliArgs, "--provider");
|
|
1836
|
-
const passphrase = consumeOption(cliArgs, "--passphrase");
|
|
1837
2690
|
const vault = consumeOption(cliArgs, "--vault") ?? "default";
|
|
1838
|
-
const mode = local ? "local" : remote ? "remote" : ref ? "ref" :
|
|
1839
|
-
const
|
|
2691
|
+
const mode = local ? "local" : remote ? "remote" : ref ? "ref" : void 0;
|
|
2692
|
+
const rawValue = stdin ? await readStdinValue() : tail[1] ?? "";
|
|
2693
|
+
const result = await setSecret(secretPath2 ?? "app.token", rawValue, {
|
|
1840
2694
|
...options,
|
|
1841
2695
|
cliArgs,
|
|
1842
2696
|
target,
|
|
1843
|
-
mode,
|
|
1844
2697
|
vault,
|
|
1845
|
-
...
|
|
1846
|
-
...
|
|
2698
|
+
...mode ? { mode } : {},
|
|
2699
|
+
...provider ? { provider } : {}
|
|
1847
2700
|
});
|
|
1848
2701
|
if (options.json) {
|
|
1849
2702
|
return printJson(result);
|
|
1850
2703
|
}
|
|
1851
|
-
return result.provider === "local" ? `set secret.${
|
|
2704
|
+
return result.provider === "local" ? `set secret.${secretPath2} in vault "${result.vault ?? "default"}" with ref "${result.ref}" and repo pointer ${result.filePath}` : `set secret.${secretPath2} via ${result.provider} in ${result.filePath}`;
|
|
1852
2705
|
}
|
|
1853
2706
|
if (action === "delete") {
|
|
1854
|
-
const
|
|
2707
|
+
const secretPath2 = tail[0];
|
|
1855
2708
|
const target = consumeOption(cliArgs, "--target") ?? "local";
|
|
1856
|
-
const result = await deleteSecret(
|
|
2709
|
+
const result = await deleteSecret(secretPath2 ?? "app.token", {
|
|
1857
2710
|
...options,
|
|
1858
2711
|
cliArgs,
|
|
1859
2712
|
target
|
|
@@ -1861,32 +2714,36 @@ async function runSecret(argsOrPath, options = {}) {
|
|
|
1861
2714
|
if (options.json) {
|
|
1862
2715
|
return printJson(result);
|
|
1863
2716
|
}
|
|
1864
|
-
return result.deleted ? `deleted secret.${
|
|
2717
|
+
return result.deleted ? `deleted secret.${secretPath2} from ${result.filePath}` : `no secret.${secretPath2} found in ${result.filePath}`;
|
|
1865
2718
|
}
|
|
1866
2719
|
const runtime = await createRuntimeService(options);
|
|
1867
|
-
const
|
|
2720
|
+
const secretPath = tail[0] ?? "app.token";
|
|
2721
|
+
const expectedVault = consumeOption(cliArgs, "--vault");
|
|
2722
|
+
const reveal = consumeFlag(cliArgs, "--reveal");
|
|
2723
|
+
const entry = runtime.graph.entries.get(`secret.${secretPath}`);
|
|
2724
|
+
const secretRef = entry?.winner.metadata?.secretRef;
|
|
2725
|
+
const value = runtime.secret(secretPath);
|
|
1868
2726
|
if (value === void 0) {
|
|
1869
|
-
throw new Error(`Missing CNOS secret path: ${
|
|
2727
|
+
throw new Error(`Missing CNOS secret path: ${secretPath}`);
|
|
1870
2728
|
}
|
|
1871
|
-
if (
|
|
1872
|
-
|
|
1873
|
-
throw new Error(
|
|
1874
|
-
`Secret ${tail[0] ?? "app.token"} is stored in vault "${vault}" as ref "${value.ref}". Provide the correct vault passphrase to resolve it.`
|
|
1875
|
-
);
|
|
2729
|
+
if (expectedVault && secretRef?.vault && secretRef.vault !== expectedVault) {
|
|
2730
|
+
throw new Error(`Secret ${secretPath} belongs to vault "${secretRef.vault}", not "${expectedVault}"`);
|
|
1876
2731
|
}
|
|
2732
|
+
const valueForOutput = reveal ? value : maskSecretValue(value);
|
|
1877
2733
|
if (options.json) {
|
|
1878
2734
|
return printJson({
|
|
1879
|
-
key: `secret.${
|
|
1880
|
-
value
|
|
2735
|
+
key: `secret.${secretPath}`,
|
|
2736
|
+
value: valueForOutput,
|
|
2737
|
+
vault: secretRef?.vault ?? "default"
|
|
1881
2738
|
});
|
|
1882
2739
|
}
|
|
1883
|
-
return printValue(
|
|
2740
|
+
return printValue(valueForOutput);
|
|
1884
2741
|
}
|
|
1885
2742
|
|
|
1886
2743
|
// src/commands/use.ts
|
|
1887
|
-
import
|
|
2744
|
+
import path12 from "path";
|
|
1888
2745
|
async function runUse(args = [], options = {}) {
|
|
1889
|
-
const root =
|
|
2746
|
+
const root = path12.resolve(options.root ?? process.cwd());
|
|
1890
2747
|
const action = args[0] ?? "show";
|
|
1891
2748
|
const hasUpdates = Boolean(options.workspace || options.profile || options.globalRoot);
|
|
1892
2749
|
if (action === "show" || !hasUpdates) {
|
|
@@ -1923,7 +2780,7 @@ async function runValidate(options = {}) {
|
|
|
1923
2780
|
// package.json
|
|
1924
2781
|
var package_default = {
|
|
1925
2782
|
name: "@kitsy/cnos-cli",
|
|
1926
|
-
version: "1.
|
|
2783
|
+
version: "1.3.0",
|
|
1927
2784
|
description: "CLI entry point and developer tooling for CNOS.",
|
|
1928
2785
|
type: "module",
|
|
1929
2786
|
main: "./dist/index.js",
|
|
@@ -2060,6 +2917,180 @@ async function runValue(argsOrPath, options = {}) {
|
|
|
2060
2917
|
return printValue(value);
|
|
2061
2918
|
}
|
|
2062
2919
|
|
|
2920
|
+
// src/commands/watch.ts
|
|
2921
|
+
import { watch } from "fs";
|
|
2922
|
+
import { spawn as spawn2 } from "child_process";
|
|
2923
|
+
import {
|
|
2924
|
+
CNOS_GRAPH_ENV_VAR as CNOS_GRAPH_ENV_VAR2,
|
|
2925
|
+
CNOS_SECRET_PAYLOAD_ENV_VAR as CNOS_SECRET_PAYLOAD_ENV_VAR2,
|
|
2926
|
+
CNOS_SESSION_KEY_ENV_VAR as CNOS_SESSION_KEY_ENV_VAR2,
|
|
2927
|
+
diffGraphs,
|
|
2928
|
+
serializeRuntimeGraph as serializeRuntimeGraph2,
|
|
2929
|
+
serializeSecretPayload as serializeSecretPayload2,
|
|
2930
|
+
watchFiles
|
|
2931
|
+
} from "@kitsy/cnos/internal";
|
|
2932
|
+
async function buildRunEnvironment(options) {
|
|
2933
|
+
const cliArgs = [...options.cliArgs ?? []];
|
|
2934
|
+
const isPublic = consumeFlag(cliArgs, "--public");
|
|
2935
|
+
const isAuthenticated = consumeFlag(cliArgs, "--auth");
|
|
2936
|
+
const framework = consumeOption(cliArgs, "--framework");
|
|
2937
|
+
const prefix = consumeOption(cliArgs, "--prefix");
|
|
2938
|
+
const runtime = await createRuntimeService({
|
|
2939
|
+
...options,
|
|
2940
|
+
cliArgs
|
|
2941
|
+
});
|
|
2942
|
+
const authenticatedSecrets = isAuthenticated ? Object.fromEntries(
|
|
2943
|
+
Array.from(runtime.graph.entries.values()).filter((entry) => entry.namespace === "secret").map((entry) => [entry.key, runtime.read(entry.key)])
|
|
2944
|
+
) : void 0;
|
|
2945
|
+
const secretPayload = authenticatedSecrets ? serializeSecretPayload2(authenticatedSecrets) : void 0;
|
|
2946
|
+
return {
|
|
2947
|
+
runtime,
|
|
2948
|
+
env: {
|
|
2949
|
+
...process.env,
|
|
2950
|
+
...isPublic ? runtime.toPublicEnv({
|
|
2951
|
+
...framework ? { framework } : {},
|
|
2952
|
+
...prefix ? { prefix } : {}
|
|
2953
|
+
}) : runtime.toEnv(),
|
|
2954
|
+
[CNOS_GRAPH_ENV_VAR2]: serializeRuntimeGraph2(runtime.graph),
|
|
2955
|
+
...secretPayload ? {
|
|
2956
|
+
[CNOS_SECRET_PAYLOAD_ENV_VAR2]: secretPayload.payload,
|
|
2957
|
+
[CNOS_SESSION_KEY_ENV_VAR2]: secretPayload.sessionKey
|
|
2958
|
+
} : {}
|
|
2959
|
+
}
|
|
2960
|
+
};
|
|
2961
|
+
}
|
|
2962
|
+
function spawnWatchedChild(command, cwd, env) {
|
|
2963
|
+
const executable = command[0];
|
|
2964
|
+
if (!executable) {
|
|
2965
|
+
throw new Error("watch requires a command after -- unless --signal is used");
|
|
2966
|
+
}
|
|
2967
|
+
return spawn2(executable, command.slice(1), {
|
|
2968
|
+
cwd,
|
|
2969
|
+
env,
|
|
2970
|
+
stdio: "inherit",
|
|
2971
|
+
shell: false
|
|
2972
|
+
});
|
|
2973
|
+
}
|
|
2974
|
+
async function startWatchLoop(options) {
|
|
2975
|
+
const cliArgs = [...options.cliArgs ?? []];
|
|
2976
|
+
const isSignal = consumeFlag(cliArgs, "--signal");
|
|
2977
|
+
const debounceMs = Number(consumeOption(cliArgs, "--debounce") ?? "300");
|
|
2978
|
+
const command = options.command ?? [];
|
|
2979
|
+
const root = options.root ?? process.cwd();
|
|
2980
|
+
let current = await buildRunEnvironment({
|
|
2981
|
+
...options,
|
|
2982
|
+
cliArgs
|
|
2983
|
+
});
|
|
2984
|
+
let child = !isSignal ? spawnWatchedChild(command, root, current.env) : void 0;
|
|
2985
|
+
const watcherMap = /* @__PURE__ */ new Map();
|
|
2986
|
+
let timer;
|
|
2987
|
+
let closed = false;
|
|
2988
|
+
const attachWatcher = (targetPath, recursive = false) => {
|
|
2989
|
+
if (watcherMap.has(targetPath)) {
|
|
2990
|
+
return;
|
|
2991
|
+
}
|
|
2992
|
+
try {
|
|
2993
|
+
const watcher = watch(
|
|
2994
|
+
targetPath,
|
|
2995
|
+
recursive ? {
|
|
2996
|
+
recursive: true
|
|
2997
|
+
} : void 0,
|
|
2998
|
+
() => {
|
|
2999
|
+
if (timer) {
|
|
3000
|
+
clearTimeout(timer);
|
|
3001
|
+
}
|
|
3002
|
+
timer = setTimeout(() => {
|
|
3003
|
+
void handleChange();
|
|
3004
|
+
}, debounceMs);
|
|
3005
|
+
}
|
|
3006
|
+
);
|
|
3007
|
+
watcherMap.set(targetPath, watcher);
|
|
3008
|
+
} catch {
|
|
3009
|
+
if (recursive) {
|
|
3010
|
+
attachWatcher(targetPath, false);
|
|
3011
|
+
}
|
|
3012
|
+
}
|
|
3013
|
+
};
|
|
3014
|
+
const refreshWatchers = async () => {
|
|
3015
|
+
const nextTargets = await watchFiles(current.runtime, options.root);
|
|
3016
|
+
attachWatcher(nextTargets.manifestPath, false);
|
|
3017
|
+
for (const workspaceRoot of nextTargets.roots) {
|
|
3018
|
+
attachWatcher(workspaceRoot, true);
|
|
3019
|
+
}
|
|
3020
|
+
for (const filePath of nextTargets.files) {
|
|
3021
|
+
attachWatcher(filePath, false);
|
|
3022
|
+
}
|
|
3023
|
+
};
|
|
3024
|
+
const handleChange = async () => {
|
|
3025
|
+
if (closed) {
|
|
3026
|
+
return;
|
|
3027
|
+
}
|
|
3028
|
+
const next = await buildRunEnvironment({
|
|
3029
|
+
...options,
|
|
3030
|
+
cliArgs
|
|
3031
|
+
});
|
|
3032
|
+
const changedKeys = diffGraphs(current.runtime.graph, next.runtime.graph);
|
|
3033
|
+
current = next;
|
|
3034
|
+
await refreshWatchers();
|
|
3035
|
+
if (changedKeys.length === 0) {
|
|
3036
|
+
return;
|
|
3037
|
+
}
|
|
3038
|
+
if (isSignal) {
|
|
3039
|
+
await options.onSignal?.({ changedKeys });
|
|
3040
|
+
process.stdout.write(`${printJson({ changedKeys })}
|
|
3041
|
+
`);
|
|
3042
|
+
return;
|
|
3043
|
+
}
|
|
3044
|
+
if (child && !child.killed) {
|
|
3045
|
+
await new Promise((resolve) => {
|
|
3046
|
+
child?.once("close", () => resolve());
|
|
3047
|
+
child?.kill();
|
|
3048
|
+
});
|
|
3049
|
+
}
|
|
3050
|
+
child = spawnWatchedChild(command, root, current.env);
|
|
3051
|
+
await options.onRestart?.({ changedKeys });
|
|
3052
|
+
};
|
|
3053
|
+
await refreshWatchers();
|
|
3054
|
+
return {
|
|
3055
|
+
async close() {
|
|
3056
|
+
closed = true;
|
|
3057
|
+
if (timer) {
|
|
3058
|
+
clearTimeout(timer);
|
|
3059
|
+
}
|
|
3060
|
+
for (const watcher of watcherMap.values()) {
|
|
3061
|
+
watcher.close();
|
|
3062
|
+
}
|
|
3063
|
+
watcherMap.clear();
|
|
3064
|
+
if (child && !child.killed) {
|
|
3065
|
+
await new Promise((resolve) => {
|
|
3066
|
+
child?.once("close", () => resolve());
|
|
3067
|
+
child?.kill();
|
|
3068
|
+
});
|
|
3069
|
+
}
|
|
3070
|
+
}
|
|
3071
|
+
};
|
|
3072
|
+
}
|
|
3073
|
+
async function runWatch(command, options = {}) {
|
|
3074
|
+
const cliArgs = [...options.cliArgs ?? []];
|
|
3075
|
+
const isSignal = consumeFlag(cliArgs, "--signal");
|
|
3076
|
+
const debounce = consumeOption(cliArgs, "--debounce");
|
|
3077
|
+
const handle = await startWatchLoop({
|
|
3078
|
+
...options,
|
|
3079
|
+
cliArgs: [
|
|
3080
|
+
...cliArgs,
|
|
3081
|
+
...isSignal ? ["--signal"] : [],
|
|
3082
|
+
...debounce ? ["--debounce", debounce] : []
|
|
3083
|
+
],
|
|
3084
|
+
command
|
|
3085
|
+
});
|
|
3086
|
+
const closeWatcher = () => {
|
|
3087
|
+
void handle.close();
|
|
3088
|
+
};
|
|
3089
|
+
process.once("SIGINT", closeWatcher);
|
|
3090
|
+
process.once("SIGTERM", closeWatcher);
|
|
3091
|
+
return isSignal ? "watching config changes in signal mode" : "watching config changes in restart mode";
|
|
3092
|
+
}
|
|
3093
|
+
|
|
2063
3094
|
// src/index.ts
|
|
2064
3095
|
function resolveHelpTopic(command, args) {
|
|
2065
3096
|
if (command === "help" || command === "help-ai") {
|
|
@@ -2068,6 +3099,9 @@ function resolveHelpTopic(command, args) {
|
|
|
2068
3099
|
if (command === "export" && args[0] === "env") {
|
|
2069
3100
|
return normalizeHelpTopic([command, args[0]]);
|
|
2070
3101
|
}
|
|
3102
|
+
if (command === "vault" && args[0] && ["create", "add", "list", "delete", "remove"].includes(args[0])) {
|
|
3103
|
+
return normalizeHelpTopic([command, args[0] === "delete" ? "remove" : args[0] === "add" ? "create" : args[0]]);
|
|
3104
|
+
}
|
|
2071
3105
|
if (command === "secret" && args[0] && ["set", "create", "add", "list", "delete", "remove"].includes(args[0])) {
|
|
2072
3106
|
if ((args[0] === "create" || args[0] === "add") && args[1] === "vault") {
|
|
2073
3107
|
return normalizeHelpTopic(["secret", "create", "vault"]);
|
|
@@ -2137,6 +3171,14 @@ async function main(argv) {
|
|
|
2137
3171
|
return;
|
|
2138
3172
|
case "onboard":
|
|
2139
3173
|
process.stdout.write(`${await runOnboard(runtimeOptions)}
|
|
3174
|
+
`);
|
|
3175
|
+
return;
|
|
3176
|
+
case "migrate":
|
|
3177
|
+
process.stdout.write(`${await runMigrate(runtimeOptions)}
|
|
3178
|
+
`);
|
|
3179
|
+
return;
|
|
3180
|
+
case "codegen":
|
|
3181
|
+
process.stdout.write(`${await runCodegen(runtimeOptions)}
|
|
2140
3182
|
`);
|
|
2141
3183
|
return;
|
|
2142
3184
|
case "read":
|
|
@@ -2149,6 +3191,10 @@ async function main(argv) {
|
|
|
2149
3191
|
return;
|
|
2150
3192
|
case "secret":
|
|
2151
3193
|
process.stdout.write(`${await runSecret(args.length > 0 ? args : ["app.token"], runtimeOptions)}
|
|
3194
|
+
`);
|
|
3195
|
+
return;
|
|
3196
|
+
case "vault":
|
|
3197
|
+
process.stdout.write(`${await runVault(args, runtimeOptions)}
|
|
2152
3198
|
`);
|
|
2153
3199
|
return;
|
|
2154
3200
|
case "use":
|
|
@@ -2157,6 +3203,10 @@ async function main(argv) {
|
|
|
2157
3203
|
return;
|
|
2158
3204
|
case "profile":
|
|
2159
3205
|
process.stdout.write(`${await runProfile(args, runtimeOptions)}
|
|
3206
|
+
`);
|
|
3207
|
+
return;
|
|
3208
|
+
case "promote":
|
|
3209
|
+
process.stdout.write(`${await runPromote(args, runtimeOptions)}
|
|
2160
3210
|
`);
|
|
2161
3211
|
return;
|
|
2162
3212
|
case "list":
|
|
@@ -2193,8 +3243,16 @@ async function main(argv) {
|
|
|
2193
3243
|
process.exitCode = result.exitCode;
|
|
2194
3244
|
return;
|
|
2195
3245
|
}
|
|
3246
|
+
case "watch":
|
|
3247
|
+
process.stdout.write(`${await runWatch(passthrough.length > 0 ? passthrough : args, runtimeOptions)}
|
|
3248
|
+
`);
|
|
3249
|
+
return;
|
|
2196
3250
|
case "diff":
|
|
2197
3251
|
process.stdout.write(`${await runDiff(args[0] ?? "local", args[1] ?? "stage", runtimeOptions)}
|
|
3252
|
+
`);
|
|
3253
|
+
return;
|
|
3254
|
+
case "drift":
|
|
3255
|
+
process.stdout.write(`${await runDrift(runtimeOptions)}
|
|
2198
3256
|
`);
|
|
2199
3257
|
return;
|
|
2200
3258
|
case "doctor":
|