@kitsy/cnos-cli 1.1.1 → 1.2.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 +646 -163
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -16,9 +16,11 @@ var COMMAND_OPTION_KEYS_WITH_VALUE = /* @__PURE__ */ new Set([
|
|
|
16
16
|
"--provider",
|
|
17
17
|
"--passphrase",
|
|
18
18
|
"--vault",
|
|
19
|
-
"--inherit"
|
|
19
|
+
"--inherit",
|
|
20
|
+
"--as",
|
|
21
|
+
"--set"
|
|
20
22
|
]);
|
|
21
|
-
var COMMAND_FLAG_KEYS = /* @__PURE__ */ new Set(["--flatten", "--public", "--local", "--remote", "--ref"]);
|
|
23
|
+
var COMMAND_FLAG_KEYS = /* @__PURE__ */ new Set(["--flatten", "--public", "--local", "--remote", "--ref", "--no-passphrase"]);
|
|
22
24
|
function normalizeCommand(argv) {
|
|
23
25
|
const [command = "doctor", ...rest] = argv;
|
|
24
26
|
const resource = rest[0];
|
|
@@ -26,12 +28,21 @@ function normalizeCommand(argv) {
|
|
|
26
28
|
if ((command === "create" || command === "add") && resource === "profile") {
|
|
27
29
|
return ["profile", "create", ...remaining];
|
|
28
30
|
}
|
|
31
|
+
if ((command === "create" || command === "add") && resource === "vault") {
|
|
32
|
+
return ["vault", "create", ...remaining];
|
|
33
|
+
}
|
|
29
34
|
if ((command === "delete" || command === "remove") && resource === "profile") {
|
|
30
35
|
return ["profile", "delete", ...remaining];
|
|
31
36
|
}
|
|
37
|
+
if ((command === "delete" || command === "remove") && resource === "vault") {
|
|
38
|
+
return ["vault", "remove", ...remaining];
|
|
39
|
+
}
|
|
32
40
|
if (command === "list" && resource === "profile") {
|
|
33
41
|
return ["profile", "list", ...remaining];
|
|
34
42
|
}
|
|
43
|
+
if (command === "list" && resource === "vault") {
|
|
44
|
+
return ["vault", "list", ...remaining];
|
|
45
|
+
}
|
|
35
46
|
if ((command === "create" || command === "add") && resource === "secret") {
|
|
36
47
|
return ["secret", "set", ...remaining];
|
|
37
48
|
}
|
|
@@ -186,16 +197,16 @@ function printJson(value) {
|
|
|
186
197
|
}
|
|
187
198
|
|
|
188
199
|
// src/services/writes.ts
|
|
189
|
-
import { mkdir, readFile, rm, writeFile } from "fs/promises";
|
|
190
|
-
import
|
|
200
|
+
import { mkdir, readFile as readFile2, rm as rm2, writeFile as writeFile2 } from "fs/promises";
|
|
201
|
+
import path2 from "path";
|
|
191
202
|
import {
|
|
192
|
-
|
|
203
|
+
getVaultPassphraseEnvVar,
|
|
193
204
|
parseYaml,
|
|
194
205
|
resolveConfigDocumentPath,
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
206
|
+
stringifyYaml as stringifyYaml2,
|
|
207
|
+
writeLocalSecret,
|
|
208
|
+
resolveConfiguredVaultPassphrase as resolveConfiguredVaultPassphrase2,
|
|
209
|
+
resolveSecretStoreRoot as resolveSecretStoreRoot2
|
|
199
210
|
} from "@kitsy/cnos/internal";
|
|
200
211
|
|
|
201
212
|
// src/services/runtime.ts
|
|
@@ -228,6 +239,125 @@ async function createRuntimeService(options = {}) {
|
|
|
228
239
|
});
|
|
229
240
|
}
|
|
230
241
|
|
|
242
|
+
// src/services/vaults.ts
|
|
243
|
+
import { readFile, rm, writeFile } from "fs/promises";
|
|
244
|
+
import path from "path";
|
|
245
|
+
import {
|
|
246
|
+
createSecretVault,
|
|
247
|
+
loadManifest,
|
|
248
|
+
listSecretVaults,
|
|
249
|
+
resolveConfiguredVaultPassphrase,
|
|
250
|
+
resolveSecretStoreRoot,
|
|
251
|
+
resolveSecretVaultFile,
|
|
252
|
+
resolveVaultDefinition,
|
|
253
|
+
stringifyYaml
|
|
254
|
+
} from "@kitsy/cnos/internal";
|
|
255
|
+
function sortVaults(vaults) {
|
|
256
|
+
return Object.fromEntries(Object.entries(vaults).sort(([left], [right]) => left.localeCompare(right)));
|
|
257
|
+
}
|
|
258
|
+
async function createVaultDefinition(name, options = {}) {
|
|
259
|
+
const vault = name.trim() || "default";
|
|
260
|
+
const provider = options.provider?.trim() || "local";
|
|
261
|
+
const noPassphrase = options.noPassphrase ?? false;
|
|
262
|
+
if (provider === "local" && noPassphrase) {
|
|
263
|
+
throw new Error("Local vaults require a passphrase");
|
|
264
|
+
}
|
|
265
|
+
const loadedManifest = await loadManifest(options.root ? { root: options.root } : {});
|
|
266
|
+
const processEnv = options.processEnv ?? process.env;
|
|
267
|
+
const passphraseEnvVar = "CNOS_SECRET_PASSPHRASE";
|
|
268
|
+
const rawManifest = {
|
|
269
|
+
...loadedManifest.rawManifest,
|
|
270
|
+
vaults: {
|
|
271
|
+
...loadedManifest.rawManifest.vaults ?? {},
|
|
272
|
+
[vault]: provider === "local" ? {
|
|
273
|
+
provider: "local",
|
|
274
|
+
passphrase: `env:${passphraseEnvVar}`
|
|
275
|
+
} : {
|
|
276
|
+
provider
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
};
|
|
280
|
+
let storePath;
|
|
281
|
+
if (provider === "local") {
|
|
282
|
+
const passphrase = options.passphrase ?? resolveConfiguredVaultPassphrase(
|
|
283
|
+
{
|
|
284
|
+
provider: "local",
|
|
285
|
+
passphrase: `env:${passphraseEnvVar}`
|
|
286
|
+
},
|
|
287
|
+
vault,
|
|
288
|
+
processEnv
|
|
289
|
+
);
|
|
290
|
+
if (!passphrase) {
|
|
291
|
+
throw new Error(`Vault "${vault}" requires --passphrase or ${passphraseEnvVar}`);
|
|
292
|
+
}
|
|
293
|
+
storePath = await createSecretVault(resolveSecretStoreRoot(processEnv), vault, passphrase);
|
|
294
|
+
}
|
|
295
|
+
await writeFile(loadedManifest.manifestPath, stringifyYaml(rawManifest), "utf8");
|
|
296
|
+
return {
|
|
297
|
+
...resolveVaultDefinition(
|
|
298
|
+
{
|
|
299
|
+
[vault]: rawManifest.vaults[vault]
|
|
300
|
+
},
|
|
301
|
+
vault
|
|
302
|
+
),
|
|
303
|
+
passphrasePolicy: provider === "local" ? "required" : "none",
|
|
304
|
+
manifestPath: loadedManifest.manifestPath,
|
|
305
|
+
...storePath ? { storePath } : {}
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
async function listVaultDefinitions(options = {}) {
|
|
309
|
+
const loadedManifest = await loadManifest(options.root ? { root: options.root } : {});
|
|
310
|
+
return Object.keys(loadedManifest.manifest.vaults).sort((left, right) => left.localeCompare(right)).map((name) => {
|
|
311
|
+
const definition = resolveVaultDefinition(loadedManifest.manifest.vaults, name);
|
|
312
|
+
return {
|
|
313
|
+
...definition,
|
|
314
|
+
passphrasePolicy: definition.requiresPassphrase ? "required" : "none"
|
|
315
|
+
};
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
async function removeVaultDefinition(name, options = {}) {
|
|
319
|
+
const vault = name.trim() || "default";
|
|
320
|
+
const loadedManifest = await loadManifest(options.root ? { root: options.root } : {});
|
|
321
|
+
if (!loadedManifest.rawManifest.vaults?.[vault]) {
|
|
322
|
+
return {
|
|
323
|
+
name: vault,
|
|
324
|
+
deleted: false,
|
|
325
|
+
manifestPath: loadedManifest.manifestPath
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
const nextVaults = { ...loadedManifest.rawManifest.vaults ?? {} };
|
|
329
|
+
delete nextVaults[vault];
|
|
330
|
+
const rawManifest = {
|
|
331
|
+
...loadedManifest.rawManifest,
|
|
332
|
+
...Object.keys(nextVaults).length > 0 ? { vaults: sortVaults(nextVaults) } : {}
|
|
333
|
+
};
|
|
334
|
+
if (Object.keys(nextVaults).length === 0) {
|
|
335
|
+
delete rawManifest.vaults;
|
|
336
|
+
}
|
|
337
|
+
await writeFile(loadedManifest.manifestPath, stringifyYaml(rawManifest), "utf8");
|
|
338
|
+
const storeRoot = resolveSecretStoreRoot(options.processEnv);
|
|
339
|
+
const vaultFile = resolveSecretVaultFile(storeRoot, vault);
|
|
340
|
+
const vaultStoreRoot = path.join(storeRoot, "vaults", vault);
|
|
341
|
+
let removedStore;
|
|
342
|
+
try {
|
|
343
|
+
await readFile(vaultFile, "utf8");
|
|
344
|
+
await rm(vaultFile, { force: true });
|
|
345
|
+
await rm(vaultStoreRoot, { recursive: true, force: true });
|
|
346
|
+
removedStore = vaultStoreRoot;
|
|
347
|
+
} catch {
|
|
348
|
+
removedStore = void 0;
|
|
349
|
+
}
|
|
350
|
+
return {
|
|
351
|
+
name: vault,
|
|
352
|
+
deleted: true,
|
|
353
|
+
manifestPath: loadedManifest.manifestPath,
|
|
354
|
+
...removedStore ? { removedStore } : {}
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
async function listLocalStoreVaults(options = {}) {
|
|
358
|
+
return listSecretVaults(resolveSecretStoreRoot(options.processEnv));
|
|
359
|
+
}
|
|
360
|
+
|
|
231
361
|
// src/services/writes.ts
|
|
232
362
|
function setNestedValue(target, pathSegments, value) {
|
|
233
363
|
const [head, ...tail] = pathSegments;
|
|
@@ -275,7 +405,7 @@ function isSecretReference(value) {
|
|
|
275
405
|
}
|
|
276
406
|
async function readYamlDocument(filePath) {
|
|
277
407
|
try {
|
|
278
|
-
return parseYaml(await
|
|
408
|
+
return parseYaml(await readFile2(filePath, "utf8")) ?? {};
|
|
279
409
|
} catch {
|
|
280
410
|
return {};
|
|
281
411
|
}
|
|
@@ -314,46 +444,45 @@ async function defineValue(namespace, configPath, rawValue, options = {}) {
|
|
|
314
444
|
const document = await readYamlDocument(filePath);
|
|
315
445
|
const parsedValue = parseScalarValue(rawValue);
|
|
316
446
|
setNestedValue(document, configPath.split("."), parsedValue);
|
|
317
|
-
await mkdir(
|
|
318
|
-
await
|
|
447
|
+
await mkdir(path2.dirname(filePath), { recursive: true });
|
|
448
|
+
await writeFile2(filePath, stringifyYaml2(document), "utf8");
|
|
319
449
|
return {
|
|
320
450
|
filePath,
|
|
321
451
|
value: parsedValue
|
|
322
452
|
};
|
|
323
453
|
}
|
|
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
454
|
async function setSecret(configPath, rawValue, options = {}) {
|
|
341
455
|
const runtime = await createRuntimeService(options);
|
|
342
456
|
const workspaceRoot = getSelectedWorkspaceRoot(options, runtime);
|
|
343
457
|
const profile = options.profile ?? runtime.graph.profile;
|
|
344
458
|
const filePath = resolveConfigDocumentPath(workspaceRoot, "secret", configPath, profile);
|
|
345
459
|
const document = await readYamlDocument(filePath);
|
|
346
|
-
const mode = options.mode ?? "local";
|
|
347
460
|
const vault = options.vault?.trim() || "default";
|
|
461
|
+
const vaultDefinition = runtime.manifest.vaults[vault];
|
|
462
|
+
const inferredProvider = vaultDefinition?.provider ?? options.provider;
|
|
463
|
+
const mode = options.mode ?? (inferredProvider === "local" ? "local" : inferredProvider === "github-secrets" ? "ref" : "local");
|
|
348
464
|
let reference;
|
|
349
465
|
let storePath;
|
|
350
466
|
if (mode === "local") {
|
|
351
|
-
const
|
|
467
|
+
const provider = inferredProvider ?? "local";
|
|
468
|
+
if (provider !== "local") {
|
|
469
|
+
throw new Error(`Vault "${vault}" uses provider "${provider}" and cannot store local secret material`);
|
|
470
|
+
}
|
|
471
|
+
const passphrase = resolveConfiguredVaultPassphrase2(
|
|
472
|
+
vaultDefinition ? { provider: "local", ...vaultDefinition.passphrase ? { passphrase: vaultDefinition.passphrase } : {} } : { provider: "local" },
|
|
473
|
+
vault,
|
|
474
|
+
{
|
|
475
|
+
...options.processEnv ?? process.env,
|
|
476
|
+
...options.passphrase ? {
|
|
477
|
+
[getVaultPassphraseEnvVar(vault)]: options.passphrase
|
|
478
|
+
} : {}
|
|
479
|
+
}
|
|
480
|
+
) ?? options.passphrase;
|
|
352
481
|
if (!passphrase) {
|
|
353
|
-
throw new Error(`Vault "${vault}" requires --passphrase or
|
|
482
|
+
throw new Error(`Vault "${vault}" requires --passphrase or its configured passphrase env var`);
|
|
354
483
|
}
|
|
355
484
|
const ref = configPath;
|
|
356
|
-
storePath = await writeLocalSecret(
|
|
485
|
+
storePath = await writeLocalSecret(resolveSecretStoreRoot2(options.processEnv), ref, rawValue, passphrase, vault);
|
|
357
486
|
reference = {
|
|
358
487
|
provider: "local",
|
|
359
488
|
ref,
|
|
@@ -361,13 +490,16 @@ async function setSecret(configPath, rawValue, options = {}) {
|
|
|
361
490
|
};
|
|
362
491
|
} else {
|
|
363
492
|
reference = {
|
|
364
|
-
provider:
|
|
365
|
-
ref: rawValue
|
|
493
|
+
provider: inferredProvider ?? (mode === "ref" ? "ref" : "remote"),
|
|
494
|
+
ref: rawValue || configPath.replace(/[^A-Za-z0-9]+/g, "_").toUpperCase(),
|
|
495
|
+
...vaultDefinition || vault !== "default" ? {
|
|
496
|
+
vault
|
|
497
|
+
} : {}
|
|
366
498
|
};
|
|
367
499
|
}
|
|
368
500
|
setNestedValue(document, configPath.split("."), reference);
|
|
369
|
-
await mkdir(
|
|
370
|
-
await
|
|
501
|
+
await mkdir(path2.dirname(filePath), { recursive: true });
|
|
502
|
+
await writeFile2(filePath, stringifyYaml2(document), "utf8");
|
|
371
503
|
return {
|
|
372
504
|
filePath,
|
|
373
505
|
provider: reference.provider,
|
|
@@ -390,18 +522,18 @@ async function deleteSecret(configPath, options = {}) {
|
|
|
390
522
|
deleted: false
|
|
391
523
|
};
|
|
392
524
|
}
|
|
393
|
-
await
|
|
525
|
+
await writeFile2(filePath, stringifyYaml2(document), "utf8");
|
|
394
526
|
let removedStore;
|
|
395
527
|
const secretRef = metadata?.secretRef;
|
|
396
528
|
if (isSecretReference(secretRef) && secretRef.provider === "local") {
|
|
397
|
-
const storePath =
|
|
398
|
-
|
|
529
|
+
const storePath = path2.join(
|
|
530
|
+
resolveSecretStoreRoot2(options.processEnv),
|
|
399
531
|
"vaults",
|
|
400
532
|
secretRef.vault ?? "default",
|
|
401
533
|
"store",
|
|
402
534
|
...secretRef.ref.split("/")
|
|
403
535
|
).concat(".json");
|
|
404
|
-
await
|
|
536
|
+
await rm2(storePath, { force: true });
|
|
405
537
|
removedStore = storePath;
|
|
406
538
|
}
|
|
407
539
|
return {
|
|
@@ -426,7 +558,7 @@ async function deleteValue(namespace, configPath, options = {}) {
|
|
|
426
558
|
deleted: false
|
|
427
559
|
};
|
|
428
560
|
}
|
|
429
|
-
await
|
|
561
|
+
await writeFile2(filePath, stringifyYaml2(document), "utf8");
|
|
430
562
|
return {
|
|
431
563
|
filePath,
|
|
432
564
|
deleted: true
|
|
@@ -502,8 +634,8 @@ async function runDiff(leftProfile, rightProfile, options = {}) {
|
|
|
502
634
|
}
|
|
503
635
|
|
|
504
636
|
// src/services/doctor.ts
|
|
505
|
-
import { readFile as
|
|
506
|
-
import
|
|
637
|
+
import { readFile as readFile3 } from "fs/promises";
|
|
638
|
+
import path3 from "path";
|
|
507
639
|
|
|
508
640
|
// src/services/validation.ts
|
|
509
641
|
import { validateRuntime } from "@kitsy/cnos/internal";
|
|
@@ -518,7 +650,7 @@ async function createValidationSummary(options = {}) {
|
|
|
518
650
|
|
|
519
651
|
// src/services/doctor.ts
|
|
520
652
|
async function checkGitignore(root) {
|
|
521
|
-
const gitignorePath =
|
|
653
|
+
const gitignorePath = path3.join(root, ".gitignore");
|
|
522
654
|
const expected = [
|
|
523
655
|
".cnos/env/.env",
|
|
524
656
|
".cnos/env/.env.*",
|
|
@@ -530,7 +662,7 @@ async function checkGitignore(root) {
|
|
|
530
662
|
"!.cnos/workspaces/*/env/.env.*.example"
|
|
531
663
|
];
|
|
532
664
|
try {
|
|
533
|
-
const content = await
|
|
665
|
+
const content = await readFile3(gitignorePath, "utf8");
|
|
534
666
|
const missing = expected.filter((entry) => !content.includes(entry));
|
|
535
667
|
return {
|
|
536
668
|
name: "gitignore",
|
|
@@ -549,7 +681,7 @@ function issueSummary(issues) {
|
|
|
549
681
|
return issues.length === 0 ? "no issues" : issues.map((issue) => issue.message).join("; ");
|
|
550
682
|
}
|
|
551
683
|
async function evaluateDoctor(options = {}) {
|
|
552
|
-
const root =
|
|
684
|
+
const root = path3.resolve(options.root ?? process.cwd());
|
|
553
685
|
const { runtime, summary } = await createValidationSummary(options);
|
|
554
686
|
const localRoot = runtime.graph.workspace.workspaceRoots.find((entry) => entry.scope === "local");
|
|
555
687
|
const globalRoot = runtime.graph.workspace.workspaceRoots.find((entry) => entry.scope === "global");
|
|
@@ -620,11 +752,17 @@ async function runDump(options = {}) {
|
|
|
620
752
|
}
|
|
621
753
|
|
|
622
754
|
// src/commands/exportEnv.ts
|
|
755
|
+
import { mkdir as mkdir2, writeFile as writeFile3 } from "fs/promises";
|
|
756
|
+
import path4 from "path";
|
|
757
|
+
function formatEnvOutput(env) {
|
|
758
|
+
return Object.entries(env).sort(([left], [right]) => left.localeCompare(right)).map(([key, value]) => `${key}=${value}`).join("\n");
|
|
759
|
+
}
|
|
623
760
|
async function runExportEnv(options = {}) {
|
|
624
761
|
const cliArgs = [...options.cliArgs ?? []];
|
|
625
762
|
const isPublic = consumeFlag(cliArgs, "--public");
|
|
626
763
|
const framework = consumeOption(cliArgs, "--framework");
|
|
627
764
|
const prefix = consumeOption(cliArgs, "--prefix");
|
|
765
|
+
const to = consumeOption(cliArgs, "--to");
|
|
628
766
|
const runtime = await createRuntimeService({
|
|
629
767
|
...options,
|
|
630
768
|
cliArgs
|
|
@@ -632,16 +770,26 @@ async function runExportEnv(options = {}) {
|
|
|
632
770
|
const env = isPublic ? runtime.toPublicEnv({
|
|
633
771
|
...framework ? { framework } : {},
|
|
634
772
|
...prefix ? { prefix } : {}
|
|
635
|
-
}) :
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
773
|
+
}) : runtime.toEnv();
|
|
774
|
+
const output = formatEnvOutput(env);
|
|
775
|
+
if (to) {
|
|
776
|
+
const targetPath = path4.resolve(options.root ?? process.cwd(), to);
|
|
777
|
+
await mkdir2(path4.dirname(targetPath), { recursive: true });
|
|
778
|
+
await writeFile3(targetPath, output, "utf8");
|
|
779
|
+
if (options.json) {
|
|
780
|
+
return printJson({
|
|
781
|
+
to: targetPath,
|
|
782
|
+
count: Object.keys(env).length,
|
|
783
|
+
public: isPublic,
|
|
784
|
+
...framework ? { framework } : {}
|
|
785
|
+
});
|
|
786
|
+
}
|
|
787
|
+
return `Wrote ${Object.keys(env).length} env vars to ${targetPath}`;
|
|
788
|
+
}
|
|
641
789
|
if (options.json) {
|
|
642
790
|
return printJson(env);
|
|
643
791
|
}
|
|
644
|
-
return
|
|
792
|
+
return output;
|
|
645
793
|
}
|
|
646
794
|
|
|
647
795
|
// src/commands/export.ts
|
|
@@ -798,15 +946,70 @@ var COMMANDS = [
|
|
|
798
946
|
{
|
|
799
947
|
flag: "--passphrase <value>",
|
|
800
948
|
description: "Passphrase used to encrypt local secret material when --local is selected."
|
|
949
|
+
},
|
|
950
|
+
{
|
|
951
|
+
flag: "--vault <name>",
|
|
952
|
+
description: "Use a manifest-defined vault. Provider behavior is inferred from the vault definition."
|
|
801
953
|
}
|
|
802
954
|
],
|
|
803
955
|
examples: [
|
|
804
956
|
"cnos secret app.token",
|
|
805
|
-
"cnos
|
|
806
|
-
"cnos
|
|
807
|
-
"cnos
|
|
957
|
+
"cnos vault create local-dev --passphrase dev-pass",
|
|
958
|
+
"cnos secret set app.token super-secret --vault local-dev",
|
|
959
|
+
"cnos vault create github-ci --provider github-secrets --no-passphrase",
|
|
960
|
+
"cnos secret set app.token APP_TOKEN --vault github-ci"
|
|
808
961
|
]
|
|
809
962
|
},
|
|
963
|
+
{
|
|
964
|
+
id: "vault",
|
|
965
|
+
summary: "Manage manifest-defined secret vaults.",
|
|
966
|
+
usage: "cnos vault [create <name> | list | remove <name>] [options] [global-options]",
|
|
967
|
+
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.",
|
|
968
|
+
options: [
|
|
969
|
+
{
|
|
970
|
+
flag: "--provider <local|github-secrets>",
|
|
971
|
+
description: "Vault provider. Defaults to local."
|
|
972
|
+
},
|
|
973
|
+
{
|
|
974
|
+
flag: "--passphrase <value>",
|
|
975
|
+
description: "Required for local vault creation unless already available in the configured passphrase env var."
|
|
976
|
+
},
|
|
977
|
+
{
|
|
978
|
+
flag: "--no-passphrase",
|
|
979
|
+
description: "Allowed for passwordless providers such as github-secrets."
|
|
980
|
+
}
|
|
981
|
+
],
|
|
982
|
+
examples: [
|
|
983
|
+
"cnos vault create local-dev --passphrase dev-pass",
|
|
984
|
+
"cnos vault create github-ci --provider github-secrets --no-passphrase",
|
|
985
|
+
"cnos vault list",
|
|
986
|
+
"cnos vault remove local-dev"
|
|
987
|
+
]
|
|
988
|
+
},
|
|
989
|
+
{
|
|
990
|
+
id: "vault create",
|
|
991
|
+
summary: "Create a manifest-defined vault.",
|
|
992
|
+
usage: "cnos vault create <name> [--provider <local|github-secrets>] [--passphrase <value>] [--no-passphrase] [global-options]",
|
|
993
|
+
description: "Creates a vault definition in .cnos/cnos.yml and, for local vaults, initializes the encrypted store under ~/.cnos/secrets.",
|
|
994
|
+
examples: [
|
|
995
|
+
"cnos vault create local-dev --passphrase dev-pass",
|
|
996
|
+
"cnos vault create github-ci --provider github-secrets --no-passphrase"
|
|
997
|
+
]
|
|
998
|
+
},
|
|
999
|
+
{
|
|
1000
|
+
id: "vault list",
|
|
1001
|
+
summary: "List manifest-defined vaults.",
|
|
1002
|
+
usage: "cnos vault list [global-options]",
|
|
1003
|
+
description: "Lists vault definitions together with provider and passphrase policy.",
|
|
1004
|
+
examples: ["cnos vault list"]
|
|
1005
|
+
},
|
|
1006
|
+
{
|
|
1007
|
+
id: "vault remove",
|
|
1008
|
+
summary: "Remove a vault definition.",
|
|
1009
|
+
usage: "cnos vault remove <name> [global-options]",
|
|
1010
|
+
description: "Removes the vault from .cnos/cnos.yml and deletes local vault store metadata when present.",
|
|
1011
|
+
examples: ["cnos vault remove local-dev"]
|
|
1012
|
+
},
|
|
810
1013
|
{
|
|
811
1014
|
id: "define",
|
|
812
1015
|
summary: "Write a value or secret into the selected workspace.",
|
|
@@ -850,7 +1053,7 @@ var COMMANDS = [
|
|
|
850
1053
|
{
|
|
851
1054
|
id: "list",
|
|
852
1055
|
summary: "List resolved config entries.",
|
|
853
|
-
usage: "cnos list [value|secret|meta|env|public|all] [--prefix <path>] [global-options]",
|
|
1056
|
+
usage: "cnos list [value|secret|meta|env|public|all] [--prefix <path>] [--framework <name>] [global-options]",
|
|
854
1057
|
description: "Lists stored config or derived projections across one namespace or the full effective graph, with optional prefix filtering.",
|
|
855
1058
|
options: [
|
|
856
1059
|
{
|
|
@@ -860,9 +1063,13 @@ var COMMANDS = [
|
|
|
860
1063
|
{
|
|
861
1064
|
flag: "--prefix <path>",
|
|
862
1065
|
description: "Filter list output to entries whose logical keys begin with this prefix."
|
|
1066
|
+
},
|
|
1067
|
+
{
|
|
1068
|
+
flag: "--framework <name>",
|
|
1069
|
+
description: "When listing public output, apply framework-specific prefixes such as vite or next."
|
|
863
1070
|
}
|
|
864
1071
|
],
|
|
865
|
-
examples: ["cnos list", "cnos list value --prefix app.", "cnos list env", "cnos list --
|
|
1072
|
+
examples: ["cnos list", "cnos list value --prefix app.", "cnos list env", "cnos list public --framework vite"]
|
|
866
1073
|
},
|
|
867
1074
|
{
|
|
868
1075
|
id: "profile",
|
|
@@ -909,29 +1116,51 @@ var COMMANDS = [
|
|
|
909
1116
|
description: "Deletes .cnos/profiles/<name>.yml.",
|
|
910
1117
|
examples: ["cnos profile delete stage"]
|
|
911
1118
|
},
|
|
1119
|
+
{
|
|
1120
|
+
id: "promote",
|
|
1121
|
+
summary: "Promote shareable config into public or env projection surfaces.",
|
|
1122
|
+
usage: "cnos promote <key...> --to <public|env> [--as <ENV_VAR>] [global-options]",
|
|
1123
|
+
description: "Adds keys to public.promote or envMapping.explicit in .cnos/cnos.yml. Sensitive or non-shareable namespaces are rejected.",
|
|
1124
|
+
options: [
|
|
1125
|
+
{
|
|
1126
|
+
flag: "--to <public|env>",
|
|
1127
|
+
description: "Choose whether the keys are promoted to the public surface or env export surface."
|
|
1128
|
+
},
|
|
1129
|
+
{
|
|
1130
|
+
flag: "--as <ENV_VAR>",
|
|
1131
|
+
description: "Required for --to env. Sets the exported env var name for the promoted key."
|
|
1132
|
+
}
|
|
1133
|
+
],
|
|
1134
|
+
examples: [
|
|
1135
|
+
"cnos promote value.flag.auth.upi_enabled --to public",
|
|
1136
|
+
"cnos promote value.server.port --to env --as PORT"
|
|
1137
|
+
]
|
|
1138
|
+
},
|
|
912
1139
|
{
|
|
913
1140
|
id: "secret set",
|
|
914
1141
|
summary: "Write a secret securely.",
|
|
915
1142
|
usage: "cnos secret set <path> <value> [--local|--remote|--ref] [--vault <name>] [--provider <name>] [--passphrase <value>] [global-options]",
|
|
916
|
-
description: "Writes a secret reference into the repo
|
|
1143
|
+
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
1144
|
examples: [
|
|
918
|
-
"cnos
|
|
919
|
-
"cnos secret set app.token super-secret --
|
|
1145
|
+
"cnos vault create db --passphrase dev-pass",
|
|
1146
|
+
"cnos secret set app.token super-secret --vault db",
|
|
1147
|
+
"cnos vault create github-ci --provider github-secrets --no-passphrase",
|
|
1148
|
+
"cnos secret set app.token APP_TOKEN --vault github-ci"
|
|
920
1149
|
]
|
|
921
1150
|
},
|
|
922
1151
|
{
|
|
923
1152
|
id: "secret create vault",
|
|
924
1153
|
summary: "Create a local secret vault.",
|
|
925
1154
|
usage: "cnos secret create vault <name> --passphrase <value> [global-options]",
|
|
926
|
-
description: "
|
|
1155
|
+
description: "Alias for cnos vault create <name>.",
|
|
927
1156
|
examples: ["cnos secret create vault db --passphrase dev-pass"]
|
|
928
1157
|
},
|
|
929
1158
|
{
|
|
930
1159
|
id: "secret list",
|
|
931
1160
|
summary: "List resolved secrets.",
|
|
932
|
-
usage: "cnos secret list [global-options]",
|
|
933
|
-
description: "Lists
|
|
934
|
-
examples: ["cnos secret list --workspace api"]
|
|
1161
|
+
usage: "cnos secret list [--vault <name>] [--provider <name>] [global-options]",
|
|
1162
|
+
description: "Lists stored secret entries for the selected workspace and profile, optionally filtered by vault or provider.",
|
|
1163
|
+
examples: ["cnos secret list --workspace api", "cnos secret list --vault github-ci"]
|
|
935
1164
|
},
|
|
936
1165
|
{
|
|
937
1166
|
id: "secret delete",
|
|
@@ -982,7 +1211,7 @@ var COMMANDS = [
|
|
|
982
1211
|
{
|
|
983
1212
|
id: "export env",
|
|
984
1213
|
summary: "Render environment variables for the selected workspace.",
|
|
985
|
-
usage: "cnos export env [--public] [--framework <name>] [--prefix <prefix>] [global-options]",
|
|
1214
|
+
usage: "cnos export env [--public] [--framework <name>] [--prefix <prefix>] [--to <path>] [global-options]",
|
|
986
1215
|
description: "Exports the effective environment as KEY=VALUE lines, or only promoted public values when --public is set.",
|
|
987
1216
|
options: [
|
|
988
1217
|
{
|
|
@@ -996,9 +1225,17 @@ var COMMANDS = [
|
|
|
996
1225
|
{
|
|
997
1226
|
flag: "--prefix <prefix>",
|
|
998
1227
|
description: "Override the generated public env prefix."
|
|
1228
|
+
},
|
|
1229
|
+
{
|
|
1230
|
+
flag: "--to <path>",
|
|
1231
|
+
description: "Write the rendered KEY=VALUE output to a file instead of stdout."
|
|
999
1232
|
}
|
|
1000
1233
|
],
|
|
1001
|
-
examples: [
|
|
1234
|
+
examples: [
|
|
1235
|
+
"cnos export env",
|
|
1236
|
+
"cnos export env --to .env.local",
|
|
1237
|
+
"cnos export env --public --framework vite --to .env.local --workspace api"
|
|
1238
|
+
]
|
|
1002
1239
|
},
|
|
1003
1240
|
{
|
|
1004
1241
|
id: "dump",
|
|
@@ -1020,9 +1257,32 @@ var COMMANDS = [
|
|
|
1020
1257
|
{
|
|
1021
1258
|
id: "run",
|
|
1022
1259
|
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
|
-
|
|
1260
|
+
usage: "cnos run [--public] [--framework <name>] [--set <logical-key=value>] [global-options] -- <command...>",
|
|
1261
|
+
description: "Resolves the active workspace and profile, injects runtime env variables, bootstraps __CNOS_GRAPH__ for singleton runtime reads, and executes the command after --.",
|
|
1262
|
+
options: [
|
|
1263
|
+
{
|
|
1264
|
+
flag: "--set <logical-key=value>",
|
|
1265
|
+
description: "Apply inline logical-key overrides for this run without touching repo config files."
|
|
1266
|
+
},
|
|
1267
|
+
{
|
|
1268
|
+
flag: "--public",
|
|
1269
|
+
description: "Inject only promoted public env variables into the child process."
|
|
1270
|
+
},
|
|
1271
|
+
{
|
|
1272
|
+
flag: "--framework <name>",
|
|
1273
|
+
description: "When used with --public, apply framework-specific prefixes such as vite or next."
|
|
1274
|
+
},
|
|
1275
|
+
{
|
|
1276
|
+
flag: "--prefix <prefix>",
|
|
1277
|
+
description: "Override the generated public env prefix for --public runs."
|
|
1278
|
+
}
|
|
1279
|
+
],
|
|
1280
|
+
examples: [
|
|
1281
|
+
"cnos run -- node server.js",
|
|
1282
|
+
"cnos run --profile stage -- node server.js",
|
|
1283
|
+
"cnos run --set value.server.port=9999 -- node server.js",
|
|
1284
|
+
"cnos run --public --framework vite -- pnpm build"
|
|
1285
|
+
]
|
|
1026
1286
|
},
|
|
1027
1287
|
{
|
|
1028
1288
|
id: "diff",
|
|
@@ -1095,17 +1355,25 @@ var INTEGRATIONS = [
|
|
|
1095
1355
|
id: "vite",
|
|
1096
1356
|
packageName: "@kitsy/cnos-vite",
|
|
1097
1357
|
entrypoint: "@kitsy/cnos-vite",
|
|
1098
|
-
summary: "Inject CNOS public env into Vite
|
|
1358
|
+
summary: "Inject CNOS public env into Vite and embed browser-readable CNOS public data.",
|
|
1099
1359
|
usage: 'import { createCnosVitePlugin } from "@kitsy/cnos-vite"',
|
|
1100
|
-
examples: [
|
|
1360
|
+
examples: [
|
|
1361
|
+
"cnos export env --public --framework vite",
|
|
1362
|
+
"vite.config.ts -> plugins: [createCnosVitePlugin()]",
|
|
1363
|
+
'browser code -> import cnos from "@kitsy/cnos/browser"'
|
|
1364
|
+
]
|
|
1101
1365
|
},
|
|
1102
1366
|
{
|
|
1103
1367
|
id: "next",
|
|
1104
1368
|
packageName: "@kitsy/cnos-next",
|
|
1105
1369
|
entrypoint: "@kitsy/cnos-next",
|
|
1106
|
-
summary: "Merge CNOS public env into
|
|
1370
|
+
summary: "Merge CNOS public env into Next and embed browser-readable CNOS public data.",
|
|
1107
1371
|
usage: 'import { withCnosNext } from "@kitsy/cnos-next"',
|
|
1108
|
-
examples: [
|
|
1372
|
+
examples: [
|
|
1373
|
+
"cnos export env --public --framework next",
|
|
1374
|
+
"next.config.mjs -> export default withCnosNext({})",
|
|
1375
|
+
'browser code -> import cnos from "@kitsy/cnos/browser"'
|
|
1376
|
+
]
|
|
1109
1377
|
}
|
|
1110
1378
|
];
|
|
1111
1379
|
var HELP_DOCUMENT = {
|
|
@@ -1232,11 +1500,11 @@ function runHelpAi(topic, cliArgs = []) {
|
|
|
1232
1500
|
}
|
|
1233
1501
|
|
|
1234
1502
|
// src/commands/init.ts
|
|
1235
|
-
import
|
|
1503
|
+
import path6 from "path";
|
|
1236
1504
|
|
|
1237
1505
|
// src/services/scaffold.ts
|
|
1238
|
-
import { mkdir as
|
|
1239
|
-
import
|
|
1506
|
+
import { mkdir as mkdir3, readFile as readFile4, writeFile as writeFile4 } from "fs/promises";
|
|
1507
|
+
import path5 from "path";
|
|
1240
1508
|
function scaffoldManifest(projectName, workspace) {
|
|
1241
1509
|
const lines = [
|
|
1242
1510
|
"version: 1",
|
|
@@ -1267,15 +1535,15 @@ function scaffoldManifest(projectName, workspace) {
|
|
|
1267
1535
|
}
|
|
1268
1536
|
async function ensureFile(filePath, content) {
|
|
1269
1537
|
try {
|
|
1270
|
-
await
|
|
1538
|
+
await readFile4(filePath, "utf8");
|
|
1271
1539
|
return false;
|
|
1272
1540
|
} catch {
|
|
1273
|
-
await
|
|
1541
|
+
await writeFile4(filePath, content, "utf8");
|
|
1274
1542
|
return true;
|
|
1275
1543
|
}
|
|
1276
1544
|
}
|
|
1277
1545
|
async function ensureGitignore(root) {
|
|
1278
|
-
const gitignorePath =
|
|
1546
|
+
const gitignorePath = path5.join(root, ".gitignore");
|
|
1279
1547
|
const requiredEntries = [
|
|
1280
1548
|
".cnos/env/.env",
|
|
1281
1549
|
".cnos/env/.env.*",
|
|
@@ -1288,7 +1556,7 @@ async function ensureGitignore(root) {
|
|
|
1288
1556
|
];
|
|
1289
1557
|
let current = "";
|
|
1290
1558
|
try {
|
|
1291
|
-
current = await
|
|
1559
|
+
current = await readFile4(gitignorePath, "utf8");
|
|
1292
1560
|
} catch {
|
|
1293
1561
|
current = "";
|
|
1294
1562
|
}
|
|
@@ -1298,18 +1566,18 @@ async function ensureGitignore(root) {
|
|
|
1298
1566
|
}
|
|
1299
1567
|
const prefix = current.trim().length > 0 ? `${current.trimEnd()}
|
|
1300
1568
|
` : "";
|
|
1301
|
-
await
|
|
1569
|
+
await writeFile4(gitignorePath, `${prefix}${missingEntries.join("\n")}
|
|
1302
1570
|
`, "utf8");
|
|
1303
1571
|
return true;
|
|
1304
1572
|
}
|
|
1305
1573
|
async function scaffoldWorkspace(root, workspace) {
|
|
1306
|
-
const cnosRoot =
|
|
1307
|
-
const workspaceRoot = workspace ?
|
|
1574
|
+
const cnosRoot = path5.join(root, ".cnos");
|
|
1575
|
+
const workspaceRoot = workspace ? path5.join(cnosRoot, "workspaces", workspace) : cnosRoot;
|
|
1308
1576
|
const createdPaths = [];
|
|
1309
|
-
await
|
|
1310
|
-
await
|
|
1311
|
-
await
|
|
1312
|
-
await
|
|
1577
|
+
await mkdir3(path5.join(workspaceRoot, "profiles"), { recursive: true });
|
|
1578
|
+
await mkdir3(path5.join(workspaceRoot, "values"), { recursive: true });
|
|
1579
|
+
await mkdir3(path5.join(workspaceRoot, "secrets"), { recursive: true });
|
|
1580
|
+
await mkdir3(path5.join(workspaceRoot, "env"), { recursive: true });
|
|
1313
1581
|
const relativePaths = workspace ? [
|
|
1314
1582
|
["workspaces", workspace, "profiles", ".gitkeep"],
|
|
1315
1583
|
["workspaces", workspace, "values", ".gitkeep"],
|
|
@@ -1322,15 +1590,15 @@ async function scaffoldWorkspace(root, workspace) {
|
|
|
1322
1590
|
["env", ".gitkeep"]
|
|
1323
1591
|
];
|
|
1324
1592
|
for (const relativePath of relativePaths) {
|
|
1325
|
-
const filePath =
|
|
1593
|
+
const filePath = path5.join(cnosRoot, ...relativePath);
|
|
1326
1594
|
if (await ensureFile(filePath, "")) {
|
|
1327
|
-
createdPaths.push(
|
|
1595
|
+
createdPaths.push(path5.relative(root, filePath).replace(/\\/g, "/"));
|
|
1328
1596
|
}
|
|
1329
1597
|
}
|
|
1330
|
-
if (await ensureFile(
|
|
1598
|
+
if (await ensureFile(path5.join(cnosRoot, "cnos.yml"), scaffoldManifest(path5.basename(root), workspace))) {
|
|
1331
1599
|
createdPaths.push(".cnos/cnos.yml");
|
|
1332
1600
|
}
|
|
1333
|
-
if (workspace && await ensureFile(
|
|
1601
|
+
if (workspace && await ensureFile(path5.join(root, ".cnos-workspace.yml"), `workspace: ${workspace}
|
|
1334
1602
|
globalRoot: ~/.cnos
|
|
1335
1603
|
`)) {
|
|
1336
1604
|
createdPaths.push(".cnos-workspace.yml");
|
|
@@ -1347,7 +1615,7 @@ globalRoot: ~/.cnos
|
|
|
1347
1615
|
|
|
1348
1616
|
// src/commands/init.ts
|
|
1349
1617
|
async function runInit(options = {}) {
|
|
1350
|
-
const root =
|
|
1618
|
+
const root = path6.resolve(options.root ?? process.cwd());
|
|
1351
1619
|
const result = await scaffoldWorkspace(root, options.workspace);
|
|
1352
1620
|
if (options.json) {
|
|
1353
1621
|
return printJson(result);
|
|
@@ -1406,34 +1674,52 @@ function printValue(value, json = false) {
|
|
|
1406
1674
|
|
|
1407
1675
|
// src/services/listing.ts
|
|
1408
1676
|
import { flattenObject as flattenObject2 } from "@kitsy/cnos/internal";
|
|
1677
|
+
function matchesSecretFilter(candidate, filter) {
|
|
1678
|
+
const secretRef = candidate.metadata?.secretRef;
|
|
1679
|
+
if (filter.vault && secretRef?.vault !== filter.vault) {
|
|
1680
|
+
return false;
|
|
1681
|
+
}
|
|
1682
|
+
if (filter.provider && secretRef?.provider !== filter.provider) {
|
|
1683
|
+
return false;
|
|
1684
|
+
}
|
|
1685
|
+
return true;
|
|
1686
|
+
}
|
|
1409
1687
|
function matchesPrefix(key, prefix) {
|
|
1410
1688
|
if (!prefix) {
|
|
1411
1689
|
return true;
|
|
1412
1690
|
}
|
|
1413
1691
|
return key.startsWith(prefix) || key.split(".").slice(1).join(".").startsWith(prefix);
|
|
1414
1692
|
}
|
|
1415
|
-
function toStoredEntry(namespace, entry) {
|
|
1693
|
+
function toStoredEntry(namespace, entry, filter = {}) {
|
|
1416
1694
|
const sourceId = namespace === "value" ? "filesystem-values" : "filesystem-secrets";
|
|
1417
1695
|
const candidates = [entry.winner, ...entry.overridden].filter((candidate) => candidate.sourceId === sourceId);
|
|
1418
1696
|
if (candidates.length === 0) {
|
|
1419
1697
|
return void 0;
|
|
1420
1698
|
}
|
|
1699
|
+
const selectedCandidate = namespace === "secret" ? candidates.find((candidate) => matchesSecretFilter(candidate, filter)) : candidates[0];
|
|
1700
|
+
if (!selectedCandidate) {
|
|
1701
|
+
return void 0;
|
|
1702
|
+
}
|
|
1421
1703
|
return {
|
|
1422
1704
|
key: entry.key,
|
|
1423
|
-
value:
|
|
1705
|
+
value: selectedCandidate.value
|
|
1424
1706
|
};
|
|
1425
1707
|
}
|
|
1426
1708
|
function listStoredNamespace(namespace, options) {
|
|
1427
1709
|
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))
|
|
1710
|
+
(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
1711
|
);
|
|
1430
1712
|
}
|
|
1431
1713
|
function listProjectedNamespace(namespace, options) {
|
|
1432
1714
|
return createRuntimeService(options).then((runtime) => {
|
|
1433
|
-
const projected = namespace === "meta" ? flattenObject2(runtime.toNamespace("meta")) : namespace === "env" ? runtime.
|
|
1434
|
-
|
|
1715
|
+
const projected = namespace === "meta" ? flattenObject2(runtime.toNamespace("meta")) : namespace === "env" ? runtime.toEnv() : runtime.toPublicEnv({
|
|
1716
|
+
...options.framework ? {
|
|
1717
|
+
framework: options.framework
|
|
1718
|
+
} : {}
|
|
1719
|
+
});
|
|
1720
|
+
const entries = namespace === "env" ? Object.entries(projected).map(([envVar, value]) => ({
|
|
1435
1721
|
key: envVar,
|
|
1436
|
-
value
|
|
1722
|
+
value
|
|
1437
1723
|
})) : Object.entries(projected).map(([key, value]) => ({
|
|
1438
1724
|
key: namespace === "meta" ? `meta.${key}` : key,
|
|
1439
1725
|
value
|
|
@@ -1482,10 +1768,12 @@ async function runList(args = [], options = {}) {
|
|
|
1482
1768
|
const cliArgs = [...options.cliArgs ?? []];
|
|
1483
1769
|
const namespace = normalizeNamespace(args[0] ?? consumeOption(cliArgs, "--namespace"));
|
|
1484
1770
|
const prefix = consumeOption(cliArgs, "--prefix");
|
|
1771
|
+
const framework = consumeOption(cliArgs, "--framework");
|
|
1485
1772
|
const entries = await listConfigEntries(namespace, {
|
|
1486
1773
|
...options,
|
|
1487
1774
|
cliArgs,
|
|
1488
|
-
...prefix ? { prefix } : {}
|
|
1775
|
+
...prefix ? { prefix } : {},
|
|
1776
|
+
...framework ? { framework } : {}
|
|
1489
1777
|
});
|
|
1490
1778
|
if (options.json) {
|
|
1491
1779
|
return printJson(entries);
|
|
@@ -1497,34 +1785,34 @@ async function runList(args = [], options = {}) {
|
|
|
1497
1785
|
}
|
|
1498
1786
|
|
|
1499
1787
|
// src/commands/onboard.ts
|
|
1500
|
-
import { copyFile, readdir, rm as
|
|
1501
|
-
import
|
|
1788
|
+
import { copyFile, readdir, rm as rm3 } from "fs/promises";
|
|
1789
|
+
import path7 from "path";
|
|
1502
1790
|
var ROOT_ENV_FILE_PATTERN = /^\.env(?:\.[A-Za-z0-9_-]+)*(?:\.example)?$/;
|
|
1503
1791
|
async function listRootEnvFiles(root) {
|
|
1504
1792
|
const entries = await readdir(root, { withFileTypes: true });
|
|
1505
1793
|
return entries.filter((entry) => entry.isFile() && ROOT_ENV_FILE_PATTERN.test(entry.name)).map((entry) => entry.name).sort((left, right) => left.localeCompare(right));
|
|
1506
1794
|
}
|
|
1507
1795
|
async function runOnboard(options = {}) {
|
|
1508
|
-
const root =
|
|
1509
|
-
const workspace = options.workspace ??
|
|
1796
|
+
const root = path7.resolve(options.root ?? process.cwd());
|
|
1797
|
+
const workspace = options.workspace ?? path7.basename(root);
|
|
1510
1798
|
const cliArgs = [...options.cliArgs ?? []];
|
|
1511
1799
|
const move = consumeFlag(cliArgs, "--move");
|
|
1512
1800
|
if (cliArgs.length > 0) {
|
|
1513
1801
|
throw new Error(`Unsupported onboard arguments: ${cliArgs.join(" ")}`);
|
|
1514
1802
|
}
|
|
1515
1803
|
const scaffold = await scaffoldWorkspace(root, workspace);
|
|
1516
|
-
const envRoot =
|
|
1804
|
+
const envRoot = path7.join(root, ".cnos", "workspaces", workspace, "env");
|
|
1517
1805
|
const rootFiles = await listRootEnvFiles(root);
|
|
1518
1806
|
const imported = [];
|
|
1519
1807
|
const skipped = [];
|
|
1520
1808
|
for (const fileName of rootFiles) {
|
|
1521
|
-
const sourcePath =
|
|
1522
|
-
const targetPath =
|
|
1809
|
+
const sourcePath = path7.join(root, fileName);
|
|
1810
|
+
const targetPath = path7.join(envRoot, fileName);
|
|
1523
1811
|
try {
|
|
1524
1812
|
await copyFile(sourcePath, targetPath);
|
|
1525
|
-
imported.push(
|
|
1813
|
+
imported.push(path7.relative(root, targetPath).replace(/\\/g, "/"));
|
|
1526
1814
|
if (move) {
|
|
1527
|
-
await
|
|
1815
|
+
await rm3(sourcePath);
|
|
1528
1816
|
}
|
|
1529
1817
|
} catch {
|
|
1530
1818
|
skipped.push(fileName);
|
|
@@ -1547,16 +1835,16 @@ async function runOnboard(options = {}) {
|
|
|
1547
1835
|
}
|
|
1548
1836
|
|
|
1549
1837
|
// src/commands/profile.ts
|
|
1550
|
-
import
|
|
1838
|
+
import path10 from "path";
|
|
1551
1839
|
|
|
1552
1840
|
// src/services/context.ts
|
|
1553
|
-
import { readFile as
|
|
1554
|
-
import
|
|
1555
|
-
import { parseYaml as parseYaml2, stringifyYaml as
|
|
1841
|
+
import { readFile as readFile5, writeFile as writeFile5 } from "fs/promises";
|
|
1842
|
+
import path8 from "path";
|
|
1843
|
+
import { parseYaml as parseYaml2, stringifyYaml as stringifyYaml3 } from "@kitsy/cnos/internal";
|
|
1556
1844
|
async function loadCliContext(root = process.cwd()) {
|
|
1557
|
-
const filePath =
|
|
1845
|
+
const filePath = path8.join(path8.resolve(root), ".cnos-workspace.yml");
|
|
1558
1846
|
try {
|
|
1559
|
-
const source = await
|
|
1847
|
+
const source = await readFile5(filePath, "utf8");
|
|
1560
1848
|
const parsed = parseYaml2(source);
|
|
1561
1849
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
1562
1850
|
return {};
|
|
@@ -1567,8 +1855,8 @@ async function loadCliContext(root = process.cwd()) {
|
|
|
1567
1855
|
}
|
|
1568
1856
|
}
|
|
1569
1857
|
async function saveCliContext(options = {}) {
|
|
1570
|
-
const root =
|
|
1571
|
-
const filePath =
|
|
1858
|
+
const root = path8.resolve(options.root ?? process.cwd());
|
|
1859
|
+
const filePath = path8.join(root, ".cnos-workspace.yml");
|
|
1572
1860
|
const current = await loadCliContext(root);
|
|
1573
1861
|
const next = {
|
|
1574
1862
|
...current.workspace ? { workspace: current.workspace } : {},
|
|
@@ -1578,7 +1866,7 @@ async function saveCliContext(options = {}) {
|
|
|
1578
1866
|
...options.profile ? { profile: options.profile } : {},
|
|
1579
1867
|
...options.globalRoot ? { globalRoot: options.globalRoot } : {}
|
|
1580
1868
|
};
|
|
1581
|
-
await
|
|
1869
|
+
await writeFile5(filePath, stringifyYaml3(next), "utf8");
|
|
1582
1870
|
return {
|
|
1583
1871
|
filePath,
|
|
1584
1872
|
context: next
|
|
@@ -1586,19 +1874,19 @@ async function saveCliContext(options = {}) {
|
|
|
1586
1874
|
}
|
|
1587
1875
|
|
|
1588
1876
|
// src/services/profiles.ts
|
|
1589
|
-
import { mkdir as
|
|
1590
|
-
import
|
|
1591
|
-
import { parseYaml as parseYaml3, stringifyYaml as
|
|
1877
|
+
import { mkdir as mkdir4, readdir as readdir2, readFile as readFile6, rm as rm4, writeFile as writeFile6 } from "fs/promises";
|
|
1878
|
+
import path9 from "path";
|
|
1879
|
+
import { parseYaml as parseYaml3, stringifyYaml as stringifyYaml4 } from "@kitsy/cnos/internal";
|
|
1592
1880
|
async function createProfileDefinition(root = process.cwd(), profile, inherit) {
|
|
1593
|
-
const filePath =
|
|
1594
|
-
await
|
|
1881
|
+
const filePath = path9.join(path9.resolve(root), ".cnos", "profiles", `${profile}.yml`);
|
|
1882
|
+
await mkdir4(path9.dirname(filePath), { recursive: true });
|
|
1595
1883
|
const document = inherit && inherit !== "base" ? {
|
|
1596
1884
|
name: profile,
|
|
1597
1885
|
extends: [inherit]
|
|
1598
1886
|
} : {
|
|
1599
1887
|
name: profile
|
|
1600
1888
|
};
|
|
1601
|
-
await
|
|
1889
|
+
await writeFile6(filePath, stringifyYaml4(document), "utf8");
|
|
1602
1890
|
return {
|
|
1603
1891
|
filePath,
|
|
1604
1892
|
profile,
|
|
@@ -1606,7 +1894,7 @@ async function createProfileDefinition(root = process.cwd(), profile, inherit) {
|
|
|
1606
1894
|
};
|
|
1607
1895
|
}
|
|
1608
1896
|
async function listProfiles(root = process.cwd()) {
|
|
1609
|
-
const profilesRoot =
|
|
1897
|
+
const profilesRoot = path9.join(path9.resolve(root), ".cnos", "profiles");
|
|
1610
1898
|
try {
|
|
1611
1899
|
const entries = await readdir2(profilesRoot, { withFileTypes: true });
|
|
1612
1900
|
const discovered = /* @__PURE__ */ new Set(["base"]);
|
|
@@ -1621,9 +1909,9 @@ async function listProfiles(root = process.cwd()) {
|
|
|
1621
1909
|
}
|
|
1622
1910
|
}
|
|
1623
1911
|
async function deleteProfileDefinition(root = process.cwd(), profile) {
|
|
1624
|
-
const filePath =
|
|
1912
|
+
const filePath = path9.join(path9.resolve(root), ".cnos", "profiles", `${profile}.yml`);
|
|
1625
1913
|
try {
|
|
1626
|
-
await
|
|
1914
|
+
await rm4(filePath);
|
|
1627
1915
|
return {
|
|
1628
1916
|
filePath,
|
|
1629
1917
|
deleted: true
|
|
@@ -1641,9 +1929,9 @@ async function readProfileDefinition(root = process.cwd(), profile = "base") {
|
|
|
1641
1929
|
name: "base"
|
|
1642
1930
|
};
|
|
1643
1931
|
}
|
|
1644
|
-
const filePath =
|
|
1932
|
+
const filePath = path9.join(path9.resolve(root), ".cnos", "profiles", `${profile}.yml`);
|
|
1645
1933
|
try {
|
|
1646
|
-
return parseYaml3(await
|
|
1934
|
+
return parseYaml3(await readFile6(filePath, "utf8")) ?? void 0;
|
|
1647
1935
|
} catch {
|
|
1648
1936
|
return void 0;
|
|
1649
1937
|
}
|
|
@@ -1665,7 +1953,7 @@ function normalizeProfileAction(args) {
|
|
|
1665
1953
|
}
|
|
1666
1954
|
async function runProfile(args, options = {}) {
|
|
1667
1955
|
const { action, tail } = normalizeProfileAction(args);
|
|
1668
|
-
const root =
|
|
1956
|
+
const root = path10.resolve(options.root ?? process.cwd());
|
|
1669
1957
|
const cliArgs = [...options.cliArgs ?? []];
|
|
1670
1958
|
if (action === "create") {
|
|
1671
1959
|
const profile = tail[0] ?? "stage";
|
|
@@ -1708,6 +1996,73 @@ async function runProfile(args, options = {}) {
|
|
|
1708
1996
|
return profiles.join("\n");
|
|
1709
1997
|
}
|
|
1710
1998
|
|
|
1999
|
+
// src/commands/promote.ts
|
|
2000
|
+
import { writeFile as writeFile7 } from "fs/promises";
|
|
2001
|
+
import {
|
|
2002
|
+
ensureProjectionAllowed,
|
|
2003
|
+
loadManifest as loadManifest2,
|
|
2004
|
+
stringifyYaml as stringifyYaml5
|
|
2005
|
+
} from "@kitsy/cnos/internal";
|
|
2006
|
+
function normalizeTarget(value) {
|
|
2007
|
+
if (value === "public" || value === "env") {
|
|
2008
|
+
return value;
|
|
2009
|
+
}
|
|
2010
|
+
throw new Error("promote requires --to public|env");
|
|
2011
|
+
}
|
|
2012
|
+
function sortRecord(record) {
|
|
2013
|
+
return Object.fromEntries(Object.entries(record).sort(([left], [right]) => left.localeCompare(right)));
|
|
2014
|
+
}
|
|
2015
|
+
async function runPromote(args = [], options = {}) {
|
|
2016
|
+
const cliArgs = [...options.cliArgs ?? []];
|
|
2017
|
+
const target = normalizeTarget(consumeOption(cliArgs, "--to"));
|
|
2018
|
+
const alias = consumeOption(cliArgs, "--as");
|
|
2019
|
+
const keys = args.filter(Boolean);
|
|
2020
|
+
if (keys.length === 0) {
|
|
2021
|
+
throw new Error("promote requires at least one logical key");
|
|
2022
|
+
}
|
|
2023
|
+
if (target === "env") {
|
|
2024
|
+
if (keys.length !== 1) {
|
|
2025
|
+
throw new Error("promote --to env requires exactly one logical key");
|
|
2026
|
+
}
|
|
2027
|
+
if (!alias) {
|
|
2028
|
+
throw new Error("promote --to env requires --as <ENV_VAR>");
|
|
2029
|
+
}
|
|
2030
|
+
}
|
|
2031
|
+
const loadedManifest = await loadManifest2(options.root ? { root: options.root } : {});
|
|
2032
|
+
for (const key of keys) {
|
|
2033
|
+
ensureProjectionAllowed(loadedManifest.manifest, key, target);
|
|
2034
|
+
}
|
|
2035
|
+
const rawManifest = {
|
|
2036
|
+
...loadedManifest.rawManifest
|
|
2037
|
+
};
|
|
2038
|
+
if (target === "public") {
|
|
2039
|
+
rawManifest.public = {
|
|
2040
|
+
...rawManifest.public ?? {},
|
|
2041
|
+
promote: Array.from(/* @__PURE__ */ new Set([...rawManifest.public?.promote ?? [], ...keys])).sort(
|
|
2042
|
+
(left, right) => left.localeCompare(right)
|
|
2043
|
+
)
|
|
2044
|
+
};
|
|
2045
|
+
} else {
|
|
2046
|
+
rawManifest.envMapping = {
|
|
2047
|
+
...rawManifest.envMapping ?? {},
|
|
2048
|
+
explicit: sortRecord({
|
|
2049
|
+
...rawManifest.envMapping?.explicit ?? {},
|
|
2050
|
+
[alias]: keys[0]
|
|
2051
|
+
})
|
|
2052
|
+
};
|
|
2053
|
+
}
|
|
2054
|
+
await writeFile7(loadedManifest.manifestPath, stringifyYaml5(rawManifest), "utf8");
|
|
2055
|
+
if (options.json) {
|
|
2056
|
+
return printJson({
|
|
2057
|
+
target,
|
|
2058
|
+
keys,
|
|
2059
|
+
...target === "env" ? { envVar: alias } : {},
|
|
2060
|
+
manifestPath: loadedManifest.manifestPath
|
|
2061
|
+
});
|
|
2062
|
+
}
|
|
2063
|
+
return target === "public" ? `promoted ${keys.join(", ")} to public in ${loadedManifest.manifestPath}` : `promoted ${keys[0]} to env as ${alias} in ${loadedManifest.manifestPath}`;
|
|
2064
|
+
}
|
|
2065
|
+
|
|
1711
2066
|
// src/commands/read.ts
|
|
1712
2067
|
async function runRead(key, options = {}) {
|
|
1713
2068
|
const runtime = await createRuntimeService(options);
|
|
@@ -1723,14 +2078,60 @@ async function runRead(key, options = {}) {
|
|
|
1723
2078
|
|
|
1724
2079
|
// src/commands/run.ts
|
|
1725
2080
|
import { spawn } from "child_process";
|
|
2081
|
+
import {
|
|
2082
|
+
CNOS_GRAPH_ENV_VAR,
|
|
2083
|
+
serializeRuntimeGraph
|
|
2084
|
+
} from "@kitsy/cnos/internal";
|
|
2085
|
+
function consumeOptions(args, flag) {
|
|
2086
|
+
const values = [];
|
|
2087
|
+
for (let index = 0; index < args.length; ) {
|
|
2088
|
+
const token = args[index];
|
|
2089
|
+
if (token === flag) {
|
|
2090
|
+
const value = args[index + 1];
|
|
2091
|
+
if (!value) {
|
|
2092
|
+
throw new Error(`Missing value for ${flag}`);
|
|
2093
|
+
}
|
|
2094
|
+
values.push(value);
|
|
2095
|
+
args.splice(index, 2);
|
|
2096
|
+
continue;
|
|
2097
|
+
}
|
|
2098
|
+
if (token?.startsWith(`${flag}=`)) {
|
|
2099
|
+
values.push(token.slice(flag.length + 1));
|
|
2100
|
+
args.splice(index, 1);
|
|
2101
|
+
continue;
|
|
2102
|
+
}
|
|
2103
|
+
index += 1;
|
|
2104
|
+
}
|
|
2105
|
+
return values;
|
|
2106
|
+
}
|
|
2107
|
+
function normalizeSetOverrides(values) {
|
|
2108
|
+
return values.map((value) => {
|
|
2109
|
+
if (!value.includes("=")) {
|
|
2110
|
+
throw new Error("--set requires <logical-key>=<value>");
|
|
2111
|
+
}
|
|
2112
|
+
return value.startsWith("--") ? value : `--${value}`;
|
|
2113
|
+
});
|
|
2114
|
+
}
|
|
1726
2115
|
async function runCommand(command, options = {}) {
|
|
1727
2116
|
if (command.length === 0) {
|
|
1728
2117
|
throw new Error("run requires a command after --");
|
|
1729
2118
|
}
|
|
1730
|
-
const
|
|
2119
|
+
const cliArgs = [...options.cliArgs ?? []];
|
|
2120
|
+
const isPublic = consumeFlag(cliArgs, "--public");
|
|
2121
|
+
const framework = consumeOption(cliArgs, "--framework");
|
|
2122
|
+
const prefix = consumeOption(cliArgs, "--prefix");
|
|
2123
|
+
const setOverrides = normalizeSetOverrides(consumeOptions(cliArgs, "--set"));
|
|
2124
|
+
const runtime = await createRuntimeService({
|
|
2125
|
+
...options,
|
|
2126
|
+
cliArgs: [...cliArgs, ...setOverrides]
|
|
2127
|
+
});
|
|
1731
2128
|
const env = {
|
|
1732
2129
|
...process.env,
|
|
1733
|
-
...runtime.
|
|
2130
|
+
...isPublic ? runtime.toPublicEnv({
|
|
2131
|
+
...framework ? { framework } : {},
|
|
2132
|
+
...prefix ? { prefix } : {}
|
|
2133
|
+
}) : runtime.toEnv(),
|
|
2134
|
+
[CNOS_GRAPH_ENV_VAR]: serializeRuntimeGraph(runtime.graph)
|
|
1734
2135
|
};
|
|
1735
2136
|
return new Promise((resolve, reject) => {
|
|
1736
2137
|
const executable = command[0];
|
|
@@ -1765,6 +2166,68 @@ async function runCommand(command, options = {}) {
|
|
|
1765
2166
|
});
|
|
1766
2167
|
}
|
|
1767
2168
|
|
|
2169
|
+
// src/commands/vault.ts
|
|
2170
|
+
function normalizeVaultAction(args) {
|
|
2171
|
+
const [action = "list", ...tail] = args;
|
|
2172
|
+
if (["create", "add", "list", "delete", "remove"].includes(action)) {
|
|
2173
|
+
return {
|
|
2174
|
+
action: action === "add" || action === "create" ? "create" : action === "delete" || action === "remove" ? "remove" : "list",
|
|
2175
|
+
tail
|
|
2176
|
+
};
|
|
2177
|
+
}
|
|
2178
|
+
return {
|
|
2179
|
+
action: "list",
|
|
2180
|
+
tail: args
|
|
2181
|
+
};
|
|
2182
|
+
}
|
|
2183
|
+
async function runVault(args = [], options = {}) {
|
|
2184
|
+
const { action, tail } = normalizeVaultAction(args);
|
|
2185
|
+
const cliArgs = [...options.cliArgs ?? []];
|
|
2186
|
+
if (action === "create") {
|
|
2187
|
+
const name = tail[0] ?? "default";
|
|
2188
|
+
const provider = consumeOption(cliArgs, "--provider") ?? "local";
|
|
2189
|
+
const passphrase = consumeOption(cliArgs, "--passphrase");
|
|
2190
|
+
const noPassphrase = consumeFlag(cliArgs, "--no-passphrase");
|
|
2191
|
+
const result = await createVaultDefinition(name, {
|
|
2192
|
+
...options,
|
|
2193
|
+
cliArgs,
|
|
2194
|
+
provider,
|
|
2195
|
+
...passphrase ? { passphrase } : {},
|
|
2196
|
+
...noPassphrase ? { noPassphrase: true } : {}
|
|
2197
|
+
});
|
|
2198
|
+
if (options.json) {
|
|
2199
|
+
return printJson(result);
|
|
2200
|
+
}
|
|
2201
|
+
return `created vault "${result.name}" with provider "${result.provider}" in ${result.manifestPath}`;
|
|
2202
|
+
}
|
|
2203
|
+
if (action === "remove") {
|
|
2204
|
+
const name = tail[0] ?? "default";
|
|
2205
|
+
const result = await removeVaultDefinition(name, options);
|
|
2206
|
+
if (options.json) {
|
|
2207
|
+
return printJson(result);
|
|
2208
|
+
}
|
|
2209
|
+
return result.deleted ? `removed vault "${result.name}"` : `vault "${result.name}" was not found`;
|
|
2210
|
+
}
|
|
2211
|
+
const [manifestVaults, localStoreVaults] = await Promise.all([
|
|
2212
|
+
listVaultDefinitions(options),
|
|
2213
|
+
listLocalStoreVaults(options)
|
|
2214
|
+
]);
|
|
2215
|
+
if (options.json) {
|
|
2216
|
+
return printJson(
|
|
2217
|
+
manifestVaults.map((vault) => ({
|
|
2218
|
+
...vault,
|
|
2219
|
+
localStore: localStoreVaults.includes(vault.name)
|
|
2220
|
+
}))
|
|
2221
|
+
);
|
|
2222
|
+
}
|
|
2223
|
+
if (manifestVaults.length === 0) {
|
|
2224
|
+
return "";
|
|
2225
|
+
}
|
|
2226
|
+
return manifestVaults.map(
|
|
2227
|
+
(vault) => `${vault.name} provider=${vault.provider} passphrase=${vault.passphrasePolicy}${localStoreVaults.includes(vault.name) ? " local-store=true" : ""}`
|
|
2228
|
+
).join("\n");
|
|
2229
|
+
}
|
|
2230
|
+
|
|
1768
2231
|
// src/commands/secret.ts
|
|
1769
2232
|
function isSecretRef(value) {
|
|
1770
2233
|
return Boolean(
|
|
@@ -1801,32 +2264,26 @@ async function runSecret(argsOrPath, options = {}) {
|
|
|
1801
2264
|
const { action, tail } = normalizeSecretCommand(args);
|
|
1802
2265
|
const cliArgs = [...options.cliArgs ?? []];
|
|
1803
2266
|
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}`;
|
|
2267
|
+
return runVault(["create", tail[0] ?? "default"], options);
|
|
1815
2268
|
}
|
|
1816
2269
|
if (action === "list") {
|
|
1817
2270
|
const prefix = consumeOption(cliArgs, "--prefix");
|
|
2271
|
+
const vault = consumeOption(cliArgs, "--vault");
|
|
2272
|
+
const provider = consumeOption(cliArgs, "--provider");
|
|
1818
2273
|
const entries = await listConfigEntries("secret", {
|
|
1819
2274
|
...options,
|
|
1820
2275
|
cliArgs,
|
|
1821
|
-
...prefix ? { prefix } : {}
|
|
2276
|
+
...prefix ? { prefix } : {},
|
|
2277
|
+
...vault ? { vault } : {},
|
|
2278
|
+
...provider ? { provider } : {}
|
|
1822
2279
|
});
|
|
1823
2280
|
if (options.json) {
|
|
1824
2281
|
return printJson(entries);
|
|
1825
2282
|
}
|
|
1826
|
-
return entries.map((
|
|
2283
|
+
return entries.map((entry2) => `${entry2.key}=${printValue(entry2.value)}`).join("\n");
|
|
1827
2284
|
}
|
|
1828
2285
|
if (action === "set") {
|
|
1829
|
-
const
|
|
2286
|
+
const secretPath2 = tail[0];
|
|
1830
2287
|
const rawValue = tail[1] ?? "";
|
|
1831
2288
|
const local = consumeFlag(cliArgs, "--local");
|
|
1832
2289
|
const remote = consumeFlag(cliArgs, "--remote");
|
|
@@ -1835,25 +2292,25 @@ async function runSecret(argsOrPath, options = {}) {
|
|
|
1835
2292
|
const provider = consumeOption(cliArgs, "--provider");
|
|
1836
2293
|
const passphrase = consumeOption(cliArgs, "--passphrase");
|
|
1837
2294
|
const vault = consumeOption(cliArgs, "--vault") ?? "default";
|
|
1838
|
-
const mode = local ? "local" : remote ? "remote" : ref ? "ref" :
|
|
1839
|
-
const result = await setSecret(
|
|
2295
|
+
const mode = local ? "local" : remote ? "remote" : ref ? "ref" : void 0;
|
|
2296
|
+
const result = await setSecret(secretPath2 ?? "app.token", rawValue, {
|
|
1840
2297
|
...options,
|
|
1841
2298
|
cliArgs,
|
|
1842
2299
|
target,
|
|
1843
|
-
mode,
|
|
1844
2300
|
vault,
|
|
2301
|
+
...mode ? { mode } : {},
|
|
1845
2302
|
...provider ? { provider } : {},
|
|
1846
2303
|
...passphrase ? { passphrase } : {}
|
|
1847
2304
|
});
|
|
1848
2305
|
if (options.json) {
|
|
1849
2306
|
return printJson(result);
|
|
1850
2307
|
}
|
|
1851
|
-
return result.provider === "local" ? `set secret.${
|
|
2308
|
+
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
2309
|
}
|
|
1853
2310
|
if (action === "delete") {
|
|
1854
|
-
const
|
|
2311
|
+
const secretPath2 = tail[0];
|
|
1855
2312
|
const target = consumeOption(cliArgs, "--target") ?? "local";
|
|
1856
|
-
const result = await deleteSecret(
|
|
2313
|
+
const result = await deleteSecret(secretPath2 ?? "app.token", {
|
|
1857
2314
|
...options,
|
|
1858
2315
|
cliArgs,
|
|
1859
2316
|
target
|
|
@@ -1861,22 +2318,37 @@ async function runSecret(argsOrPath, options = {}) {
|
|
|
1861
2318
|
if (options.json) {
|
|
1862
2319
|
return printJson(result);
|
|
1863
2320
|
}
|
|
1864
|
-
return result.deleted ? `deleted secret.${
|
|
2321
|
+
return result.deleted ? `deleted secret.${secretPath2} from ${result.filePath}` : `no secret.${secretPath2} found in ${result.filePath}`;
|
|
1865
2322
|
}
|
|
1866
2323
|
const runtime = await createRuntimeService(options);
|
|
1867
|
-
const
|
|
2324
|
+
const secretPath = tail[0] ?? "app.token";
|
|
2325
|
+
const expectedVault = consumeOption(cliArgs, "--vault");
|
|
2326
|
+
const entry = runtime.graph.entries.get(`secret.${secretPath}`);
|
|
2327
|
+
const secretRef = entry?.winner.metadata?.secretRef;
|
|
2328
|
+
const value = runtime.secret(secretPath);
|
|
1868
2329
|
if (value === void 0) {
|
|
1869
|
-
throw new Error(`Missing CNOS secret path: ${
|
|
2330
|
+
throw new Error(`Missing CNOS secret path: ${secretPath}`);
|
|
2331
|
+
}
|
|
2332
|
+
if (expectedVault && secretRef?.vault && secretRef.vault !== expectedVault) {
|
|
2333
|
+
throw new Error(`Secret ${secretPath} belongs to vault "${secretRef.vault}", not "${expectedVault}"`);
|
|
1870
2334
|
}
|
|
1871
2335
|
if (isSecretRef(value)) {
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
2336
|
+
if (value.provider === "local") {
|
|
2337
|
+
const vault = value.vault ?? "default";
|
|
2338
|
+
throw new Error(
|
|
2339
|
+
`Secret ${secretPath} is stored in vault "${vault}" as ref "${value.ref}". Provide the correct vault passphrase to resolve it.`
|
|
2340
|
+
);
|
|
2341
|
+
}
|
|
2342
|
+
if (value.provider === "github-secrets") {
|
|
2343
|
+
throw new Error(
|
|
2344
|
+
`Secret ${secretPath} is backed by GitHub secrets via ref "${value.ref}". Set that env var in the current process or CI job to resolve it.`
|
|
2345
|
+
);
|
|
2346
|
+
}
|
|
2347
|
+
throw new Error(`Secret ${secretPath} is stored as a ${value.provider} reference "${value.ref}" and is not resolved.`);
|
|
1876
2348
|
}
|
|
1877
2349
|
if (options.json) {
|
|
1878
2350
|
return printJson({
|
|
1879
|
-
key: `secret.${
|
|
2351
|
+
key: `secret.${secretPath}`,
|
|
1880
2352
|
value
|
|
1881
2353
|
});
|
|
1882
2354
|
}
|
|
@@ -1884,9 +2356,9 @@ async function runSecret(argsOrPath, options = {}) {
|
|
|
1884
2356
|
}
|
|
1885
2357
|
|
|
1886
2358
|
// src/commands/use.ts
|
|
1887
|
-
import
|
|
2359
|
+
import path11 from "path";
|
|
1888
2360
|
async function runUse(args = [], options = {}) {
|
|
1889
|
-
const root =
|
|
2361
|
+
const root = path11.resolve(options.root ?? process.cwd());
|
|
1890
2362
|
const action = args[0] ?? "show";
|
|
1891
2363
|
const hasUpdates = Boolean(options.workspace || options.profile || options.globalRoot);
|
|
1892
2364
|
if (action === "show" || !hasUpdates) {
|
|
@@ -1923,7 +2395,7 @@ async function runValidate(options = {}) {
|
|
|
1923
2395
|
// package.json
|
|
1924
2396
|
var package_default = {
|
|
1925
2397
|
name: "@kitsy/cnos-cli",
|
|
1926
|
-
version: "1.
|
|
2398
|
+
version: "1.2.0",
|
|
1927
2399
|
description: "CLI entry point and developer tooling for CNOS.",
|
|
1928
2400
|
type: "module",
|
|
1929
2401
|
main: "./dist/index.js",
|
|
@@ -2068,6 +2540,9 @@ function resolveHelpTopic(command, args) {
|
|
|
2068
2540
|
if (command === "export" && args[0] === "env") {
|
|
2069
2541
|
return normalizeHelpTopic([command, args[0]]);
|
|
2070
2542
|
}
|
|
2543
|
+
if (command === "vault" && args[0] && ["create", "add", "list", "delete", "remove"].includes(args[0])) {
|
|
2544
|
+
return normalizeHelpTopic([command, args[0] === "delete" ? "remove" : args[0] === "add" ? "create" : args[0]]);
|
|
2545
|
+
}
|
|
2071
2546
|
if (command === "secret" && args[0] && ["set", "create", "add", "list", "delete", "remove"].includes(args[0])) {
|
|
2072
2547
|
if ((args[0] === "create" || args[0] === "add") && args[1] === "vault") {
|
|
2073
2548
|
return normalizeHelpTopic(["secret", "create", "vault"]);
|
|
@@ -2149,6 +2624,10 @@ async function main(argv) {
|
|
|
2149
2624
|
return;
|
|
2150
2625
|
case "secret":
|
|
2151
2626
|
process.stdout.write(`${await runSecret(args.length > 0 ? args : ["app.token"], runtimeOptions)}
|
|
2627
|
+
`);
|
|
2628
|
+
return;
|
|
2629
|
+
case "vault":
|
|
2630
|
+
process.stdout.write(`${await runVault(args, runtimeOptions)}
|
|
2152
2631
|
`);
|
|
2153
2632
|
return;
|
|
2154
2633
|
case "use":
|
|
@@ -2157,6 +2636,10 @@ async function main(argv) {
|
|
|
2157
2636
|
return;
|
|
2158
2637
|
case "profile":
|
|
2159
2638
|
process.stdout.write(`${await runProfile(args, runtimeOptions)}
|
|
2639
|
+
`);
|
|
2640
|
+
return;
|
|
2641
|
+
case "promote":
|
|
2642
|
+
process.stdout.write(`${await runPromote(args, runtimeOptions)}
|
|
2160
2643
|
`);
|
|
2161
2644
|
return;
|
|
2162
2645
|
case "list":
|