@kitsy/cnos-cli 1.8.4 → 1.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +532 -178
- package/package.json +3 -2
package/dist/index.js
CHANGED
|
@@ -27,6 +27,9 @@ var COMMAND_OPTION_KEYS_WITH_VALUE = /* @__PURE__ */ new Set([
|
|
|
27
27
|
"--expr",
|
|
28
28
|
"--extends",
|
|
29
29
|
"--workspaces",
|
|
30
|
+
"--host",
|
|
31
|
+
"--port",
|
|
32
|
+
"--api-port",
|
|
30
33
|
"--env",
|
|
31
34
|
"--yaml",
|
|
32
35
|
"--toml",
|
|
@@ -281,7 +284,7 @@ function printJson(value) {
|
|
|
281
284
|
|
|
282
285
|
// src/services/projections.ts
|
|
283
286
|
import { mkdir, writeFile } from "fs/promises";
|
|
284
|
-
import
|
|
287
|
+
import path4 from "path";
|
|
285
288
|
import { resolveBrowserData, resolveFrameworkEnv, resolveServerProjection } from "@kitsy/cnos/build";
|
|
286
289
|
import { stringifyYaml } from "@kitsy/cnos/internal";
|
|
287
290
|
|
|
@@ -313,6 +316,88 @@ function resolveFilesystemBasePath(root, cwd = process.cwd()) {
|
|
|
313
316
|
return path2.resolve(root);
|
|
314
317
|
}
|
|
315
318
|
|
|
319
|
+
// src/services/secretEnvBuild.ts
|
|
320
|
+
import { readFile } from "fs/promises";
|
|
321
|
+
import path3 from "path";
|
|
322
|
+
import readline from "readline/promises";
|
|
323
|
+
import { spawnSync } from "child_process";
|
|
324
|
+
function isInteractiveSession() {
|
|
325
|
+
return process.stdin.isTTY && process.stdout.isTTY && !process.env.CI;
|
|
326
|
+
}
|
|
327
|
+
function getSecretEnvMappings(runtime) {
|
|
328
|
+
return Object.entries(runtime.manifest.envMapping.explicit).filter(([, logicalKey]) => runtime.graph.entries.get(logicalKey)?.namespace === "secret").map(([envVar, logicalKey]) => ({
|
|
329
|
+
envVar,
|
|
330
|
+
logicalKey
|
|
331
|
+
})).sort((left, right) => left.envVar.localeCompare(right.envVar));
|
|
332
|
+
}
|
|
333
|
+
function resolveGitRoot(cwd) {
|
|
334
|
+
const result = spawnSync("git", ["rev-parse", "--show-toplevel"], {
|
|
335
|
+
cwd,
|
|
336
|
+
encoding: "utf8",
|
|
337
|
+
shell: process.platform === "win32"
|
|
338
|
+
});
|
|
339
|
+
if (result.status !== 0) {
|
|
340
|
+
return void 0;
|
|
341
|
+
}
|
|
342
|
+
const value = result.stdout.trim();
|
|
343
|
+
return value ? path3.resolve(value) : void 0;
|
|
344
|
+
}
|
|
345
|
+
function isGitIgnored(repoRoot, targetPath) {
|
|
346
|
+
const relativeTarget = path3.relative(repoRoot, targetPath);
|
|
347
|
+
if (!relativeTarget || relativeTarget.startsWith("..") || path3.isAbsolute(relativeTarget)) {
|
|
348
|
+
return false;
|
|
349
|
+
}
|
|
350
|
+
const result = spawnSync("git", ["check-ignore", "--quiet", "--no-index", relativeTarget], {
|
|
351
|
+
cwd: repoRoot,
|
|
352
|
+
encoding: "utf8",
|
|
353
|
+
shell: process.platform === "win32"
|
|
354
|
+
});
|
|
355
|
+
return result.status === 0;
|
|
356
|
+
}
|
|
357
|
+
async function assertSecretEnvTargetIsGitIgnored(targetPath, cwd) {
|
|
358
|
+
const repoRoot = resolveGitRoot(cwd);
|
|
359
|
+
if (!repoRoot) {
|
|
360
|
+
throw new Error(
|
|
361
|
+
`Cannot write revealed secrets to ${targetPath} because CNOS could not verify gitignore protection. Run inside a git repo or omit --reveal.`
|
|
362
|
+
);
|
|
363
|
+
}
|
|
364
|
+
const gitignorePath = path3.join(repoRoot, ".gitignore");
|
|
365
|
+
try {
|
|
366
|
+
await readFile(gitignorePath, "utf8");
|
|
367
|
+
} catch {
|
|
368
|
+
throw new Error(
|
|
369
|
+
`Cannot write revealed secrets to ${targetPath} because ${gitignorePath} is missing. Add a gitignored env target or omit --reveal.`
|
|
370
|
+
);
|
|
371
|
+
}
|
|
372
|
+
if (!isGitIgnored(repoRoot, targetPath)) {
|
|
373
|
+
const relativeTarget = path3.relative(repoRoot, targetPath).replace(/\\/g, "/");
|
|
374
|
+
throw new Error(
|
|
375
|
+
`Cannot write revealed secrets to ${targetPath} because ${relativeTarget} is not gitignored. Add an ignore rule first, then re-run cnos build env --reveal.`
|
|
376
|
+
);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
async function confirmSecretEnvBuild(targetPath, mappings) {
|
|
380
|
+
if (!isInteractiveSession()) {
|
|
381
|
+
return;
|
|
382
|
+
}
|
|
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
|
+
const rl = readline.createInterface({
|
|
388
|
+
input: process.stdin,
|
|
389
|
+
output: process.stdout
|
|
390
|
+
});
|
|
391
|
+
try {
|
|
392
|
+
const answer = (await rl.question("Continue writing secrets to this env artifact? [y/N] ")).trim().toLowerCase();
|
|
393
|
+
if (answer !== "y" && answer !== "yes") {
|
|
394
|
+
throw new Error("Aborted secret env build.");
|
|
395
|
+
}
|
|
396
|
+
} finally {
|
|
397
|
+
rl.close();
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
316
401
|
// src/services/projections.ts
|
|
317
402
|
function stringifyScalar(value) {
|
|
318
403
|
if (value === void 0 || value === null) {
|
|
@@ -351,8 +436,8 @@ function formatKeyValueMap(values, format) {
|
|
|
351
436
|
}
|
|
352
437
|
}
|
|
353
438
|
async function writeProjectionFile(to, output, root = process.cwd()) {
|
|
354
|
-
const targetPath =
|
|
355
|
-
await mkdir(
|
|
439
|
+
const targetPath = path4.resolve(root, to);
|
|
440
|
+
await mkdir(path4.dirname(targetPath), { recursive: true });
|
|
356
441
|
await writeFile(targetPath, output, "utf8");
|
|
357
442
|
return targetPath;
|
|
358
443
|
}
|
|
@@ -404,25 +489,32 @@ async function buildPublicProjectionArtifact(to, options = {}, format = "dotenv"
|
|
|
404
489
|
return { targetPath, output, env };
|
|
405
490
|
}
|
|
406
491
|
async function buildEnvProjectionArtifact(to, options = {}, format = "dotenv") {
|
|
492
|
+
const cliArgs = [...options.cliArgs ?? []];
|
|
493
|
+
const revealSecrets = cliArgs.includes("--reveal");
|
|
494
|
+
const basePath = resolveFilesystemBasePath(options.root, options.cwd ?? process.cwd());
|
|
495
|
+
const targetPath = path4.resolve(basePath, to);
|
|
407
496
|
const runtime = await createRuntimeService({
|
|
408
497
|
...options,
|
|
409
498
|
cacheMode: "build",
|
|
410
|
-
cliArgs
|
|
499
|
+
cliArgs,
|
|
500
|
+
secretResolution: "lazy"
|
|
411
501
|
});
|
|
412
|
-
const
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
502
|
+
const secretMappings = getSecretEnvMappings(runtime);
|
|
503
|
+
if (revealSecrets && secretMappings.length > 0) {
|
|
504
|
+
await assertSecretEnvTargetIsGitIgnored(targetPath, basePath);
|
|
505
|
+
await confirmSecretEnvBuild(targetPath, secretMappings);
|
|
506
|
+
}
|
|
507
|
+
const env = runtime.toEnv({
|
|
508
|
+
includeSecrets: revealSecrets
|
|
509
|
+
});
|
|
510
|
+
for (const { envVar } of secretMappings) {
|
|
511
|
+
if (!revealSecrets && !(envVar in env)) {
|
|
416
512
|
env[envVar] = "****";
|
|
417
513
|
}
|
|
418
514
|
}
|
|
419
515
|
const output = formatKeyValueMap(env, format);
|
|
420
|
-
const
|
|
421
|
-
|
|
422
|
-
output,
|
|
423
|
-
resolveFilesystemBasePath(options.root, options.cwd ?? process.cwd())
|
|
424
|
-
);
|
|
425
|
-
return { targetPath, output, env };
|
|
516
|
+
const writtenTargetPath = await writeProjectionFile(to, output, basePath);
|
|
517
|
+
return { targetPath: writtenTargetPath, output, env };
|
|
426
518
|
}
|
|
427
519
|
|
|
428
520
|
// src/commands/build.ts
|
|
@@ -490,7 +582,7 @@ async function runBuild(subcommand, options = {}) {
|
|
|
490
582
|
|
|
491
583
|
// src/services/cache.ts
|
|
492
584
|
import { readdir, rm, stat } from "fs/promises";
|
|
493
|
-
import
|
|
585
|
+
import path5 from "path";
|
|
494
586
|
import {
|
|
495
587
|
loadManifest,
|
|
496
588
|
parseGitUri,
|
|
@@ -507,7 +599,7 @@ async function computeDirectorySize(targetPath) {
|
|
|
507
599
|
}
|
|
508
600
|
const entries = await readdir(targetPath, { withFileTypes: true });
|
|
509
601
|
const sizes = await Promise.all(
|
|
510
|
-
entries.map((entry) => computeDirectorySize(
|
|
602
|
+
entries.map((entry) => computeDirectorySize(path5.join(targetPath, entry.name)))
|
|
511
603
|
);
|
|
512
604
|
return sizes.reduce((sum, value) => sum + value, 0);
|
|
513
605
|
} catch {
|
|
@@ -515,13 +607,13 @@ async function computeDirectorySize(targetPath) {
|
|
|
515
607
|
}
|
|
516
608
|
}
|
|
517
609
|
async function listCachedRoots(processEnv = process.env) {
|
|
518
|
-
const rootsDir =
|
|
610
|
+
const rootsDir = path5.join(resolveCnosCacheRoot(processEnv), "roots");
|
|
519
611
|
try {
|
|
520
612
|
const entries = await readdir(rootsDir, { withFileTypes: true });
|
|
521
613
|
const records = await Promise.all(
|
|
522
614
|
entries.filter((entry) => entry.isDirectory()).map(async (entry) => {
|
|
523
|
-
const cacheDir =
|
|
524
|
-
const metadata = await readRemoteRootCacheMetadata(
|
|
615
|
+
const cacheDir = path5.join(rootsDir, entry.name);
|
|
616
|
+
const metadata = await readRemoteRootCacheMetadata(path5.join(cacheDir, ".cnos-cache-meta.json"));
|
|
525
617
|
if (!metadata) {
|
|
526
618
|
return void 0;
|
|
527
619
|
}
|
|
@@ -646,11 +738,11 @@ async function runCache(args = [], options = {}) {
|
|
|
646
738
|
}
|
|
647
739
|
|
|
648
740
|
// src/commands/define.ts
|
|
649
|
-
import
|
|
741
|
+
import path7 from "path";
|
|
650
742
|
|
|
651
743
|
// src/services/writes.ts
|
|
652
|
-
import { mkdir as mkdir2, readFile, writeFile as writeFile2 } from "fs/promises";
|
|
653
|
-
import
|
|
744
|
+
import { mkdir as mkdir2, readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
|
|
745
|
+
import path6 from "path";
|
|
654
746
|
import {
|
|
655
747
|
getNamespaceDefinition,
|
|
656
748
|
normalizeDerivedValue,
|
|
@@ -730,7 +822,7 @@ function isSecretReference(value) {
|
|
|
730
822
|
}
|
|
731
823
|
async function readYamlDocument(filePath) {
|
|
732
824
|
try {
|
|
733
|
-
return parseYaml(await
|
|
825
|
+
return parseYaml(await readFile2(filePath, "utf8")) ?? {};
|
|
734
826
|
} catch {
|
|
735
827
|
return {};
|
|
736
828
|
}
|
|
@@ -794,7 +886,7 @@ async function defineValue(namespace, configPath, rawValue, options = {}) {
|
|
|
794
886
|
parsedValue = parseScalarValue(rawValue);
|
|
795
887
|
}
|
|
796
888
|
setNestedValue(document, configPath.split("."), parsedValue);
|
|
797
|
-
await mkdir2(
|
|
889
|
+
await mkdir2(path6.dirname(filePath), { recursive: true });
|
|
798
890
|
await writeFile2(filePath, stringifyYaml2(document), "utf8");
|
|
799
891
|
return {
|
|
800
892
|
filePath,
|
|
@@ -835,7 +927,7 @@ async function setSecret(configPath, rawValue, options = {}) {
|
|
|
835
927
|
};
|
|
836
928
|
}
|
|
837
929
|
setNestedValue(document, configPath.split("."), reference);
|
|
838
|
-
await mkdir2(
|
|
930
|
+
await mkdir2(path6.dirname(filePath), { recursive: true });
|
|
839
931
|
await writeFile2(filePath, stringifyYaml2(document), "utf8");
|
|
840
932
|
return {
|
|
841
933
|
filePath,
|
|
@@ -918,7 +1010,7 @@ async function deleteValue(namespace, configPath, options = {}) {
|
|
|
918
1010
|
// src/commands/define.ts
|
|
919
1011
|
async function runDefine(namespace, configPath, rawValue, options = {}) {
|
|
920
1012
|
const cliArgs = [...options.cliArgs ?? []];
|
|
921
|
-
const root =
|
|
1013
|
+
const root = path7.resolve(options.root ?? process.cwd());
|
|
922
1014
|
const target = consumeOption(cliArgs, "--target") ?? "local";
|
|
923
1015
|
const local = consumeFlag(cliArgs, "--local");
|
|
924
1016
|
const remote = consumeFlag(cliArgs, "--remote");
|
|
@@ -949,7 +1041,7 @@ async function runDefine(namespace, configPath, rawValue, options = {}) {
|
|
|
949
1041
|
|
|
950
1042
|
// src/services/envMaterialization.ts
|
|
951
1043
|
import { mkdir as mkdir3, writeFile as writeFile3 } from "fs/promises";
|
|
952
|
-
import
|
|
1044
|
+
import path8 from "path";
|
|
953
1045
|
function resolveEnvFromRuntime(runtime, cliArgs = []) {
|
|
954
1046
|
const args = [...cliArgs];
|
|
955
1047
|
const isPublic = consumeFlag(args, "--public");
|
|
@@ -976,11 +1068,11 @@ async function resolveMaterializedEnv(options = {}) {
|
|
|
976
1068
|
};
|
|
977
1069
|
}
|
|
978
1070
|
function resolveMaterializedEnvTarget(to, root = process.cwd()) {
|
|
979
|
-
return
|
|
1071
|
+
return path8.resolve(root, to);
|
|
980
1072
|
}
|
|
981
1073
|
async function writeMaterializedEnvFile(to, output, root = process.cwd()) {
|
|
982
1074
|
const targetPath = resolveMaterializedEnvTarget(to, root);
|
|
983
|
-
await mkdir3(
|
|
1075
|
+
await mkdir3(path8.dirname(targetPath), { recursive: true });
|
|
984
1076
|
await writeFile3(targetPath, output, "utf8");
|
|
985
1077
|
return targetPath;
|
|
986
1078
|
}
|
|
@@ -999,22 +1091,25 @@ async function materializeEnvToFile(to, options = {}) {
|
|
|
999
1091
|
|
|
1000
1092
|
// src/services/spawn.ts
|
|
1001
1093
|
import { spawn } from "child_process";
|
|
1002
|
-
function
|
|
1003
|
-
|
|
1004
|
-
return false;
|
|
1005
|
-
}
|
|
1006
|
-
return !/[\\/]/.test(command);
|
|
1094
|
+
function shouldUseWindowsCommandShim(command) {
|
|
1095
|
+
return process.platform === "win32" && !/[\\/]/.test(command);
|
|
1007
1096
|
}
|
|
1008
1097
|
function spawnCommand(command, options) {
|
|
1009
1098
|
const executable = command[0];
|
|
1010
1099
|
if (!executable) {
|
|
1011
1100
|
throw new Error("A command is required.");
|
|
1012
1101
|
}
|
|
1102
|
+
if (shouldUseWindowsCommandShim(executable)) {
|
|
1103
|
+
return spawn(process.env.ComSpec ?? "cmd.exe", ["/d", "/s", "/c", ...command], {
|
|
1104
|
+
cwd: options.cwd,
|
|
1105
|
+
env: options.env,
|
|
1106
|
+
stdio: options.stdio ?? "inherit"
|
|
1107
|
+
});
|
|
1108
|
+
}
|
|
1013
1109
|
return spawn(executable, command.slice(1), {
|
|
1014
1110
|
cwd: options.cwd,
|
|
1015
1111
|
env: options.env,
|
|
1016
|
-
stdio: options.stdio ?? "inherit"
|
|
1017
|
-
shell: shouldUseShellForCommand(executable)
|
|
1112
|
+
stdio: options.stdio ?? "inherit"
|
|
1018
1113
|
});
|
|
1019
1114
|
}
|
|
1020
1115
|
|
|
@@ -1238,8 +1333,8 @@ async function runDiff(leftProfile, rightProfile, options = {}) {
|
|
|
1238
1333
|
}
|
|
1239
1334
|
|
|
1240
1335
|
// src/services/doctor.ts
|
|
1241
|
-
import { readdir as readdir2, readFile as
|
|
1242
|
-
import
|
|
1336
|
+
import { readdir as readdir2, readFile as readFile3 } from "fs/promises";
|
|
1337
|
+
import path9 from "path";
|
|
1243
1338
|
import {
|
|
1244
1339
|
detectLegacyVaultFormat,
|
|
1245
1340
|
isSecretReference as isSecretReference2,
|
|
@@ -1262,7 +1357,7 @@ async function createValidationSummary(options = {}) {
|
|
|
1262
1357
|
|
|
1263
1358
|
// src/services/doctor.ts
|
|
1264
1359
|
async function checkGitignore(root) {
|
|
1265
|
-
const gitignorePath =
|
|
1360
|
+
const gitignorePath = path9.join(root, ".gitignore");
|
|
1266
1361
|
const expected = [
|
|
1267
1362
|
".cnos/env/.env",
|
|
1268
1363
|
".cnos/env/.env.*",
|
|
@@ -1274,7 +1369,7 @@ async function checkGitignore(root) {
|
|
|
1274
1369
|
"!.cnos/workspaces/*/env/.env.*.example"
|
|
1275
1370
|
];
|
|
1276
1371
|
try {
|
|
1277
|
-
const content = await
|
|
1372
|
+
const content = await readFile3(gitignorePath, "utf8");
|
|
1278
1373
|
const missing = expected.filter((entry) => !content.includes(entry));
|
|
1279
1374
|
return {
|
|
1280
1375
|
name: "gitignore",
|
|
@@ -1297,12 +1392,12 @@ async function collectYamlFiles(root) {
|
|
|
1297
1392
|
const entries = await readdir2(root, { withFileTypes: true });
|
|
1298
1393
|
const results = [];
|
|
1299
1394
|
for (const entry of entries) {
|
|
1300
|
-
const target =
|
|
1395
|
+
const target = path9.join(root, entry.name);
|
|
1301
1396
|
if (entry.isDirectory()) {
|
|
1302
1397
|
results.push(...await collectYamlFiles(target));
|
|
1303
1398
|
continue;
|
|
1304
1399
|
}
|
|
1305
|
-
if (entry.isFile() && [".yml", ".yaml"].includes(
|
|
1400
|
+
if (entry.isFile() && [".yml", ".yaml"].includes(path9.extname(entry.name).toLowerCase())) {
|
|
1306
1401
|
results.push(target);
|
|
1307
1402
|
}
|
|
1308
1403
|
}
|
|
@@ -1327,12 +1422,12 @@ async function checkSecretSecurity(options, runtime) {
|
|
|
1327
1422
|
);
|
|
1328
1423
|
const legacyDetected = legacyPaths.filter((entry) => Boolean(entry.path));
|
|
1329
1424
|
const secretFiles = await Promise.all(
|
|
1330
|
-
runtime.graph.workspace.workspaceRoots.filter((root) => root.scope === "local").map((root) => collectYamlFiles(
|
|
1425
|
+
runtime.graph.workspace.workspaceRoots.filter((root) => root.scope === "local").map((root) => collectYamlFiles(path9.join(root.path, "secrets")))
|
|
1331
1426
|
);
|
|
1332
1427
|
const plaintextFiles = [];
|
|
1333
1428
|
for (const file of secretFiles.flat()) {
|
|
1334
1429
|
try {
|
|
1335
|
-
const parsed = parseYaml2(await
|
|
1430
|
+
const parsed = parseYaml2(await readFile3(file, "utf8"));
|
|
1336
1431
|
if (hasPlaintextSecret(parsed)) {
|
|
1337
1432
|
plaintextFiles.push(file);
|
|
1338
1433
|
}
|
|
@@ -1851,20 +1946,20 @@ var COMMANDS = [
|
|
|
1851
1946
|
},
|
|
1852
1947
|
{
|
|
1853
1948
|
id: "vault auth",
|
|
1854
|
-
summary: "Authenticate a vault
|
|
1949
|
+
summary: "Authenticate a vault and cache reusable local auth state.",
|
|
1855
1950
|
usage: "cnos vault auth <name> [--store-keychain] [global-options]",
|
|
1856
|
-
description: "Authenticates an existing local vault using env, keychain, or prompt-based auth and stores a derived session key for later CNOS commands
|
|
1951
|
+
description: "Authenticates an existing local vault using env, keychain, or prompt-based auth and stores a derived session key under ~/.cnos/secrets/sessions for later CNOS commands until logout. With --store-keychain, CNOS also writes the derived key to the OS keychain.",
|
|
1857
1952
|
examples: ["cnos vault auth local-dev", "cnos vault auth local-dev --store-keychain"]
|
|
1858
1953
|
},
|
|
1859
1954
|
{
|
|
1860
1955
|
id: "vault logout",
|
|
1861
|
-
summary: "Clear vault auth state
|
|
1956
|
+
summary: "Clear cached vault auth state.",
|
|
1862
1957
|
usage: "cnos vault logout <name> [global-options]",
|
|
1863
|
-
description: "Removes
|
|
1958
|
+
description: "Removes cached vault session auth for the selected vault or all vaults when used with --all. This does not remove any stored OS keychain entry.",
|
|
1864
1959
|
options: [
|
|
1865
1960
|
{
|
|
1866
1961
|
flag: "--all",
|
|
1867
|
-
description: "Clear all
|
|
1962
|
+
description: "Clear all cached vault auth sessions from ~/.cnos/secrets/sessions."
|
|
1868
1963
|
}
|
|
1869
1964
|
],
|
|
1870
1965
|
examples: ["cnos vault logout local-dev", "cnos vault logout --all"]
|
|
@@ -2124,8 +2219,8 @@ var COMMANDS = [
|
|
|
2124
2219
|
{
|
|
2125
2220
|
id: "build env",
|
|
2126
2221
|
summary: "Build a flat env-file artifact from CNOS.",
|
|
2127
|
-
usage: "cnos build env --to <path> [--format <dotenv|docker-env|json|shell|toml|yaml>] [global-options]",
|
|
2128
|
-
description: "Builds a deterministic KEY=VALUE artifact for legacy build and runtime workflows.
|
|
2222
|
+
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.",
|
|
2129
2224
|
options: [
|
|
2130
2225
|
{
|
|
2131
2226
|
flag: "--to <path>",
|
|
@@ -2134,11 +2229,16 @@ var COMMANDS = [
|
|
|
2134
2229
|
{
|
|
2135
2230
|
flag: "--format <dotenv|docker-env|json|shell|toml|yaml>",
|
|
2136
2231
|
description: "Select the output format. Defaults to dotenv."
|
|
2232
|
+
},
|
|
2233
|
+
{
|
|
2234
|
+
flag: "--reveal",
|
|
2235
|
+
description: "Write concrete values for secret env mappings after gitignore verification and an interactive warning prompt."
|
|
2137
2236
|
}
|
|
2138
2237
|
],
|
|
2139
2238
|
examples: [
|
|
2140
2239
|
"cnos build env --profile local --to .env.local",
|
|
2141
2240
|
"cnos build env --profile stage --to .env.stage",
|
|
2241
|
+
"cnos build env --profile prod --reveal --to .env.production.local",
|
|
2142
2242
|
"cnos build env --profile prod --format yaml --to env.yaml"
|
|
2143
2243
|
]
|
|
2144
2244
|
},
|
|
@@ -2252,13 +2352,17 @@ var COMMANDS = [
|
|
|
2252
2352
|
{
|
|
2253
2353
|
id: "run",
|
|
2254
2354
|
summary: "Run a child process with CNOS env injected.",
|
|
2255
|
-
usage: "cnos run [--public] [--framework <name>] [--set <logical-key=value>] [global-options] -- <command...>",
|
|
2256
|
-
description: "Resolves the active workspace and profile, injects runtime env variables, bootstraps __CNOS_GRAPH__ for singleton runtime reads, and executes the command after --.",
|
|
2355
|
+
usage: "cnos run [--public] [--auth] [--framework <name>] [--set <logical-key=value>] [global-options] -- <command...>",
|
|
2356
|
+
description: "Resolves the active workspace and profile, injects runtime env variables, includes explicit secret env mappings for private runs, bootstraps __CNOS_GRAPH__ for singleton runtime reads, and executes the command after --.",
|
|
2257
2357
|
options: [
|
|
2258
2358
|
{
|
|
2259
2359
|
flag: "--set <logical-key=value>",
|
|
2260
2360
|
description: "Apply inline logical-key overrides for this run without touching repo config files."
|
|
2261
2361
|
},
|
|
2362
|
+
{
|
|
2363
|
+
flag: "--auth",
|
|
2364
|
+
description: "Resolve secrets eagerly and pass an encrypted secret payload to bootstrapped CNOS runtimes in the child process."
|
|
2365
|
+
},
|
|
2262
2366
|
{
|
|
2263
2367
|
flag: "--public",
|
|
2264
2368
|
description: "Inject only promoted public env variables into the child process."
|
|
@@ -2275,6 +2379,7 @@ var COMMANDS = [
|
|
|
2275
2379
|
examples: [
|
|
2276
2380
|
"cnos run -- node server.js",
|
|
2277
2381
|
"cnos run --profile stage -- node server.js",
|
|
2382
|
+
"cnos run --auth -- node server.js",
|
|
2278
2383
|
"cnos run --set value.server.port=9999 -- node server.js",
|
|
2279
2384
|
"cnos run --public --framework vite -- pnpm build"
|
|
2280
2385
|
]
|
|
@@ -2457,6 +2562,27 @@ var COMMANDS = [
|
|
|
2457
2562
|
],
|
|
2458
2563
|
examples: ["cnos help-ai --format json", "cnos help-ai export env --format json"]
|
|
2459
2564
|
},
|
|
2565
|
+
{
|
|
2566
|
+
id: "ui",
|
|
2567
|
+
summary: "Launch the CNOS local UI.",
|
|
2568
|
+
usage: "cnos ui [--host <host>] [--port <port>] [--api-port <port>] [global-options]",
|
|
2569
|
+
description: "Starts a local CNOS API server plus the Vite-powered React UI for browsing values, env mappings, public config, and inspect data.",
|
|
2570
|
+
options: [
|
|
2571
|
+
{
|
|
2572
|
+
flag: "--host <host>",
|
|
2573
|
+
description: "Host for the UI dev server. Defaults to 127.0.0.1."
|
|
2574
|
+
},
|
|
2575
|
+
{
|
|
2576
|
+
flag: "--port <port>",
|
|
2577
|
+
description: "Port for the UI dev server. Defaults to 4310."
|
|
2578
|
+
},
|
|
2579
|
+
{
|
|
2580
|
+
flag: "--api-port <port>",
|
|
2581
|
+
description: "Port for the backing CNOS API server. Defaults to 4311."
|
|
2582
|
+
}
|
|
2583
|
+
],
|
|
2584
|
+
examples: ["cnos ui", "cnos ui --port 4400 --api-port 4401"]
|
|
2585
|
+
},
|
|
2460
2586
|
{
|
|
2461
2587
|
id: "version",
|
|
2462
2588
|
summary: "Print the installed CNOS CLI version.",
|
|
@@ -2618,11 +2744,11 @@ function runHelpAi(topic, cliArgs = []) {
|
|
|
2618
2744
|
}
|
|
2619
2745
|
|
|
2620
2746
|
// src/commands/init.ts
|
|
2621
|
-
import
|
|
2747
|
+
import path11 from "path";
|
|
2622
2748
|
|
|
2623
2749
|
// src/services/scaffold.ts
|
|
2624
|
-
import { mkdir as mkdir4, readFile as
|
|
2625
|
-
import
|
|
2750
|
+
import { mkdir as mkdir4, readFile as readFile4, writeFile as writeFile4 } from "fs/promises";
|
|
2751
|
+
import path10 from "path";
|
|
2626
2752
|
function scaffoldManifest(projectName, options = {}) {
|
|
2627
2753
|
const mode = options.mode ?? "regular";
|
|
2628
2754
|
const baseWorkspace = options.workspace ?? "base";
|
|
@@ -2662,7 +2788,7 @@ function scaffoldManifest(projectName, options = {}) {
|
|
|
2662
2788
|
}
|
|
2663
2789
|
async function ensureFile(filePath, content) {
|
|
2664
2790
|
try {
|
|
2665
|
-
await
|
|
2791
|
+
await readFile4(filePath, "utf8");
|
|
2666
2792
|
return false;
|
|
2667
2793
|
} catch {
|
|
2668
2794
|
await writeFile4(filePath, content, "utf8");
|
|
@@ -2670,7 +2796,7 @@ async function ensureFile(filePath, content) {
|
|
|
2670
2796
|
}
|
|
2671
2797
|
}
|
|
2672
2798
|
async function ensureGitignore(root) {
|
|
2673
|
-
const gitignorePath =
|
|
2799
|
+
const gitignorePath = path10.join(root, ".gitignore");
|
|
2674
2800
|
const requiredEntries = [
|
|
2675
2801
|
".cnos/env/.env",
|
|
2676
2802
|
".cnos/env/.env.*",
|
|
@@ -2683,7 +2809,7 @@ async function ensureGitignore(root) {
|
|
|
2683
2809
|
];
|
|
2684
2810
|
let current = "";
|
|
2685
2811
|
try {
|
|
2686
|
-
current = await
|
|
2812
|
+
current = await readFile4(gitignorePath, "utf8");
|
|
2687
2813
|
} catch {
|
|
2688
2814
|
current = "";
|
|
2689
2815
|
}
|
|
@@ -2698,12 +2824,12 @@ async function ensureGitignore(root) {
|
|
|
2698
2824
|
return true;
|
|
2699
2825
|
}
|
|
2700
2826
|
async function ensureWorkspaceLayout(cnosRoot, workspace) {
|
|
2701
|
-
const workspaceRoot = workspace ?
|
|
2827
|
+
const workspaceRoot = workspace ? path10.join(cnosRoot, "workspaces", workspace) : cnosRoot;
|
|
2702
2828
|
const createdPaths = [];
|
|
2703
|
-
await mkdir4(
|
|
2704
|
-
await mkdir4(
|
|
2705
|
-
await mkdir4(
|
|
2706
|
-
await mkdir4(
|
|
2829
|
+
await mkdir4(path10.join(workspaceRoot, "profiles"), { recursive: true });
|
|
2830
|
+
await mkdir4(path10.join(workspaceRoot, "values"), { recursive: true });
|
|
2831
|
+
await mkdir4(path10.join(workspaceRoot, "secrets"), { recursive: true });
|
|
2832
|
+
await mkdir4(path10.join(workspaceRoot, "env"), { recursive: true });
|
|
2707
2833
|
const relativePaths = workspace ? [
|
|
2708
2834
|
["workspaces", workspace, "profiles", ".gitkeep"],
|
|
2709
2835
|
["workspaces", workspace, "values", ".gitkeep"],
|
|
@@ -2716,16 +2842,16 @@ async function ensureWorkspaceLayout(cnosRoot, workspace) {
|
|
|
2716
2842
|
["env", ".gitkeep"]
|
|
2717
2843
|
];
|
|
2718
2844
|
for (const relativePath of relativePaths) {
|
|
2719
|
-
const filePath =
|
|
2845
|
+
const filePath = path10.join(cnosRoot, ...relativePath);
|
|
2720
2846
|
if (await ensureFile(filePath, "")) {
|
|
2721
|
-
createdPaths.push(
|
|
2847
|
+
createdPaths.push(path10.relative(path10.dirname(cnosRoot), filePath).replace(/\\/g, "/"));
|
|
2722
2848
|
}
|
|
2723
2849
|
}
|
|
2724
2850
|
return createdPaths;
|
|
2725
2851
|
}
|
|
2726
2852
|
async function ensureCnosrc(root, workspace) {
|
|
2727
2853
|
return ensureFile(
|
|
2728
|
-
|
|
2854
|
+
path10.join(root, ".cnosrc.yml"),
|
|
2729
2855
|
workspace ? `root: ./.cnos
|
|
2730
2856
|
workspace: ${workspace}
|
|
2731
2857
|
` : "root: ./.cnos\n"
|
|
@@ -2735,7 +2861,7 @@ async function scaffoldProject(root, options = {}) {
|
|
|
2735
2861
|
const mode = options.mode ?? "regular";
|
|
2736
2862
|
const baseWorkspace = options.workspace ?? "base";
|
|
2737
2863
|
const childWorkspaces = mode === "workspace" ? (options.workspaces ?? []).filter((workspaceId) => workspaceId !== baseWorkspace) : [];
|
|
2738
|
-
const cnosRoot =
|
|
2864
|
+
const cnosRoot = path10.join(root, ".cnos");
|
|
2739
2865
|
const createdPaths = [];
|
|
2740
2866
|
if (mode === "workspace") {
|
|
2741
2867
|
createdPaths.push(
|
|
@@ -2749,13 +2875,13 @@ async function scaffoldProject(root, options = {}) {
|
|
|
2749
2875
|
} else {
|
|
2750
2876
|
createdPaths.push(...(await ensureWorkspaceLayout(cnosRoot)).map((entry) => entry.replace(/^\.cnos\//, ".cnos/")));
|
|
2751
2877
|
}
|
|
2752
|
-
if (await ensureFile(
|
|
2878
|
+
if (await ensureFile(path10.join(cnosRoot, "cnos.yml"), scaffoldManifest(path10.basename(root), options))) {
|
|
2753
2879
|
createdPaths.push(".cnos/cnos.yml");
|
|
2754
2880
|
}
|
|
2755
2881
|
if (await ensureCnosrc(root, mode === "workspace" ? baseWorkspace : void 0)) {
|
|
2756
2882
|
createdPaths.push(".cnosrc.yml");
|
|
2757
2883
|
}
|
|
2758
|
-
if (mode === "workspace" && await ensureFile(
|
|
2884
|
+
if (mode === "workspace" && await ensureFile(path10.join(root, ".cnos-workspace.yml"), `workspace: ${baseWorkspace}
|
|
2759
2885
|
globalRoot: ~/.cnos
|
|
2760
2886
|
`)) {
|
|
2761
2887
|
createdPaths.push(".cnos-workspace.yml");
|
|
@@ -2779,7 +2905,7 @@ function parseWorkspaceList(value) {
|
|
|
2779
2905
|
return value.split(",").map((entry) => entry.trim()).filter(Boolean);
|
|
2780
2906
|
}
|
|
2781
2907
|
async function runInit(options = {}) {
|
|
2782
|
-
const root =
|
|
2908
|
+
const root = path11.resolve(options.root ?? process.cwd());
|
|
2783
2909
|
const cliArgs = [...options.cliArgs ?? []];
|
|
2784
2910
|
const modeOption = consumeOption(cliArgs, "--mode");
|
|
2785
2911
|
const workspacesOption = consumeOption(cliArgs, "--workspaces");
|
|
@@ -3019,7 +3145,7 @@ async function runList(args = [], options = {}) {
|
|
|
3019
3145
|
}
|
|
3020
3146
|
|
|
3021
3147
|
// src/commands/migrate.ts
|
|
3022
|
-
import
|
|
3148
|
+
import path12 from "path";
|
|
3023
3149
|
import {
|
|
3024
3150
|
applyManifestMappings,
|
|
3025
3151
|
loadManifest as loadManifest4,
|
|
@@ -3047,7 +3173,7 @@ async function runMigrate(options = {}) {
|
|
|
3047
3173
|
...typeof options.cacheTtlSeconds === "number" ? { cacheTtlSeconds: options.cacheTtlSeconds } : {},
|
|
3048
3174
|
...options.forceRefresh ? { forceRefresh: true } : {}
|
|
3049
3175
|
});
|
|
3050
|
-
const scanRoot =
|
|
3176
|
+
const scanRoot = path12.resolve(manifest.consumerRoot, scan ?? "src");
|
|
3051
3177
|
const usages = await scanEnvUsage(scanRoot);
|
|
3052
3178
|
const uniqueProposals = new Map(usages.map((usage) => [usage.envVar, proposeMapping(usage.envVar)]));
|
|
3053
3179
|
const proposals = Array.from(uniqueProposals.values()).sort((left, right) => left.envVar.localeCompare(right.envVar));
|
|
@@ -3109,7 +3235,7 @@ async function runMigrate(options = {}) {
|
|
|
3109
3235
|
}
|
|
3110
3236
|
|
|
3111
3237
|
// src/commands/namespace.ts
|
|
3112
|
-
import
|
|
3238
|
+
import path13 from "path";
|
|
3113
3239
|
function normalizeCommand2(args) {
|
|
3114
3240
|
const [actionOrPath, ...tail] = args;
|
|
3115
3241
|
if (!actionOrPath) {
|
|
@@ -3132,7 +3258,7 @@ function normalizeCommand2(args) {
|
|
|
3132
3258
|
async function runNamespace(namespace, args = [], options = {}) {
|
|
3133
3259
|
const { action, tail } = normalizeCommand2(args);
|
|
3134
3260
|
const cliArgs = [...options.cliArgs ?? []];
|
|
3135
|
-
const root =
|
|
3261
|
+
const root = path13.resolve(options.root ?? process.cwd());
|
|
3136
3262
|
if (action === "list") {
|
|
3137
3263
|
const prefix = consumeOption(cliArgs, "--prefix");
|
|
3138
3264
|
const entries = await listConfigEntries(namespace, {
|
|
@@ -3202,9 +3328,9 @@ async function runNamespace(namespace, args = [], options = {}) {
|
|
|
3202
3328
|
}
|
|
3203
3329
|
|
|
3204
3330
|
// src/commands/onboard.ts
|
|
3205
|
-
import { copyFile, mkdir as mkdir5, readdir as readdir3, rm as rm2, stat as stat2, readFile as
|
|
3206
|
-
import
|
|
3207
|
-
import
|
|
3331
|
+
import { copyFile, mkdir as mkdir5, readdir as readdir3, rm as rm2, stat as stat2, readFile as readFile5 } from "fs/promises";
|
|
3332
|
+
import path14 from "path";
|
|
3333
|
+
import readline2 from "readline/promises";
|
|
3208
3334
|
import { loadManifest as loadManifest5, parseYaml as parseYaml3 } from "@kitsy/cnos/internal";
|
|
3209
3335
|
import { parse as parseToml } from "smol-toml";
|
|
3210
3336
|
var ROOT_ENV_FILE_PATTERN = /^\.env(?:\.[A-Za-z0-9_-]+)*(?:\.example)?$/;
|
|
@@ -3273,7 +3399,7 @@ function flattenStructured(value, prefixSegments, currentKey = []) {
|
|
|
3273
3399
|
);
|
|
3274
3400
|
}
|
|
3275
3401
|
async function parseSource(input, prefixSegments) {
|
|
3276
|
-
const content = await
|
|
3402
|
+
const content = await readFile5(input.filePath, "utf8");
|
|
3277
3403
|
switch (input.kind) {
|
|
3278
3404
|
case "env":
|
|
3279
3405
|
return Object.entries(parseEnv(content)).map(([sourceKey, value]) => {
|
|
@@ -3289,7 +3415,7 @@ async function parseSource(input, prefixSegments) {
|
|
|
3289
3415
|
}
|
|
3290
3416
|
}
|
|
3291
3417
|
function detectKindFromPath(filePath) {
|
|
3292
|
-
const ext =
|
|
3418
|
+
const ext = path14.extname(filePath).toLowerCase();
|
|
3293
3419
|
switch (ext) {
|
|
3294
3420
|
case ".env":
|
|
3295
3421
|
return "env";
|
|
@@ -3320,7 +3446,7 @@ function formatProposals(proposed) {
|
|
|
3320
3446
|
});
|
|
3321
3447
|
}
|
|
3322
3448
|
async function promptForMaterialize() {
|
|
3323
|
-
const rl =
|
|
3449
|
+
const rl = readline2.createInterface({
|
|
3324
3450
|
input: process.stdin,
|
|
3325
3451
|
output: process.stdout
|
|
3326
3452
|
});
|
|
@@ -3356,19 +3482,19 @@ function resolveSourceInputs(root, cliArgs) {
|
|
|
3356
3482
|
if (!source) {
|
|
3357
3483
|
return [];
|
|
3358
3484
|
}
|
|
3359
|
-
const resolvedPath =
|
|
3485
|
+
const resolvedPath = path14.resolve(root, source.filePath);
|
|
3360
3486
|
return [
|
|
3361
3487
|
{
|
|
3362
3488
|
kind: source.kind,
|
|
3363
3489
|
filePath: resolvedPath,
|
|
3364
|
-
displayName:
|
|
3490
|
+
displayName: path14.basename(resolvedPath)
|
|
3365
3491
|
}
|
|
3366
3492
|
];
|
|
3367
3493
|
}
|
|
3368
3494
|
return [];
|
|
3369
3495
|
}
|
|
3370
3496
|
async function runOnboard(options = {}) {
|
|
3371
|
-
const root =
|
|
3497
|
+
const root = path14.resolve(options.root ?? process.cwd());
|
|
3372
3498
|
const cliArgs = [...options.cliArgs ?? []];
|
|
3373
3499
|
const move = consumeFlag(cliArgs, "--move");
|
|
3374
3500
|
const materialize = consumeFlag(cliArgs, "--materialize");
|
|
@@ -3382,7 +3508,7 @@ async function runOnboard(options = {}) {
|
|
|
3382
3508
|
throw new Error(`Unsupported onboard arguments: ${cliArgs.join(" ")}`);
|
|
3383
3509
|
}
|
|
3384
3510
|
let scaffolded = [];
|
|
3385
|
-
const manifestPath =
|
|
3511
|
+
const manifestPath = path14.join(root, ".cnos", "cnos.yml");
|
|
3386
3512
|
if (!await exists(manifestPath)) {
|
|
3387
3513
|
const scaffold = await scaffoldProject(root, {
|
|
3388
3514
|
mode: options.workspace && options.workspace !== "base" ? "workspace" : "regular",
|
|
@@ -3402,11 +3528,11 @@ async function runOnboard(options = {}) {
|
|
|
3402
3528
|
if (!isWorkspaceMode && options.workspace && options.workspace !== "base") {
|
|
3403
3529
|
throw new Error("This repo is still in regular mode. Run `cnos workspace enable` before onboarding into a child workspace.");
|
|
3404
3530
|
}
|
|
3405
|
-
const envRoot = isWorkspaceMode ?
|
|
3531
|
+
const envRoot = isWorkspaceMode ? path14.join(root, ".cnos", "workspaces", selectedWorkspace, "env") : path14.join(root, ".cnos", "env");
|
|
3406
3532
|
await mkdir5(envRoot, { recursive: true });
|
|
3407
3533
|
const rootFiles = explicitSources.length > 0 ? explicitSources : (await listRootEnvFiles(root)).map((fileName) => ({
|
|
3408
3534
|
kind: "env",
|
|
3409
|
-
filePath:
|
|
3535
|
+
filePath: path14.join(root, fileName),
|
|
3410
3536
|
displayName: fileName
|
|
3411
3537
|
}));
|
|
3412
3538
|
const imported = [];
|
|
@@ -3414,10 +3540,10 @@ async function runOnboard(options = {}) {
|
|
|
3414
3540
|
const prefixSegments = buildPrefixSegments(prefix);
|
|
3415
3541
|
const proposed = [];
|
|
3416
3542
|
for (const source of rootFiles) {
|
|
3417
|
-
const targetPath =
|
|
3543
|
+
const targetPath = path14.join(envRoot, source.displayName);
|
|
3418
3544
|
try {
|
|
3419
3545
|
await copyFile(source.filePath, targetPath);
|
|
3420
|
-
imported.push(
|
|
3546
|
+
imported.push(path14.relative(root, targetPath).replace(/\\/g, "/"));
|
|
3421
3547
|
if (move) {
|
|
3422
3548
|
await rm2(source.filePath);
|
|
3423
3549
|
}
|
|
@@ -3455,7 +3581,7 @@ async function runOnboard(options = {}) {
|
|
|
3455
3581
|
}
|
|
3456
3582
|
const lines = [
|
|
3457
3583
|
`onboarded ${selectedWorkspace} at ${root}`,
|
|
3458
|
-
`Imported ${imported.length} source file(s) into ${
|
|
3584
|
+
`Imported ${imported.length} source file(s) into ${path14.relative(root, envRoot).replace(/\\/g, "/") || ".cnos/env"} using ${result.mode}.`,
|
|
3459
3585
|
"",
|
|
3460
3586
|
`Discovered ${proposed.length} proposed value mapping(s):`,
|
|
3461
3587
|
...formatProposals(proposed)
|
|
@@ -3474,16 +3600,16 @@ async function runOnboard(options = {}) {
|
|
|
3474
3600
|
}
|
|
3475
3601
|
|
|
3476
3602
|
// src/commands/profile.ts
|
|
3477
|
-
import
|
|
3603
|
+
import path17 from "path";
|
|
3478
3604
|
|
|
3479
3605
|
// src/services/context.ts
|
|
3480
|
-
import { readFile as
|
|
3481
|
-
import
|
|
3606
|
+
import { readFile as readFile6, writeFile as writeFile5 } from "fs/promises";
|
|
3607
|
+
import path15 from "path";
|
|
3482
3608
|
import { parseYaml as parseYaml4, stringifyYaml as stringifyYaml3 } from "@kitsy/cnos/internal";
|
|
3483
3609
|
async function loadCliContext(root = process.cwd()) {
|
|
3484
|
-
const filePath =
|
|
3610
|
+
const filePath = path15.join(path15.resolve(root), ".cnos-workspace.yml");
|
|
3485
3611
|
try {
|
|
3486
|
-
const source = await
|
|
3612
|
+
const source = await readFile6(filePath, "utf8");
|
|
3487
3613
|
const parsed = parseYaml4(source);
|
|
3488
3614
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
3489
3615
|
return {};
|
|
@@ -3494,8 +3620,8 @@ async function loadCliContext(root = process.cwd()) {
|
|
|
3494
3620
|
}
|
|
3495
3621
|
}
|
|
3496
3622
|
async function saveCliContext(options = {}) {
|
|
3497
|
-
const root =
|
|
3498
|
-
const filePath =
|
|
3623
|
+
const root = path15.resolve(options.root ?? process.cwd());
|
|
3624
|
+
const filePath = path15.join(root, ".cnos-workspace.yml");
|
|
3499
3625
|
const current = await loadCliContext(root);
|
|
3500
3626
|
const next = {
|
|
3501
3627
|
...current.workspace ? { workspace: current.workspace } : {},
|
|
@@ -3513,21 +3639,21 @@ async function saveCliContext(options = {}) {
|
|
|
3513
3639
|
}
|
|
3514
3640
|
|
|
3515
3641
|
// src/services/profiles.ts
|
|
3516
|
-
import { mkdir as mkdir6, readdir as readdir4, readFile as
|
|
3517
|
-
import
|
|
3642
|
+
import { mkdir as mkdir6, readdir as readdir4, readFile as readFile7, rm as rm3, writeFile as writeFile6 } from "fs/promises";
|
|
3643
|
+
import path16 from "path";
|
|
3518
3644
|
import { loadManifest as loadManifest6, parseYaml as parseYaml5, stringifyYaml as stringifyYaml4 } from "@kitsy/cnos/internal";
|
|
3519
3645
|
async function resolveProfilesRoot(root = process.cwd()) {
|
|
3520
3646
|
try {
|
|
3521
3647
|
const loadedManifest = await loadManifest6({ root });
|
|
3522
|
-
return
|
|
3648
|
+
return path16.join(loadedManifest.manifestRoot, "profiles");
|
|
3523
3649
|
} catch {
|
|
3524
3650
|
const loadedManifest = await loadManifest6({ cwd: root });
|
|
3525
|
-
return
|
|
3651
|
+
return path16.join(loadedManifest.manifestRoot, "profiles");
|
|
3526
3652
|
}
|
|
3527
3653
|
}
|
|
3528
3654
|
async function createProfileDefinition(root = process.cwd(), profile, inherit, options = {}) {
|
|
3529
|
-
const filePath =
|
|
3530
|
-
await mkdir6(
|
|
3655
|
+
const filePath = path16.join(await resolveProfilesRoot(root), `${profile}.yml`);
|
|
3656
|
+
await mkdir6(path16.dirname(filePath), { recursive: true });
|
|
3531
3657
|
const document = options.noInherit ? {
|
|
3532
3658
|
name: profile,
|
|
3533
3659
|
activate: {
|
|
@@ -3565,7 +3691,7 @@ async function listProfiles(root = process.cwd()) {
|
|
|
3565
3691
|
}
|
|
3566
3692
|
}
|
|
3567
3693
|
async function deleteProfileDefinition(root = process.cwd(), profile) {
|
|
3568
|
-
const filePath =
|
|
3694
|
+
const filePath = path16.join(await resolveProfilesRoot(root), `${profile}.yml`);
|
|
3569
3695
|
try {
|
|
3570
3696
|
await rm3(filePath);
|
|
3571
3697
|
return {
|
|
@@ -3585,9 +3711,9 @@ async function readProfileDefinition(root = process.cwd(), profile = "base") {
|
|
|
3585
3711
|
name: "base"
|
|
3586
3712
|
};
|
|
3587
3713
|
}
|
|
3588
|
-
const filePath =
|
|
3714
|
+
const filePath = path16.join(await resolveProfilesRoot(root), `${profile}.yml`);
|
|
3589
3715
|
try {
|
|
3590
|
-
return parseYaml5(await
|
|
3716
|
+
return parseYaml5(await readFile7(filePath, "utf8")) ?? void 0;
|
|
3591
3717
|
} catch {
|
|
3592
3718
|
return void 0;
|
|
3593
3719
|
}
|
|
@@ -3632,7 +3758,7 @@ async function runProfile(args, options = {}) {
|
|
|
3632
3758
|
if (action === "use") {
|
|
3633
3759
|
const profile = tail[0] ?? "base";
|
|
3634
3760
|
const result = await saveCliContext({
|
|
3635
|
-
root:
|
|
3761
|
+
root: path17.resolve(root),
|
|
3636
3762
|
profile
|
|
3637
3763
|
});
|
|
3638
3764
|
if (options.json) {
|
|
@@ -3663,7 +3789,7 @@ async function runProfile(args, options = {}) {
|
|
|
3663
3789
|
}
|
|
3664
3790
|
|
|
3665
3791
|
// src/commands/promote.ts
|
|
3666
|
-
import
|
|
3792
|
+
import path18 from "path";
|
|
3667
3793
|
import { writeFile as writeFile7 } from "fs/promises";
|
|
3668
3794
|
import {
|
|
3669
3795
|
ensureProjectionAllowed,
|
|
@@ -3680,7 +3806,7 @@ function sortRecord(record) {
|
|
|
3680
3806
|
return Object.fromEntries(Object.entries(record).sort(([left], [right]) => left.localeCompare(right)));
|
|
3681
3807
|
}
|
|
3682
3808
|
async function runPromote(args = [], options = {}) {
|
|
3683
|
-
const root =
|
|
3809
|
+
const root = path18.resolve(options.root ?? process.cwd());
|
|
3684
3810
|
const cliArgs = [...options.cliArgs ?? []];
|
|
3685
3811
|
const target = normalizeTarget(consumeOption(cliArgs, "--to"));
|
|
3686
3812
|
const alias = consumeOption(cliArgs, "--as");
|
|
@@ -3760,8 +3886,11 @@ async function runRead(key, options = {}) {
|
|
|
3760
3886
|
import {
|
|
3761
3887
|
CNOS_GRAPH_ENV_VAR,
|
|
3762
3888
|
CNOS_PROJECTION_ENV_VAR,
|
|
3889
|
+
CNOS_SECRET_PAYLOAD_ENV_VAR,
|
|
3890
|
+
CNOS_SESSION_KEY_ENV_VAR,
|
|
3763
3891
|
serializeServerProjection,
|
|
3764
|
-
serializeRuntimeGraph
|
|
3892
|
+
serializeRuntimeGraph,
|
|
3893
|
+
serializeSecretPayload
|
|
3765
3894
|
} from "@kitsy/cnos/internal";
|
|
3766
3895
|
function consumeOptions(args, flag) {
|
|
3767
3896
|
const values = [];
|
|
@@ -3799,7 +3928,7 @@ async function runCommand(command, options = {}) {
|
|
|
3799
3928
|
}
|
|
3800
3929
|
const cliArgs = [...options.cliArgs ?? []];
|
|
3801
3930
|
const isPublic = consumeFlag(cliArgs, "--public");
|
|
3802
|
-
consumeFlag(cliArgs, "--auth");
|
|
3931
|
+
const isAuthenticated = consumeFlag(cliArgs, "--auth");
|
|
3803
3932
|
const framework = consumeOption(cliArgs, "--framework");
|
|
3804
3933
|
const prefix = consumeOption(cliArgs, "--prefix");
|
|
3805
3934
|
const setOverrides = normalizeSetOverrides(consumeOptions(cliArgs, "--set"));
|
|
@@ -3807,14 +3936,22 @@ async function runCommand(command, options = {}) {
|
|
|
3807
3936
|
...options,
|
|
3808
3937
|
cliArgs: [...cliArgs, ...setOverrides]
|
|
3809
3938
|
});
|
|
3939
|
+
const authenticatedSecrets = isAuthenticated ? Object.fromEntries(
|
|
3940
|
+
Array.from(runtime.graph.entries.values()).filter((entry) => entry.namespace === "secret").map((entry) => [entry.key, runtime.read(entry.key)]).filter((entry) => entry[1] !== void 0)
|
|
3941
|
+
) : void 0;
|
|
3942
|
+
const secretPayload = authenticatedSecrets ? serializeSecretPayload(authenticatedSecrets) : void 0;
|
|
3810
3943
|
const env = {
|
|
3811
3944
|
...process.env,
|
|
3812
3945
|
...isPublic ? runtime.toPublicEnv({
|
|
3813
3946
|
...framework ? { framework } : {},
|
|
3814
3947
|
...prefix ? { prefix } : {}
|
|
3815
|
-
}) : runtime.toEnv(),
|
|
3948
|
+
}) : runtime.toEnv({ includeSecrets: true }),
|
|
3816
3949
|
[CNOS_PROJECTION_ENV_VAR]: serializeServerProjection(runtime.toServerProjection()),
|
|
3817
|
-
[CNOS_GRAPH_ENV_VAR]: serializeRuntimeGraph(runtime.graph)
|
|
3950
|
+
[CNOS_GRAPH_ENV_VAR]: serializeRuntimeGraph(runtime.graph),
|
|
3951
|
+
...secretPayload ? {
|
|
3952
|
+
[CNOS_SECRET_PAYLOAD_ENV_VAR]: secretPayload.payload,
|
|
3953
|
+
[CNOS_SESSION_KEY_ENV_VAR]: secretPayload.sessionKey
|
|
3954
|
+
} : {}
|
|
3818
3955
|
};
|
|
3819
3956
|
return new Promise((resolve, reject) => {
|
|
3820
3957
|
const executable = command[0];
|
|
@@ -3849,14 +3986,14 @@ async function runCommand(command, options = {}) {
|
|
|
3849
3986
|
}
|
|
3850
3987
|
|
|
3851
3988
|
// src/commands/secret.ts
|
|
3852
|
-
import
|
|
3989
|
+
import path21 from "path";
|
|
3853
3990
|
|
|
3854
3991
|
// src/commands/vault.ts
|
|
3855
|
-
import
|
|
3992
|
+
import path20 from "path";
|
|
3856
3993
|
|
|
3857
3994
|
// src/services/vaults.ts
|
|
3858
3995
|
import { rm as rm4, writeFile as writeFile8 } from "fs/promises";
|
|
3859
|
-
import
|
|
3996
|
+
import path19 from "path";
|
|
3860
3997
|
import {
|
|
3861
3998
|
clearAllVaultSessionKeys,
|
|
3862
3999
|
clearVaultSessionKey,
|
|
@@ -3985,7 +4122,7 @@ async function removeVaultDefinition(name, options = {}) {
|
|
|
3985
4122
|
delete rawManifest.vaults;
|
|
3986
4123
|
}
|
|
3987
4124
|
await writeFile8(loadedManifest.manifestPath, stringifyYaml6(rawManifest), "utf8");
|
|
3988
|
-
const vaultRoot =
|
|
4125
|
+
const vaultRoot = path19.join(resolveSecretStoreRoot2(options.processEnv), "vaults", vault);
|
|
3989
4126
|
let removedStore;
|
|
3990
4127
|
try {
|
|
3991
4128
|
await rm4(vaultRoot, { recursive: true, force: true });
|
|
@@ -4086,7 +4223,7 @@ function normalizeVaultAction(args) {
|
|
|
4086
4223
|
async function runVault(args = [], options = {}) {
|
|
4087
4224
|
const { action, tail } = normalizeVaultAction(args);
|
|
4088
4225
|
const cliArgs = [...options.cliArgs ?? []];
|
|
4089
|
-
const root =
|
|
4226
|
+
const root = path20.resolve(options.root ?? process.cwd());
|
|
4090
4227
|
if (consumeOption(cliArgs, "--passphrase")) {
|
|
4091
4228
|
throw new Error("The --passphrase option is not supported in CNOS 1.4. Use env, keychain, or prompt-based auth.");
|
|
4092
4229
|
}
|
|
@@ -4192,7 +4329,7 @@ async function runSecret(argsOrPath, options = {}) {
|
|
|
4192
4329
|
const args = Array.isArray(argsOrPath) ? argsOrPath : [argsOrPath];
|
|
4193
4330
|
const { action, tail } = normalizeSecretCommand(args);
|
|
4194
4331
|
const cliArgs = [...options.cliArgs ?? []];
|
|
4195
|
-
const root =
|
|
4332
|
+
const root = path21.resolve(options.root ?? process.cwd());
|
|
4196
4333
|
if (consumeOption(cliArgs, "--passphrase")) {
|
|
4197
4334
|
throw new Error("The --passphrase option is not supported in CNOS 1.4. Use env, keychain, or prompt-based auth.");
|
|
4198
4335
|
}
|
|
@@ -4295,9 +4432,9 @@ async function runSecret(argsOrPath, options = {}) {
|
|
|
4295
4432
|
}
|
|
4296
4433
|
|
|
4297
4434
|
// src/commands/use.ts
|
|
4298
|
-
import
|
|
4435
|
+
import path22 from "path";
|
|
4299
4436
|
async function runUse(args = [], options = {}) {
|
|
4300
|
-
const root =
|
|
4437
|
+
const root = path22.resolve(options.root ?? process.cwd());
|
|
4301
4438
|
const action = args[0];
|
|
4302
4439
|
const hasUpdates = Boolean(options.workspace || options.profile || options.globalRoot);
|
|
4303
4440
|
if (action === "show" || !action && !hasUpdates) {
|
|
@@ -4319,6 +4456,219 @@ async function runUse(args = [], options = {}) {
|
|
|
4319
4456
|
return `updated CLI context in ${displayPath(result.filePath, root)}`;
|
|
4320
4457
|
}
|
|
4321
4458
|
|
|
4459
|
+
// src/commands/ui.ts
|
|
4460
|
+
import { createServer } from "http";
|
|
4461
|
+
import path23 from "path";
|
|
4462
|
+
import { createRequire } from "module";
|
|
4463
|
+
import { loadManifest as loadManifest9 } from "@kitsy/cnos/internal";
|
|
4464
|
+
function parsePort(value, fallback, flag) {
|
|
4465
|
+
if (!value) {
|
|
4466
|
+
return fallback;
|
|
4467
|
+
}
|
|
4468
|
+
const parsed = Number(value);
|
|
4469
|
+
if (!Number.isInteger(parsed) || parsed <= 0 || parsed > 65535) {
|
|
4470
|
+
throw new Error(`Invalid value for ${flag}: ${value}`);
|
|
4471
|
+
}
|
|
4472
|
+
return parsed;
|
|
4473
|
+
}
|
|
4474
|
+
function resolveUiPackageRoot() {
|
|
4475
|
+
const require2 = createRequire(import.meta.url);
|
|
4476
|
+
try {
|
|
4477
|
+
const packageJsonPath = require2.resolve("@kitsy/cnos-ui/package.json");
|
|
4478
|
+
return path23.dirname(packageJsonPath);
|
|
4479
|
+
} catch {
|
|
4480
|
+
throw new Error("Unable to resolve @kitsy/cnos-ui. Install workspace dependencies before running `cnos ui`.");
|
|
4481
|
+
}
|
|
4482
|
+
}
|
|
4483
|
+
function writeJson(response, statusCode, payload) {
|
|
4484
|
+
response.statusCode = statusCode;
|
|
4485
|
+
response.setHeader("Content-Type", "application/json; charset=utf-8");
|
|
4486
|
+
response.end(`${printJson(payload)}
|
|
4487
|
+
`);
|
|
4488
|
+
}
|
|
4489
|
+
function maskInspectResult(key, value) {
|
|
4490
|
+
if (!key.startsWith("secret.")) {
|
|
4491
|
+
return value;
|
|
4492
|
+
}
|
|
4493
|
+
return {
|
|
4494
|
+
...value,
|
|
4495
|
+
value: maskSecretValue(value.value),
|
|
4496
|
+
overridden: value.overridden.map((entry) => ({
|
|
4497
|
+
...entry,
|
|
4498
|
+
value: maskSecretValue(entry.value)
|
|
4499
|
+
}))
|
|
4500
|
+
};
|
|
4501
|
+
}
|
|
4502
|
+
function maskListEntry(entry) {
|
|
4503
|
+
if (!entry.key.startsWith("secret.")) {
|
|
4504
|
+
return entry;
|
|
4505
|
+
}
|
|
4506
|
+
return {
|
|
4507
|
+
...entry,
|
|
4508
|
+
value: maskSecretValue(entry.value)
|
|
4509
|
+
};
|
|
4510
|
+
}
|
|
4511
|
+
function toRuntimeOptionsFromQuery(baseOptions, searchParams) {
|
|
4512
|
+
const workspace = searchParams.get("workspace")?.trim();
|
|
4513
|
+
const profile = searchParams.get("profile")?.trim();
|
|
4514
|
+
return {
|
|
4515
|
+
...baseOptions,
|
|
4516
|
+
...workspace ? { workspace } : {},
|
|
4517
|
+
...profile ? { profile } : {}
|
|
4518
|
+
};
|
|
4519
|
+
}
|
|
4520
|
+
async function handleSummary(options, searchParams) {
|
|
4521
|
+
const runtimeOptions = toRuntimeOptionsFromQuery(options, searchParams);
|
|
4522
|
+
const runtime = await createRuntimeService({
|
|
4523
|
+
...runtimeOptions,
|
|
4524
|
+
secretResolution: "lazy"
|
|
4525
|
+
});
|
|
4526
|
+
const loadedManifest = await loadManifest9({
|
|
4527
|
+
...options.root ? { root: options.root } : {},
|
|
4528
|
+
...options.cwd ? { cwd: options.cwd } : {},
|
|
4529
|
+
...options.processEnv ? { processEnv: options.processEnv } : {}
|
|
4530
|
+
});
|
|
4531
|
+
const declaredWorkspaces = Object.keys(loadedManifest.manifest.workspaces.items);
|
|
4532
|
+
const workspaces = declaredWorkspaces.length > 0 ? declaredWorkspaces.sort((left, right) => left.localeCompare(right)) : ["base"];
|
|
4533
|
+
const profiles = await listProfiles(loadedManifest.consumerRoot);
|
|
4534
|
+
const envEntries = runtime.toEnv();
|
|
4535
|
+
const publicEntries = runtime.toPublicEnv();
|
|
4536
|
+
const counts = Array.from(runtime.graph.entries.values()).reduce((acc, entry) => {
|
|
4537
|
+
acc.all += 1;
|
|
4538
|
+
acc[entry.namespace] = (acc[entry.namespace] ?? 0) + 1;
|
|
4539
|
+
return acc;
|
|
4540
|
+
}, { all: 0 });
|
|
4541
|
+
return {
|
|
4542
|
+
project: runtime.manifest.project.name,
|
|
4543
|
+
workspace: runtime.graph.workspace.workspaceId,
|
|
4544
|
+
workspaceSource: runtime.graph.workspace.workspaceSource,
|
|
4545
|
+
workspaceChain: runtime.graph.workspace.workspaceChain,
|
|
4546
|
+
profile: runtime.graph.profile,
|
|
4547
|
+
profileSource: runtime.graph.profileSource,
|
|
4548
|
+
counts: {
|
|
4549
|
+
...counts,
|
|
4550
|
+
env: Object.keys(envEntries).length,
|
|
4551
|
+
public: Object.keys(publicEntries).length
|
|
4552
|
+
},
|
|
4553
|
+
envMapping: Object.entries(runtime.manifest.envMapping.explicit).map(([envVar, logicalKey]) => ({
|
|
4554
|
+
envVar,
|
|
4555
|
+
logicalKey,
|
|
4556
|
+
secret: runtime.graph.entries.get(logicalKey)?.namespace === "secret"
|
|
4557
|
+
})),
|
|
4558
|
+
promoted: runtime.manifest.public.promote,
|
|
4559
|
+
workspaces,
|
|
4560
|
+
profiles,
|
|
4561
|
+
runtimeNamespaces: Object.keys(runtime.manifest.runtimeNamespaces),
|
|
4562
|
+
vaults: Object.keys(runtime.manifest.vaults)
|
|
4563
|
+
};
|
|
4564
|
+
}
|
|
4565
|
+
async function handleRequest(request, response, options) {
|
|
4566
|
+
const url = new URL(request.url ?? "/", "http://127.0.0.1");
|
|
4567
|
+
if (request.method !== "GET") {
|
|
4568
|
+
writeJson(response, 405, { error: "Method not allowed" });
|
|
4569
|
+
return;
|
|
4570
|
+
}
|
|
4571
|
+
if (url.pathname === "/api/health") {
|
|
4572
|
+
writeJson(response, 200, { ok: true });
|
|
4573
|
+
return;
|
|
4574
|
+
}
|
|
4575
|
+
if (url.pathname === "/api/summary") {
|
|
4576
|
+
writeJson(response, 200, await handleSummary(options, url.searchParams));
|
|
4577
|
+
return;
|
|
4578
|
+
}
|
|
4579
|
+
if (url.pathname === "/api/list") {
|
|
4580
|
+
const namespace = url.searchParams.get("namespace") ?? "value";
|
|
4581
|
+
const prefix = url.searchParams.get("prefix") ?? void 0;
|
|
4582
|
+
const runtimeOptions = toRuntimeOptionsFromQuery(options, url.searchParams);
|
|
4583
|
+
const entries = await listConfigEntries(namespace, {
|
|
4584
|
+
...runtimeOptions,
|
|
4585
|
+
...prefix ? { prefix } : {},
|
|
4586
|
+
...namespace === "secret" ? { secretResolution: "lazy" } : {}
|
|
4587
|
+
});
|
|
4588
|
+
writeJson(response, 200, {
|
|
4589
|
+
namespace,
|
|
4590
|
+
entries: entries.map((entry) => maskListEntry(entry))
|
|
4591
|
+
});
|
|
4592
|
+
return;
|
|
4593
|
+
}
|
|
4594
|
+
if (url.pathname === "/api/inspect") {
|
|
4595
|
+
const key = url.searchParams.get("key");
|
|
4596
|
+
if (!key) {
|
|
4597
|
+
writeJson(response, 400, { error: "Missing key query parameter" });
|
|
4598
|
+
return;
|
|
4599
|
+
}
|
|
4600
|
+
const runtimeOptions = toRuntimeOptionsFromQuery(options, url.searchParams);
|
|
4601
|
+
const runtime = await createRuntimeService({
|
|
4602
|
+
...runtimeOptions,
|
|
4603
|
+
...key.startsWith("secret.") ? { secretResolution: "lazy" } : {}
|
|
4604
|
+
});
|
|
4605
|
+
writeJson(response, 200, maskInspectResult(key, runtime.inspect(key)));
|
|
4606
|
+
return;
|
|
4607
|
+
}
|
|
4608
|
+
writeJson(response, 404, { error: "Not found" });
|
|
4609
|
+
}
|
|
4610
|
+
function resolveUiUrl(host, port) {
|
|
4611
|
+
if (host === "0.0.0.0" || host === "::") {
|
|
4612
|
+
return `http://127.0.0.1:${port}`;
|
|
4613
|
+
}
|
|
4614
|
+
return `http://${host}:${port}`;
|
|
4615
|
+
}
|
|
4616
|
+
async function runUi(options = {}) {
|
|
4617
|
+
const cliArgs = [...options.cliArgs ?? []];
|
|
4618
|
+
const host = consumeOption(cliArgs, "--host") ?? "127.0.0.1";
|
|
4619
|
+
const port = parsePort(consumeOption(cliArgs, "--port"), 4310, "--port");
|
|
4620
|
+
const apiPort = parsePort(consumeOption(cliArgs, "--api-port"), 4311, "--api-port");
|
|
4621
|
+
if (cliArgs.length > 0) {
|
|
4622
|
+
throw new Error(`Unsupported ui arguments: ${cliArgs.join(" ")}`);
|
|
4623
|
+
}
|
|
4624
|
+
const uiRoot = resolveUiPackageRoot();
|
|
4625
|
+
const apiServer = createServer((request, response) => {
|
|
4626
|
+
void handleRequest(request, response, options).catch((error) => {
|
|
4627
|
+
writeJson(response, 500, {
|
|
4628
|
+
error: error instanceof Error ? error.message : String(error)
|
|
4629
|
+
});
|
|
4630
|
+
});
|
|
4631
|
+
});
|
|
4632
|
+
await new Promise((resolve, reject) => {
|
|
4633
|
+
apiServer.once("error", reject);
|
|
4634
|
+
apiServer.listen(apiPort, "127.0.0.1", () => resolve());
|
|
4635
|
+
});
|
|
4636
|
+
const uiProcess = spawnCommand(
|
|
4637
|
+
["pnpm", "exec", "vite", "--host", host, "--port", String(port)],
|
|
4638
|
+
{
|
|
4639
|
+
cwd: uiRoot,
|
|
4640
|
+
env: {
|
|
4641
|
+
...process.env,
|
|
4642
|
+
...options.processEnv,
|
|
4643
|
+
CNOS_UI_API_TARGET: `http://127.0.0.1:${apiPort}`
|
|
4644
|
+
},
|
|
4645
|
+
stdio: "inherit"
|
|
4646
|
+
}
|
|
4647
|
+
);
|
|
4648
|
+
const uiUrl = resolveUiUrl(host, port);
|
|
4649
|
+
const apiAddress = apiServer.address();
|
|
4650
|
+
console.log(`CNOS UI running at ${uiUrl}`);
|
|
4651
|
+
console.log(`CNOS UI API running at http://127.0.0.1:${apiAddress?.port ?? apiPort}`);
|
|
4652
|
+
const shutdown = () => {
|
|
4653
|
+
apiServer.close();
|
|
4654
|
+
if (!uiProcess.killed) {
|
|
4655
|
+
uiProcess.kill("SIGTERM");
|
|
4656
|
+
}
|
|
4657
|
+
};
|
|
4658
|
+
process.once("SIGINT", shutdown);
|
|
4659
|
+
process.once("SIGTERM", shutdown);
|
|
4660
|
+
try {
|
|
4661
|
+
await new Promise((resolve, reject) => {
|
|
4662
|
+
uiProcess.once("error", reject);
|
|
4663
|
+
uiProcess.once("exit", () => resolve());
|
|
4664
|
+
});
|
|
4665
|
+
} finally {
|
|
4666
|
+
process.removeListener("SIGINT", shutdown);
|
|
4667
|
+
process.removeListener("SIGTERM", shutdown);
|
|
4668
|
+
apiServer.close();
|
|
4669
|
+
}
|
|
4670
|
+
}
|
|
4671
|
+
|
|
4322
4672
|
// src/commands/validate.ts
|
|
4323
4673
|
async function runValidate(options = {}) {
|
|
4324
4674
|
const { summary } = await createValidationSummary(options);
|
|
@@ -4334,7 +4684,7 @@ async function runValidate(options = {}) {
|
|
|
4334
4684
|
// package.json
|
|
4335
4685
|
var package_default = {
|
|
4336
4686
|
name: "@kitsy/cnos-cli",
|
|
4337
|
-
version: "1.
|
|
4687
|
+
version: "1.9.0",
|
|
4338
4688
|
description: "CLI entry point and developer tooling for CNOS.",
|
|
4339
4689
|
type: "module",
|
|
4340
4690
|
main: "./dist/index.js",
|
|
@@ -4371,6 +4721,7 @@ var package_default = {
|
|
|
4371
4721
|
},
|
|
4372
4722
|
dependencies: {
|
|
4373
4723
|
"@kitsy/cnos": "workspace:*",
|
|
4724
|
+
"@kitsy/cnos-ui": "workspace:*",
|
|
4374
4725
|
"smol-toml": "^1.4.2"
|
|
4375
4726
|
},
|
|
4376
4727
|
scripts: {
|
|
@@ -4390,7 +4741,7 @@ function runVersion() {
|
|
|
4390
4741
|
}
|
|
4391
4742
|
|
|
4392
4743
|
// src/commands/value.ts
|
|
4393
|
-
import
|
|
4744
|
+
import path24 from "path";
|
|
4394
4745
|
function normalizeValueCommand(args) {
|
|
4395
4746
|
const [actionOrPath, ...tail] = args;
|
|
4396
4747
|
if (!actionOrPath) {
|
|
@@ -4414,7 +4765,7 @@ async function runValue(argsOrPath, options = {}) {
|
|
|
4414
4765
|
const args = Array.isArray(argsOrPath) ? argsOrPath : [argsOrPath];
|
|
4415
4766
|
const { action, tail } = normalizeValueCommand(args);
|
|
4416
4767
|
const cliArgs = [...options.cliArgs ?? []];
|
|
4417
|
-
const root =
|
|
4768
|
+
const root = path24.resolve(options.root ?? process.cwd());
|
|
4418
4769
|
if (action === "list") {
|
|
4419
4770
|
const prefix = consumeOption(cliArgs, "--prefix");
|
|
4420
4771
|
const entries = await listConfigEntries("value", {
|
|
@@ -4485,10 +4836,10 @@ async function runValue(argsOrPath, options = {}) {
|
|
|
4485
4836
|
// src/commands/watch.ts
|
|
4486
4837
|
import {
|
|
4487
4838
|
CNOS_GRAPH_ENV_VAR as CNOS_GRAPH_ENV_VAR2,
|
|
4488
|
-
CNOS_SECRET_PAYLOAD_ENV_VAR,
|
|
4489
|
-
CNOS_SESSION_KEY_ENV_VAR,
|
|
4839
|
+
CNOS_SECRET_PAYLOAD_ENV_VAR as CNOS_SECRET_PAYLOAD_ENV_VAR2,
|
|
4840
|
+
CNOS_SESSION_KEY_ENV_VAR as CNOS_SESSION_KEY_ENV_VAR2,
|
|
4490
4841
|
serializeRuntimeGraph as serializeRuntimeGraph2,
|
|
4491
|
-
serializeSecretPayload
|
|
4842
|
+
serializeSecretPayload as serializeSecretPayload2
|
|
4492
4843
|
} from "@kitsy/cnos/internal";
|
|
4493
4844
|
async function buildRunEnvironment(options) {
|
|
4494
4845
|
const cliArgs = [...options.cliArgs ?? []];
|
|
@@ -4504,7 +4855,7 @@ async function buildRunEnvironment(options) {
|
|
|
4504
4855
|
const authenticatedSecrets = isAuthenticated ? Object.fromEntries(
|
|
4505
4856
|
Array.from(runtime.graph.entries.values()).filter((entry) => entry.namespace === "secret").map((entry) => [entry.key, runtime.read(entry.key)])
|
|
4506
4857
|
) : void 0;
|
|
4507
|
-
const secretPayload = authenticatedSecrets ?
|
|
4858
|
+
const secretPayload = authenticatedSecrets ? serializeSecretPayload2(authenticatedSecrets) : void 0;
|
|
4508
4859
|
return {
|
|
4509
4860
|
runtime,
|
|
4510
4861
|
env: {
|
|
@@ -4512,11 +4863,11 @@ async function buildRunEnvironment(options) {
|
|
|
4512
4863
|
...isPublic ? runtime.toPublicEnv({
|
|
4513
4864
|
...framework ? { framework } : {},
|
|
4514
4865
|
...prefix ? { prefix } : {}
|
|
4515
|
-
}) : runtime.toEnv(),
|
|
4866
|
+
}) : runtime.toEnv({ includeSecrets: true }),
|
|
4516
4867
|
[CNOS_GRAPH_ENV_VAR2]: serializeRuntimeGraph2(runtime.graph),
|
|
4517
4868
|
...secretPayload ? {
|
|
4518
|
-
[
|
|
4519
|
-
[
|
|
4869
|
+
[CNOS_SECRET_PAYLOAD_ENV_VAR2]: secretPayload.payload,
|
|
4870
|
+
[CNOS_SESSION_KEY_ENV_VAR2]: secretPayload.sessionKey
|
|
4520
4871
|
} : {}
|
|
4521
4872
|
}
|
|
4522
4873
|
};
|
|
@@ -4610,9 +4961,9 @@ async function runWatch(command, options = {}) {
|
|
|
4610
4961
|
}
|
|
4611
4962
|
|
|
4612
4963
|
// src/commands/workspace.ts
|
|
4613
|
-
import { cp, mkdir as mkdir7, readdir as readdir5, readFile as
|
|
4614
|
-
import
|
|
4615
|
-
import { loadManifest as
|
|
4964
|
+
import { cp, mkdir as mkdir7, readdir as readdir5, readFile as readFile8, rename, rm as rm5, stat as stat3, writeFile as writeFile9 } from "fs/promises";
|
|
4965
|
+
import path25 from "path";
|
|
4966
|
+
import { loadManifest as loadManifest10, parseYaml as parseYaml6, stringifyYaml as stringifyYaml7 } from "@kitsy/cnos/internal";
|
|
4616
4967
|
async function exists2(targetPath) {
|
|
4617
4968
|
try {
|
|
4618
4969
|
await stat3(targetPath);
|
|
@@ -4625,7 +4976,7 @@ async function copyIfExists(source, target) {
|
|
|
4625
4976
|
if (!await exists2(source)) {
|
|
4626
4977
|
return;
|
|
4627
4978
|
}
|
|
4628
|
-
await mkdir7(
|
|
4979
|
+
await mkdir7(path25.dirname(target), { recursive: true });
|
|
4629
4980
|
await cp(source, target, { recursive: true, force: true });
|
|
4630
4981
|
}
|
|
4631
4982
|
async function moveIfExists(source, target, force = false) {
|
|
@@ -4637,22 +4988,22 @@ async function moveIfExists(source, target, force = false) {
|
|
|
4637
4988
|
} else if (await exists2(target)) {
|
|
4638
4989
|
throw new Error(`Refusing to overwrite existing path ${target}. Use --force to replace it.`);
|
|
4639
4990
|
}
|
|
4640
|
-
await mkdir7(
|
|
4991
|
+
await mkdir7(path25.dirname(target), { recursive: true });
|
|
4641
4992
|
await rename(source, target);
|
|
4642
4993
|
return true;
|
|
4643
4994
|
}
|
|
4644
4995
|
async function mergeWorkspaceRootsIntoStandalone(targetCnosRoot, sourceRoots) {
|
|
4645
4996
|
for (const sourceRoot of sourceRoots) {
|
|
4646
4997
|
for (const folderName of ["values", "secrets", "env", "profiles"]) {
|
|
4647
|
-
await copyIfExists(
|
|
4998
|
+
await copyIfExists(path25.join(sourceRoot, folderName), path25.join(targetCnosRoot, folderName));
|
|
4648
4999
|
}
|
|
4649
5000
|
}
|
|
4650
5001
|
}
|
|
4651
5002
|
async function writeAnchor(packageRoot, manifestRoot, workspace) {
|
|
4652
|
-
const relativeRoot =
|
|
5003
|
+
const relativeRoot = path25.relative(packageRoot, manifestRoot).replace(/\\/g, "/");
|
|
4653
5004
|
const rootValue = relativeRoot.length === 0 ? "./.cnos" : relativeRoot.startsWith(".") ? relativeRoot : `./${relativeRoot}`;
|
|
4654
5005
|
await writeFile9(
|
|
4655
|
-
|
|
5006
|
+
path25.join(packageRoot, ".cnosrc.yml"),
|
|
4656
5007
|
stringifyYaml7({
|
|
4657
5008
|
root: rootValue,
|
|
4658
5009
|
...workspace ? { workspace } : {}
|
|
@@ -4689,7 +5040,7 @@ function splitExtends(value) {
|
|
|
4689
5040
|
}
|
|
4690
5041
|
async function hasDirectConfigData(cnosRoot) {
|
|
4691
5042
|
for (const folderName of ["values", "secrets", "env", "profiles"]) {
|
|
4692
|
-
const folder =
|
|
5043
|
+
const folder = path25.join(cnosRoot, folderName);
|
|
4693
5044
|
if (!await exists2(folder)) {
|
|
4694
5045
|
continue;
|
|
4695
5046
|
}
|
|
@@ -4701,8 +5052,8 @@ async function hasDirectConfigData(cnosRoot) {
|
|
|
4701
5052
|
return false;
|
|
4702
5053
|
}
|
|
4703
5054
|
async function updateRootAnchorToWorkspace(packageRoot, workspaceId) {
|
|
4704
|
-
const anchorPath =
|
|
4705
|
-
const current = await exists2(anchorPath) ? parseYaml6(await
|
|
5055
|
+
const anchorPath = path25.join(packageRoot, ".cnosrc.yml");
|
|
5056
|
+
const current = await exists2(anchorPath) ? parseYaml6(await readFile8(anchorPath, "utf8")) : void 0;
|
|
4706
5057
|
await writeFile9(
|
|
4707
5058
|
anchorPath,
|
|
4708
5059
|
stringifyYaml7({
|
|
@@ -4713,8 +5064,8 @@ async function updateRootAnchorToWorkspace(packageRoot, workspaceId) {
|
|
|
4713
5064
|
);
|
|
4714
5065
|
}
|
|
4715
5066
|
async function updateWorkspaceContext(packageRoot, workspaceId) {
|
|
4716
|
-
const workspacePath =
|
|
4717
|
-
const current = await exists2(workspacePath) ? parseYaml6(await
|
|
5067
|
+
const workspacePath = path25.join(packageRoot, ".cnos-workspace.yml");
|
|
5068
|
+
const current = await exists2(workspacePath) ? parseYaml6(await readFile8(workspacePath, "utf8")) : void 0;
|
|
4718
5069
|
await writeFile9(
|
|
4719
5070
|
workspacePath,
|
|
4720
5071
|
stringifyYaml7({
|
|
@@ -4726,11 +5077,11 @@ async function updateWorkspaceContext(packageRoot, workspaceId) {
|
|
|
4726
5077
|
);
|
|
4727
5078
|
}
|
|
4728
5079
|
async function runDetach(packageRoot, options = {}) {
|
|
4729
|
-
const loaded = await
|
|
5080
|
+
const loaded = await loadManifest10({ cwd: packageRoot });
|
|
4730
5081
|
if (!loaded.anchorPath || !loaded.anchoredWorkspace) {
|
|
4731
5082
|
throw new Error("workspace detach requires a package-local .cnosrc.yml with a workspace binding");
|
|
4732
5083
|
}
|
|
4733
|
-
const targetCnosRoot =
|
|
5084
|
+
const targetCnosRoot = path25.join(packageRoot, ".cnos");
|
|
4734
5085
|
const force = consumeFlag([...options.cliArgs ?? []], "--force");
|
|
4735
5086
|
if (await exists2(targetCnosRoot) && !force) {
|
|
4736
5087
|
throw new Error(`Refusing to detach because ${displayPath(targetCnosRoot, packageRoot)} already exists. Use --force to overwrite.`);
|
|
@@ -4747,11 +5098,11 @@ async function runDetach(packageRoot, options = {}) {
|
|
|
4747
5098
|
await mkdir7(targetCnosRoot, { recursive: true });
|
|
4748
5099
|
await mergeWorkspaceRootsIntoStandalone(targetCnosRoot, localRoots);
|
|
4749
5100
|
await writeFile9(
|
|
4750
|
-
|
|
5101
|
+
path25.join(targetCnosRoot, "cnos.yml"),
|
|
4751
5102
|
stringifyYaml7(createDetachedManifest(loaded.rawManifest)),
|
|
4752
5103
|
"utf8"
|
|
4753
5104
|
);
|
|
4754
|
-
const relativeRoot =
|
|
5105
|
+
const relativeRoot = path25.relative(packageRoot, loaded.manifestRoot).replace(/\\/g, "/");
|
|
4755
5106
|
const marker = {
|
|
4756
5107
|
detachedFrom: relativeRoot || ".",
|
|
4757
5108
|
detachedWorkspace: loaded.anchoredWorkspace,
|
|
@@ -4761,8 +5112,8 @@ async function runDetach(packageRoot, options = {}) {
|
|
|
4761
5112
|
workspace: loaded.anchoredWorkspace
|
|
4762
5113
|
}
|
|
4763
5114
|
};
|
|
4764
|
-
await writeFile9(
|
|
4765
|
-
await writeFile9(
|
|
5115
|
+
await writeFile9(path25.join(targetCnosRoot, ".detached"), stringifyYaml7(marker), "utf8");
|
|
5116
|
+
await writeFile9(path25.join(packageRoot, ".cnosrc.yml"), stringifyYaml7({ root: "./.cnos" }), "utf8");
|
|
4766
5117
|
if (options.json) {
|
|
4767
5118
|
return printJson({
|
|
4768
5119
|
packageRoot,
|
|
@@ -4775,24 +5126,24 @@ async function runDetach(packageRoot, options = {}) {
|
|
|
4775
5126
|
async function runAttach(packageRoot, options = {}) {
|
|
4776
5127
|
const cliArgs = [...options.cliArgs ?? []];
|
|
4777
5128
|
const force = consumeFlag(cliArgs, "--force");
|
|
4778
|
-
const childCnosRoot =
|
|
4779
|
-
const markerPath =
|
|
5129
|
+
const childCnosRoot = path25.join(packageRoot, ".cnos");
|
|
5130
|
+
const markerPath = path25.join(childCnosRoot, ".detached");
|
|
4780
5131
|
if (!await exists2(markerPath)) {
|
|
4781
5132
|
throw new Error("workspace attach requires a detached package with .cnos/.detached");
|
|
4782
5133
|
}
|
|
4783
|
-
const marker = parseYaml6(await
|
|
5134
|
+
const marker = parseYaml6(await readFile8(markerPath, "utf8"));
|
|
4784
5135
|
if (!marker?.originalCnosrc?.root || !marker.detachedWorkspace) {
|
|
4785
5136
|
throw new Error("Invalid .detached marker");
|
|
4786
5137
|
}
|
|
4787
|
-
const parentManifestRoot =
|
|
4788
|
-
const parentLoaded = await
|
|
5138
|
+
const parentManifestRoot = path25.resolve(packageRoot, marker.originalCnosrc.root);
|
|
5139
|
+
const parentLoaded = await loadManifest10({ root: parentManifestRoot });
|
|
4789
5140
|
if (parentLoaded.rootResolution.readOnly) {
|
|
4790
5141
|
throw new Error(
|
|
4791
5142
|
`Cannot attach workspace because the parent CNOS root is remote and read-only (${parentLoaded.rootResolution.rootUri}).`
|
|
4792
5143
|
);
|
|
4793
5144
|
}
|
|
4794
5145
|
const workspaceId = marker.originalCnosrc.workspace ?? marker.detachedWorkspace;
|
|
4795
|
-
const parentWorkspaceRoot =
|
|
5146
|
+
const parentWorkspaceRoot = path25.join(parentLoaded.manifestRoot, "workspaces", workspaceId);
|
|
4796
5147
|
if (await exists2(parentWorkspaceRoot) && !force) {
|
|
4797
5148
|
throw new Error(`workspace "${workspaceId}" already exists in parent root. Use --force to overwrite.`);
|
|
4798
5149
|
}
|
|
@@ -4801,7 +5152,7 @@ async function runAttach(packageRoot, options = {}) {
|
|
|
4801
5152
|
}
|
|
4802
5153
|
await mkdir7(parentWorkspaceRoot, { recursive: true });
|
|
4803
5154
|
for (const folderName of ["values", "secrets", "env", "profiles"]) {
|
|
4804
|
-
await copyIfExists(
|
|
5155
|
+
await copyIfExists(path25.join(childCnosRoot, folderName), path25.join(parentWorkspaceRoot, folderName));
|
|
4805
5156
|
}
|
|
4806
5157
|
const rawManifest = structuredClone(parentLoaded.rawManifest);
|
|
4807
5158
|
const workspaces = rawManifest.workspaces ?? {};
|
|
@@ -4809,8 +5160,8 @@ async function runAttach(packageRoot, options = {}) {
|
|
|
4809
5160
|
items[workspaceId] = items[workspaceId] ?? {};
|
|
4810
5161
|
workspaces.items = items;
|
|
4811
5162
|
rawManifest.workspaces = workspaces;
|
|
4812
|
-
await writeFile9(
|
|
4813
|
-
const archivePath =
|
|
5163
|
+
await writeFile9(path25.join(parentLoaded.manifestRoot, "cnos.yml"), stringifyYaml7(rawManifest), "utf8");
|
|
5164
|
+
const archivePath = path25.join(packageRoot, ".cnos.detached.bak");
|
|
4814
5165
|
await rm5(archivePath, { recursive: true, force: true });
|
|
4815
5166
|
await rename(childCnosRoot, archivePath);
|
|
4816
5167
|
await writeAnchor(packageRoot, parentLoaded.manifestRoot, workspaceId);
|
|
@@ -4825,7 +5176,7 @@ async function runAttach(packageRoot, options = {}) {
|
|
|
4825
5176
|
return `attached workspace ${workspaceId} to ${displayPath(parentLoaded.manifestRoot, packageRoot)}`;
|
|
4826
5177
|
}
|
|
4827
5178
|
async function runList2(manifestCwd, options = {}) {
|
|
4828
|
-
const loaded = await
|
|
5179
|
+
const loaded = await loadManifest10({
|
|
4829
5180
|
...options.root ? { root: options.root } : {},
|
|
4830
5181
|
cwd: manifestCwd,
|
|
4831
5182
|
...options.processEnv ? { processEnv: options.processEnv } : {}
|
|
@@ -4834,7 +5185,7 @@ async function runList2(manifestCwd, options = {}) {
|
|
|
4834
5185
|
id,
|
|
4835
5186
|
extends: config.extends,
|
|
4836
5187
|
default: loaded.manifest.workspaces.default === id,
|
|
4837
|
-
path:
|
|
5188
|
+
path: path25.join(loaded.manifestRoot, "workspaces", id)
|
|
4838
5189
|
})).sort((left, right) => left.id.localeCompare(right.id));
|
|
4839
5190
|
if (options.json) {
|
|
4840
5191
|
return printJson({
|
|
@@ -4858,7 +5209,7 @@ async function runEnable(manifestCwd, packageRoot, options = {}) {
|
|
|
4858
5209
|
if (cliArgs.length > 0) {
|
|
4859
5210
|
throw new Error(`Unsupported workspace arguments: ${cliArgs.join(" ")}`);
|
|
4860
5211
|
}
|
|
4861
|
-
const loaded = await
|
|
5212
|
+
const loaded = await loadManifest10({
|
|
4862
5213
|
...options.root ? { root: options.root } : {},
|
|
4863
5214
|
cwd: manifestCwd,
|
|
4864
5215
|
...options.processEnv ? { processEnv: options.processEnv } : {}
|
|
@@ -4875,13 +5226,13 @@ async function runEnable(manifestCwd, packageRoot, options = {}) {
|
|
|
4875
5226
|
throw new Error("This CNOS root is already in workspace mode.");
|
|
4876
5227
|
}
|
|
4877
5228
|
const cnosRoot = loaded.manifestRoot;
|
|
4878
|
-
const baseWorkspaceRoot =
|
|
5229
|
+
const baseWorkspaceRoot = path25.join(cnosRoot, "workspaces", "base");
|
|
4879
5230
|
if (await exists2(baseWorkspaceRoot)) {
|
|
4880
5231
|
throw new Error("Cannot enable workspace mode because .cnos/workspaces/base already exists.");
|
|
4881
5232
|
}
|
|
4882
5233
|
const moved = [];
|
|
4883
5234
|
for (const folderName of ["values", "secrets", "env", "profiles"]) {
|
|
4884
|
-
if (await moveIfExists(
|
|
5235
|
+
if (await moveIfExists(path25.join(cnosRoot, folderName), path25.join(baseWorkspaceRoot, folderName))) {
|
|
4885
5236
|
moved.push(folderName);
|
|
4886
5237
|
}
|
|
4887
5238
|
}
|
|
@@ -4891,19 +5242,19 @@ async function runEnable(manifestCwd, packageRoot, options = {}) {
|
|
|
4891
5242
|
base: {}
|
|
4892
5243
|
};
|
|
4893
5244
|
rawManifest.workspaces = rawWorkspaces;
|
|
4894
|
-
await writeFile9(
|
|
5245
|
+
await writeFile9(path25.join(cnosRoot, "cnos.yml"), stringifyYaml7(rawManifest), "utf8");
|
|
4895
5246
|
await updateRootAnchorToWorkspace(packageRoot, "base");
|
|
4896
5247
|
await updateWorkspaceContext(packageRoot, "base");
|
|
4897
|
-
await ensureGitignore(
|
|
5248
|
+
await ensureGitignore(path25.dirname(cnosRoot));
|
|
4898
5249
|
if (options.json) {
|
|
4899
5250
|
return printJson({
|
|
4900
|
-
root:
|
|
5251
|
+
root: path25.dirname(cnosRoot),
|
|
4901
5252
|
workspace: "base",
|
|
4902
5253
|
moved
|
|
4903
5254
|
});
|
|
4904
5255
|
}
|
|
4905
5256
|
const movedSummary = moved.length > 0 ? `; moved ${moved.join(", ")} into .cnos/workspaces/base` : "";
|
|
4906
|
-
return `enabled workspace mode at ${displayPath(
|
|
5257
|
+
return `enabled workspace mode at ${displayPath(path25.dirname(cnosRoot), packageRoot)} with base workspace${movedSummary}`;
|
|
4907
5258
|
}
|
|
4908
5259
|
async function runAddOrScaffold(action, workspaceId, manifestCwd, packageRoot, options = {}) {
|
|
4909
5260
|
const cliArgs = [...options.cliArgs ?? []];
|
|
@@ -4912,7 +5263,7 @@ async function runAddOrScaffold(action, workspaceId, manifestCwd, packageRoot, o
|
|
|
4912
5263
|
if (cliArgs.length > 0) {
|
|
4913
5264
|
throw new Error(`Unsupported workspace arguments: ${cliArgs.join(" ")}`);
|
|
4914
5265
|
}
|
|
4915
|
-
const loaded = await
|
|
5266
|
+
const loaded = await loadManifest10({
|
|
4916
5267
|
...options.root ? { root: options.root } : {},
|
|
4917
5268
|
cwd: manifestCwd,
|
|
4918
5269
|
...options.processEnv ? { processEnv: options.processEnv } : {}
|
|
@@ -4942,15 +5293,15 @@ async function runAddOrScaffold(action, workspaceId, manifestCwd, packageRoot, o
|
|
|
4942
5293
|
rawWorkspaces.items = rawItems;
|
|
4943
5294
|
rawWorkspaces.default = rawWorkspaces.default ?? workspaceId;
|
|
4944
5295
|
rawManifest.workspaces = rawWorkspaces;
|
|
4945
|
-
const workspaceRoot =
|
|
5296
|
+
const workspaceRoot = path25.join(cnosRoot, "workspaces", workspaceId);
|
|
4946
5297
|
const created = await ensureWorkspaceLayout(cnosRoot, workspaceId);
|
|
4947
|
-
await writeFile9(
|
|
4948
|
-
await ensureGitignore(
|
|
5298
|
+
await writeFile9(path25.join(cnosRoot, "cnos.yml"), stringifyYaml7(rawManifest), "utf8");
|
|
5299
|
+
await ensureGitignore(path25.dirname(cnosRoot));
|
|
4949
5300
|
await writeAnchor(packageRoot, cnosRoot, workspaceId);
|
|
4950
5301
|
await updateWorkspaceContext(packageRoot, workspaceId);
|
|
4951
5302
|
const result = {
|
|
4952
5303
|
workspace: workspaceId,
|
|
4953
|
-
root:
|
|
5304
|
+
root: path25.dirname(cnosRoot),
|
|
4954
5305
|
packageRoot,
|
|
4955
5306
|
created
|
|
4956
5307
|
};
|
|
@@ -4966,7 +5317,7 @@ async function runRemove(workspaceId, manifestCwd, options = {}) {
|
|
|
4966
5317
|
if (cliArgs.length > 0) {
|
|
4967
5318
|
throw new Error(`Unsupported workspace arguments: ${cliArgs.join(" ")}`);
|
|
4968
5319
|
}
|
|
4969
|
-
const loaded = await
|
|
5320
|
+
const loaded = await loadManifest10({
|
|
4970
5321
|
...options.root ? { root: options.root } : {},
|
|
4971
5322
|
cwd: manifestCwd,
|
|
4972
5323
|
...options.processEnv ? { processEnv: options.processEnv } : {}
|
|
@@ -4988,8 +5339,8 @@ async function runRemove(workspaceId, manifestCwd, options = {}) {
|
|
|
4988
5339
|
delete rawItems[workspaceId];
|
|
4989
5340
|
rawWorkspaces.items = rawItems;
|
|
4990
5341
|
rawManifest.workspaces = rawWorkspaces;
|
|
4991
|
-
await writeFile9(
|
|
4992
|
-
await rm5(
|
|
5342
|
+
await writeFile9(path25.join(loaded.manifestRoot, "cnos.yml"), stringifyYaml7(rawManifest), "utf8");
|
|
5343
|
+
await rm5(path25.join(loaded.manifestRoot, "workspaces", workspaceId), { recursive: true, force: true });
|
|
4993
5344
|
if (options.json) {
|
|
4994
5345
|
return printJson({
|
|
4995
5346
|
workspace: workspaceId,
|
|
@@ -5001,8 +5352,8 @@ async function runRemove(workspaceId, manifestCwd, options = {}) {
|
|
|
5001
5352
|
async function runWorkspace(args = [], options = {}) {
|
|
5002
5353
|
const [action, workspaceArg] = args;
|
|
5003
5354
|
const baseCliArgs = [...options.cliArgs ?? []];
|
|
5004
|
-
const manifestCwd =
|
|
5005
|
-
const packageRoot =
|
|
5355
|
+
const manifestCwd = path25.resolve(options.root ?? process.cwd());
|
|
5356
|
+
const packageRoot = path25.resolve(consumeOption(baseCliArgs, "--package-root") ?? options.root ?? process.cwd());
|
|
5006
5357
|
switch (action) {
|
|
5007
5358
|
case "attach":
|
|
5008
5359
|
return runAttach(packageRoot, { ...options, cliArgs: baseCliArgs });
|
|
@@ -5125,6 +5476,9 @@ async function main(argv) {
|
|
|
5125
5476
|
process.stdout.write(`${runVersion()}
|
|
5126
5477
|
`);
|
|
5127
5478
|
return;
|
|
5479
|
+
case "ui":
|
|
5480
|
+
await runUi(runtimeOptions);
|
|
5481
|
+
return;
|
|
5128
5482
|
case "init":
|
|
5129
5483
|
process.stdout.write(`${await runInit(runtimeOptions)}
|
|
5130
5484
|
`);
|