@kitsy/cnos-cli 1.9.0 → 1.9.1
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 +320 -99
- 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,12 +2161,17 @@ 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
|
{
|
|
@@ -2133,9 +2197,13 @@ var COMMANDS = [
|
|
|
2133
2197
|
{
|
|
2134
2198
|
id: "secret list",
|
|
2135
2199
|
summary: "List resolved secrets.",
|
|
2136
|
-
usage: "cnos secret list [--vault <name>] [--provider <name>] [global-options]",
|
|
2137
|
-
description: "Lists
|
|
2138
|
-
examples: [
|
|
2200
|
+
usage: "cnos secret list [--vault <name>] [--provider <name>] [--reveal] [global-options]",
|
|
2201
|
+
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.",
|
|
2202
|
+
examples: [
|
|
2203
|
+
"cnos secret list --workspace api",
|
|
2204
|
+
"cnos secret list --vault github-ci",
|
|
2205
|
+
"cnos secret list --workspace api --reveal"
|
|
2206
|
+
]
|
|
2139
2207
|
},
|
|
2140
2208
|
{
|
|
2141
2209
|
id: "secret delete",
|
|
@@ -2220,7 +2288,7 @@ var COMMANDS = [
|
|
|
2220
2288
|
id: "build env",
|
|
2221
2289
|
summary: "Build a flat env-file artifact from CNOS.",
|
|
2222
2290
|
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.",
|
|
2291
|
+
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
2292
|
options: [
|
|
2225
2293
|
{
|
|
2226
2294
|
flag: "--to <path>",
|
|
@@ -2472,9 +2540,9 @@ var COMMANDS = [
|
|
|
2472
2540
|
{
|
|
2473
2541
|
id: "doctor",
|
|
2474
2542
|
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"]
|
|
2543
|
+
usage: "cnos doctor [--fix-secret-env-mappings] [global-options]",
|
|
2544
|
+
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.",
|
|
2545
|
+
examples: ["cnos doctor", "cnos doctor --workspace api --json", "cnos doctor --fix-secret-env-mappings"]
|
|
2478
2546
|
},
|
|
2479
2547
|
{
|
|
2480
2548
|
id: "drift",
|
|
@@ -2747,7 +2815,7 @@ function runHelpAi(topic, cliArgs = []) {
|
|
|
2747
2815
|
import path11 from "path";
|
|
2748
2816
|
|
|
2749
2817
|
// src/services/scaffold.ts
|
|
2750
|
-
import { mkdir as mkdir4, readFile as readFile4, writeFile as
|
|
2818
|
+
import { mkdir as mkdir4, readFile as readFile4, writeFile as writeFile5 } from "fs/promises";
|
|
2751
2819
|
import path10 from "path";
|
|
2752
2820
|
function scaffoldManifest(projectName, options = {}) {
|
|
2753
2821
|
const mode = options.mode ?? "regular";
|
|
@@ -2791,7 +2859,7 @@ async function ensureFile(filePath, content) {
|
|
|
2791
2859
|
await readFile4(filePath, "utf8");
|
|
2792
2860
|
return false;
|
|
2793
2861
|
} catch {
|
|
2794
|
-
await
|
|
2862
|
+
await writeFile5(filePath, content, "utf8");
|
|
2795
2863
|
return true;
|
|
2796
2864
|
}
|
|
2797
2865
|
}
|
|
@@ -2819,7 +2887,7 @@ async function ensureGitignore(root) {
|
|
|
2819
2887
|
}
|
|
2820
2888
|
const prefix = current.trim().length > 0 ? `${current.trimEnd()}
|
|
2821
2889
|
` : "";
|
|
2822
|
-
await
|
|
2890
|
+
await writeFile5(gitignorePath, `${prefix}${missingEntries.join("\n")}
|
|
2823
2891
|
`, "utf8");
|
|
2824
2892
|
return true;
|
|
2825
2893
|
}
|
|
@@ -2995,6 +3063,45 @@ async function runInspect(key, options = {}) {
|
|
|
2995
3063
|
return printInspect(printable);
|
|
2996
3064
|
}
|
|
2997
3065
|
|
|
3066
|
+
// src/format/printTable.ts
|
|
3067
|
+
function stringifyCell(value) {
|
|
3068
|
+
if (value === void 0 || value === null) {
|
|
3069
|
+
return "";
|
|
3070
|
+
}
|
|
3071
|
+
if (typeof value === "string") {
|
|
3072
|
+
return value;
|
|
3073
|
+
}
|
|
3074
|
+
if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") {
|
|
3075
|
+
return String(value);
|
|
3076
|
+
}
|
|
3077
|
+
return JSON.stringify(value);
|
|
3078
|
+
}
|
|
3079
|
+
function printTable(rows) {
|
|
3080
|
+
if (rows.length === 0) {
|
|
3081
|
+
return "";
|
|
3082
|
+
}
|
|
3083
|
+
const columns = Array.from(
|
|
3084
|
+
rows.reduce((set, row) => {
|
|
3085
|
+
for (const key of Object.keys(row)) {
|
|
3086
|
+
set.add(key);
|
|
3087
|
+
}
|
|
3088
|
+
return set;
|
|
3089
|
+
}, /* @__PURE__ */ new Set())
|
|
3090
|
+
);
|
|
3091
|
+
const widths = columns.map(
|
|
3092
|
+
(column) => Math.max(
|
|
3093
|
+
column.length,
|
|
3094
|
+
...rows.map((row) => stringifyCell(row[column]).length)
|
|
3095
|
+
)
|
|
3096
|
+
);
|
|
3097
|
+
const renderRow = (row) => columns.map((column, index) => stringifyCell(row[column]).padEnd(widths[index], " ")).join(" ").trimEnd();
|
|
3098
|
+
return [
|
|
3099
|
+
columns.map((column, index) => column.padEnd(widths[index], " ")).join(" ").trimEnd(),
|
|
3100
|
+
widths.map((width) => "-".repeat(width)).join(" "),
|
|
3101
|
+
...rows.map(renderRow)
|
|
3102
|
+
].join("\n");
|
|
3103
|
+
}
|
|
3104
|
+
|
|
2998
3105
|
// src/format/printValue.ts
|
|
2999
3106
|
function printValue(value, json = false) {
|
|
3000
3107
|
if (json) {
|
|
@@ -3040,6 +3147,10 @@ function toStoredEntry(namespace, entry, filter = {}) {
|
|
|
3040
3147
|
return {
|
|
3041
3148
|
key: entry.key,
|
|
3042
3149
|
value: selectedCandidate.value,
|
|
3150
|
+
...namespace === "secret" ? {
|
|
3151
|
+
vault: selectedCandidate.metadata?.secretRef?.vault ?? "default",
|
|
3152
|
+
provider: selectedCandidate.metadata?.secretRef?.provider ?? "local"
|
|
3153
|
+
} : {},
|
|
3043
3154
|
...typeof selectedCandidate.value === "object" && selectedCandidate.value !== null && !Array.isArray(selectedCandidate.value) && "$derive" in selectedCandidate.value ? {
|
|
3044
3155
|
derived: true
|
|
3045
3156
|
} : {}
|
|
@@ -3050,20 +3161,35 @@ async function listStoredNamespace(namespace, options) {
|
|
|
3050
3161
|
...options,
|
|
3051
3162
|
...namespace === "secret" ? { secretResolution: "lazy" } : {}
|
|
3052
3163
|
});
|
|
3053
|
-
|
|
3164
|
+
const revealSecrets = namespace === "secret" && (options.cliArgs?.includes("--reveal") ?? false);
|
|
3165
|
+
const results = [];
|
|
3166
|
+
for (const entry of Array.from(runtime.graph.entries.values()).filter((candidate) => candidate.namespace === namespace)) {
|
|
3054
3167
|
const stored = toStoredEntry(namespace, entry, options);
|
|
3055
3168
|
if (!stored) {
|
|
3056
|
-
|
|
3169
|
+
continue;
|
|
3057
3170
|
}
|
|
3058
|
-
|
|
3171
|
+
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;
|
|
3172
|
+
if (value === void 0 || !matchesPrefix(stored.key, options.prefix)) {
|
|
3173
|
+
continue;
|
|
3174
|
+
}
|
|
3175
|
+
results.push({
|
|
3059
3176
|
...stored,
|
|
3060
|
-
value
|
|
3061
|
-
};
|
|
3062
|
-
}
|
|
3177
|
+
value
|
|
3178
|
+
});
|
|
3179
|
+
}
|
|
3180
|
+
return results.sort((left, right) => left.key.localeCompare(right.key));
|
|
3063
3181
|
}
|
|
3064
3182
|
function listProjectedNamespace(namespace, options) {
|
|
3065
|
-
return createRuntimeService(
|
|
3066
|
-
|
|
3183
|
+
return createRuntimeService({
|
|
3184
|
+
...options,
|
|
3185
|
+
...namespace === "env" ? { secretResolution: "lazy" } : {}
|
|
3186
|
+
}).then(async (runtime) => {
|
|
3187
|
+
const revealSecrets = options.cliArgs?.includes("--reveal") ?? false;
|
|
3188
|
+
const secretMappings = namespace === "env" ? getSecretEnvMappings(runtime) : [];
|
|
3189
|
+
if (namespace === "env" && revealSecrets && secretMappings.length > 0) {
|
|
3190
|
+
await hydrateSecretEnvMappings(runtime, secretMappings);
|
|
3191
|
+
}
|
|
3192
|
+
const projected = namespace === "meta" ? flattenObject2(runtime.toNamespace("meta")) : namespace === "env" ? revealSecrets ? runtime.toEnv({ includeSecrets: true }) : applyMaskedSecretEnvMappings(runtime.toEnv(), secretMappings) : namespace === "public" ? runtime.toPublicEnv({
|
|
3067
3193
|
...options.framework ? {
|
|
3068
3194
|
framework: options.framework
|
|
3069
3195
|
} : {}
|
|
@@ -3129,11 +3255,15 @@ async function runList(args = [], options = {}) {
|
|
|
3129
3255
|
const namespace = normalizeNamespace(args[0] ?? consumeOption(cliArgs, "--namespace"));
|
|
3130
3256
|
const prefix = consumeOption(cliArgs, "--prefix");
|
|
3131
3257
|
const framework = consumeOption(cliArgs, "--framework");
|
|
3258
|
+
const vault = consumeOption(cliArgs, "--vault");
|
|
3259
|
+
const provider = consumeOption(cliArgs, "--provider");
|
|
3132
3260
|
const entries = await listConfigEntries(namespace, {
|
|
3133
3261
|
...options,
|
|
3134
3262
|
cliArgs,
|
|
3135
3263
|
...prefix ? { prefix } : {},
|
|
3136
|
-
...framework ? { framework } : {}
|
|
3264
|
+
...framework ? { framework } : {},
|
|
3265
|
+
...vault ? { vault } : {},
|
|
3266
|
+
...provider ? { provider } : {}
|
|
3137
3267
|
});
|
|
3138
3268
|
if (options.json) {
|
|
3139
3269
|
return printJson(entries);
|
|
@@ -3141,6 +3271,16 @@ async function runList(args = [], options = {}) {
|
|
|
3141
3271
|
if (entries.length === 0) {
|
|
3142
3272
|
return "";
|
|
3143
3273
|
}
|
|
3274
|
+
if (namespace === "secret") {
|
|
3275
|
+
return printTable(
|
|
3276
|
+
entries.map((entry) => ({
|
|
3277
|
+
key: entry.key,
|
|
3278
|
+
value: printValue(entry.value),
|
|
3279
|
+
vault: entry.vault ?? "default",
|
|
3280
|
+
provider: entry.provider ?? "local"
|
|
3281
|
+
}))
|
|
3282
|
+
);
|
|
3283
|
+
}
|
|
3144
3284
|
return entries.map((entry) => `${entry.key}=${printValue(entry.value)}${entry.derived ? " (derived)" : ""}`).join("\n");
|
|
3145
3285
|
}
|
|
3146
3286
|
|
|
@@ -3603,9 +3743,9 @@ async function runOnboard(options = {}) {
|
|
|
3603
3743
|
import path17 from "path";
|
|
3604
3744
|
|
|
3605
3745
|
// src/services/context.ts
|
|
3606
|
-
import { readFile as readFile6, writeFile as
|
|
3746
|
+
import { readFile as readFile6, writeFile as writeFile6 } from "fs/promises";
|
|
3607
3747
|
import path15 from "path";
|
|
3608
|
-
import { parseYaml as parseYaml4, stringifyYaml as
|
|
3748
|
+
import { parseYaml as parseYaml4, stringifyYaml as stringifyYaml4 } from "@kitsy/cnos/internal";
|
|
3609
3749
|
async function loadCliContext(root = process.cwd()) {
|
|
3610
3750
|
const filePath = path15.join(path15.resolve(root), ".cnos-workspace.yml");
|
|
3611
3751
|
try {
|
|
@@ -3631,7 +3771,7 @@ async function saveCliContext(options = {}) {
|
|
|
3631
3771
|
...options.profile ? { profile: options.profile } : {},
|
|
3632
3772
|
...options.globalRoot ? { globalRoot: options.globalRoot } : {}
|
|
3633
3773
|
};
|
|
3634
|
-
await
|
|
3774
|
+
await writeFile6(filePath, stringifyYaml4(next), "utf8");
|
|
3635
3775
|
return {
|
|
3636
3776
|
filePath,
|
|
3637
3777
|
context: next
|
|
@@ -3639,9 +3779,9 @@ async function saveCliContext(options = {}) {
|
|
|
3639
3779
|
}
|
|
3640
3780
|
|
|
3641
3781
|
// src/services/profiles.ts
|
|
3642
|
-
import { mkdir as mkdir6, readdir as readdir4, readFile as readFile7, rm as rm3, writeFile as
|
|
3782
|
+
import { mkdir as mkdir6, readdir as readdir4, readFile as readFile7, rm as rm3, writeFile as writeFile7 } from "fs/promises";
|
|
3643
3783
|
import path16 from "path";
|
|
3644
|
-
import { loadManifest as loadManifest6, parseYaml as parseYaml5, stringifyYaml as
|
|
3784
|
+
import { loadManifest as loadManifest6, parseYaml as parseYaml5, stringifyYaml as stringifyYaml5 } from "@kitsy/cnos/internal";
|
|
3645
3785
|
async function resolveProfilesRoot(root = process.cwd()) {
|
|
3646
3786
|
try {
|
|
3647
3787
|
const loadedManifest = await loadManifest6({ root });
|
|
@@ -3667,7 +3807,7 @@ async function createProfileDefinition(root = process.cwd(), profile, inherit, o
|
|
|
3667
3807
|
} : {
|
|
3668
3808
|
name: profile
|
|
3669
3809
|
};
|
|
3670
|
-
await
|
|
3810
|
+
await writeFile7(filePath, stringifyYaml5(document), "utf8");
|
|
3671
3811
|
return {
|
|
3672
3812
|
filePath,
|
|
3673
3813
|
profile,
|
|
@@ -3790,11 +3930,11 @@ async function runProfile(args, options = {}) {
|
|
|
3790
3930
|
|
|
3791
3931
|
// src/commands/promote.ts
|
|
3792
3932
|
import path18 from "path";
|
|
3793
|
-
import { writeFile as
|
|
3933
|
+
import { writeFile as writeFile8 } from "fs/promises";
|
|
3794
3934
|
import {
|
|
3795
3935
|
ensureProjectionAllowed,
|
|
3796
3936
|
loadManifest as loadManifest7,
|
|
3797
|
-
stringifyYaml as
|
|
3937
|
+
stringifyYaml as stringifyYaml6
|
|
3798
3938
|
} from "@kitsy/cnos/internal";
|
|
3799
3939
|
function normalizeTarget(value) {
|
|
3800
3940
|
if (value === "public" || value === "env") {
|
|
@@ -3810,6 +3950,7 @@ async function runPromote(args = [], options = {}) {
|
|
|
3810
3950
|
const cliArgs = [...options.cliArgs ?? []];
|
|
3811
3951
|
const target = normalizeTarget(consumeOption(cliArgs, "--to"));
|
|
3812
3952
|
const alias = consumeOption(cliArgs, "--as");
|
|
3953
|
+
const allowSecret = cliArgs.includes("--allow-secret");
|
|
3813
3954
|
const keys = args.filter(Boolean);
|
|
3814
3955
|
if (keys.length === 0) {
|
|
3815
3956
|
throw new Error("promote requires at least one logical key");
|
|
@@ -3821,6 +3962,8 @@ async function runPromote(args = [], options = {}) {
|
|
|
3821
3962
|
if (!alias) {
|
|
3822
3963
|
throw new Error("promote --to env requires --as <ENV_VAR>");
|
|
3823
3964
|
}
|
|
3965
|
+
} else if (allowSecret) {
|
|
3966
|
+
throw new Error("--allow-secret is only supported with promote --to env");
|
|
3824
3967
|
}
|
|
3825
3968
|
const loadedManifest = await loadManifest7({
|
|
3826
3969
|
...options.root ? { root: options.root } : {},
|
|
@@ -3832,7 +3975,9 @@ async function runPromote(args = [], options = {}) {
|
|
|
3832
3975
|
});
|
|
3833
3976
|
await assertWritableConfigRoot(`promote ${keys.join(", ")}`, options);
|
|
3834
3977
|
for (const key of keys) {
|
|
3835
|
-
ensureProjectionAllowed(loadedManifest.manifest, key, target
|
|
3978
|
+
ensureProjectionAllowed(loadedManifest.manifest, key, target, {
|
|
3979
|
+
allowSecretForEnv: allowSecret && target === "env"
|
|
3980
|
+
});
|
|
3836
3981
|
}
|
|
3837
3982
|
const rawManifest = {
|
|
3838
3983
|
...loadedManifest.rawManifest
|
|
@@ -3853,7 +3998,7 @@ async function runPromote(args = [], options = {}) {
|
|
|
3853
3998
|
})
|
|
3854
3999
|
};
|
|
3855
4000
|
}
|
|
3856
|
-
await
|
|
4001
|
+
await writeFile8(loadedManifest.manifestPath, stringifyYaml6(rawManifest), "utf8");
|
|
3857
4002
|
if (options.json) {
|
|
3858
4003
|
return printJson({
|
|
3859
4004
|
target,
|
|
@@ -3862,7 +4007,7 @@ async function runPromote(args = [], options = {}) {
|
|
|
3862
4007
|
manifestPath: loadedManifest.manifestPath
|
|
3863
4008
|
});
|
|
3864
4009
|
}
|
|
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)}`;
|
|
4010
|
+
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
4011
|
}
|
|
3867
4012
|
|
|
3868
4013
|
// src/commands/read.ts
|
|
@@ -3992,7 +4137,7 @@ import path21 from "path";
|
|
|
3992
4137
|
import path20 from "path";
|
|
3993
4138
|
|
|
3994
4139
|
// src/services/vaults.ts
|
|
3995
|
-
import { rm as rm4, writeFile as
|
|
4140
|
+
import { rm as rm4, writeFile as writeFile9 } from "fs/promises";
|
|
3996
4141
|
import path19 from "path";
|
|
3997
4142
|
import {
|
|
3998
4143
|
clearAllVaultSessionKeys,
|
|
@@ -4006,7 +4151,7 @@ import {
|
|
|
4006
4151
|
resolveSecretStoreRoot as resolveSecretStoreRoot2,
|
|
4007
4152
|
resolveVaultAuth as resolveVaultAuth2,
|
|
4008
4153
|
resolveVaultDefinition,
|
|
4009
|
-
stringifyYaml as
|
|
4154
|
+
stringifyYaml as stringifyYaml7,
|
|
4010
4155
|
writeKeychain,
|
|
4011
4156
|
writeVaultSessionKey
|
|
4012
4157
|
} from "@kitsy/cnos/internal";
|
|
@@ -4055,7 +4200,7 @@ async function createVaultDefinition(name, options = {}) {
|
|
|
4055
4200
|
[vault]: vaultDefinition
|
|
4056
4201
|
}
|
|
4057
4202
|
};
|
|
4058
|
-
await
|
|
4203
|
+
await writeFile9(loadedManifest.manifestPath, stringifyYaml7(rawManifest), "utf8");
|
|
4059
4204
|
const definition = resolveVaultDefinition({ [vault]: vaultDefinition }, vault);
|
|
4060
4205
|
if (provider === "local") {
|
|
4061
4206
|
const auth = await resolveVaultAuth2(vault, vaultDefinition, options.processEnv ?? process.env);
|
|
@@ -4121,7 +4266,7 @@ async function removeVaultDefinition(name, options = {}) {
|
|
|
4121
4266
|
if (Object.keys(nextVaults).length === 0) {
|
|
4122
4267
|
delete rawManifest.vaults;
|
|
4123
4268
|
}
|
|
4124
|
-
await
|
|
4269
|
+
await writeFile9(loadedManifest.manifestPath, stringifyYaml7(rawManifest), "utf8");
|
|
4125
4270
|
const vaultRoot = path19.join(resolveSecretStoreRoot2(options.processEnv), "vaults", vault);
|
|
4126
4271
|
let removedStore;
|
|
4127
4272
|
try {
|
|
@@ -4337,34 +4482,27 @@ async function runSecret(argsOrPath, options = {}) {
|
|
|
4337
4482
|
return runVault(["create", tail[0] ?? "default"], options);
|
|
4338
4483
|
}
|
|
4339
4484
|
if (action === "list") {
|
|
4340
|
-
const runtime2 = await createRuntimeService({
|
|
4341
|
-
...options,
|
|
4342
|
-
secretResolution: "lazy"
|
|
4343
|
-
});
|
|
4344
4485
|
const prefix = consumeOption(cliArgs, "--prefix");
|
|
4345
4486
|
const vault = consumeOption(cliArgs, "--vault");
|
|
4346
4487
|
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));
|
|
4488
|
+
const entries = await listConfigEntries("secret", {
|
|
4489
|
+
...options,
|
|
4490
|
+
cliArgs,
|
|
4491
|
+
...prefix ? { prefix } : {},
|
|
4492
|
+
...vault ? { vault } : {},
|
|
4493
|
+
...provider ? { provider } : {}
|
|
4494
|
+
});
|
|
4364
4495
|
if (options.json) {
|
|
4365
4496
|
return printJson(entries);
|
|
4366
4497
|
}
|
|
4367
|
-
return
|
|
4498
|
+
return printTable(
|
|
4499
|
+
entries.map((entry2) => ({
|
|
4500
|
+
key: entry2.key,
|
|
4501
|
+
value: printValue(entry2.value),
|
|
4502
|
+
vault: entry2.vault ?? "default",
|
|
4503
|
+
provider: entry2.provider ?? "local"
|
|
4504
|
+
}))
|
|
4505
|
+
);
|
|
4368
4506
|
}
|
|
4369
4507
|
if (action === "set") {
|
|
4370
4508
|
const secretPath2 = tail[0];
|
|
@@ -4486,6 +4624,16 @@ function writeJson(response, statusCode, payload) {
|
|
|
4486
4624
|
response.end(`${printJson(payload)}
|
|
4487
4625
|
`);
|
|
4488
4626
|
}
|
|
4627
|
+
async function readJsonBody(request) {
|
|
4628
|
+
const chunks = [];
|
|
4629
|
+
for await (const chunk of request) {
|
|
4630
|
+
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
4631
|
+
}
|
|
4632
|
+
if (chunks.length === 0) {
|
|
4633
|
+
return {};
|
|
4634
|
+
}
|
|
4635
|
+
return JSON.parse(Buffer.concat(chunks).toString("utf8"));
|
|
4636
|
+
}
|
|
4489
4637
|
function maskInspectResult(key, value) {
|
|
4490
4638
|
if (!key.startsWith("secret.")) {
|
|
4491
4639
|
return value;
|
|
@@ -4517,6 +4665,24 @@ function toRuntimeOptionsFromQuery(baseOptions, searchParams) {
|
|
|
4517
4665
|
...profile ? { profile } : {}
|
|
4518
4666
|
};
|
|
4519
4667
|
}
|
|
4668
|
+
function toRuntimeOptionsFromBody(baseOptions, body) {
|
|
4669
|
+
const workspace = typeof body.workspace === "string" ? body.workspace.trim() : "";
|
|
4670
|
+
const profile = typeof body.profile === "string" ? body.profile.trim() : "";
|
|
4671
|
+
return {
|
|
4672
|
+
...baseOptions,
|
|
4673
|
+
...workspace ? { workspace } : {},
|
|
4674
|
+
...profile ? { profile } : {}
|
|
4675
|
+
};
|
|
4676
|
+
}
|
|
4677
|
+
function withUiPassphrase(processEnv, passphrase) {
|
|
4678
|
+
if (!passphrase?.trim()) {
|
|
4679
|
+
return processEnv;
|
|
4680
|
+
}
|
|
4681
|
+
return {
|
|
4682
|
+
...processEnv ?? process.env,
|
|
4683
|
+
CNOS_SECRET_PASSPHRASE: passphrase.trim()
|
|
4684
|
+
};
|
|
4685
|
+
}
|
|
4520
4686
|
async function handleSummary(options, searchParams) {
|
|
4521
4687
|
const runtimeOptions = toRuntimeOptionsFromQuery(options, searchParams);
|
|
4522
4688
|
const runtime = await createRuntimeService({
|
|
@@ -4562,21 +4728,62 @@ async function handleSummary(options, searchParams) {
|
|
|
4562
4728
|
vaults: Object.keys(runtime.manifest.vaults)
|
|
4563
4729
|
};
|
|
4564
4730
|
}
|
|
4731
|
+
async function handleRevealList(body, options) {
|
|
4732
|
+
const prefix = typeof body.prefix === "string" ? body.prefix.trim() : "";
|
|
4733
|
+
const passphrase = typeof body.passphrase === "string" ? body.passphrase : void 0;
|
|
4734
|
+
const runtimeOptions = toRuntimeOptionsFromBody(options, body);
|
|
4735
|
+
const runtime = await createRuntimeService({
|
|
4736
|
+
...runtimeOptions,
|
|
4737
|
+
processEnv: withUiPassphrase(options.processEnv, passphrase),
|
|
4738
|
+
secretResolution: "lazy"
|
|
4739
|
+
});
|
|
4740
|
+
const entries = [];
|
|
4741
|
+
for (const entry of Array.from(runtime.graph.entries.values()).filter((candidate) => candidate.namespace === "secret").filter((candidate) => {
|
|
4742
|
+
if (!prefix) {
|
|
4743
|
+
return true;
|
|
4744
|
+
}
|
|
4745
|
+
return candidate.key.startsWith(prefix) || candidate.key.split(".").slice(1).join(".").startsWith(prefix);
|
|
4746
|
+
}).sort((left, right) => left.key.localeCompare(right.key))) {
|
|
4747
|
+
await runtime.refreshSecret(entry.key);
|
|
4748
|
+
entries.push({
|
|
4749
|
+
key: entry.key,
|
|
4750
|
+
value: runtime.read(entry.key),
|
|
4751
|
+
...typeof entry.winner.value === "object" && entry.winner.value !== null && !Array.isArray(entry.winner.value) && "$derive" in entry.winner.value ? { derived: true } : {}
|
|
4752
|
+
});
|
|
4753
|
+
}
|
|
4754
|
+
return {
|
|
4755
|
+
namespace: "secret",
|
|
4756
|
+
entries
|
|
4757
|
+
};
|
|
4758
|
+
}
|
|
4759
|
+
async function handleRevealInspect(body, options) {
|
|
4760
|
+
const key = typeof body.key === "string" ? body.key.trim() : "";
|
|
4761
|
+
if (!key) {
|
|
4762
|
+
throw new Error("Missing key");
|
|
4763
|
+
}
|
|
4764
|
+
const passphrase = typeof body.passphrase === "string" ? body.passphrase : void 0;
|
|
4765
|
+
const runtimeOptions = toRuntimeOptionsFromBody(options, body);
|
|
4766
|
+
const runtime = await createRuntimeService({
|
|
4767
|
+
...runtimeOptions,
|
|
4768
|
+
processEnv: withUiPassphrase(options.processEnv, passphrase),
|
|
4769
|
+
...key.startsWith("secret.") ? { secretResolution: "lazy" } : {}
|
|
4770
|
+
});
|
|
4771
|
+
if (key.startsWith("secret.")) {
|
|
4772
|
+
await runtime.refreshSecret(key);
|
|
4773
|
+
}
|
|
4774
|
+
return runtime.inspect(key);
|
|
4775
|
+
}
|
|
4565
4776
|
async function handleRequest(request, response, options) {
|
|
4566
4777
|
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") {
|
|
4778
|
+
if (request.method === "GET" && url.pathname === "/api/health") {
|
|
4572
4779
|
writeJson(response, 200, { ok: true });
|
|
4573
4780
|
return;
|
|
4574
4781
|
}
|
|
4575
|
-
if (url.pathname === "/api/summary") {
|
|
4782
|
+
if (request.method === "GET" && url.pathname === "/api/summary") {
|
|
4576
4783
|
writeJson(response, 200, await handleSummary(options, url.searchParams));
|
|
4577
4784
|
return;
|
|
4578
4785
|
}
|
|
4579
|
-
if (url.pathname === "/api/list") {
|
|
4786
|
+
if (request.method === "GET" && url.pathname === "/api/list") {
|
|
4580
4787
|
const namespace = url.searchParams.get("namespace") ?? "value";
|
|
4581
4788
|
const prefix = url.searchParams.get("prefix") ?? void 0;
|
|
4582
4789
|
const runtimeOptions = toRuntimeOptionsFromQuery(options, url.searchParams);
|
|
@@ -4591,7 +4798,7 @@ async function handleRequest(request, response, options) {
|
|
|
4591
4798
|
});
|
|
4592
4799
|
return;
|
|
4593
4800
|
}
|
|
4594
|
-
if (url.pathname === "/api/inspect") {
|
|
4801
|
+
if (request.method === "GET" && url.pathname === "/api/inspect") {
|
|
4595
4802
|
const key = url.searchParams.get("key");
|
|
4596
4803
|
if (!key) {
|
|
4597
4804
|
writeJson(response, 400, { error: "Missing key query parameter" });
|
|
@@ -4605,6 +4812,20 @@ async function handleRequest(request, response, options) {
|
|
|
4605
4812
|
writeJson(response, 200, maskInspectResult(key, runtime.inspect(key)));
|
|
4606
4813
|
return;
|
|
4607
4814
|
}
|
|
4815
|
+
if (request.method === "POST" && url.pathname === "/api/reveal/list") {
|
|
4816
|
+
const body = await readJsonBody(request);
|
|
4817
|
+
writeJson(response, 200, await handleRevealList(body, options));
|
|
4818
|
+
return;
|
|
4819
|
+
}
|
|
4820
|
+
if (request.method === "POST" && url.pathname === "/api/reveal/inspect") {
|
|
4821
|
+
const body = await readJsonBody(request);
|
|
4822
|
+
writeJson(response, 200, await handleRevealInspect(body, options));
|
|
4823
|
+
return;
|
|
4824
|
+
}
|
|
4825
|
+
if (request.method !== "GET" && request.method !== "POST") {
|
|
4826
|
+
writeJson(response, 405, { error: "Method not allowed" });
|
|
4827
|
+
return;
|
|
4828
|
+
}
|
|
4608
4829
|
writeJson(response, 404, { error: "Not found" });
|
|
4609
4830
|
}
|
|
4610
4831
|
function resolveUiUrl(host, port) {
|
|
@@ -4684,7 +4905,7 @@ async function runValidate(options = {}) {
|
|
|
4684
4905
|
// package.json
|
|
4685
4906
|
var package_default = {
|
|
4686
4907
|
name: "@kitsy/cnos-cli",
|
|
4687
|
-
version: "1.9.
|
|
4908
|
+
version: "1.9.1",
|
|
4688
4909
|
description: "CLI entry point and developer tooling for CNOS.",
|
|
4689
4910
|
type: "module",
|
|
4690
4911
|
main: "./dist/index.js",
|
|
@@ -4961,9 +5182,9 @@ async function runWatch(command, options = {}) {
|
|
|
4961
5182
|
}
|
|
4962
5183
|
|
|
4963
5184
|
// 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
|
|
5185
|
+
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
5186
|
import path25 from "path";
|
|
4966
|
-
import { loadManifest as loadManifest10, parseYaml as parseYaml6, stringifyYaml as
|
|
5187
|
+
import { loadManifest as loadManifest10, parseYaml as parseYaml6, stringifyYaml as stringifyYaml8 } from "@kitsy/cnos/internal";
|
|
4967
5188
|
async function exists2(targetPath) {
|
|
4968
5189
|
try {
|
|
4969
5190
|
await stat3(targetPath);
|
|
@@ -5002,9 +5223,9 @@ async function mergeWorkspaceRootsIntoStandalone(targetCnosRoot, sourceRoots) {
|
|
|
5002
5223
|
async function writeAnchor(packageRoot, manifestRoot, workspace) {
|
|
5003
5224
|
const relativeRoot = path25.relative(packageRoot, manifestRoot).replace(/\\/g, "/");
|
|
5004
5225
|
const rootValue = relativeRoot.length === 0 ? "./.cnos" : relativeRoot.startsWith(".") ? relativeRoot : `./${relativeRoot}`;
|
|
5005
|
-
await
|
|
5226
|
+
await writeFile10(
|
|
5006
5227
|
path25.join(packageRoot, ".cnosrc.yml"),
|
|
5007
|
-
|
|
5228
|
+
stringifyYaml8({
|
|
5008
5229
|
root: rootValue,
|
|
5009
5230
|
...workspace ? { workspace } : {}
|
|
5010
5231
|
}),
|
|
@@ -5054,9 +5275,9 @@ async function hasDirectConfigData(cnosRoot) {
|
|
|
5054
5275
|
async function updateRootAnchorToWorkspace(packageRoot, workspaceId) {
|
|
5055
5276
|
const anchorPath = path25.join(packageRoot, ".cnosrc.yml");
|
|
5056
5277
|
const current = await exists2(anchorPath) ? parseYaml6(await readFile8(anchorPath, "utf8")) : void 0;
|
|
5057
|
-
await
|
|
5278
|
+
await writeFile10(
|
|
5058
5279
|
anchorPath,
|
|
5059
|
-
|
|
5280
|
+
stringifyYaml8({
|
|
5060
5281
|
root: typeof current?.root === "string" ? current.root : "./.cnos",
|
|
5061
5282
|
workspace: workspaceId
|
|
5062
5283
|
}),
|
|
@@ -5066,9 +5287,9 @@ async function updateRootAnchorToWorkspace(packageRoot, workspaceId) {
|
|
|
5066
5287
|
async function updateWorkspaceContext(packageRoot, workspaceId) {
|
|
5067
5288
|
const workspacePath = path25.join(packageRoot, ".cnos-workspace.yml");
|
|
5068
5289
|
const current = await exists2(workspacePath) ? parseYaml6(await readFile8(workspacePath, "utf8")) : void 0;
|
|
5069
|
-
await
|
|
5290
|
+
await writeFile10(
|
|
5070
5291
|
workspacePath,
|
|
5071
|
-
|
|
5292
|
+
stringifyYaml8({
|
|
5072
5293
|
workspace: workspaceId,
|
|
5073
5294
|
...typeof current?.profile === "string" ? { profile: current.profile } : {},
|
|
5074
5295
|
...typeof current?.globalRoot === "string" ? { globalRoot: current.globalRoot } : { globalRoot: "~/.cnos" }
|
|
@@ -5097,9 +5318,9 @@ async function runDetach(packageRoot, options = {}) {
|
|
|
5097
5318
|
const localRoots = runtime.graph.workspace.workspaceRoots.filter((root) => root.scope === "local").map((root) => root.path);
|
|
5098
5319
|
await mkdir7(targetCnosRoot, { recursive: true });
|
|
5099
5320
|
await mergeWorkspaceRootsIntoStandalone(targetCnosRoot, localRoots);
|
|
5100
|
-
await
|
|
5321
|
+
await writeFile10(
|
|
5101
5322
|
path25.join(targetCnosRoot, "cnos.yml"),
|
|
5102
|
-
|
|
5323
|
+
stringifyYaml8(createDetachedManifest(loaded.rawManifest)),
|
|
5103
5324
|
"utf8"
|
|
5104
5325
|
);
|
|
5105
5326
|
const relativeRoot = path25.relative(packageRoot, loaded.manifestRoot).replace(/\\/g, "/");
|
|
@@ -5112,8 +5333,8 @@ async function runDetach(packageRoot, options = {}) {
|
|
|
5112
5333
|
workspace: loaded.anchoredWorkspace
|
|
5113
5334
|
}
|
|
5114
5335
|
};
|
|
5115
|
-
await
|
|
5116
|
-
await
|
|
5336
|
+
await writeFile10(path25.join(targetCnosRoot, ".detached"), stringifyYaml8(marker), "utf8");
|
|
5337
|
+
await writeFile10(path25.join(packageRoot, ".cnosrc.yml"), stringifyYaml8({ root: "./.cnos" }), "utf8");
|
|
5117
5338
|
if (options.json) {
|
|
5118
5339
|
return printJson({
|
|
5119
5340
|
packageRoot,
|
|
@@ -5160,7 +5381,7 @@ async function runAttach(packageRoot, options = {}) {
|
|
|
5160
5381
|
items[workspaceId] = items[workspaceId] ?? {};
|
|
5161
5382
|
workspaces.items = items;
|
|
5162
5383
|
rawManifest.workspaces = workspaces;
|
|
5163
|
-
await
|
|
5384
|
+
await writeFile10(path25.join(parentLoaded.manifestRoot, "cnos.yml"), stringifyYaml8(rawManifest), "utf8");
|
|
5164
5385
|
const archivePath = path25.join(packageRoot, ".cnos.detached.bak");
|
|
5165
5386
|
await rm5(archivePath, { recursive: true, force: true });
|
|
5166
5387
|
await rename(childCnosRoot, archivePath);
|
|
@@ -5242,7 +5463,7 @@ async function runEnable(manifestCwd, packageRoot, options = {}) {
|
|
|
5242
5463
|
base: {}
|
|
5243
5464
|
};
|
|
5244
5465
|
rawManifest.workspaces = rawWorkspaces;
|
|
5245
|
-
await
|
|
5466
|
+
await writeFile10(path25.join(cnosRoot, "cnos.yml"), stringifyYaml8(rawManifest), "utf8");
|
|
5246
5467
|
await updateRootAnchorToWorkspace(packageRoot, "base");
|
|
5247
5468
|
await updateWorkspaceContext(packageRoot, "base");
|
|
5248
5469
|
await ensureGitignore(path25.dirname(cnosRoot));
|
|
@@ -5295,7 +5516,7 @@ async function runAddOrScaffold(action, workspaceId, manifestCwd, packageRoot, o
|
|
|
5295
5516
|
rawManifest.workspaces = rawWorkspaces;
|
|
5296
5517
|
const workspaceRoot = path25.join(cnosRoot, "workspaces", workspaceId);
|
|
5297
5518
|
const created = await ensureWorkspaceLayout(cnosRoot, workspaceId);
|
|
5298
|
-
await
|
|
5519
|
+
await writeFile10(path25.join(cnosRoot, "cnos.yml"), stringifyYaml8(rawManifest), "utf8");
|
|
5299
5520
|
await ensureGitignore(path25.dirname(cnosRoot));
|
|
5300
5521
|
await writeAnchor(packageRoot, cnosRoot, workspaceId);
|
|
5301
5522
|
await updateWorkspaceContext(packageRoot, workspaceId);
|
|
@@ -5339,7 +5560,7 @@ async function runRemove(workspaceId, manifestCwd, options = {}) {
|
|
|
5339
5560
|
delete rawItems[workspaceId];
|
|
5340
5561
|
rawWorkspaces.items = rawItems;
|
|
5341
5562
|
rawManifest.workspaces = rawWorkspaces;
|
|
5342
|
-
await
|
|
5563
|
+
await writeFile10(path25.join(loaded.manifestRoot, "cnos.yml"), stringifyYaml8(rawManifest), "utf8");
|
|
5343
5564
|
await rm5(path25.join(loaded.manifestRoot, "workspaces", workspaceId), { recursive: true, force: true });
|
|
5344
5565
|
if (options.json) {
|
|
5345
5566
|
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.1",
|
|
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.1",
|
|
41
|
+
"@kitsy/cnos": "1.9.1"
|
|
42
42
|
},
|
|
43
43
|
"scripts": {
|
|
44
44
|
"build": "tsup src/index.ts --format esm --dts",
|