@kitsy/cnos-cli 1.9.0 → 1.9.2
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 +368 -103
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -54,7 +54,9 @@ var COMMAND_FLAG_KEYS = /* @__PURE__ */ new Set([
|
|
|
54
54
|
"--signal",
|
|
55
55
|
"--derive",
|
|
56
56
|
"--materialize",
|
|
57
|
-
"--source-only"
|
|
57
|
+
"--source-only",
|
|
58
|
+
"--allow-secret",
|
|
59
|
+
"--fix-secret-env-mappings"
|
|
58
60
|
]);
|
|
59
61
|
function normalizeCommand(argv) {
|
|
60
62
|
const [command = "doctor", ...rest] = argv;
|
|
@@ -324,12 +326,31 @@ import { spawnSync } from "child_process";
|
|
|
324
326
|
function isInteractiveSession() {
|
|
325
327
|
return process.stdin.isTTY && process.stdout.isTTY && !process.env.CI;
|
|
326
328
|
}
|
|
329
|
+
function printSecretEnvBuildWarnings(targetPath, mappings) {
|
|
330
|
+
console.error(`!WARN CNOS detected explicit secret env mappings for ${targetPath}.`);
|
|
331
|
+
console.error(`!WARN Writing revealed env artifacts is a security risk and may leak plaintext secrets outside CNOS.`);
|
|
332
|
+
console.error(`!WARN Secret env vars: ${mappings.map((mapping) => mapping.envVar).join(", ")}`);
|
|
333
|
+
}
|
|
327
334
|
function getSecretEnvMappings(runtime) {
|
|
328
335
|
return Object.entries(runtime.manifest.envMapping.explicit).filter(([, logicalKey]) => runtime.graph.entries.get(logicalKey)?.namespace === "secret").map(([envVar, logicalKey]) => ({
|
|
329
336
|
envVar,
|
|
330
337
|
logicalKey
|
|
331
338
|
})).sort((left, right) => left.envVar.localeCompare(right.envVar));
|
|
332
339
|
}
|
|
340
|
+
async function hydrateSecretEnvMappings(runtime, mappings) {
|
|
341
|
+
for (const mapping of mappings) {
|
|
342
|
+
await runtime.refreshSecret(mapping.logicalKey);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
function applyMaskedSecretEnvMappings(env, mappings) {
|
|
346
|
+
const nextEnv = { ...env };
|
|
347
|
+
for (const { envVar } of mappings) {
|
|
348
|
+
if (!(envVar in nextEnv)) {
|
|
349
|
+
nextEnv[envVar] = "****";
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
return nextEnv;
|
|
353
|
+
}
|
|
333
354
|
function resolveGitRoot(cwd) {
|
|
334
355
|
const result = spawnSync("git", ["rev-parse", "--show-toplevel"], {
|
|
335
356
|
cwd,
|
|
@@ -377,19 +398,16 @@ async function assertSecretEnvTargetIsGitIgnored(targetPath, cwd) {
|
|
|
377
398
|
}
|
|
378
399
|
}
|
|
379
400
|
async function confirmSecretEnvBuild(targetPath, mappings) {
|
|
401
|
+
printSecretEnvBuildWarnings(targetPath, mappings);
|
|
380
402
|
if (!isInteractiveSession()) {
|
|
381
403
|
return;
|
|
382
404
|
}
|
|
383
|
-
console.error(`!WARN CNOS is about to write resolved secret values into ${targetPath}.`);
|
|
384
|
-
console.error(
|
|
385
|
-
`!WARN Secret env vars: ${mappings.map((mapping) => mapping.envVar).join(", ")}`
|
|
386
|
-
);
|
|
387
405
|
const rl = readline.createInterface({
|
|
388
406
|
input: process.stdin,
|
|
389
407
|
output: process.stdout
|
|
390
408
|
});
|
|
391
409
|
try {
|
|
392
|
-
const answer = (await rl.question("
|
|
410
|
+
const answer = (await rl.question("Do you want to continue? [y/N] ")).trim().toLowerCase();
|
|
393
411
|
if (answer !== "y" && answer !== "yes") {
|
|
394
412
|
throw new Error("Aborted secret env build.");
|
|
395
413
|
}
|
|
@@ -503,14 +521,13 @@ async function buildEnvProjectionArtifact(to, options = {}, format = "dotenv") {
|
|
|
503
521
|
if (revealSecrets && secretMappings.length > 0) {
|
|
504
522
|
await assertSecretEnvTargetIsGitIgnored(targetPath, basePath);
|
|
505
523
|
await confirmSecretEnvBuild(targetPath, secretMappings);
|
|
524
|
+
await hydrateSecretEnvMappings(runtime, secretMappings);
|
|
506
525
|
}
|
|
507
|
-
|
|
526
|
+
let env = runtime.toEnv({
|
|
508
527
|
includeSecrets: revealSecrets
|
|
509
528
|
});
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
env[envVar] = "****";
|
|
513
|
-
}
|
|
529
|
+
if (!revealSecrets) {
|
|
530
|
+
env = applyMaskedSecretEnvMappings(env, secretMappings);
|
|
514
531
|
}
|
|
515
532
|
const output = formatKeyValueMap(env, format);
|
|
516
533
|
const writtenTargetPath = await writeProjectionFile(to, output, basePath);
|
|
@@ -1333,7 +1350,7 @@ async function runDiff(leftProfile, rightProfile, options = {}) {
|
|
|
1333
1350
|
}
|
|
1334
1351
|
|
|
1335
1352
|
// src/services/doctor.ts
|
|
1336
|
-
import { readdir as readdir2, readFile as readFile3 } from "fs/promises";
|
|
1353
|
+
import { readdir as readdir2, readFile as readFile3, writeFile as writeFile4 } from "fs/promises";
|
|
1337
1354
|
import path9 from "path";
|
|
1338
1355
|
import {
|
|
1339
1356
|
detectLegacyVaultFormat,
|
|
@@ -1341,7 +1358,8 @@ import {
|
|
|
1341
1358
|
loadManifest as loadManifest3,
|
|
1342
1359
|
parseYaml as parseYaml2,
|
|
1343
1360
|
readKeychain,
|
|
1344
|
-
resolveSecretStoreRoot
|
|
1361
|
+
resolveSecretStoreRoot,
|
|
1362
|
+
stringifyYaml as stringifyYaml3
|
|
1345
1363
|
} from "@kitsy/cnos/internal";
|
|
1346
1364
|
|
|
1347
1365
|
// src/services/validation.ts
|
|
@@ -1443,6 +1461,7 @@ async function checkSecretSecurity(options, runtime) {
|
|
|
1443
1461
|
const warnings = [
|
|
1444
1462
|
...legacyDetected.map((entry) => `legacy vault ${entry.vault}: ${entry.path}`),
|
|
1445
1463
|
...plaintextFiles.map((file) => `plaintext secret file: ${file}`),
|
|
1464
|
+
...Object.entries(runtime.manifest.envMapping.explicit).filter(([, logicalKey]) => logicalKey.startsWith("secret.")).map(([envVar, logicalKey]) => `secret env mapping: ${envVar} -> ${logicalKey}`),
|
|
1446
1465
|
...keychainWarnings.filter((entry) => !entry.value).map((entry) => `no keychain entry for vault ${entry.vault} (${entry.source})`)
|
|
1447
1466
|
];
|
|
1448
1467
|
return {
|
|
@@ -1451,6 +1470,39 @@ async function checkSecretSecurity(options, runtime) {
|
|
|
1451
1470
|
details: warnings.length === 0 ? "no legacy vaults, plaintext secret files, or missing keychain entries" : warnings.join("; ")
|
|
1452
1471
|
};
|
|
1453
1472
|
}
|
|
1473
|
+
async function repairSecretEnvMappings(options = {}) {
|
|
1474
|
+
const loadedManifest = await loadManifest3({
|
|
1475
|
+
...options.root ? { root: options.root } : {},
|
|
1476
|
+
...options.cwd ? { cwd: options.cwd } : {},
|
|
1477
|
+
...options.processEnv ? { processEnv: options.processEnv } : {},
|
|
1478
|
+
...options.cacheMode ? { cacheMode: options.cacheMode } : {},
|
|
1479
|
+
...typeof options.cacheTtlSeconds === "number" ? { cacheTtlSeconds: options.cacheTtlSeconds } : {},
|
|
1480
|
+
...options.forceRefresh ? { forceRefresh: true } : {}
|
|
1481
|
+
});
|
|
1482
|
+
const explicit = loadedManifest.rawManifest.envMapping?.explicit ?? {};
|
|
1483
|
+
const removed = Object.entries(explicit).filter(([, logicalKey]) => logicalKey.startsWith("secret.")).map(([envVar, logicalKey]) => ({ envVar, logicalKey }));
|
|
1484
|
+
if (removed.length === 0) {
|
|
1485
|
+
return {
|
|
1486
|
+
manifestPath: loadedManifest.manifestPath,
|
|
1487
|
+
removed
|
|
1488
|
+
};
|
|
1489
|
+
}
|
|
1490
|
+
const nextExplicit = Object.fromEntries(
|
|
1491
|
+
Object.entries(explicit).filter(([, logicalKey]) => !logicalKey.startsWith("secret."))
|
|
1492
|
+
);
|
|
1493
|
+
const nextRawManifest = {
|
|
1494
|
+
...loadedManifest.rawManifest,
|
|
1495
|
+
envMapping: {
|
|
1496
|
+
...loadedManifest.rawManifest.envMapping ?? {},
|
|
1497
|
+
explicit: nextExplicit
|
|
1498
|
+
}
|
|
1499
|
+
};
|
|
1500
|
+
await writeFile4(loadedManifest.manifestPath, stringifyYaml3(nextRawManifest), "utf8");
|
|
1501
|
+
return {
|
|
1502
|
+
manifestPath: loadedManifest.manifestPath,
|
|
1503
|
+
removed
|
|
1504
|
+
};
|
|
1505
|
+
}
|
|
1454
1506
|
async function evaluateDoctor(options = {}) {
|
|
1455
1507
|
const root = resolveFilesystemBasePath(options.root, options.cwd ?? process.cwd());
|
|
1456
1508
|
const loadedManifest = await loadManifest3({
|
|
@@ -1508,15 +1560,22 @@ async function evaluateDoctor(options = {}) {
|
|
|
1508
1560
|
|
|
1509
1561
|
// src/commands/doctor.ts
|
|
1510
1562
|
async function runDoctor(options = {}) {
|
|
1563
|
+
const cliArgs = [...options.cliArgs ?? []];
|
|
1564
|
+
const shouldFixSecretEnvMappings = cliArgs.includes("--fix-secret-env-mappings");
|
|
1565
|
+
const repairResult = shouldFixSecretEnvMappings ? await repairSecretEnvMappings(options) : void 0;
|
|
1511
1566
|
const checks = await evaluateDoctor(options);
|
|
1512
1567
|
const hasFailures = checks.some((check) => !check.ok);
|
|
1513
1568
|
if (hasFailures) {
|
|
1514
1569
|
process.exitCode = 1;
|
|
1515
1570
|
}
|
|
1516
1571
|
if (options.json) {
|
|
1517
|
-
return printJson(
|
|
1572
|
+
return printJson({
|
|
1573
|
+
...repairResult ? { repair: repairResult } : {},
|
|
1574
|
+
checks
|
|
1575
|
+
});
|
|
1518
1576
|
}
|
|
1519
|
-
|
|
1577
|
+
const repairLine = repairResult ? repairResult.removed.length > 0 ? `REPAIRED secret-env-mappings: removed ${repairResult.removed.map((entry) => `${entry.envVar} -> ${entry.logicalKey}`).join(", ")}` : "REPAIRED secret-env-mappings: no secret env mappings found" : void 0;
|
|
1578
|
+
return [repairLine, ...checks.map((check) => `${check.ok ? "OK" : "FAIL"} ${check.name}: ${check.details}`)].filter((value) => Boolean(value)).join("\n");
|
|
1520
1579
|
}
|
|
1521
1580
|
|
|
1522
1581
|
// src/commands/dump.ts
|
|
@@ -2092,8 +2151,8 @@ var COMMANDS = [
|
|
|
2092
2151
|
{
|
|
2093
2152
|
id: "promote",
|
|
2094
2153
|
summary: "Promote shareable config into public or env projection surfaces.",
|
|
2095
|
-
usage: "cnos promote <key...> --to <public|env> [--as <ENV_VAR>] [global-options]",
|
|
2096
|
-
description: "Adds keys to public.promote or envMapping.explicit in .cnos/cnos.yml. Sensitive or non-shareable namespaces are rejected, but
|
|
2154
|
+
usage: "cnos promote <key...> --to <public|env> [--as <ENV_VAR>] [--allow-secret] [global-options]",
|
|
2155
|
+
description: "Adds keys to public.promote or envMapping.explicit in .cnos/cnos.yml. Sensitive or non-shareable namespaces are rejected by default, but secret.* may be mapped to env explicitly when you pass --allow-secret. public never allows secret promotion.",
|
|
2097
2156
|
options: [
|
|
2098
2157
|
{
|
|
2099
2158
|
flag: "--to <public|env>",
|
|
@@ -2102,23 +2161,30 @@ var COMMANDS = [
|
|
|
2102
2161
|
{
|
|
2103
2162
|
flag: "--as <ENV_VAR>",
|
|
2104
2163
|
description: "Required for --to env. Sets the exported env var name for the promoted key."
|
|
2164
|
+
},
|
|
2165
|
+
{
|
|
2166
|
+
flag: "--allow-secret",
|
|
2167
|
+
description: "Allow secret.* only for --to env. This does not permit secret promotion to public."
|
|
2105
2168
|
}
|
|
2106
2169
|
],
|
|
2107
2170
|
examples: [
|
|
2108
2171
|
"cnos promote value.flag.auth.upi_enabled --to public",
|
|
2109
2172
|
"cnos promote flags.upi_enabled --to public",
|
|
2110
|
-
"cnos promote value.server.port --to env --as PORT"
|
|
2173
|
+
"cnos promote value.server.port --to env --as PORT",
|
|
2174
|
+
"cnos promote secret.db.password --to env --as POSTGRES_PASSWORD --allow-secret"
|
|
2111
2175
|
]
|
|
2112
2176
|
},
|
|
2113
2177
|
{
|
|
2114
2178
|
id: "secret set",
|
|
2115
2179
|
summary: "Write a secret securely.",
|
|
2116
|
-
usage: "cnos secret set <path>
|
|
2117
|
-
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 an environment-backed vault is selected, CNOS writes an env-backed ref for CI or cloud runtimes.",
|
|
2180
|
+
usage: "cnos secret set <path> [value] [--local|--remote|--ref] [--vault <name>] [--provider <name>] [--stdin] [global-options]",
|
|
2181
|
+
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 an environment-backed vault is selected, CNOS writes an env-backed ref for CI or cloud runtimes. If [value] is omitted, CNOS prompts for a masked value interactively; use --stdin for pipelines.",
|
|
2118
2182
|
examples: [
|
|
2119
2183
|
"cnos vault create db",
|
|
2120
2184
|
"cnos vault auth db",
|
|
2121
2185
|
"cnos secret set app.token super-secret --vault db",
|
|
2186
|
+
"cnos secret set app.token --vault db",
|
|
2187
|
+
'printf "super-secret" | cnos secret set app.token --vault db --stdin',
|
|
2122
2188
|
"cnos vault create github-ci --provider environment --no-passphrase",
|
|
2123
2189
|
"cnos secret set app.token APP_TOKEN --vault github-ci"
|
|
2124
2190
|
]
|
|
@@ -2133,9 +2199,13 @@ var COMMANDS = [
|
|
|
2133
2199
|
{
|
|
2134
2200
|
id: "secret list",
|
|
2135
2201
|
summary: "List resolved secrets.",
|
|
2136
|
-
usage: "cnos secret list [--vault <name>] [--provider <name>] [global-options]",
|
|
2137
|
-
description: "Lists
|
|
2138
|
-
examples: [
|
|
2202
|
+
usage: "cnos secret list [--vault <name>] [--provider <name>] [--reveal] [global-options]",
|
|
2203
|
+
description: "Lists secret keys for the selected workspace and profile as masked values by default, or as resolved values when --reveal is supplied. Supports optional vault and provider filtering.",
|
|
2204
|
+
examples: [
|
|
2205
|
+
"cnos secret list --workspace api",
|
|
2206
|
+
"cnos secret list --vault github-ci",
|
|
2207
|
+
"cnos secret list --workspace api --reveal"
|
|
2208
|
+
]
|
|
2139
2209
|
},
|
|
2140
2210
|
{
|
|
2141
2211
|
id: "secret delete",
|
|
@@ -2220,7 +2290,7 @@ var COMMANDS = [
|
|
|
2220
2290
|
id: "build env",
|
|
2221
2291
|
summary: "Build a flat env-file artifact from CNOS.",
|
|
2222
2292
|
usage: "cnos build env --to <path> [--format <dotenv|docker-env|json|shell|toml|yaml>] [--reveal] [global-options]",
|
|
2223
|
-
description: "Builds a deterministic KEY=VALUE artifact for legacy build and runtime workflows. Secret env mappings stay masked by default; use --reveal only when the target env file is gitignored and you intentionally want concrete secret values.",
|
|
2293
|
+
description: "Builds a deterministic KEY=VALUE artifact for legacy build and runtime workflows. Secret env mappings stay masked by default; use --reveal only when the target env file is gitignored and you intentionally want concrete secret values. CNOS prints explicit risk warnings before revealed secret writes.",
|
|
2224
2294
|
options: [
|
|
2225
2295
|
{
|
|
2226
2296
|
flag: "--to <path>",
|
|
@@ -2472,9 +2542,9 @@ var COMMANDS = [
|
|
|
2472
2542
|
{
|
|
2473
2543
|
id: "doctor",
|
|
2474
2544
|
summary: "Run repository and workspace diagnostics.",
|
|
2475
|
-
usage: "cnos doctor [global-options]",
|
|
2476
|
-
description: "Checks manifest/workspace setup, gitignore coverage, and related diagnostics for the selected workspace.",
|
|
2477
|
-
examples: ["cnos doctor", "cnos doctor --workspace api --json"]
|
|
2545
|
+
usage: "cnos doctor [--fix-secret-env-mappings] [global-options]",
|
|
2546
|
+
description: "Checks manifest/workspace setup, gitignore coverage, and related diagnostics for the selected workspace. Secret env mappings are reported as a security risk; use --fix-secret-env-mappings to remove them from envMapping.explicit in one shot.",
|
|
2547
|
+
examples: ["cnos doctor", "cnos doctor --workspace api --json", "cnos doctor --fix-secret-env-mappings"]
|
|
2478
2548
|
},
|
|
2479
2549
|
{
|
|
2480
2550
|
id: "drift",
|
|
@@ -2747,7 +2817,7 @@ function runHelpAi(topic, cliArgs = []) {
|
|
|
2747
2817
|
import path11 from "path";
|
|
2748
2818
|
|
|
2749
2819
|
// src/services/scaffold.ts
|
|
2750
|
-
import { mkdir as mkdir4, readFile as readFile4, writeFile as
|
|
2820
|
+
import { mkdir as mkdir4, readFile as readFile4, writeFile as writeFile5 } from "fs/promises";
|
|
2751
2821
|
import path10 from "path";
|
|
2752
2822
|
function scaffoldManifest(projectName, options = {}) {
|
|
2753
2823
|
const mode = options.mode ?? "regular";
|
|
@@ -2791,7 +2861,7 @@ async function ensureFile(filePath, content) {
|
|
|
2791
2861
|
await readFile4(filePath, "utf8");
|
|
2792
2862
|
return false;
|
|
2793
2863
|
} catch {
|
|
2794
|
-
await
|
|
2864
|
+
await writeFile5(filePath, content, "utf8");
|
|
2795
2865
|
return true;
|
|
2796
2866
|
}
|
|
2797
2867
|
}
|
|
@@ -2819,7 +2889,7 @@ async function ensureGitignore(root) {
|
|
|
2819
2889
|
}
|
|
2820
2890
|
const prefix = current.trim().length > 0 ? `${current.trimEnd()}
|
|
2821
2891
|
` : "";
|
|
2822
|
-
await
|
|
2892
|
+
await writeFile5(gitignorePath, `${prefix}${missingEntries.join("\n")}
|
|
2823
2893
|
`, "utf8");
|
|
2824
2894
|
return true;
|
|
2825
2895
|
}
|
|
@@ -2995,6 +3065,45 @@ async function runInspect(key, options = {}) {
|
|
|
2995
3065
|
return printInspect(printable);
|
|
2996
3066
|
}
|
|
2997
3067
|
|
|
3068
|
+
// src/format/printTable.ts
|
|
3069
|
+
function stringifyCell(value) {
|
|
3070
|
+
if (value === void 0 || value === null) {
|
|
3071
|
+
return "";
|
|
3072
|
+
}
|
|
3073
|
+
if (typeof value === "string") {
|
|
3074
|
+
return value;
|
|
3075
|
+
}
|
|
3076
|
+
if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") {
|
|
3077
|
+
return String(value);
|
|
3078
|
+
}
|
|
3079
|
+
return JSON.stringify(value);
|
|
3080
|
+
}
|
|
3081
|
+
function printTable(rows) {
|
|
3082
|
+
if (rows.length === 0) {
|
|
3083
|
+
return "";
|
|
3084
|
+
}
|
|
3085
|
+
const columns = Array.from(
|
|
3086
|
+
rows.reduce((set, row) => {
|
|
3087
|
+
for (const key of Object.keys(row)) {
|
|
3088
|
+
set.add(key);
|
|
3089
|
+
}
|
|
3090
|
+
return set;
|
|
3091
|
+
}, /* @__PURE__ */ new Set())
|
|
3092
|
+
);
|
|
3093
|
+
const widths = columns.map(
|
|
3094
|
+
(column) => Math.max(
|
|
3095
|
+
column.length,
|
|
3096
|
+
...rows.map((row) => stringifyCell(row[column]).length)
|
|
3097
|
+
)
|
|
3098
|
+
);
|
|
3099
|
+
const renderRow = (row) => columns.map((column, index) => stringifyCell(row[column]).padEnd(widths[index], " ")).join(" ").trimEnd();
|
|
3100
|
+
return [
|
|
3101
|
+
columns.map((column, index) => column.padEnd(widths[index], " ")).join(" ").trimEnd(),
|
|
3102
|
+
widths.map((width) => "-".repeat(width)).join(" "),
|
|
3103
|
+
...rows.map(renderRow)
|
|
3104
|
+
].join("\n");
|
|
3105
|
+
}
|
|
3106
|
+
|
|
2998
3107
|
// src/format/printValue.ts
|
|
2999
3108
|
function printValue(value, json = false) {
|
|
3000
3109
|
if (json) {
|
|
@@ -3040,6 +3149,10 @@ function toStoredEntry(namespace, entry, filter = {}) {
|
|
|
3040
3149
|
return {
|
|
3041
3150
|
key: entry.key,
|
|
3042
3151
|
value: selectedCandidate.value,
|
|
3152
|
+
...namespace === "secret" ? {
|
|
3153
|
+
vault: selectedCandidate.metadata?.secretRef?.vault ?? "default",
|
|
3154
|
+
provider: selectedCandidate.metadata?.secretRef?.provider ?? "local"
|
|
3155
|
+
} : {},
|
|
3043
3156
|
...typeof selectedCandidate.value === "object" && selectedCandidate.value !== null && !Array.isArray(selectedCandidate.value) && "$derive" in selectedCandidate.value ? {
|
|
3044
3157
|
derived: true
|
|
3045
3158
|
} : {}
|
|
@@ -3050,20 +3163,35 @@ async function listStoredNamespace(namespace, options) {
|
|
|
3050
3163
|
...options,
|
|
3051
3164
|
...namespace === "secret" ? { secretResolution: "lazy" } : {}
|
|
3052
3165
|
});
|
|
3053
|
-
|
|
3166
|
+
const revealSecrets = namespace === "secret" && (options.cliArgs?.includes("--reveal") ?? false);
|
|
3167
|
+
const results = [];
|
|
3168
|
+
for (const entry of Array.from(runtime.graph.entries.values()).filter((candidate) => candidate.namespace === namespace)) {
|
|
3054
3169
|
const stored = toStoredEntry(namespace, entry, options);
|
|
3055
3170
|
if (!stored) {
|
|
3056
|
-
|
|
3171
|
+
continue;
|
|
3057
3172
|
}
|
|
3058
|
-
|
|
3173
|
+
const value = namespace === "secret" ? revealSecrets ? (await runtime.refreshSecret(entry.key), runtime.secret(entry.key.slice("secret.".length))) : maskSecretValue(stored.value) : stored.derived ? runtime.read(entry.key) : stored.value;
|
|
3174
|
+
if (value === void 0 || !matchesPrefix(stored.key, options.prefix)) {
|
|
3175
|
+
continue;
|
|
3176
|
+
}
|
|
3177
|
+
results.push({
|
|
3059
3178
|
...stored,
|
|
3060
|
-
value
|
|
3061
|
-
};
|
|
3062
|
-
}
|
|
3179
|
+
value
|
|
3180
|
+
});
|
|
3181
|
+
}
|
|
3182
|
+
return results.sort((left, right) => left.key.localeCompare(right.key));
|
|
3063
3183
|
}
|
|
3064
3184
|
function listProjectedNamespace(namespace, options) {
|
|
3065
|
-
return createRuntimeService(
|
|
3066
|
-
|
|
3185
|
+
return createRuntimeService({
|
|
3186
|
+
...options,
|
|
3187
|
+
...namespace === "env" ? { secretResolution: "lazy" } : {}
|
|
3188
|
+
}).then(async (runtime) => {
|
|
3189
|
+
const revealSecrets = options.cliArgs?.includes("--reveal") ?? false;
|
|
3190
|
+
const secretMappings = namespace === "env" ? getSecretEnvMappings(runtime) : [];
|
|
3191
|
+
if (namespace === "env" && revealSecrets && secretMappings.length > 0) {
|
|
3192
|
+
await hydrateSecretEnvMappings(runtime, secretMappings);
|
|
3193
|
+
}
|
|
3194
|
+
const projected = namespace === "meta" ? flattenObject2(runtime.toNamespace("meta")) : namespace === "env" ? revealSecrets ? runtime.toEnv({ includeSecrets: true }) : applyMaskedSecretEnvMappings(runtime.toEnv(), secretMappings) : namespace === "public" ? runtime.toPublicEnv({
|
|
3067
3195
|
...options.framework ? {
|
|
3068
3196
|
framework: options.framework
|
|
3069
3197
|
} : {}
|
|
@@ -3129,11 +3257,15 @@ async function runList(args = [], options = {}) {
|
|
|
3129
3257
|
const namespace = normalizeNamespace(args[0] ?? consumeOption(cliArgs, "--namespace"));
|
|
3130
3258
|
const prefix = consumeOption(cliArgs, "--prefix");
|
|
3131
3259
|
const framework = consumeOption(cliArgs, "--framework");
|
|
3260
|
+
const vault = consumeOption(cliArgs, "--vault");
|
|
3261
|
+
const provider = consumeOption(cliArgs, "--provider");
|
|
3132
3262
|
const entries = await listConfigEntries(namespace, {
|
|
3133
3263
|
...options,
|
|
3134
3264
|
cliArgs,
|
|
3135
3265
|
...prefix ? { prefix } : {},
|
|
3136
|
-
...framework ? { framework } : {}
|
|
3266
|
+
...framework ? { framework } : {},
|
|
3267
|
+
...vault ? { vault } : {},
|
|
3268
|
+
...provider ? { provider } : {}
|
|
3137
3269
|
});
|
|
3138
3270
|
if (options.json) {
|
|
3139
3271
|
return printJson(entries);
|
|
@@ -3141,6 +3273,16 @@ async function runList(args = [], options = {}) {
|
|
|
3141
3273
|
if (entries.length === 0) {
|
|
3142
3274
|
return "";
|
|
3143
3275
|
}
|
|
3276
|
+
if (namespace === "secret") {
|
|
3277
|
+
return printTable(
|
|
3278
|
+
entries.map((entry) => ({
|
|
3279
|
+
key: entry.key,
|
|
3280
|
+
value: printValue(entry.value),
|
|
3281
|
+
vault: entry.vault ?? "default",
|
|
3282
|
+
provider: entry.provider ?? "local"
|
|
3283
|
+
}))
|
|
3284
|
+
);
|
|
3285
|
+
}
|
|
3144
3286
|
return entries.map((entry) => `${entry.key}=${printValue(entry.value)}${entry.derived ? " (derived)" : ""}`).join("\n");
|
|
3145
3287
|
}
|
|
3146
3288
|
|
|
@@ -3603,9 +3745,9 @@ async function runOnboard(options = {}) {
|
|
|
3603
3745
|
import path17 from "path";
|
|
3604
3746
|
|
|
3605
3747
|
// src/services/context.ts
|
|
3606
|
-
import { readFile as readFile6, writeFile as
|
|
3748
|
+
import { readFile as readFile6, writeFile as writeFile6 } from "fs/promises";
|
|
3607
3749
|
import path15 from "path";
|
|
3608
|
-
import { parseYaml as parseYaml4, stringifyYaml as
|
|
3750
|
+
import { parseYaml as parseYaml4, stringifyYaml as stringifyYaml4 } from "@kitsy/cnos/internal";
|
|
3609
3751
|
async function loadCliContext(root = process.cwd()) {
|
|
3610
3752
|
const filePath = path15.join(path15.resolve(root), ".cnos-workspace.yml");
|
|
3611
3753
|
try {
|
|
@@ -3631,7 +3773,7 @@ async function saveCliContext(options = {}) {
|
|
|
3631
3773
|
...options.profile ? { profile: options.profile } : {},
|
|
3632
3774
|
...options.globalRoot ? { globalRoot: options.globalRoot } : {}
|
|
3633
3775
|
};
|
|
3634
|
-
await
|
|
3776
|
+
await writeFile6(filePath, stringifyYaml4(next), "utf8");
|
|
3635
3777
|
return {
|
|
3636
3778
|
filePath,
|
|
3637
3779
|
context: next
|
|
@@ -3639,9 +3781,9 @@ async function saveCliContext(options = {}) {
|
|
|
3639
3781
|
}
|
|
3640
3782
|
|
|
3641
3783
|
// src/services/profiles.ts
|
|
3642
|
-
import { mkdir as mkdir6, readdir as readdir4, readFile as readFile7, rm as rm3, writeFile as
|
|
3784
|
+
import { mkdir as mkdir6, readdir as readdir4, readFile as readFile7, rm as rm3, writeFile as writeFile7 } from "fs/promises";
|
|
3643
3785
|
import path16 from "path";
|
|
3644
|
-
import { loadManifest as loadManifest6, parseYaml as parseYaml5, stringifyYaml as
|
|
3786
|
+
import { loadManifest as loadManifest6, parseYaml as parseYaml5, stringifyYaml as stringifyYaml5 } from "@kitsy/cnos/internal";
|
|
3645
3787
|
async function resolveProfilesRoot(root = process.cwd()) {
|
|
3646
3788
|
try {
|
|
3647
3789
|
const loadedManifest = await loadManifest6({ root });
|
|
@@ -3667,7 +3809,7 @@ async function createProfileDefinition(root = process.cwd(), profile, inherit, o
|
|
|
3667
3809
|
} : {
|
|
3668
3810
|
name: profile
|
|
3669
3811
|
};
|
|
3670
|
-
await
|
|
3812
|
+
await writeFile7(filePath, stringifyYaml5(document), "utf8");
|
|
3671
3813
|
return {
|
|
3672
3814
|
filePath,
|
|
3673
3815
|
profile,
|
|
@@ -3790,11 +3932,11 @@ async function runProfile(args, options = {}) {
|
|
|
3790
3932
|
|
|
3791
3933
|
// src/commands/promote.ts
|
|
3792
3934
|
import path18 from "path";
|
|
3793
|
-
import { writeFile as
|
|
3935
|
+
import { writeFile as writeFile8 } from "fs/promises";
|
|
3794
3936
|
import {
|
|
3795
3937
|
ensureProjectionAllowed,
|
|
3796
3938
|
loadManifest as loadManifest7,
|
|
3797
|
-
stringifyYaml as
|
|
3939
|
+
stringifyYaml as stringifyYaml6
|
|
3798
3940
|
} from "@kitsy/cnos/internal";
|
|
3799
3941
|
function normalizeTarget(value) {
|
|
3800
3942
|
if (value === "public" || value === "env") {
|
|
@@ -3810,6 +3952,7 @@ async function runPromote(args = [], options = {}) {
|
|
|
3810
3952
|
const cliArgs = [...options.cliArgs ?? []];
|
|
3811
3953
|
const target = normalizeTarget(consumeOption(cliArgs, "--to"));
|
|
3812
3954
|
const alias = consumeOption(cliArgs, "--as");
|
|
3955
|
+
const allowSecret = cliArgs.includes("--allow-secret");
|
|
3813
3956
|
const keys = args.filter(Boolean);
|
|
3814
3957
|
if (keys.length === 0) {
|
|
3815
3958
|
throw new Error("promote requires at least one logical key");
|
|
@@ -3821,6 +3964,8 @@ async function runPromote(args = [], options = {}) {
|
|
|
3821
3964
|
if (!alias) {
|
|
3822
3965
|
throw new Error("promote --to env requires --as <ENV_VAR>");
|
|
3823
3966
|
}
|
|
3967
|
+
} else if (allowSecret) {
|
|
3968
|
+
throw new Error("--allow-secret is only supported with promote --to env");
|
|
3824
3969
|
}
|
|
3825
3970
|
const loadedManifest = await loadManifest7({
|
|
3826
3971
|
...options.root ? { root: options.root } : {},
|
|
@@ -3832,7 +3977,9 @@ async function runPromote(args = [], options = {}) {
|
|
|
3832
3977
|
});
|
|
3833
3978
|
await assertWritableConfigRoot(`promote ${keys.join(", ")}`, options);
|
|
3834
3979
|
for (const key of keys) {
|
|
3835
|
-
ensureProjectionAllowed(loadedManifest.manifest, key, target
|
|
3980
|
+
ensureProjectionAllowed(loadedManifest.manifest, key, target, {
|
|
3981
|
+
allowSecretForEnv: allowSecret && target === "env"
|
|
3982
|
+
});
|
|
3836
3983
|
}
|
|
3837
3984
|
const rawManifest = {
|
|
3838
3985
|
...loadedManifest.rawManifest
|
|
@@ -3853,7 +4000,7 @@ async function runPromote(args = [], options = {}) {
|
|
|
3853
4000
|
})
|
|
3854
4001
|
};
|
|
3855
4002
|
}
|
|
3856
|
-
await
|
|
4003
|
+
await writeFile8(loadedManifest.manifestPath, stringifyYaml6(rawManifest), "utf8");
|
|
3857
4004
|
if (options.json) {
|
|
3858
4005
|
return printJson({
|
|
3859
4006
|
target,
|
|
@@ -3862,7 +4009,7 @@ async function runPromote(args = [], options = {}) {
|
|
|
3862
4009
|
manifestPath: loadedManifest.manifestPath
|
|
3863
4010
|
});
|
|
3864
4011
|
}
|
|
3865
|
-
return target === "public" ? `promoted ${keys.join(", ")} to public in ${displayPath(loadedManifest.manifestPath, root)}` : `promoted ${keys[0]} to env as ${alias} in ${displayPath(loadedManifest.manifestPath, root)}`;
|
|
4012
|
+
return target === "public" ? `promoted ${keys.join(", ")} to public in ${displayPath(loadedManifest.manifestPath, root)}` : `promoted ${keys[0]} to env as ${alias}${allowSecret ? " with secret override" : ""} in ${displayPath(loadedManifest.manifestPath, root)}`;
|
|
3866
4013
|
}
|
|
3867
4014
|
|
|
3868
4015
|
// src/commands/read.ts
|
|
@@ -3987,12 +4134,14 @@ async function runCommand(command, options = {}) {
|
|
|
3987
4134
|
|
|
3988
4135
|
// src/commands/secret.ts
|
|
3989
4136
|
import path21 from "path";
|
|
4137
|
+
import readline3 from "readline";
|
|
4138
|
+
import { Writable } from "stream";
|
|
3990
4139
|
|
|
3991
4140
|
// src/commands/vault.ts
|
|
3992
4141
|
import path20 from "path";
|
|
3993
4142
|
|
|
3994
4143
|
// src/services/vaults.ts
|
|
3995
|
-
import { rm as rm4, writeFile as
|
|
4144
|
+
import { rm as rm4, writeFile as writeFile9 } from "fs/promises";
|
|
3996
4145
|
import path19 from "path";
|
|
3997
4146
|
import {
|
|
3998
4147
|
clearAllVaultSessionKeys,
|
|
@@ -4006,7 +4155,7 @@ import {
|
|
|
4006
4155
|
resolveSecretStoreRoot as resolveSecretStoreRoot2,
|
|
4007
4156
|
resolveVaultAuth as resolveVaultAuth2,
|
|
4008
4157
|
resolveVaultDefinition,
|
|
4009
|
-
stringifyYaml as
|
|
4158
|
+
stringifyYaml as stringifyYaml7,
|
|
4010
4159
|
writeKeychain,
|
|
4011
4160
|
writeVaultSessionKey
|
|
4012
4161
|
} from "@kitsy/cnos/internal";
|
|
@@ -4055,7 +4204,7 @@ async function createVaultDefinition(name, options = {}) {
|
|
|
4055
4204
|
[vault]: vaultDefinition
|
|
4056
4205
|
}
|
|
4057
4206
|
};
|
|
4058
|
-
await
|
|
4207
|
+
await writeFile9(loadedManifest.manifestPath, stringifyYaml7(rawManifest), "utf8");
|
|
4059
4208
|
const definition = resolveVaultDefinition({ [vault]: vaultDefinition }, vault);
|
|
4060
4209
|
if (provider === "local") {
|
|
4061
4210
|
const auth = await resolveVaultAuth2(vault, vaultDefinition, options.processEnv ?? process.env);
|
|
@@ -4121,7 +4270,7 @@ async function removeVaultDefinition(name, options = {}) {
|
|
|
4121
4270
|
if (Object.keys(nextVaults).length === 0) {
|
|
4122
4271
|
delete rawManifest.vaults;
|
|
4123
4272
|
}
|
|
4124
|
-
await
|
|
4273
|
+
await writeFile9(loadedManifest.manifestPath, stringifyYaml7(rawManifest), "utf8");
|
|
4125
4274
|
const vaultRoot = path19.join(resolveSecretStoreRoot2(options.processEnv), "vaults", vault);
|
|
4126
4275
|
let removedStore;
|
|
4127
4276
|
try {
|
|
@@ -4325,6 +4474,45 @@ async function readStdinValue() {
|
|
|
4325
4474
|
}
|
|
4326
4475
|
return Buffer.concat(chunks).toString("utf8").trimEnd();
|
|
4327
4476
|
}
|
|
4477
|
+
async function promptHiddenValue(message) {
|
|
4478
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
4479
|
+
throw new Error("Cannot prompt for a secret value in non-interactive mode. Pass <value> explicitly or use --stdin.");
|
|
4480
|
+
}
|
|
4481
|
+
const mutableStdout = new WritableMask();
|
|
4482
|
+
const rl = readline3.createInterface({
|
|
4483
|
+
input: process.stdin,
|
|
4484
|
+
output: mutableStdout,
|
|
4485
|
+
terminal: true
|
|
4486
|
+
});
|
|
4487
|
+
try {
|
|
4488
|
+
mutableStdout.muted = true;
|
|
4489
|
+
const value = await new Promise((resolve) => {
|
|
4490
|
+
rl.question(message, resolve);
|
|
4491
|
+
});
|
|
4492
|
+
process.stdout.write("\n");
|
|
4493
|
+
return value;
|
|
4494
|
+
} finally {
|
|
4495
|
+
rl.close();
|
|
4496
|
+
}
|
|
4497
|
+
}
|
|
4498
|
+
async function resolveSecretSetValue(secretPath, providedValue, stdin) {
|
|
4499
|
+
if (stdin) {
|
|
4500
|
+
return readStdinValue();
|
|
4501
|
+
}
|
|
4502
|
+
if (providedValue !== void 0) {
|
|
4503
|
+
return providedValue;
|
|
4504
|
+
}
|
|
4505
|
+
return promptHiddenValue(`Enter value for secret "${secretPath}": `);
|
|
4506
|
+
}
|
|
4507
|
+
var WritableMask = class extends Writable {
|
|
4508
|
+
muted = false;
|
|
4509
|
+
_write(chunk, _encoding, callback) {
|
|
4510
|
+
if (!this.muted) {
|
|
4511
|
+
process.stdout.write(chunk);
|
|
4512
|
+
}
|
|
4513
|
+
callback();
|
|
4514
|
+
}
|
|
4515
|
+
};
|
|
4328
4516
|
async function runSecret(argsOrPath, options = {}) {
|
|
4329
4517
|
const args = Array.isArray(argsOrPath) ? argsOrPath : [argsOrPath];
|
|
4330
4518
|
const { action, tail } = normalizeSecretCommand(args);
|
|
@@ -4337,34 +4525,27 @@ async function runSecret(argsOrPath, options = {}) {
|
|
|
4337
4525
|
return runVault(["create", tail[0] ?? "default"], options);
|
|
4338
4526
|
}
|
|
4339
4527
|
if (action === "list") {
|
|
4340
|
-
const runtime2 = await createRuntimeService({
|
|
4341
|
-
...options,
|
|
4342
|
-
secretResolution: "lazy"
|
|
4343
|
-
});
|
|
4344
4528
|
const prefix = consumeOption(cliArgs, "--prefix");
|
|
4345
4529
|
const vault = consumeOption(cliArgs, "--vault");
|
|
4346
4530
|
const provider = consumeOption(cliArgs, "--provider");
|
|
4347
|
-
const entries =
|
|
4348
|
-
|
|
4349
|
-
|
|
4350
|
-
|
|
4351
|
-
}
|
|
4352
|
-
|
|
4353
|
-
|
|
4354
|
-
}
|
|
4355
|
-
return true;
|
|
4356
|
-
}).map((entry2) => {
|
|
4357
|
-
const secretRef2 = entry2.winner.metadata?.secretRef;
|
|
4358
|
-
return {
|
|
4359
|
-
key: entry2.key,
|
|
4360
|
-
vault: secretRef2?.vault ?? "default",
|
|
4361
|
-
provider: secretRef2?.provider ?? "local"
|
|
4362
|
-
};
|
|
4363
|
-
}).sort((left, right) => left.key.localeCompare(right.key));
|
|
4531
|
+
const entries = await listConfigEntries("secret", {
|
|
4532
|
+
...options,
|
|
4533
|
+
cliArgs,
|
|
4534
|
+
...prefix ? { prefix } : {},
|
|
4535
|
+
...vault ? { vault } : {},
|
|
4536
|
+
...provider ? { provider } : {}
|
|
4537
|
+
});
|
|
4364
4538
|
if (options.json) {
|
|
4365
4539
|
return printJson(entries);
|
|
4366
4540
|
}
|
|
4367
|
-
return
|
|
4541
|
+
return printTable(
|
|
4542
|
+
entries.map((entry2) => ({
|
|
4543
|
+
key: entry2.key,
|
|
4544
|
+
value: printValue(entry2.value),
|
|
4545
|
+
vault: entry2.vault ?? "default",
|
|
4546
|
+
provider: entry2.provider ?? "local"
|
|
4547
|
+
}))
|
|
4548
|
+
);
|
|
4368
4549
|
}
|
|
4369
4550
|
if (action === "set") {
|
|
4370
4551
|
const secretPath2 = tail[0];
|
|
@@ -4376,8 +4557,9 @@ async function runSecret(argsOrPath, options = {}) {
|
|
|
4376
4557
|
const provider = consumeOption(cliArgs, "--provider");
|
|
4377
4558
|
const vault = consumeOption(cliArgs, "--vault") ?? "default";
|
|
4378
4559
|
const mode = local ? "local" : remote ? "remote" : ref ? "ref" : void 0;
|
|
4379
|
-
const
|
|
4380
|
-
const
|
|
4560
|
+
const resolvedSecretPath = secretPath2 ?? "app.token";
|
|
4561
|
+
const rawValue = await resolveSecretSetValue(resolvedSecretPath, tail[1], stdin);
|
|
4562
|
+
const result = await setSecret(resolvedSecretPath, rawValue, {
|
|
4381
4563
|
...options,
|
|
4382
4564
|
cliArgs,
|
|
4383
4565
|
target,
|
|
@@ -4486,6 +4668,16 @@ function writeJson(response, statusCode, payload) {
|
|
|
4486
4668
|
response.end(`${printJson(payload)}
|
|
4487
4669
|
`);
|
|
4488
4670
|
}
|
|
4671
|
+
async function readJsonBody(request) {
|
|
4672
|
+
const chunks = [];
|
|
4673
|
+
for await (const chunk of request) {
|
|
4674
|
+
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
4675
|
+
}
|
|
4676
|
+
if (chunks.length === 0) {
|
|
4677
|
+
return {};
|
|
4678
|
+
}
|
|
4679
|
+
return JSON.parse(Buffer.concat(chunks).toString("utf8"));
|
|
4680
|
+
}
|
|
4489
4681
|
function maskInspectResult(key, value) {
|
|
4490
4682
|
if (!key.startsWith("secret.")) {
|
|
4491
4683
|
return value;
|
|
@@ -4517,6 +4709,24 @@ function toRuntimeOptionsFromQuery(baseOptions, searchParams) {
|
|
|
4517
4709
|
...profile ? { profile } : {}
|
|
4518
4710
|
};
|
|
4519
4711
|
}
|
|
4712
|
+
function toRuntimeOptionsFromBody(baseOptions, body) {
|
|
4713
|
+
const workspace = typeof body.workspace === "string" ? body.workspace.trim() : "";
|
|
4714
|
+
const profile = typeof body.profile === "string" ? body.profile.trim() : "";
|
|
4715
|
+
return {
|
|
4716
|
+
...baseOptions,
|
|
4717
|
+
...workspace ? { workspace } : {},
|
|
4718
|
+
...profile ? { profile } : {}
|
|
4719
|
+
};
|
|
4720
|
+
}
|
|
4721
|
+
function withUiPassphrase(processEnv, passphrase) {
|
|
4722
|
+
if (!passphrase?.trim()) {
|
|
4723
|
+
return processEnv;
|
|
4724
|
+
}
|
|
4725
|
+
return {
|
|
4726
|
+
...processEnv ?? process.env,
|
|
4727
|
+
CNOS_SECRET_PASSPHRASE: passphrase.trim()
|
|
4728
|
+
};
|
|
4729
|
+
}
|
|
4520
4730
|
async function handleSummary(options, searchParams) {
|
|
4521
4731
|
const runtimeOptions = toRuntimeOptionsFromQuery(options, searchParams);
|
|
4522
4732
|
const runtime = await createRuntimeService({
|
|
@@ -4562,21 +4772,62 @@ async function handleSummary(options, searchParams) {
|
|
|
4562
4772
|
vaults: Object.keys(runtime.manifest.vaults)
|
|
4563
4773
|
};
|
|
4564
4774
|
}
|
|
4775
|
+
async function handleRevealList(body, options) {
|
|
4776
|
+
const prefix = typeof body.prefix === "string" ? body.prefix.trim() : "";
|
|
4777
|
+
const passphrase = typeof body.passphrase === "string" ? body.passphrase : void 0;
|
|
4778
|
+
const runtimeOptions = toRuntimeOptionsFromBody(options, body);
|
|
4779
|
+
const runtime = await createRuntimeService({
|
|
4780
|
+
...runtimeOptions,
|
|
4781
|
+
processEnv: withUiPassphrase(options.processEnv, passphrase),
|
|
4782
|
+
secretResolution: "lazy"
|
|
4783
|
+
});
|
|
4784
|
+
const entries = [];
|
|
4785
|
+
for (const entry of Array.from(runtime.graph.entries.values()).filter((candidate) => candidate.namespace === "secret").filter((candidate) => {
|
|
4786
|
+
if (!prefix) {
|
|
4787
|
+
return true;
|
|
4788
|
+
}
|
|
4789
|
+
return candidate.key.startsWith(prefix) || candidate.key.split(".").slice(1).join(".").startsWith(prefix);
|
|
4790
|
+
}).sort((left, right) => left.key.localeCompare(right.key))) {
|
|
4791
|
+
await runtime.refreshSecret(entry.key);
|
|
4792
|
+
entries.push({
|
|
4793
|
+
key: entry.key,
|
|
4794
|
+
value: runtime.read(entry.key),
|
|
4795
|
+
...typeof entry.winner.value === "object" && entry.winner.value !== null && !Array.isArray(entry.winner.value) && "$derive" in entry.winner.value ? { derived: true } : {}
|
|
4796
|
+
});
|
|
4797
|
+
}
|
|
4798
|
+
return {
|
|
4799
|
+
namespace: "secret",
|
|
4800
|
+
entries
|
|
4801
|
+
};
|
|
4802
|
+
}
|
|
4803
|
+
async function handleRevealInspect(body, options) {
|
|
4804
|
+
const key = typeof body.key === "string" ? body.key.trim() : "";
|
|
4805
|
+
if (!key) {
|
|
4806
|
+
throw new Error("Missing key");
|
|
4807
|
+
}
|
|
4808
|
+
const passphrase = typeof body.passphrase === "string" ? body.passphrase : void 0;
|
|
4809
|
+
const runtimeOptions = toRuntimeOptionsFromBody(options, body);
|
|
4810
|
+
const runtime = await createRuntimeService({
|
|
4811
|
+
...runtimeOptions,
|
|
4812
|
+
processEnv: withUiPassphrase(options.processEnv, passphrase),
|
|
4813
|
+
...key.startsWith("secret.") ? { secretResolution: "lazy" } : {}
|
|
4814
|
+
});
|
|
4815
|
+
if (key.startsWith("secret.")) {
|
|
4816
|
+
await runtime.refreshSecret(key);
|
|
4817
|
+
}
|
|
4818
|
+
return runtime.inspect(key);
|
|
4819
|
+
}
|
|
4565
4820
|
async function handleRequest(request, response, options) {
|
|
4566
4821
|
const url = new URL(request.url ?? "/", "http://127.0.0.1");
|
|
4567
|
-
if (request.method
|
|
4568
|
-
writeJson(response, 405, { error: "Method not allowed" });
|
|
4569
|
-
return;
|
|
4570
|
-
}
|
|
4571
|
-
if (url.pathname === "/api/health") {
|
|
4822
|
+
if (request.method === "GET" && url.pathname === "/api/health") {
|
|
4572
4823
|
writeJson(response, 200, { ok: true });
|
|
4573
4824
|
return;
|
|
4574
4825
|
}
|
|
4575
|
-
if (url.pathname === "/api/summary") {
|
|
4826
|
+
if (request.method === "GET" && url.pathname === "/api/summary") {
|
|
4576
4827
|
writeJson(response, 200, await handleSummary(options, url.searchParams));
|
|
4577
4828
|
return;
|
|
4578
4829
|
}
|
|
4579
|
-
if (url.pathname === "/api/list") {
|
|
4830
|
+
if (request.method === "GET" && url.pathname === "/api/list") {
|
|
4580
4831
|
const namespace = url.searchParams.get("namespace") ?? "value";
|
|
4581
4832
|
const prefix = url.searchParams.get("prefix") ?? void 0;
|
|
4582
4833
|
const runtimeOptions = toRuntimeOptionsFromQuery(options, url.searchParams);
|
|
@@ -4591,7 +4842,7 @@ async function handleRequest(request, response, options) {
|
|
|
4591
4842
|
});
|
|
4592
4843
|
return;
|
|
4593
4844
|
}
|
|
4594
|
-
if (url.pathname === "/api/inspect") {
|
|
4845
|
+
if (request.method === "GET" && url.pathname === "/api/inspect") {
|
|
4595
4846
|
const key = url.searchParams.get("key");
|
|
4596
4847
|
if (!key) {
|
|
4597
4848
|
writeJson(response, 400, { error: "Missing key query parameter" });
|
|
@@ -4605,6 +4856,20 @@ async function handleRequest(request, response, options) {
|
|
|
4605
4856
|
writeJson(response, 200, maskInspectResult(key, runtime.inspect(key)));
|
|
4606
4857
|
return;
|
|
4607
4858
|
}
|
|
4859
|
+
if (request.method === "POST" && url.pathname === "/api/reveal/list") {
|
|
4860
|
+
const body = await readJsonBody(request);
|
|
4861
|
+
writeJson(response, 200, await handleRevealList(body, options));
|
|
4862
|
+
return;
|
|
4863
|
+
}
|
|
4864
|
+
if (request.method === "POST" && url.pathname === "/api/reveal/inspect") {
|
|
4865
|
+
const body = await readJsonBody(request);
|
|
4866
|
+
writeJson(response, 200, await handleRevealInspect(body, options));
|
|
4867
|
+
return;
|
|
4868
|
+
}
|
|
4869
|
+
if (request.method !== "GET" && request.method !== "POST") {
|
|
4870
|
+
writeJson(response, 405, { error: "Method not allowed" });
|
|
4871
|
+
return;
|
|
4872
|
+
}
|
|
4608
4873
|
writeJson(response, 404, { error: "Not found" });
|
|
4609
4874
|
}
|
|
4610
4875
|
function resolveUiUrl(host, port) {
|
|
@@ -4684,7 +4949,7 @@ async function runValidate(options = {}) {
|
|
|
4684
4949
|
// package.json
|
|
4685
4950
|
var package_default = {
|
|
4686
4951
|
name: "@kitsy/cnos-cli",
|
|
4687
|
-
version: "1.9.
|
|
4952
|
+
version: "1.9.2",
|
|
4688
4953
|
description: "CLI entry point and developer tooling for CNOS.",
|
|
4689
4954
|
type: "module",
|
|
4690
4955
|
main: "./dist/index.js",
|
|
@@ -4961,9 +5226,9 @@ async function runWatch(command, options = {}) {
|
|
|
4961
5226
|
}
|
|
4962
5227
|
|
|
4963
5228
|
// src/commands/workspace.ts
|
|
4964
|
-
import { cp, mkdir as mkdir7, readdir as readdir5, readFile as readFile8, rename, rm as rm5, stat as stat3, writeFile as
|
|
5229
|
+
import { cp, mkdir as mkdir7, readdir as readdir5, readFile as readFile8, rename, rm as rm5, stat as stat3, writeFile as writeFile10 } from "fs/promises";
|
|
4965
5230
|
import path25 from "path";
|
|
4966
|
-
import { loadManifest as loadManifest10, parseYaml as parseYaml6, stringifyYaml as
|
|
5231
|
+
import { loadManifest as loadManifest10, parseYaml as parseYaml6, stringifyYaml as stringifyYaml8 } from "@kitsy/cnos/internal";
|
|
4967
5232
|
async function exists2(targetPath) {
|
|
4968
5233
|
try {
|
|
4969
5234
|
await stat3(targetPath);
|
|
@@ -5002,9 +5267,9 @@ async function mergeWorkspaceRootsIntoStandalone(targetCnosRoot, sourceRoots) {
|
|
|
5002
5267
|
async function writeAnchor(packageRoot, manifestRoot, workspace) {
|
|
5003
5268
|
const relativeRoot = path25.relative(packageRoot, manifestRoot).replace(/\\/g, "/");
|
|
5004
5269
|
const rootValue = relativeRoot.length === 0 ? "./.cnos" : relativeRoot.startsWith(".") ? relativeRoot : `./${relativeRoot}`;
|
|
5005
|
-
await
|
|
5270
|
+
await writeFile10(
|
|
5006
5271
|
path25.join(packageRoot, ".cnosrc.yml"),
|
|
5007
|
-
|
|
5272
|
+
stringifyYaml8({
|
|
5008
5273
|
root: rootValue,
|
|
5009
5274
|
...workspace ? { workspace } : {}
|
|
5010
5275
|
}),
|
|
@@ -5054,9 +5319,9 @@ async function hasDirectConfigData(cnosRoot) {
|
|
|
5054
5319
|
async function updateRootAnchorToWorkspace(packageRoot, workspaceId) {
|
|
5055
5320
|
const anchorPath = path25.join(packageRoot, ".cnosrc.yml");
|
|
5056
5321
|
const current = await exists2(anchorPath) ? parseYaml6(await readFile8(anchorPath, "utf8")) : void 0;
|
|
5057
|
-
await
|
|
5322
|
+
await writeFile10(
|
|
5058
5323
|
anchorPath,
|
|
5059
|
-
|
|
5324
|
+
stringifyYaml8({
|
|
5060
5325
|
root: typeof current?.root === "string" ? current.root : "./.cnos",
|
|
5061
5326
|
workspace: workspaceId
|
|
5062
5327
|
}),
|
|
@@ -5066,9 +5331,9 @@ async function updateRootAnchorToWorkspace(packageRoot, workspaceId) {
|
|
|
5066
5331
|
async function updateWorkspaceContext(packageRoot, workspaceId) {
|
|
5067
5332
|
const workspacePath = path25.join(packageRoot, ".cnos-workspace.yml");
|
|
5068
5333
|
const current = await exists2(workspacePath) ? parseYaml6(await readFile8(workspacePath, "utf8")) : void 0;
|
|
5069
|
-
await
|
|
5334
|
+
await writeFile10(
|
|
5070
5335
|
workspacePath,
|
|
5071
|
-
|
|
5336
|
+
stringifyYaml8({
|
|
5072
5337
|
workspace: workspaceId,
|
|
5073
5338
|
...typeof current?.profile === "string" ? { profile: current.profile } : {},
|
|
5074
5339
|
...typeof current?.globalRoot === "string" ? { globalRoot: current.globalRoot } : { globalRoot: "~/.cnos" }
|
|
@@ -5097,9 +5362,9 @@ async function runDetach(packageRoot, options = {}) {
|
|
|
5097
5362
|
const localRoots = runtime.graph.workspace.workspaceRoots.filter((root) => root.scope === "local").map((root) => root.path);
|
|
5098
5363
|
await mkdir7(targetCnosRoot, { recursive: true });
|
|
5099
5364
|
await mergeWorkspaceRootsIntoStandalone(targetCnosRoot, localRoots);
|
|
5100
|
-
await
|
|
5365
|
+
await writeFile10(
|
|
5101
5366
|
path25.join(targetCnosRoot, "cnos.yml"),
|
|
5102
|
-
|
|
5367
|
+
stringifyYaml8(createDetachedManifest(loaded.rawManifest)),
|
|
5103
5368
|
"utf8"
|
|
5104
5369
|
);
|
|
5105
5370
|
const relativeRoot = path25.relative(packageRoot, loaded.manifestRoot).replace(/\\/g, "/");
|
|
@@ -5112,8 +5377,8 @@ async function runDetach(packageRoot, options = {}) {
|
|
|
5112
5377
|
workspace: loaded.anchoredWorkspace
|
|
5113
5378
|
}
|
|
5114
5379
|
};
|
|
5115
|
-
await
|
|
5116
|
-
await
|
|
5380
|
+
await writeFile10(path25.join(targetCnosRoot, ".detached"), stringifyYaml8(marker), "utf8");
|
|
5381
|
+
await writeFile10(path25.join(packageRoot, ".cnosrc.yml"), stringifyYaml8({ root: "./.cnos" }), "utf8");
|
|
5117
5382
|
if (options.json) {
|
|
5118
5383
|
return printJson({
|
|
5119
5384
|
packageRoot,
|
|
@@ -5160,7 +5425,7 @@ async function runAttach(packageRoot, options = {}) {
|
|
|
5160
5425
|
items[workspaceId] = items[workspaceId] ?? {};
|
|
5161
5426
|
workspaces.items = items;
|
|
5162
5427
|
rawManifest.workspaces = workspaces;
|
|
5163
|
-
await
|
|
5428
|
+
await writeFile10(path25.join(parentLoaded.manifestRoot, "cnos.yml"), stringifyYaml8(rawManifest), "utf8");
|
|
5164
5429
|
const archivePath = path25.join(packageRoot, ".cnos.detached.bak");
|
|
5165
5430
|
await rm5(archivePath, { recursive: true, force: true });
|
|
5166
5431
|
await rename(childCnosRoot, archivePath);
|
|
@@ -5242,7 +5507,7 @@ async function runEnable(manifestCwd, packageRoot, options = {}) {
|
|
|
5242
5507
|
base: {}
|
|
5243
5508
|
};
|
|
5244
5509
|
rawManifest.workspaces = rawWorkspaces;
|
|
5245
|
-
await
|
|
5510
|
+
await writeFile10(path25.join(cnosRoot, "cnos.yml"), stringifyYaml8(rawManifest), "utf8");
|
|
5246
5511
|
await updateRootAnchorToWorkspace(packageRoot, "base");
|
|
5247
5512
|
await updateWorkspaceContext(packageRoot, "base");
|
|
5248
5513
|
await ensureGitignore(path25.dirname(cnosRoot));
|
|
@@ -5295,7 +5560,7 @@ async function runAddOrScaffold(action, workspaceId, manifestCwd, packageRoot, o
|
|
|
5295
5560
|
rawManifest.workspaces = rawWorkspaces;
|
|
5296
5561
|
const workspaceRoot = path25.join(cnosRoot, "workspaces", workspaceId);
|
|
5297
5562
|
const created = await ensureWorkspaceLayout(cnosRoot, workspaceId);
|
|
5298
|
-
await
|
|
5563
|
+
await writeFile10(path25.join(cnosRoot, "cnos.yml"), stringifyYaml8(rawManifest), "utf8");
|
|
5299
5564
|
await ensureGitignore(path25.dirname(cnosRoot));
|
|
5300
5565
|
await writeAnchor(packageRoot, cnosRoot, workspaceId);
|
|
5301
5566
|
await updateWorkspaceContext(packageRoot, workspaceId);
|
|
@@ -5339,7 +5604,7 @@ async function runRemove(workspaceId, manifestCwd, options = {}) {
|
|
|
5339
5604
|
delete rawItems[workspaceId];
|
|
5340
5605
|
rawWorkspaces.items = rawItems;
|
|
5341
5606
|
rawManifest.workspaces = rawWorkspaces;
|
|
5342
|
-
await
|
|
5607
|
+
await writeFile10(path25.join(loaded.manifestRoot, "cnos.yml"), stringifyYaml8(rawManifest), "utf8");
|
|
5343
5608
|
await rm5(path25.join(loaded.manifestRoot, "workspaces", workspaceId), { recursive: true, force: true });
|
|
5344
5609
|
if (options.json) {
|
|
5345
5610
|
return printJson({
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kitsy/cnos-cli",
|
|
3
|
-
"version": "1.9.
|
|
3
|
+
"version": "1.9.2",
|
|
4
4
|
"description": "CLI entry point and developer tooling for CNOS.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -37,8 +37,8 @@
|
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|
|
39
39
|
"smol-toml": "^1.4.2",
|
|
40
|
-
"@kitsy/cnos": "1.9.
|
|
41
|
-
"@kitsy/cnos
|
|
40
|
+
"@kitsy/cnos-ui": "1.9.2",
|
|
41
|
+
"@kitsy/cnos": "1.9.2"
|
|
42
42
|
},
|
|
43
43
|
"scripts": {
|
|
44
44
|
"build": "tsup src/index.ts --format esm --dts",
|