@kitsy/cnos-cli 1.6.0 → 1.7.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 +613 -150
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -5,7 +5,8 @@ var OPTION_KEYS = {
|
|
|
5
5
|
"--root": "root",
|
|
6
6
|
"--workspace": "workspace",
|
|
7
7
|
"--profile": "profile",
|
|
8
|
-
"--global-root": "globalRoot"
|
|
8
|
+
"--global-root": "globalRoot",
|
|
9
|
+
"--cache-ttl": "cacheTtlSeconds"
|
|
9
10
|
};
|
|
10
11
|
var COMMAND_OPTION_KEYS_WITH_VALUE = /* @__PURE__ */ new Set([
|
|
11
12
|
"--format",
|
|
@@ -21,7 +22,8 @@ var COMMAND_OPTION_KEYS_WITH_VALUE = /* @__PURE__ */ new Set([
|
|
|
21
22
|
"--inherit",
|
|
22
23
|
"--as",
|
|
23
24
|
"--set",
|
|
24
|
-
"--debounce"
|
|
25
|
+
"--debounce",
|
|
26
|
+
"--expr"
|
|
25
27
|
]);
|
|
26
28
|
var COMMAND_FLAG_KEYS = /* @__PURE__ */ new Set([
|
|
27
29
|
"--flatten",
|
|
@@ -39,7 +41,8 @@ var COMMAND_FLAG_KEYS = /* @__PURE__ */ new Set([
|
|
|
39
41
|
"--dry-run",
|
|
40
42
|
"--apply",
|
|
41
43
|
"--rewrite",
|
|
42
|
-
"--signal"
|
|
44
|
+
"--signal",
|
|
45
|
+
"--derive"
|
|
43
46
|
]);
|
|
44
47
|
function normalizeCommand(argv) {
|
|
45
48
|
const [command = "doctor", ...rest] = argv;
|
|
@@ -104,6 +107,14 @@ function normalizeCommand(argv) {
|
|
|
104
107
|
return [command, ...rest];
|
|
105
108
|
}
|
|
106
109
|
function setOption(options, key, value) {
|
|
110
|
+
if (key === "cacheTtlSeconds") {
|
|
111
|
+
const parsed = Number(value);
|
|
112
|
+
if (!Number.isFinite(parsed) || parsed < 0) {
|
|
113
|
+
throw new Error(`Invalid value for --cache-ttl: ${value}`);
|
|
114
|
+
}
|
|
115
|
+
options.cacheTtlSeconds = parsed;
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
107
118
|
options[key] = value;
|
|
108
119
|
}
|
|
109
120
|
function parseArgs(argv) {
|
|
@@ -253,7 +264,7 @@ function printJson(value) {
|
|
|
253
264
|
|
|
254
265
|
// src/services/projections.ts
|
|
255
266
|
import { mkdir, writeFile } from "fs/promises";
|
|
256
|
-
import
|
|
267
|
+
import path3 from "path";
|
|
257
268
|
import { resolveBrowserData, resolveFrameworkEnv, resolveServerProjection } from "@kitsy/cnos/build";
|
|
258
269
|
import { stringifyYaml } from "@kitsy/cnos/internal";
|
|
259
270
|
|
|
@@ -266,6 +277,9 @@ async function createRuntimeService(options = {}) {
|
|
|
266
277
|
...options.workspace ? { workspace: options.workspace } : {},
|
|
267
278
|
...options.profile ? { profile: options.profile } : {},
|
|
268
279
|
...options.globalRoot ? { globalRoot: options.globalRoot } : {},
|
|
280
|
+
cacheMode: options.cacheMode ?? "runtime",
|
|
281
|
+
...typeof options.cacheTtlSeconds === "number" ? { cacheTtlSeconds: options.cacheTtlSeconds } : {},
|
|
282
|
+
...options.forceRefresh ? { forceRefresh: true } : {},
|
|
269
283
|
...options.cliArgs && options.cliArgs.length > 0 ? { cliArgs: options.cliArgs } : {},
|
|
270
284
|
...options.secretResolution ? { secretResolution: options.secretResolution } : {},
|
|
271
285
|
...typeof options.secretRefreshTtl === "number" ? { secretRefreshTtl: options.secretRefreshTtl } : {},
|
|
@@ -273,6 +287,15 @@ async function createRuntimeService(options = {}) {
|
|
|
273
287
|
});
|
|
274
288
|
}
|
|
275
289
|
|
|
290
|
+
// src/services/paths.ts
|
|
291
|
+
import path2 from "path";
|
|
292
|
+
function resolveFilesystemBasePath(root, cwd = process.cwd()) {
|
|
293
|
+
if (!root || root.startsWith("git+") || root.startsWith("cnos://")) {
|
|
294
|
+
return path2.resolve(cwd);
|
|
295
|
+
}
|
|
296
|
+
return path2.resolve(root);
|
|
297
|
+
}
|
|
298
|
+
|
|
276
299
|
// src/services/projections.ts
|
|
277
300
|
function stringifyScalar(value) {
|
|
278
301
|
if (value === void 0 || value === null) {
|
|
@@ -311,37 +334,62 @@ function formatKeyValueMap(values, format) {
|
|
|
311
334
|
}
|
|
312
335
|
}
|
|
313
336
|
async function writeProjectionFile(to, output, root = process.cwd()) {
|
|
314
|
-
const targetPath =
|
|
315
|
-
await mkdir(
|
|
337
|
+
const targetPath = path3.resolve(root, to);
|
|
338
|
+
await mkdir(path3.dirname(targetPath), { recursive: true });
|
|
316
339
|
await writeFile(targetPath, output, "utf8");
|
|
317
340
|
return targetPath;
|
|
318
341
|
}
|
|
319
342
|
async function buildServerProjectionArtifact(to, options = {}, format = "json") {
|
|
320
|
-
const projection = await resolveServerProjection(
|
|
343
|
+
const projection = await resolveServerProjection({
|
|
344
|
+
...options,
|
|
345
|
+
cacheMode: "build"
|
|
346
|
+
});
|
|
321
347
|
const output = format === "yaml" ? stringifyYaml(projection) : `${JSON.stringify(projection, null, 2)}
|
|
322
348
|
`;
|
|
323
|
-
const targetPath = await writeProjectionFile(
|
|
349
|
+
const targetPath = await writeProjectionFile(
|
|
350
|
+
to,
|
|
351
|
+
output,
|
|
352
|
+
resolveFilesystemBasePath(options.root, options.cwd ?? process.cwd())
|
|
353
|
+
);
|
|
324
354
|
return { targetPath, output };
|
|
325
355
|
}
|
|
326
356
|
async function buildBrowserProjectionArtifact(to, options = {}, format = "json") {
|
|
327
|
-
const projection = await resolveBrowserData(
|
|
357
|
+
const projection = await resolveBrowserData({
|
|
358
|
+
...options,
|
|
359
|
+
cacheMode: "build"
|
|
360
|
+
});
|
|
328
361
|
const output = format === "yaml" ? stringifyYaml(projection) : `${JSON.stringify(projection, null, 2)}
|
|
329
362
|
`;
|
|
330
|
-
const targetPath = await writeProjectionFile(
|
|
363
|
+
const targetPath = await writeProjectionFile(
|
|
364
|
+
to,
|
|
365
|
+
output,
|
|
366
|
+
resolveFilesystemBasePath(options.root, options.cwd ?? process.cwd())
|
|
367
|
+
);
|
|
331
368
|
return { targetPath, output };
|
|
332
369
|
}
|
|
333
370
|
async function buildPublicProjectionArtifact(to, options = {}, format = "dotenv") {
|
|
334
371
|
const cliArgs = [...options.cliArgs ?? []];
|
|
335
372
|
const frameworkIndex = cliArgs.indexOf("--framework");
|
|
336
373
|
const framework = frameworkIndex >= 0 && cliArgs[frameworkIndex + 1] ? cliArgs[frameworkIndex + 1] : "generic";
|
|
337
|
-
const env = await resolveFrameworkEnv(
|
|
374
|
+
const env = await resolveFrameworkEnv(
|
|
375
|
+
{
|
|
376
|
+
...options,
|
|
377
|
+
cacheMode: "build"
|
|
378
|
+
},
|
|
379
|
+
framework
|
|
380
|
+
);
|
|
338
381
|
const output = formatKeyValueMap(env, format);
|
|
339
|
-
const targetPath = await writeProjectionFile(
|
|
382
|
+
const targetPath = await writeProjectionFile(
|
|
383
|
+
to,
|
|
384
|
+
output,
|
|
385
|
+
resolveFilesystemBasePath(options.root, options.cwd ?? process.cwd())
|
|
386
|
+
);
|
|
340
387
|
return { targetPath, output, env };
|
|
341
388
|
}
|
|
342
389
|
async function buildEnvProjectionArtifact(to, options = {}, format = "dotenv") {
|
|
343
390
|
const runtime = await createRuntimeService({
|
|
344
391
|
...options,
|
|
392
|
+
cacheMode: "build",
|
|
345
393
|
cliArgs: [...options.cliArgs ?? []]
|
|
346
394
|
});
|
|
347
395
|
const env = runtime.toEnv();
|
|
@@ -352,7 +400,11 @@ async function buildEnvProjectionArtifact(to, options = {}, format = "dotenv") {
|
|
|
352
400
|
}
|
|
353
401
|
}
|
|
354
402
|
const output = formatKeyValueMap(env, format);
|
|
355
|
-
const targetPath = await writeProjectionFile(
|
|
403
|
+
const targetPath = await writeProjectionFile(
|
|
404
|
+
to,
|
|
405
|
+
output,
|
|
406
|
+
resolveFilesystemBasePath(options.root, options.cwd ?? process.cwd())
|
|
407
|
+
);
|
|
356
408
|
return { targetPath, output, env };
|
|
357
409
|
}
|
|
358
410
|
|
|
@@ -413,23 +465,208 @@ async function runBuild(subcommand, options = {}) {
|
|
|
413
465
|
...provenanceTarget ? { provenance: provenanceTarget } : {}
|
|
414
466
|
});
|
|
415
467
|
}
|
|
416
|
-
return `built ${subcommand} artifact at ${displayPath(
|
|
468
|
+
return `built ${subcommand} artifact at ${displayPath(
|
|
469
|
+
targetPath,
|
|
470
|
+
resolveFilesystemBasePath(options.root, options.cwd ?? process.cwd())
|
|
471
|
+
)}`;
|
|
417
472
|
}
|
|
418
473
|
|
|
419
|
-
// src/
|
|
474
|
+
// src/services/cache.ts
|
|
475
|
+
import { readdir, rm, stat } from "fs/promises";
|
|
420
476
|
import path4 from "path";
|
|
477
|
+
import {
|
|
478
|
+
loadManifest,
|
|
479
|
+
parseGitUri,
|
|
480
|
+
readRemoteRootCacheMetadata,
|
|
481
|
+
resolveCnosCacheRoot,
|
|
482
|
+
resolveRemoteRootCachePaths,
|
|
483
|
+
resolveRootUri
|
|
484
|
+
} from "@kitsy/cnos/internal";
|
|
485
|
+
async function computeDirectorySize(targetPath) {
|
|
486
|
+
try {
|
|
487
|
+
const info = await stat(targetPath);
|
|
488
|
+
if (!info.isDirectory()) {
|
|
489
|
+
return info.size;
|
|
490
|
+
}
|
|
491
|
+
const entries = await readdir(targetPath, { withFileTypes: true });
|
|
492
|
+
const sizes = await Promise.all(
|
|
493
|
+
entries.map((entry) => computeDirectorySize(path4.join(targetPath, entry.name)))
|
|
494
|
+
);
|
|
495
|
+
return sizes.reduce((sum, value) => sum + value, 0);
|
|
496
|
+
} catch {
|
|
497
|
+
return 0;
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
async function listCachedRoots(processEnv = process.env) {
|
|
501
|
+
const rootsDir = path4.join(resolveCnosCacheRoot(processEnv), "roots");
|
|
502
|
+
try {
|
|
503
|
+
const entries = await readdir(rootsDir, { withFileTypes: true });
|
|
504
|
+
const records = await Promise.all(
|
|
505
|
+
entries.filter((entry) => entry.isDirectory()).map(async (entry) => {
|
|
506
|
+
const cacheDir = path4.join(rootsDir, entry.name);
|
|
507
|
+
const metadata = await readRemoteRootCacheMetadata(path4.join(cacheDir, ".cnos-cache-meta.json"));
|
|
508
|
+
if (!metadata) {
|
|
509
|
+
return void 0;
|
|
510
|
+
}
|
|
511
|
+
return {
|
|
512
|
+
uri: metadata.uri,
|
|
513
|
+
cacheDir,
|
|
514
|
+
cachedAt: metadata.cachedAt,
|
|
515
|
+
resolvedCommit: metadata.resolvedCommit,
|
|
516
|
+
immutable: metadata.isImmutable,
|
|
517
|
+
ref: metadata.ref,
|
|
518
|
+
subpath: metadata.subpath,
|
|
519
|
+
sizeBytes: await computeDirectorySize(cacheDir)
|
|
520
|
+
};
|
|
521
|
+
})
|
|
522
|
+
);
|
|
523
|
+
return records.filter((record) => Boolean(record)).sort((left, right) => left.uri.localeCompare(right.uri));
|
|
524
|
+
} catch {
|
|
525
|
+
return [];
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
async function clearCachedRoots(uri, processEnv = process.env) {
|
|
529
|
+
if (uri) {
|
|
530
|
+
const paths = resolveRemoteRootCachePaths(uri, processEnv);
|
|
531
|
+
await rm(paths.cacheDir, { recursive: true, force: true });
|
|
532
|
+
return { cleared: [uri] };
|
|
533
|
+
}
|
|
534
|
+
const records = await listCachedRoots(processEnv);
|
|
535
|
+
await Promise.all(records.map((record) => rm(record.cacheDir, { recursive: true, force: true })));
|
|
536
|
+
return {
|
|
537
|
+
cleared: records.map((record) => record.uri)
|
|
538
|
+
};
|
|
539
|
+
}
|
|
540
|
+
async function refreshCachedRoots(uri, options = {}) {
|
|
541
|
+
const processEnv = options.processEnv ?? process.env;
|
|
542
|
+
if (uri) {
|
|
543
|
+
const parsed = parseGitUri(uri);
|
|
544
|
+
await resolveRootUri(uri, process.cwd(), {
|
|
545
|
+
processEnv,
|
|
546
|
+
cacheMode: "build",
|
|
547
|
+
forceRefresh: true
|
|
548
|
+
});
|
|
549
|
+
return { refreshed: [parsed.uri] };
|
|
550
|
+
}
|
|
551
|
+
const loadedManifest = await loadManifest({
|
|
552
|
+
...options.root ? { root: options.root } : {},
|
|
553
|
+
...options.cwd ? { cwd: options.cwd } : {},
|
|
554
|
+
processEnv
|
|
555
|
+
}).catch(() => void 0);
|
|
556
|
+
if (loadedManifest?.rootResolution.remote && loadedManifest.rootResolution.protocol === "git") {
|
|
557
|
+
await resolveRootUri(loadedManifest.rootResolution.rootUri, loadedManifest.consumerRoot, {
|
|
558
|
+
processEnv,
|
|
559
|
+
cacheMode: "build",
|
|
560
|
+
forceRefresh: true
|
|
561
|
+
});
|
|
562
|
+
return {
|
|
563
|
+
refreshed: [loadedManifest.rootResolution.rootUri]
|
|
564
|
+
};
|
|
565
|
+
}
|
|
566
|
+
const records = await listCachedRoots(processEnv);
|
|
567
|
+
const mutable = records.filter((record) => !record.immutable);
|
|
568
|
+
for (const record of mutable) {
|
|
569
|
+
await resolveRootUri(record.uri, process.cwd(), {
|
|
570
|
+
processEnv,
|
|
571
|
+
cacheMode: "build",
|
|
572
|
+
forceRefresh: true
|
|
573
|
+
});
|
|
574
|
+
}
|
|
575
|
+
return {
|
|
576
|
+
refreshed: mutable.map((record) => record.uri)
|
|
577
|
+
};
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
// src/commands/cache.ts
|
|
581
|
+
function normalizeAction(args) {
|
|
582
|
+
const [action = "list", target] = args;
|
|
583
|
+
if (action === "list" || action === "clear" || action === "refresh") {
|
|
584
|
+
return {
|
|
585
|
+
action,
|
|
586
|
+
...typeof target === "string" ? { target } : {}
|
|
587
|
+
};
|
|
588
|
+
}
|
|
589
|
+
return {
|
|
590
|
+
action: "list",
|
|
591
|
+
...typeof args[0] === "string" ? { target: args[0] } : {}
|
|
592
|
+
};
|
|
593
|
+
}
|
|
594
|
+
function formatBytes(sizeBytes) {
|
|
595
|
+
if (sizeBytes < 1024) {
|
|
596
|
+
return `${sizeBytes} B`;
|
|
597
|
+
}
|
|
598
|
+
if (sizeBytes < 1024 * 1024) {
|
|
599
|
+
return `${Math.round(sizeBytes / 1024 * 10) / 10} KB`;
|
|
600
|
+
}
|
|
601
|
+
return `${Math.round(sizeBytes / (1024 * 1024) * 10) / 10} MB`;
|
|
602
|
+
}
|
|
603
|
+
async function runCache(args = [], options = {}) {
|
|
604
|
+
const { action, target } = normalizeAction(args);
|
|
605
|
+
if (action === "clear") {
|
|
606
|
+
const result = await clearCachedRoots(target, options.processEnv ?? process.env);
|
|
607
|
+
return options.json ? printJson(result) : `cleared ${result.cleared.length} cached root(s)`;
|
|
608
|
+
}
|
|
609
|
+
if (action === "refresh") {
|
|
610
|
+
const result = await refreshCachedRoots(target, options);
|
|
611
|
+
return options.json ? printJson(result) : `refreshed ${result.refreshed.length} cached root(s)`;
|
|
612
|
+
}
|
|
613
|
+
const records = await listCachedRoots(options.processEnv ?? process.env);
|
|
614
|
+
if (options.json) {
|
|
615
|
+
return printJson(records);
|
|
616
|
+
}
|
|
617
|
+
if (records.length === 0) {
|
|
618
|
+
return "no cached remote roots";
|
|
619
|
+
}
|
|
620
|
+
return records.map(
|
|
621
|
+
(record) => [
|
|
622
|
+
record.uri,
|
|
623
|
+
` cached: ${record.cachedAt}`,
|
|
624
|
+
` commit: ${record.resolvedCommit}`,
|
|
625
|
+
` immutable: ${record.immutable ? "yes" : "no"}`,
|
|
626
|
+
` size: ${formatBytes(record.sizeBytes)}`
|
|
627
|
+
].join("\n")
|
|
628
|
+
).join("\n\n");
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
// src/commands/define.ts
|
|
632
|
+
import path6 from "path";
|
|
421
633
|
|
|
422
634
|
// src/services/writes.ts
|
|
423
635
|
import { mkdir as mkdir2, readFile, writeFile as writeFile2 } from "fs/promises";
|
|
424
|
-
import
|
|
636
|
+
import path5 from "path";
|
|
425
637
|
import {
|
|
426
638
|
getNamespaceDefinition,
|
|
639
|
+
normalizeDerivedValue,
|
|
640
|
+
parseDerivation,
|
|
427
641
|
createSecretVaultProvider,
|
|
642
|
+
validateDerivedTargetNamespace,
|
|
643
|
+
validateParsedDerivation,
|
|
428
644
|
parseYaml,
|
|
429
645
|
resolveConfigDocumentPath,
|
|
430
646
|
resolveVaultAuth,
|
|
431
647
|
stringifyYaml as stringifyYaml2
|
|
432
648
|
} from "@kitsy/cnos/internal";
|
|
649
|
+
|
|
650
|
+
// src/services/rootAccess.ts
|
|
651
|
+
import { loadManifest as loadManifest2 } from "@kitsy/cnos/internal";
|
|
652
|
+
async function assertWritableConfigRoot(action, options = {}) {
|
|
653
|
+
const loadedManifest = await loadManifest2({
|
|
654
|
+
...options.root ? { root: options.root } : {},
|
|
655
|
+
...options.cwd ? { cwd: options.cwd } : {},
|
|
656
|
+
...options.processEnv ? { processEnv: options.processEnv } : {},
|
|
657
|
+
...options.cacheMode ? { cacheMode: options.cacheMode } : {},
|
|
658
|
+
...typeof options.cacheTtlSeconds === "number" ? { cacheTtlSeconds: options.cacheTtlSeconds } : {},
|
|
659
|
+
...options.forceRefresh ? { forceRefresh: true } : {}
|
|
660
|
+
});
|
|
661
|
+
if (!loadedManifest.rootResolution.readOnly) {
|
|
662
|
+
return;
|
|
663
|
+
}
|
|
664
|
+
throw new Error(
|
|
665
|
+
`Cannot ${action} because the active CNOS root is remote and read-only (${loadedManifest.rootResolution.rootUri}). Clone the config repo and edit it directly.`
|
|
666
|
+
);
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
// src/services/writes.ts
|
|
433
670
|
function setNestedValue(target, pathSegments, value) {
|
|
434
671
|
const [head, ...tail] = pathSegments;
|
|
435
672
|
if (!head) {
|
|
@@ -495,6 +732,7 @@ function getSelectedWorkspaceRoot(options, runtime) {
|
|
|
495
732
|
return workspaceRoot.path;
|
|
496
733
|
}
|
|
497
734
|
async function defineValue(namespace, configPath, rawValue, options = {}) {
|
|
735
|
+
await assertWritableConfigRoot(`write ${namespace}.${configPath}`, options);
|
|
498
736
|
if (namespace === "secret") {
|
|
499
737
|
const secret = await setSecret(configPath, rawValue, {
|
|
500
738
|
...options,
|
|
@@ -527,9 +765,17 @@ async function defineValue(namespace, configPath, rawValue, options = {}) {
|
|
|
527
765
|
const profile = options.profile ?? runtime.graph.profile;
|
|
528
766
|
const filePath = resolveConfigDocumentPath(workspaceRoot, namespace, configPath, profile);
|
|
529
767
|
const document = await readYamlDocument(filePath);
|
|
530
|
-
|
|
768
|
+
let parsedValue;
|
|
769
|
+
if (options.deriveExpression !== void 0) {
|
|
770
|
+
validateDerivedTargetNamespace(runtime.manifest, namespace);
|
|
771
|
+
const derivedValue = normalizeDerivedValue(options.deriveExpression, options.deriveExprMode ?? false);
|
|
772
|
+
validateParsedDerivation(runtime.manifest, parseDerivation(derivedValue));
|
|
773
|
+
parsedValue = derivedValue;
|
|
774
|
+
} else {
|
|
775
|
+
parsedValue = parseScalarValue(rawValue);
|
|
776
|
+
}
|
|
531
777
|
setNestedValue(document, configPath.split("."), parsedValue);
|
|
532
|
-
await mkdir2(
|
|
778
|
+
await mkdir2(path5.dirname(filePath), { recursive: true });
|
|
533
779
|
await writeFile2(filePath, stringifyYaml2(document), "utf8");
|
|
534
780
|
return {
|
|
535
781
|
filePath,
|
|
@@ -567,7 +813,7 @@ async function setSecret(configPath, rawValue, options = {}) {
|
|
|
567
813
|
};
|
|
568
814
|
}
|
|
569
815
|
setNestedValue(document, configPath.split("."), reference);
|
|
570
|
-
await mkdir2(
|
|
816
|
+
await mkdir2(path5.dirname(filePath), { recursive: true });
|
|
571
817
|
await writeFile2(filePath, stringifyYaml2(document), "utf8");
|
|
572
818
|
return {
|
|
573
819
|
filePath,
|
|
@@ -577,6 +823,7 @@ async function setSecret(configPath, rawValue, options = {}) {
|
|
|
577
823
|
};
|
|
578
824
|
}
|
|
579
825
|
async function deleteSecret(configPath, options = {}) {
|
|
826
|
+
await assertWritableConfigRoot(`delete secret.${configPath}`, options);
|
|
580
827
|
const runtime = await createRuntimeService(options);
|
|
581
828
|
const workspaceRoot = getSelectedWorkspaceRoot(options, runtime);
|
|
582
829
|
const profile = options.profile ?? runtime.graph.profile;
|
|
@@ -607,6 +854,7 @@ async function deleteSecret(configPath, options = {}) {
|
|
|
607
854
|
};
|
|
608
855
|
}
|
|
609
856
|
async function deleteValue(namespace, configPath, options = {}) {
|
|
857
|
+
await assertWritableConfigRoot(`delete ${namespace}.${configPath}`, options);
|
|
610
858
|
if (namespace === "secret") {
|
|
611
859
|
return deleteSecret(configPath, options);
|
|
612
860
|
}
|
|
@@ -645,7 +893,7 @@ async function deleteValue(namespace, configPath, options = {}) {
|
|
|
645
893
|
// src/commands/define.ts
|
|
646
894
|
async function runDefine(namespace, configPath, rawValue, options = {}) {
|
|
647
895
|
const cliArgs = [...options.cliArgs ?? []];
|
|
648
|
-
const root =
|
|
896
|
+
const root = path6.resolve(options.root ?? process.cwd());
|
|
649
897
|
const target = consumeOption(cliArgs, "--target") ?? "local";
|
|
650
898
|
const local = consumeFlag(cliArgs, "--local");
|
|
651
899
|
const remote = consumeFlag(cliArgs, "--remote");
|
|
@@ -676,7 +924,7 @@ async function runDefine(namespace, configPath, rawValue, options = {}) {
|
|
|
676
924
|
|
|
677
925
|
// src/services/envMaterialization.ts
|
|
678
926
|
import { mkdir as mkdir3, writeFile as writeFile3 } from "fs/promises";
|
|
679
|
-
import
|
|
927
|
+
import path7 from "path";
|
|
680
928
|
function resolveEnvFromRuntime(runtime, cliArgs = []) {
|
|
681
929
|
const args = [...cliArgs];
|
|
682
930
|
const isPublic = consumeFlag(args, "--public");
|
|
@@ -703,17 +951,21 @@ async function resolveMaterializedEnv(options = {}) {
|
|
|
703
951
|
};
|
|
704
952
|
}
|
|
705
953
|
function resolveMaterializedEnvTarget(to, root = process.cwd()) {
|
|
706
|
-
return
|
|
954
|
+
return path7.resolve(root, to);
|
|
707
955
|
}
|
|
708
956
|
async function writeMaterializedEnvFile(to, output, root = process.cwd()) {
|
|
709
957
|
const targetPath = resolveMaterializedEnvTarget(to, root);
|
|
710
|
-
await mkdir3(
|
|
958
|
+
await mkdir3(path7.dirname(targetPath), { recursive: true });
|
|
711
959
|
await writeFile3(targetPath, output, "utf8");
|
|
712
960
|
return targetPath;
|
|
713
961
|
}
|
|
714
962
|
async function materializeEnvToFile(to, options = {}) {
|
|
715
963
|
const result = await resolveMaterializedEnv(options);
|
|
716
|
-
const targetPath = await writeMaterializedEnvFile(
|
|
964
|
+
const targetPath = await writeMaterializedEnvFile(
|
|
965
|
+
to,
|
|
966
|
+
result.output,
|
|
967
|
+
resolveFilesystemBasePath(options.root, options.cwd ?? process.cwd())
|
|
968
|
+
);
|
|
717
969
|
return {
|
|
718
970
|
...result,
|
|
719
971
|
targetPath
|
|
@@ -834,6 +1086,7 @@ async function startDevEnvLoop(command, options = {}) {
|
|
|
834
1086
|
const writeCurrent = async () => {
|
|
835
1087
|
await materializeEnvToFile(to, {
|
|
836
1088
|
...options,
|
|
1089
|
+
cacheMode: "dev",
|
|
837
1090
|
cliArgs: [...cliArgs]
|
|
838
1091
|
});
|
|
839
1092
|
};
|
|
@@ -847,6 +1100,7 @@ async function startDevEnvLoop(command, options = {}) {
|
|
|
847
1100
|
}
|
|
848
1101
|
const watcher = await startGraphWatchLoop({
|
|
849
1102
|
...options,
|
|
1103
|
+
cacheMode: "dev",
|
|
850
1104
|
cliArgs,
|
|
851
1105
|
debounceMs,
|
|
852
1106
|
async onChange(payload) {
|
|
@@ -903,7 +1157,10 @@ async function runDev(subcommand, command, options = {}) {
|
|
|
903
1157
|
};
|
|
904
1158
|
process.once("SIGINT", closeLoop);
|
|
905
1159
|
process.once("SIGTERM", closeLoop);
|
|
906
|
-
const targetPath = displayPath(
|
|
1160
|
+
const targetPath = displayPath(
|
|
1161
|
+
to,
|
|
1162
|
+
resolveFilesystemBasePath(options.root, options.cwd ?? process.cwd())
|
|
1163
|
+
);
|
|
907
1164
|
return isSignal ? `watching config changes and rewriting ${targetPath} in signal mode` : `watching config changes, rewriting ${targetPath}, and restarting the child process`;
|
|
908
1165
|
}
|
|
909
1166
|
|
|
@@ -956,11 +1213,12 @@ async function runDiff(leftProfile, rightProfile, options = {}) {
|
|
|
956
1213
|
}
|
|
957
1214
|
|
|
958
1215
|
// src/services/doctor.ts
|
|
959
|
-
import { readdir, readFile as readFile2 } from "fs/promises";
|
|
960
|
-
import
|
|
1216
|
+
import { readdir as readdir2, readFile as readFile2 } from "fs/promises";
|
|
1217
|
+
import path8 from "path";
|
|
961
1218
|
import {
|
|
962
1219
|
detectLegacyVaultFormat,
|
|
963
1220
|
isSecretReference as isSecretReference2,
|
|
1221
|
+
loadManifest as loadManifest3,
|
|
964
1222
|
parseYaml as parseYaml2,
|
|
965
1223
|
readKeychain,
|
|
966
1224
|
resolveSecretStoreRoot
|
|
@@ -979,7 +1237,7 @@ async function createValidationSummary(options = {}) {
|
|
|
979
1237
|
|
|
980
1238
|
// src/services/doctor.ts
|
|
981
1239
|
async function checkGitignore(root) {
|
|
982
|
-
const gitignorePath =
|
|
1240
|
+
const gitignorePath = path8.join(root, ".gitignore");
|
|
983
1241
|
const expected = [
|
|
984
1242
|
".cnos/env/.env",
|
|
985
1243
|
".cnos/env/.env.*",
|
|
@@ -1011,15 +1269,15 @@ function issueSummary(issues) {
|
|
|
1011
1269
|
}
|
|
1012
1270
|
async function collectYamlFiles(root) {
|
|
1013
1271
|
try {
|
|
1014
|
-
const entries = await
|
|
1272
|
+
const entries = await readdir2(root, { withFileTypes: true });
|
|
1015
1273
|
const results = [];
|
|
1016
1274
|
for (const entry of entries) {
|
|
1017
|
-
const target =
|
|
1275
|
+
const target = path8.join(root, entry.name);
|
|
1018
1276
|
if (entry.isDirectory()) {
|
|
1019
1277
|
results.push(...await collectYamlFiles(target));
|
|
1020
1278
|
continue;
|
|
1021
1279
|
}
|
|
1022
|
-
if (entry.isFile() && [".yml", ".yaml"].includes(
|
|
1280
|
+
if (entry.isFile() && [".yml", ".yaml"].includes(path8.extname(entry.name).toLowerCase())) {
|
|
1023
1281
|
results.push(target);
|
|
1024
1282
|
}
|
|
1025
1283
|
}
|
|
@@ -1044,7 +1302,7 @@ async function checkSecretSecurity(options, runtime) {
|
|
|
1044
1302
|
);
|
|
1045
1303
|
const legacyDetected = legacyPaths.filter((entry) => Boolean(entry.path));
|
|
1046
1304
|
const secretFiles = await Promise.all(
|
|
1047
|
-
runtime.graph.workspace.workspaceRoots.filter((root) => root.scope === "local").map((root) => collectYamlFiles(
|
|
1305
|
+
runtime.graph.workspace.workspaceRoots.filter((root) => root.scope === "local").map((root) => collectYamlFiles(path8.join(root.path, "secrets")))
|
|
1048
1306
|
);
|
|
1049
1307
|
const plaintextFiles = [];
|
|
1050
1308
|
for (const file of secretFiles.flat()) {
|
|
@@ -1074,7 +1332,15 @@ async function checkSecretSecurity(options, runtime) {
|
|
|
1074
1332
|
};
|
|
1075
1333
|
}
|
|
1076
1334
|
async function evaluateDoctor(options = {}) {
|
|
1077
|
-
const root =
|
|
1335
|
+
const root = resolveFilesystemBasePath(options.root, options.cwd ?? process.cwd());
|
|
1336
|
+
const loadedManifest = await loadManifest3({
|
|
1337
|
+
...options.root ? { root: options.root } : {},
|
|
1338
|
+
...options.cwd ? { cwd: options.cwd } : {},
|
|
1339
|
+
...options.processEnv ? { processEnv: options.processEnv } : {},
|
|
1340
|
+
...options.cacheMode ? { cacheMode: options.cacheMode } : {},
|
|
1341
|
+
...typeof options.cacheTtlSeconds === "number" ? { cacheTtlSeconds: options.cacheTtlSeconds } : {},
|
|
1342
|
+
...options.forceRefresh ? { forceRefresh: true } : {}
|
|
1343
|
+
});
|
|
1078
1344
|
const { runtime, summary } = await createValidationSummary(options);
|
|
1079
1345
|
const localRoot = runtime.graph.workspace.workspaceRoots.find((entry) => entry.scope === "local");
|
|
1080
1346
|
const globalRoot = runtime.graph.workspace.workspaceRoots.find((entry) => entry.scope === "global");
|
|
@@ -1090,6 +1356,11 @@ async function evaluateDoctor(options = {}) {
|
|
|
1090
1356
|
ok: true,
|
|
1091
1357
|
details: `${runtime.graph.workspace.workspaceId} via ${runtime.graph.workspace.workspaceSource}`
|
|
1092
1358
|
},
|
|
1359
|
+
{
|
|
1360
|
+
name: "root",
|
|
1361
|
+
ok: true,
|
|
1362
|
+
details: loadedManifest.rootResolution.remote ? `${loadedManifest.rootResolution.rootUri} -> ${loadedManifest.manifestRoot}${loadedManifest.rootResolution.immutable ? " | immutable" : " | mutable ref"}${loadedManifest.rootResolution.resolvedCommit ? ` | commit ${loadedManifest.rootResolution.resolvedCommit}` : ""}` : loadedManifest.manifestRoot
|
|
1363
|
+
},
|
|
1093
1364
|
{
|
|
1094
1365
|
name: "namespaces",
|
|
1095
1366
|
ok: true,
|
|
@@ -1214,7 +1485,10 @@ async function runExportEnv(options = {}) {
|
|
|
1214
1485
|
...framework ? { framework } : {}
|
|
1215
1486
|
});
|
|
1216
1487
|
}
|
|
1217
|
-
return `Wrote ${Object.keys(result2.env).length} env vars to ${displayPath(
|
|
1488
|
+
return `Wrote ${Object.keys(result2.env).length} env vars to ${displayPath(
|
|
1489
|
+
result2.targetPath,
|
|
1490
|
+
resolveFilesystemBasePath(options.root, options.cwd ?? process.cwd())
|
|
1491
|
+
)}`;
|
|
1218
1492
|
}
|
|
1219
1493
|
const result = await resolveMaterializedEnv(baseOptions);
|
|
1220
1494
|
if (options.json) {
|
|
@@ -1235,7 +1509,7 @@ async function runExport(subcommand, options = {}) {
|
|
|
1235
1509
|
var GLOBAL_OPTIONS = [
|
|
1236
1510
|
{
|
|
1237
1511
|
flag: "--root <path>",
|
|
1238
|
-
description: "Resolve the CNOS project from a specific filesystem root."
|
|
1512
|
+
description: "Resolve the CNOS project from a specific filesystem root or remote root URI."
|
|
1239
1513
|
},
|
|
1240
1514
|
{
|
|
1241
1515
|
flag: "--workspace <id>",
|
|
@@ -1249,6 +1523,10 @@ var GLOBAL_OPTIONS = [
|
|
|
1249
1523
|
flag: "--global-root <path>",
|
|
1250
1524
|
description: "Override the configured global CNOS root used for workspace layering."
|
|
1251
1525
|
},
|
|
1526
|
+
{
|
|
1527
|
+
flag: "--cache-ttl <seconds>",
|
|
1528
|
+
description: "Override the remote-root cache TTL for mutable refs during this invocation."
|
|
1529
|
+
},
|
|
1252
1530
|
{
|
|
1253
1531
|
flag: "--json",
|
|
1254
1532
|
description: "Emit JSON output for commands that support structured responses."
|
|
@@ -1263,6 +1541,39 @@ var GLOBAL_OPTIONS = [
|
|
|
1263
1541
|
}
|
|
1264
1542
|
];
|
|
1265
1543
|
var COMMANDS = [
|
|
1544
|
+
{
|
|
1545
|
+
id: "cache",
|
|
1546
|
+
summary: "Inspect and manage cached remote roots.",
|
|
1547
|
+
usage: "cnos cache [list|clear|refresh] [root-uri] [global-options]",
|
|
1548
|
+
description: "Lists cached git-backed remote roots, clears cache entries, or forces a refresh for mutable refs.",
|
|
1549
|
+
examples: [
|
|
1550
|
+
"cnos cache list",
|
|
1551
|
+
"cnos cache clear",
|
|
1552
|
+
"cnos cache clear git+https://github.com/org/config.git#v2.1.0",
|
|
1553
|
+
"cnos cache refresh"
|
|
1554
|
+
]
|
|
1555
|
+
},
|
|
1556
|
+
{
|
|
1557
|
+
id: "cache list",
|
|
1558
|
+
summary: "List cached remote roots.",
|
|
1559
|
+
usage: "cnos cache list [global-options]",
|
|
1560
|
+
description: "Lists git-backed remote roots cached under ~/.cnos/cache together with cache time, resolved commit, immutability, and size.",
|
|
1561
|
+
examples: ["cnos cache list"]
|
|
1562
|
+
},
|
|
1563
|
+
{
|
|
1564
|
+
id: "cache clear",
|
|
1565
|
+
summary: "Clear cached remote roots.",
|
|
1566
|
+
usage: "cnos cache clear [root-uri] [global-options]",
|
|
1567
|
+
description: "Removes all cached remote roots by default, or clears one specific cached root when a full remote URI is provided.",
|
|
1568
|
+
examples: ["cnos cache clear", "cnos cache clear git+https://github.com/org/config.git#main"]
|
|
1569
|
+
},
|
|
1570
|
+
{
|
|
1571
|
+
id: "cache refresh",
|
|
1572
|
+
summary: "Force refresh mutable cached remote roots.",
|
|
1573
|
+
usage: "cnos cache refresh [root-uri] [global-options]",
|
|
1574
|
+
description: "Re-fetches a specific git-backed remote root, or refreshes the active remote root / all mutable cached roots when no URI is provided.",
|
|
1575
|
+
examples: ["cnos cache refresh", "cnos cache refresh git+ssh://git@github.com/org/config.git#main"]
|
|
1576
|
+
},
|
|
1266
1577
|
{
|
|
1267
1578
|
id: "init",
|
|
1268
1579
|
summary: "Scaffold a workspace-aware CNOS tree in the current project.",
|
|
@@ -1330,6 +1641,14 @@ var COMMANDS = [
|
|
|
1330
1641
|
flag: "--target <local|global>",
|
|
1331
1642
|
description: "Choose whether writes land in the local project workspace or the configured global root."
|
|
1332
1643
|
},
|
|
1644
|
+
{
|
|
1645
|
+
flag: "--derive",
|
|
1646
|
+
description: "Write a derived value instead of a literal. Use the second positional value as a template, or combine with --expr."
|
|
1647
|
+
},
|
|
1648
|
+
{
|
|
1649
|
+
flag: "--expr <expression>",
|
|
1650
|
+
description: "With --derive, write an expression-form derived value instead of template shorthand."
|
|
1651
|
+
},
|
|
1333
1652
|
{
|
|
1334
1653
|
flag: "--prefix <path>",
|
|
1335
1654
|
description: "Filter value list output to keys that begin with this logical path or key prefix."
|
|
@@ -1338,6 +1657,8 @@ var COMMANDS = [
|
|
|
1338
1657
|
examples: [
|
|
1339
1658
|
"cnos value app.name",
|
|
1340
1659
|
"cnos value set server.port 3000",
|
|
1660
|
+
"cnos value set app.origin --derive '${value.app.protocol}://${value.app.host}'",
|
|
1661
|
+
`cnos value set app.display_name --derive --expr "coalesce(value.app.custom_name, value.app.name, 'Unnamed')"`,
|
|
1341
1662
|
"cnos add value app.name demo",
|
|
1342
1663
|
"cnos value list --prefix app."
|
|
1343
1664
|
]
|
|
@@ -1345,9 +1666,28 @@ var COMMANDS = [
|
|
|
1345
1666
|
{
|
|
1346
1667
|
id: "value set",
|
|
1347
1668
|
summary: "Write a value.",
|
|
1348
|
-
usage: "cnos value set <path> <value> [--target <local|global>] [global-options]",
|
|
1349
|
-
description: "Writes a
|
|
1350
|
-
|
|
1669
|
+
usage: "cnos value set <path> <value> [--target <local|global>] [--derive] [--expr <expression>] [global-options]",
|
|
1670
|
+
description: "Writes either a literal value or a first-class derived value into the selected workspace or explicit global target.",
|
|
1671
|
+
options: [
|
|
1672
|
+
{
|
|
1673
|
+
flag: "--target <local|global>",
|
|
1674
|
+
description: "Choose whether writes land in the local project workspace or the configured global root."
|
|
1675
|
+
},
|
|
1676
|
+
{
|
|
1677
|
+
flag: "--derive",
|
|
1678
|
+
description: "Interpret the provided value as a derived template, or combine with --expr for expression syntax."
|
|
1679
|
+
},
|
|
1680
|
+
{
|
|
1681
|
+
flag: "--expr <expression>",
|
|
1682
|
+
description: "With --derive, store the value as an expression-form derivation instead of template shorthand."
|
|
1683
|
+
}
|
|
1684
|
+
],
|
|
1685
|
+
examples: [
|
|
1686
|
+
"cnos value set app.name demo",
|
|
1687
|
+
"cnos value set app.origin --derive '${value.app.protocol}://${value.app.host}'",
|
|
1688
|
+
`cnos value set app.display_name --derive --expr "coalesce(value.app.custom_name, value.app.name, 'Unnamed')"`,
|
|
1689
|
+
"cnos add value server.port 3000 --target global"
|
|
1690
|
+
]
|
|
1351
1691
|
},
|
|
1352
1692
|
{
|
|
1353
1693
|
id: "value list",
|
|
@@ -1520,7 +1860,7 @@ var COMMANDS = [
|
|
|
1520
1860
|
id: "list",
|
|
1521
1861
|
summary: "List resolved config entries.",
|
|
1522
1862
|
usage: "cnos list [<namespace>|all] [--prefix <path>] [--framework <name>] [global-options]",
|
|
1523
|
-
description: "Lists stored config or derived projections across one namespace or the full effective graph, with optional prefix filtering. Custom data namespaces such as flags are supported, and process exposes server-only ambient runtime state.",
|
|
1863
|
+
description: "Lists stored config or derived projections across one namespace or the full effective graph, with optional prefix filtering. Derived values are annotated with `(derived)`. Custom data namespaces such as flags are supported, and process exposes server-only ambient runtime state.",
|
|
1524
1864
|
options: [
|
|
1525
1865
|
{
|
|
1526
1866
|
flag: "--namespace <name>",
|
|
@@ -1646,7 +1986,7 @@ var COMMANDS = [
|
|
|
1646
1986
|
id: "inspect",
|
|
1647
1987
|
summary: "Inspect the winning value and provenance for a key.",
|
|
1648
1988
|
usage: "cnos inspect <key> [global-options]",
|
|
1649
|
-
description: "Shows the resolved value, namespace, active profile, workspace context, and the
|
|
1989
|
+
description: "Shows the resolved value, namespace, active profile, workspace context, loader/origin, and derived-expression metadata when the key is computed from other CNOS keys or runtime namespaces.",
|
|
1650
1990
|
arguments: [
|
|
1651
1991
|
{
|
|
1652
1992
|
name: "key",
|
|
@@ -2050,6 +2390,7 @@ var HELP_DOCUMENT = {
|
|
|
2050
2390
|
examples: [
|
|
2051
2391
|
"cnos use --profile stage",
|
|
2052
2392
|
"cnos doctor --workspace api",
|
|
2393
|
+
"cnos cache list",
|
|
2053
2394
|
"cnos build env --profile stage --to .env.stage",
|
|
2054
2395
|
"cnos dev env --profile local --to .env.local -- pnpm dev",
|
|
2055
2396
|
"cnos export env --public --framework vite",
|
|
@@ -2165,11 +2506,11 @@ function runHelpAi(topic, cliArgs = []) {
|
|
|
2165
2506
|
}
|
|
2166
2507
|
|
|
2167
2508
|
// src/commands/init.ts
|
|
2168
|
-
import
|
|
2509
|
+
import path10 from "path";
|
|
2169
2510
|
|
|
2170
2511
|
// src/services/scaffold.ts
|
|
2171
2512
|
import { mkdir as mkdir4, readFile as readFile3, writeFile as writeFile4 } from "fs/promises";
|
|
2172
|
-
import
|
|
2513
|
+
import path9 from "path";
|
|
2173
2514
|
function scaffoldManifest(projectName, workspace) {
|
|
2174
2515
|
const lines = [
|
|
2175
2516
|
"version: 1",
|
|
@@ -2208,7 +2549,7 @@ async function ensureFile(filePath, content) {
|
|
|
2208
2549
|
}
|
|
2209
2550
|
}
|
|
2210
2551
|
async function ensureGitignore(root) {
|
|
2211
|
-
const gitignorePath =
|
|
2552
|
+
const gitignorePath = path9.join(root, ".gitignore");
|
|
2212
2553
|
const requiredEntries = [
|
|
2213
2554
|
".cnos/env/.env",
|
|
2214
2555
|
".cnos/env/.env.*",
|
|
@@ -2236,13 +2577,13 @@ async function ensureGitignore(root) {
|
|
|
2236
2577
|
return true;
|
|
2237
2578
|
}
|
|
2238
2579
|
async function scaffoldWorkspace(root, workspace) {
|
|
2239
|
-
const cnosRoot =
|
|
2240
|
-
const workspaceRoot = workspace ?
|
|
2580
|
+
const cnosRoot = path9.join(root, ".cnos");
|
|
2581
|
+
const workspaceRoot = workspace ? path9.join(cnosRoot, "workspaces", workspace) : cnosRoot;
|
|
2241
2582
|
const createdPaths = [];
|
|
2242
|
-
await mkdir4(
|
|
2243
|
-
await mkdir4(
|
|
2244
|
-
await mkdir4(
|
|
2245
|
-
await mkdir4(
|
|
2583
|
+
await mkdir4(path9.join(workspaceRoot, "profiles"), { recursive: true });
|
|
2584
|
+
await mkdir4(path9.join(workspaceRoot, "values"), { recursive: true });
|
|
2585
|
+
await mkdir4(path9.join(workspaceRoot, "secrets"), { recursive: true });
|
|
2586
|
+
await mkdir4(path9.join(workspaceRoot, "env"), { recursive: true });
|
|
2246
2587
|
const relativePaths = workspace ? [
|
|
2247
2588
|
["workspaces", workspace, "profiles", ".gitkeep"],
|
|
2248
2589
|
["workspaces", workspace, "values", ".gitkeep"],
|
|
@@ -2255,23 +2596,23 @@ async function scaffoldWorkspace(root, workspace) {
|
|
|
2255
2596
|
["env", ".gitkeep"]
|
|
2256
2597
|
];
|
|
2257
2598
|
for (const relativePath of relativePaths) {
|
|
2258
|
-
const filePath =
|
|
2599
|
+
const filePath = path9.join(cnosRoot, ...relativePath);
|
|
2259
2600
|
if (await ensureFile(filePath, "")) {
|
|
2260
|
-
createdPaths.push(
|
|
2601
|
+
createdPaths.push(path9.relative(root, filePath).replace(/\\/g, "/"));
|
|
2261
2602
|
}
|
|
2262
2603
|
}
|
|
2263
|
-
if (await ensureFile(
|
|
2604
|
+
if (await ensureFile(path9.join(cnosRoot, "cnos.yml"), scaffoldManifest(path9.basename(root), workspace))) {
|
|
2264
2605
|
createdPaths.push(".cnos/cnos.yml");
|
|
2265
2606
|
}
|
|
2266
2607
|
if (await ensureFile(
|
|
2267
|
-
|
|
2608
|
+
path9.join(root, ".cnosrc.yml"),
|
|
2268
2609
|
workspace ? `root: ./.cnos
|
|
2269
2610
|
workspace: ${workspace}
|
|
2270
2611
|
` : "root: ./.cnos\n"
|
|
2271
2612
|
)) {
|
|
2272
2613
|
createdPaths.push(".cnosrc.yml");
|
|
2273
2614
|
}
|
|
2274
|
-
if (workspace && await ensureFile(
|
|
2615
|
+
if (workspace && await ensureFile(path9.join(root, ".cnos-workspace.yml"), `workspace: ${workspace}
|
|
2275
2616
|
globalRoot: ~/.cnos
|
|
2276
2617
|
`)) {
|
|
2277
2618
|
createdPaths.push(".cnos-workspace.yml");
|
|
@@ -2288,7 +2629,7 @@ globalRoot: ~/.cnos
|
|
|
2288
2629
|
|
|
2289
2630
|
// src/commands/init.ts
|
|
2290
2631
|
async function runInit(options = {}) {
|
|
2291
|
-
const root =
|
|
2632
|
+
const root = path10.resolve(options.root ?? process.cwd());
|
|
2292
2633
|
const result = await scaffoldWorkspace(root, options.workspace);
|
|
2293
2634
|
if (options.json) {
|
|
2294
2635
|
return printJson(result);
|
|
@@ -2324,6 +2665,22 @@ function printInspect(record) {
|
|
|
2324
2665
|
`overridden: ${record.overridden.map((entry) => `${entry.sourceId}@${entry.workspaceId}=${String(entry.value)}`).join(", ")}`
|
|
2325
2666
|
);
|
|
2326
2667
|
}
|
|
2668
|
+
if (record.derived) {
|
|
2669
|
+
lines.push(`derivedType: ${record.derived.type}`);
|
|
2670
|
+
lines.push(`derivedExpression: ${record.derived.expression}`);
|
|
2671
|
+
lines.push(`runtimeDependent: ${record.derived.runtimeDependent ? "yes" : "no"}`);
|
|
2672
|
+
if (record.derived.runtimeNamespaces.length > 0) {
|
|
2673
|
+
lines.push(`runtimeNamespaces: ${record.derived.runtimeNamespaces.join(", ")}`);
|
|
2674
|
+
}
|
|
2675
|
+
if (record.derived.dependencies.length > 0) {
|
|
2676
|
+
lines.push(
|
|
2677
|
+
`dependencies: ${record.derived.dependencies.map((entry) => `${entry.key}=${String(entry.value)}${entry.runtimeNamespace ? ` (${entry.runtimeNamespace})` : ""}`).join(", ")}`
|
|
2678
|
+
);
|
|
2679
|
+
}
|
|
2680
|
+
if (record.derived.promotionWarning) {
|
|
2681
|
+
lines.push(`warning: ${record.derived.promotionWarning}`);
|
|
2682
|
+
}
|
|
2683
|
+
}
|
|
2327
2684
|
return lines.join("\n");
|
|
2328
2685
|
}
|
|
2329
2686
|
|
|
@@ -2391,13 +2748,24 @@ function toStoredEntry(namespace, entry, filter = {}) {
|
|
|
2391
2748
|
}
|
|
2392
2749
|
return {
|
|
2393
2750
|
key: entry.key,
|
|
2394
|
-
value: selectedCandidate.value
|
|
2751
|
+
value: selectedCandidate.value,
|
|
2752
|
+
...typeof selectedCandidate.value === "object" && selectedCandidate.value !== null && !Array.isArray(selectedCandidate.value) && "$derive" in selectedCandidate.value ? {
|
|
2753
|
+
derived: true
|
|
2754
|
+
} : {}
|
|
2395
2755
|
};
|
|
2396
2756
|
}
|
|
2397
|
-
function listStoredNamespace(namespace, options) {
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2757
|
+
async function listStoredNamespace(namespace, options) {
|
|
2758
|
+
const runtime = await createRuntimeService(options);
|
|
2759
|
+
return Array.from(runtime.graph.entries.values()).filter((entry) => entry.namespace === namespace).map((entry) => {
|
|
2760
|
+
const stored = toStoredEntry(namespace, entry, options);
|
|
2761
|
+
if (!stored) {
|
|
2762
|
+
return void 0;
|
|
2763
|
+
}
|
|
2764
|
+
return {
|
|
2765
|
+
...stored,
|
|
2766
|
+
value: stored.derived ? runtime.read(entry.key) : stored.value
|
|
2767
|
+
};
|
|
2768
|
+
}).filter((entry) => Boolean(entry)).filter((entry) => entry.value !== void 0).filter((entry) => matchesPrefix(entry.key, options.prefix)).sort((left, right) => left.key.localeCompare(right.key));
|
|
2401
2769
|
}
|
|
2402
2770
|
function listProjectedNamespace(namespace, options) {
|
|
2403
2771
|
return createRuntimeService(options).then((runtime) => {
|
|
@@ -2479,14 +2847,14 @@ async function runList(args = [], options = {}) {
|
|
|
2479
2847
|
if (entries.length === 0) {
|
|
2480
2848
|
return "";
|
|
2481
2849
|
}
|
|
2482
|
-
return entries.map((entry) => `${entry.key}=${printValue(entry.value)}`).join("\n");
|
|
2850
|
+
return entries.map((entry) => `${entry.key}=${printValue(entry.value)}${entry.derived ? " (derived)" : ""}`).join("\n");
|
|
2483
2851
|
}
|
|
2484
2852
|
|
|
2485
2853
|
// src/commands/migrate.ts
|
|
2486
|
-
import
|
|
2854
|
+
import path11 from "path";
|
|
2487
2855
|
import {
|
|
2488
2856
|
applyManifestMappings,
|
|
2489
|
-
loadManifest,
|
|
2857
|
+
loadManifest as loadManifest4,
|
|
2490
2858
|
proposeMapping,
|
|
2491
2859
|
rewriteSourceFiles,
|
|
2492
2860
|
scanEnvUsage
|
|
@@ -2500,8 +2868,18 @@ async function runMigrate(options = {}) {
|
|
|
2500
2868
|
if (cliArgs.length > 0) {
|
|
2501
2869
|
throw new Error(`Unknown migrate options: ${cliArgs.join(" ")}`);
|
|
2502
2870
|
}
|
|
2503
|
-
|
|
2504
|
-
|
|
2871
|
+
if (apply) {
|
|
2872
|
+
await assertWritableConfigRoot("apply migration mappings", options);
|
|
2873
|
+
}
|
|
2874
|
+
const manifest = await loadManifest4({
|
|
2875
|
+
...options.root ? { root: options.root } : {},
|
|
2876
|
+
...options.cwd ? { cwd: options.cwd } : {},
|
|
2877
|
+
...options.processEnv ? { processEnv: options.processEnv } : {},
|
|
2878
|
+
...options.cacheMode ? { cacheMode: options.cacheMode } : {},
|
|
2879
|
+
...typeof options.cacheTtlSeconds === "number" ? { cacheTtlSeconds: options.cacheTtlSeconds } : {},
|
|
2880
|
+
...options.forceRefresh ? { forceRefresh: true } : {}
|
|
2881
|
+
});
|
|
2882
|
+
const scanRoot = path11.resolve(manifest.consumerRoot, scan ?? "src");
|
|
2505
2883
|
const usages = await scanEnvUsage(scanRoot);
|
|
2506
2884
|
const uniqueProposals = new Map(usages.map((usage) => [usage.envVar, proposeMapping(usage.envVar)]));
|
|
2507
2885
|
const proposals = Array.from(uniqueProposals.values()).sort((left, right) => left.envVar.localeCompare(right.envVar));
|
|
@@ -2563,7 +2941,7 @@ async function runMigrate(options = {}) {
|
|
|
2563
2941
|
}
|
|
2564
2942
|
|
|
2565
2943
|
// src/commands/namespace.ts
|
|
2566
|
-
import
|
|
2944
|
+
import path12 from "path";
|
|
2567
2945
|
function normalizeCommand2(args) {
|
|
2568
2946
|
const [actionOrPath, ...tail] = args;
|
|
2569
2947
|
if (!actionOrPath) {
|
|
@@ -2586,7 +2964,7 @@ function normalizeCommand2(args) {
|
|
|
2586
2964
|
async function runNamespace(namespace, args = [], options = {}) {
|
|
2587
2965
|
const { action, tail } = normalizeCommand2(args);
|
|
2588
2966
|
const cliArgs = [...options.cliArgs ?? []];
|
|
2589
|
-
const root =
|
|
2967
|
+
const root = path12.resolve(options.root ?? process.cwd());
|
|
2590
2968
|
if (action === "list") {
|
|
2591
2969
|
const prefix = consumeOption(cliArgs, "--prefix");
|
|
2592
2970
|
const entries = await listConfigEntries(namespace, {
|
|
@@ -2597,16 +2975,24 @@ async function runNamespace(namespace, args = [], options = {}) {
|
|
|
2597
2975
|
if (options.json) {
|
|
2598
2976
|
return printJson(entries);
|
|
2599
2977
|
}
|
|
2600
|
-
return entries.map((entry) => `${entry.key}=${printValue(entry.value)}`).join("\n");
|
|
2978
|
+
return entries.map((entry) => `${entry.key}=${printValue(entry.value)}${entry.derived ? " (derived)" : ""}`).join("\n");
|
|
2601
2979
|
}
|
|
2602
2980
|
if (action === "set") {
|
|
2603
2981
|
const configPath2 = tail[0] ?? "app.name";
|
|
2604
|
-
const
|
|
2982
|
+
const derive = consumeFlag(cliArgs, "--derive");
|
|
2983
|
+
const expr = consumeOption(cliArgs, "--expr");
|
|
2984
|
+
const deriveArg = derive && !expr && cliArgs[0] && !cliArgs[0].startsWith("--") ? cliArgs.shift() : void 0;
|
|
2985
|
+
const rawValue = derive ? "" : tail[1] ?? "";
|
|
2986
|
+
const deriveExpression = derive ? expr ?? tail[1] ?? deriveArg ?? "" : void 0;
|
|
2605
2987
|
const target = consumeOption(cliArgs, "--target") ?? "local";
|
|
2606
2988
|
const result = await defineValue(namespace, configPath2, rawValue, {
|
|
2607
2989
|
...options,
|
|
2608
2990
|
cliArgs,
|
|
2609
|
-
target
|
|
2991
|
+
target,
|
|
2992
|
+
...deriveExpression !== void 0 ? {
|
|
2993
|
+
deriveExpression,
|
|
2994
|
+
deriveExprMode: Boolean(expr)
|
|
2995
|
+
} : {}
|
|
2610
2996
|
});
|
|
2611
2997
|
if (options.json) {
|
|
2612
2998
|
return printJson({
|
|
@@ -2648,34 +3034,34 @@ async function runNamespace(namespace, args = [], options = {}) {
|
|
|
2648
3034
|
}
|
|
2649
3035
|
|
|
2650
3036
|
// src/commands/onboard.ts
|
|
2651
|
-
import { copyFile, readdir as
|
|
2652
|
-
import
|
|
3037
|
+
import { copyFile, readdir as readdir3, rm as rm2 } from "fs/promises";
|
|
3038
|
+
import path13 from "path";
|
|
2653
3039
|
var ROOT_ENV_FILE_PATTERN = /^\.env(?:\.[A-Za-z0-9_-]+)*(?:\.example)?$/;
|
|
2654
3040
|
async function listRootEnvFiles(root) {
|
|
2655
|
-
const entries = await
|
|
3041
|
+
const entries = await readdir3(root, { withFileTypes: true });
|
|
2656
3042
|
return entries.filter((entry) => entry.isFile() && ROOT_ENV_FILE_PATTERN.test(entry.name)).map((entry) => entry.name).sort((left, right) => left.localeCompare(right));
|
|
2657
3043
|
}
|
|
2658
3044
|
async function runOnboard(options = {}) {
|
|
2659
|
-
const root =
|
|
2660
|
-
const workspace = options.workspace ??
|
|
3045
|
+
const root = path13.resolve(options.root ?? process.cwd());
|
|
3046
|
+
const workspace = options.workspace ?? path13.basename(root);
|
|
2661
3047
|
const cliArgs = [...options.cliArgs ?? []];
|
|
2662
3048
|
const move = consumeFlag(cliArgs, "--move");
|
|
2663
3049
|
if (cliArgs.length > 0) {
|
|
2664
3050
|
throw new Error(`Unsupported onboard arguments: ${cliArgs.join(" ")}`);
|
|
2665
3051
|
}
|
|
2666
3052
|
const scaffold = await scaffoldWorkspace(root, workspace);
|
|
2667
|
-
const envRoot =
|
|
3053
|
+
const envRoot = path13.join(root, ".cnos", "workspaces", workspace, "env");
|
|
2668
3054
|
const rootFiles = await listRootEnvFiles(root);
|
|
2669
3055
|
const imported = [];
|
|
2670
3056
|
const skipped = [];
|
|
2671
3057
|
for (const fileName of rootFiles) {
|
|
2672
|
-
const sourcePath =
|
|
2673
|
-
const targetPath =
|
|
3058
|
+
const sourcePath = path13.join(root, fileName);
|
|
3059
|
+
const targetPath = path13.join(envRoot, fileName);
|
|
2674
3060
|
try {
|
|
2675
3061
|
await copyFile(sourcePath, targetPath);
|
|
2676
|
-
imported.push(
|
|
3062
|
+
imported.push(path13.relative(root, targetPath).replace(/\\/g, "/"));
|
|
2677
3063
|
if (move) {
|
|
2678
|
-
await
|
|
3064
|
+
await rm2(sourcePath);
|
|
2679
3065
|
}
|
|
2680
3066
|
} catch {
|
|
2681
3067
|
skipped.push(fileName);
|
|
@@ -2698,14 +3084,14 @@ async function runOnboard(options = {}) {
|
|
|
2698
3084
|
}
|
|
2699
3085
|
|
|
2700
3086
|
// src/commands/profile.ts
|
|
2701
|
-
import
|
|
3087
|
+
import path16 from "path";
|
|
2702
3088
|
|
|
2703
3089
|
// src/services/context.ts
|
|
2704
3090
|
import { readFile as readFile4, writeFile as writeFile5 } from "fs/promises";
|
|
2705
|
-
import
|
|
3091
|
+
import path14 from "path";
|
|
2706
3092
|
import { parseYaml as parseYaml3, stringifyYaml as stringifyYaml3 } from "@kitsy/cnos/internal";
|
|
2707
3093
|
async function loadCliContext(root = process.cwd()) {
|
|
2708
|
-
const filePath =
|
|
3094
|
+
const filePath = path14.join(path14.resolve(root), ".cnos-workspace.yml");
|
|
2709
3095
|
try {
|
|
2710
3096
|
const source = await readFile4(filePath, "utf8");
|
|
2711
3097
|
const parsed = parseYaml3(source);
|
|
@@ -2718,8 +3104,8 @@ async function loadCliContext(root = process.cwd()) {
|
|
|
2718
3104
|
}
|
|
2719
3105
|
}
|
|
2720
3106
|
async function saveCliContext(options = {}) {
|
|
2721
|
-
const root =
|
|
2722
|
-
const filePath =
|
|
3107
|
+
const root = path14.resolve(options.root ?? process.cwd());
|
|
3108
|
+
const filePath = path14.join(root, ".cnos-workspace.yml");
|
|
2723
3109
|
const current = await loadCliContext(root);
|
|
2724
3110
|
const next = {
|
|
2725
3111
|
...current.workspace ? { workspace: current.workspace } : {},
|
|
@@ -2737,12 +3123,21 @@ async function saveCliContext(options = {}) {
|
|
|
2737
3123
|
}
|
|
2738
3124
|
|
|
2739
3125
|
// src/services/profiles.ts
|
|
2740
|
-
import { mkdir as mkdir5, readdir as
|
|
2741
|
-
import
|
|
2742
|
-
import { parseYaml as parseYaml4, stringifyYaml as stringifyYaml4 } from "@kitsy/cnos/internal";
|
|
3126
|
+
import { mkdir as mkdir5, readdir as readdir4, readFile as readFile5, rm as rm3, writeFile as writeFile6 } from "fs/promises";
|
|
3127
|
+
import path15 from "path";
|
|
3128
|
+
import { loadManifest as loadManifest5, parseYaml as parseYaml4, stringifyYaml as stringifyYaml4 } from "@kitsy/cnos/internal";
|
|
3129
|
+
async function resolveProfilesRoot(root = process.cwd()) {
|
|
3130
|
+
try {
|
|
3131
|
+
const loadedManifest = await loadManifest5({ root });
|
|
3132
|
+
return path15.join(loadedManifest.manifestRoot, "profiles");
|
|
3133
|
+
} catch {
|
|
3134
|
+
const loadedManifest = await loadManifest5({ cwd: root });
|
|
3135
|
+
return path15.join(loadedManifest.manifestRoot, "profiles");
|
|
3136
|
+
}
|
|
3137
|
+
}
|
|
2743
3138
|
async function createProfileDefinition(root = process.cwd(), profile, inherit, options = {}) {
|
|
2744
|
-
const filePath =
|
|
2745
|
-
await mkdir5(
|
|
3139
|
+
const filePath = path15.join(await resolveProfilesRoot(root), `${profile}.yml`);
|
|
3140
|
+
await mkdir5(path15.dirname(filePath), { recursive: true });
|
|
2746
3141
|
const document = options.noInherit ? {
|
|
2747
3142
|
name: profile,
|
|
2748
3143
|
activate: {
|
|
@@ -2765,9 +3160,9 @@ async function createProfileDefinition(root = process.cwd(), profile, inherit, o
|
|
|
2765
3160
|
};
|
|
2766
3161
|
}
|
|
2767
3162
|
async function listProfiles(root = process.cwd()) {
|
|
2768
|
-
const profilesRoot =
|
|
3163
|
+
const profilesRoot = await resolveProfilesRoot(root);
|
|
2769
3164
|
try {
|
|
2770
|
-
const entries = await
|
|
3165
|
+
const entries = await readdir4(profilesRoot, { withFileTypes: true });
|
|
2771
3166
|
const discovered = /* @__PURE__ */ new Set(["base"]);
|
|
2772
3167
|
for (const entry of entries) {
|
|
2773
3168
|
if (entry.isFile() && entry.name.endsWith(".yml")) {
|
|
@@ -2780,9 +3175,9 @@ async function listProfiles(root = process.cwd()) {
|
|
|
2780
3175
|
}
|
|
2781
3176
|
}
|
|
2782
3177
|
async function deleteProfileDefinition(root = process.cwd(), profile) {
|
|
2783
|
-
const filePath =
|
|
3178
|
+
const filePath = path15.join(await resolveProfilesRoot(root), `${profile}.yml`);
|
|
2784
3179
|
try {
|
|
2785
|
-
await
|
|
3180
|
+
await rm3(filePath);
|
|
2786
3181
|
return {
|
|
2787
3182
|
filePath,
|
|
2788
3183
|
deleted: true
|
|
@@ -2800,7 +3195,7 @@ async function readProfileDefinition(root = process.cwd(), profile = "base") {
|
|
|
2800
3195
|
name: "base"
|
|
2801
3196
|
};
|
|
2802
3197
|
}
|
|
2803
|
-
const filePath =
|
|
3198
|
+
const filePath = path15.join(await resolveProfilesRoot(root), `${profile}.yml`);
|
|
2804
3199
|
try {
|
|
2805
3200
|
return parseYaml4(await readFile5(filePath, "utf8")) ?? void 0;
|
|
2806
3201
|
} catch {
|
|
@@ -2824,9 +3219,11 @@ function normalizeProfileAction(args) {
|
|
|
2824
3219
|
}
|
|
2825
3220
|
async function runProfile(args, options = {}) {
|
|
2826
3221
|
const { action, tail } = normalizeProfileAction(args);
|
|
2827
|
-
const root =
|
|
3222
|
+
const root = options.root ?? process.cwd();
|
|
3223
|
+
const displayRoot = resolveFilesystemBasePath(options.root, options.cwd ?? process.cwd());
|
|
2828
3224
|
const cliArgs = [...options.cliArgs ?? []];
|
|
2829
3225
|
if (action === "create") {
|
|
3226
|
+
await assertWritableConfigRoot(`create profile ${tail[0] ?? "stage"}`, options);
|
|
2830
3227
|
const profile = tail[0] ?? "stage";
|
|
2831
3228
|
const inherit = consumeOption(cliArgs, "--inherit");
|
|
2832
3229
|
const noInherit = consumeFlag(cliArgs, "--no-inherit");
|
|
@@ -2838,22 +3235,23 @@ async function runProfile(args, options = {}) {
|
|
|
2838
3235
|
return printJson(result);
|
|
2839
3236
|
}
|
|
2840
3237
|
if (noInherit) {
|
|
2841
|
-
return `created profile ${profile} at ${displayPath(result.filePath,
|
|
3238
|
+
return `created profile ${profile} at ${displayPath(result.filePath, displayRoot)} without inheriting base`;
|
|
2842
3239
|
}
|
|
2843
|
-
return `created profile ${profile} at ${displayPath(result.filePath,
|
|
3240
|
+
return `created profile ${profile} at ${displayPath(result.filePath, displayRoot)}; inherits values from base by default`;
|
|
2844
3241
|
}
|
|
2845
3242
|
if (action === "use") {
|
|
2846
3243
|
const profile = tail[0] ?? "base";
|
|
2847
3244
|
const result = await saveCliContext({
|
|
2848
|
-
root,
|
|
3245
|
+
root: path16.resolve(root),
|
|
2849
3246
|
profile
|
|
2850
3247
|
});
|
|
2851
3248
|
if (options.json) {
|
|
2852
3249
|
return printJson(result);
|
|
2853
3250
|
}
|
|
2854
|
-
return `active profile set to ${profile} in ${displayPath(result.filePath,
|
|
3251
|
+
return `active profile set to ${profile} in ${displayPath(result.filePath, displayRoot)}`;
|
|
2855
3252
|
}
|
|
2856
3253
|
if (action === "delete") {
|
|
3254
|
+
await assertWritableConfigRoot(`delete profile ${tail[0] ?? "base"}`, options);
|
|
2857
3255
|
const profile = tail[0] ?? "base";
|
|
2858
3256
|
const result = await deleteProfileDefinition(root, profile);
|
|
2859
3257
|
if (options.json) {
|
|
@@ -2875,11 +3273,11 @@ async function runProfile(args, options = {}) {
|
|
|
2875
3273
|
}
|
|
2876
3274
|
|
|
2877
3275
|
// src/commands/promote.ts
|
|
2878
|
-
import
|
|
3276
|
+
import path17 from "path";
|
|
2879
3277
|
import { writeFile as writeFile7 } from "fs/promises";
|
|
2880
3278
|
import {
|
|
2881
3279
|
ensureProjectionAllowed,
|
|
2882
|
-
loadManifest as
|
|
3280
|
+
loadManifest as loadManifest6,
|
|
2883
3281
|
stringifyYaml as stringifyYaml5
|
|
2884
3282
|
} from "@kitsy/cnos/internal";
|
|
2885
3283
|
function normalizeTarget(value) {
|
|
@@ -2892,7 +3290,7 @@ function sortRecord(record) {
|
|
|
2892
3290
|
return Object.fromEntries(Object.entries(record).sort(([left], [right]) => left.localeCompare(right)));
|
|
2893
3291
|
}
|
|
2894
3292
|
async function runPromote(args = [], options = {}) {
|
|
2895
|
-
const root =
|
|
3293
|
+
const root = path17.resolve(options.root ?? process.cwd());
|
|
2896
3294
|
const cliArgs = [...options.cliArgs ?? []];
|
|
2897
3295
|
const target = normalizeTarget(consumeOption(cliArgs, "--to"));
|
|
2898
3296
|
const alias = consumeOption(cliArgs, "--as");
|
|
@@ -2908,7 +3306,15 @@ async function runPromote(args = [], options = {}) {
|
|
|
2908
3306
|
throw new Error("promote --to env requires --as <ENV_VAR>");
|
|
2909
3307
|
}
|
|
2910
3308
|
}
|
|
2911
|
-
const loadedManifest = await
|
|
3309
|
+
const loadedManifest = await loadManifest6({
|
|
3310
|
+
...options.root ? { root: options.root } : {},
|
|
3311
|
+
...options.cwd ? { cwd: options.cwd } : {},
|
|
3312
|
+
...options.processEnv ? { processEnv: options.processEnv } : {},
|
|
3313
|
+
...options.cacheMode ? { cacheMode: options.cacheMode } : {},
|
|
3314
|
+
...typeof options.cacheTtlSeconds === "number" ? { cacheTtlSeconds: options.cacheTtlSeconds } : {},
|
|
3315
|
+
...options.forceRefresh ? { forceRefresh: true } : {}
|
|
3316
|
+
});
|
|
3317
|
+
await assertWritableConfigRoot(`promote ${keys.join(", ")}`, options);
|
|
2912
3318
|
for (const key of keys) {
|
|
2913
3319
|
ensureProjectionAllowed(loadedManifest.manifest, key, target);
|
|
2914
3320
|
}
|
|
@@ -3053,21 +3459,21 @@ async function runCommand(command, options = {}) {
|
|
|
3053
3459
|
}
|
|
3054
3460
|
|
|
3055
3461
|
// src/commands/secret.ts
|
|
3056
|
-
import
|
|
3462
|
+
import path20 from "path";
|
|
3057
3463
|
|
|
3058
3464
|
// src/commands/vault.ts
|
|
3059
|
-
import
|
|
3465
|
+
import path19 from "path";
|
|
3060
3466
|
|
|
3061
3467
|
// src/services/vaults.ts
|
|
3062
|
-
import { rm as
|
|
3063
|
-
import
|
|
3468
|
+
import { rm as rm4, writeFile as writeFile8 } from "fs/promises";
|
|
3469
|
+
import path18 from "path";
|
|
3064
3470
|
import {
|
|
3065
3471
|
clearAllVaultSessionKeys,
|
|
3066
3472
|
clearVaultSessionKey,
|
|
3067
3473
|
createSecretVault,
|
|
3068
3474
|
deriveVaultKey,
|
|
3069
3475
|
listLocalSecrets,
|
|
3070
|
-
loadManifest as
|
|
3476
|
+
loadManifest as loadManifest7,
|
|
3071
3477
|
listSecretVaults,
|
|
3072
3478
|
readVaultMetadata,
|
|
3073
3479
|
resolveSecretStoreRoot as resolveSecretStoreRoot2,
|
|
@@ -3100,12 +3506,20 @@ function defaultLocalAuthSources(vault) {
|
|
|
3100
3506
|
return [`env:CNOS_SECRET_PASSPHRASE_${token}`, "env:CNOS_SECRET_PASSPHRASE", `keychain:cnos/${vault}`, "prompt"];
|
|
3101
3507
|
}
|
|
3102
3508
|
async function createVaultDefinition(name, options = {}) {
|
|
3509
|
+
await assertWritableConfigRoot(`create vault ${name}`, options);
|
|
3103
3510
|
const vault = name.trim() || "default";
|
|
3104
3511
|
const provider = options.provider?.trim() || "local";
|
|
3105
3512
|
if (provider === "local" && (options.noPassphrase ?? false)) {
|
|
3106
3513
|
throw new Error("Local vaults cannot be passwordless.");
|
|
3107
3514
|
}
|
|
3108
|
-
const loadedManifest = await
|
|
3515
|
+
const loadedManifest = await loadManifest7({
|
|
3516
|
+
...options.root ? { root: options.root } : {},
|
|
3517
|
+
...options.cwd ? { cwd: options.cwd } : {},
|
|
3518
|
+
...options.processEnv ? { processEnv: options.processEnv } : {},
|
|
3519
|
+
...options.cacheMode ? { cacheMode: options.cacheMode } : {},
|
|
3520
|
+
...typeof options.cacheTtlSeconds === "number" ? { cacheTtlSeconds: options.cacheTtlSeconds } : {},
|
|
3521
|
+
...options.forceRefresh ? { forceRefresh: true } : {}
|
|
3522
|
+
});
|
|
3109
3523
|
const vaultDefinition = buildVaultDefinition(vault, provider);
|
|
3110
3524
|
const rawManifest = {
|
|
3111
3525
|
...loadedManifest.rawManifest,
|
|
@@ -3135,7 +3549,14 @@ async function createVaultDefinition(name, options = {}) {
|
|
|
3135
3549
|
};
|
|
3136
3550
|
}
|
|
3137
3551
|
async function listVaultDefinitions(options = {}) {
|
|
3138
|
-
const loadedManifest = await
|
|
3552
|
+
const loadedManifest = await loadManifest7({
|
|
3553
|
+
...options.root ? { root: options.root } : {},
|
|
3554
|
+
...options.cwd ? { cwd: options.cwd } : {},
|
|
3555
|
+
...options.processEnv ? { processEnv: options.processEnv } : {},
|
|
3556
|
+
...options.cacheMode ? { cacheMode: options.cacheMode } : {},
|
|
3557
|
+
...typeof options.cacheTtlSeconds === "number" ? { cacheTtlSeconds: options.cacheTtlSeconds } : {},
|
|
3558
|
+
...options.forceRefresh ? { forceRefresh: true } : {}
|
|
3559
|
+
});
|
|
3139
3560
|
const localStoreVaults = await listSecretVaults(resolveSecretStoreRoot2(options.processEnv));
|
|
3140
3561
|
return Object.keys(loadedManifest.manifest.vaults).sort((left, right) => left.localeCompare(right)).map((name) => {
|
|
3141
3562
|
const definition = resolveVaultDefinition(loadedManifest.manifest.vaults, name);
|
|
@@ -3147,8 +3568,16 @@ async function listVaultDefinitions(options = {}) {
|
|
|
3147
3568
|
});
|
|
3148
3569
|
}
|
|
3149
3570
|
async function removeVaultDefinition(name, options = {}) {
|
|
3571
|
+
await assertWritableConfigRoot(`remove vault ${name}`, options);
|
|
3150
3572
|
const vault = name.trim() || "default";
|
|
3151
|
-
const loadedManifest = await
|
|
3573
|
+
const loadedManifest = await loadManifest7({
|
|
3574
|
+
...options.root ? { root: options.root } : {},
|
|
3575
|
+
...options.cwd ? { cwd: options.cwd } : {},
|
|
3576
|
+
...options.processEnv ? { processEnv: options.processEnv } : {},
|
|
3577
|
+
...options.cacheMode ? { cacheMode: options.cacheMode } : {},
|
|
3578
|
+
...typeof options.cacheTtlSeconds === "number" ? { cacheTtlSeconds: options.cacheTtlSeconds } : {},
|
|
3579
|
+
...options.forceRefresh ? { forceRefresh: true } : {}
|
|
3580
|
+
});
|
|
3152
3581
|
if (!loadedManifest.rawManifest.vaults?.[vault]) {
|
|
3153
3582
|
return {
|
|
3154
3583
|
name: vault,
|
|
@@ -3166,10 +3595,10 @@ async function removeVaultDefinition(name, options = {}) {
|
|
|
3166
3595
|
delete rawManifest.vaults;
|
|
3167
3596
|
}
|
|
3168
3597
|
await writeFile8(loadedManifest.manifestPath, stringifyYaml6(rawManifest), "utf8");
|
|
3169
|
-
const vaultRoot =
|
|
3598
|
+
const vaultRoot = path18.join(resolveSecretStoreRoot2(options.processEnv), "vaults", vault);
|
|
3170
3599
|
let removedStore;
|
|
3171
3600
|
try {
|
|
3172
|
-
await
|
|
3601
|
+
await rm4(vaultRoot, { recursive: true, force: true });
|
|
3173
3602
|
removedStore = vaultRoot;
|
|
3174
3603
|
} catch {
|
|
3175
3604
|
removedStore = void 0;
|
|
@@ -3187,7 +3616,14 @@ async function listLocalStoreVaults(options = {}) {
|
|
|
3187
3616
|
}
|
|
3188
3617
|
async function authenticateVault(name, options = {}) {
|
|
3189
3618
|
const vault = name.trim() || "default";
|
|
3190
|
-
const loadedManifest = await
|
|
3619
|
+
const loadedManifest = await loadManifest7({
|
|
3620
|
+
...options.root ? { root: options.root } : {},
|
|
3621
|
+
...options.cwd ? { cwd: options.cwd } : {},
|
|
3622
|
+
...options.processEnv ? { processEnv: options.processEnv } : {},
|
|
3623
|
+
...options.cacheMode ? { cacheMode: options.cacheMode } : {},
|
|
3624
|
+
...typeof options.cacheTtlSeconds === "number" ? { cacheTtlSeconds: options.cacheTtlSeconds } : {},
|
|
3625
|
+
...options.forceRefresh ? { forceRefresh: true } : {}
|
|
3626
|
+
});
|
|
3191
3627
|
const definition = loadedManifest.manifest.vaults[vault];
|
|
3192
3628
|
if (!definition) {
|
|
3193
3629
|
throw new Error(`Unknown vault "${vault}"`);
|
|
@@ -3260,7 +3696,7 @@ function normalizeVaultAction(args) {
|
|
|
3260
3696
|
async function runVault(args = [], options = {}) {
|
|
3261
3697
|
const { action, tail } = normalizeVaultAction(args);
|
|
3262
3698
|
const cliArgs = [...options.cliArgs ?? []];
|
|
3263
|
-
const root =
|
|
3699
|
+
const root = path19.resolve(options.root ?? process.cwd());
|
|
3264
3700
|
if (consumeOption(cliArgs, "--passphrase")) {
|
|
3265
3701
|
throw new Error("The --passphrase option is not supported in CNOS 1.4. Use env, keychain, or prompt-based auth.");
|
|
3266
3702
|
}
|
|
@@ -3366,7 +3802,7 @@ async function runSecret(argsOrPath, options = {}) {
|
|
|
3366
3802
|
const args = Array.isArray(argsOrPath) ? argsOrPath : [argsOrPath];
|
|
3367
3803
|
const { action, tail } = normalizeSecretCommand(args);
|
|
3368
3804
|
const cliArgs = [...options.cliArgs ?? []];
|
|
3369
|
-
const root =
|
|
3805
|
+
const root = path20.resolve(options.root ?? process.cwd());
|
|
3370
3806
|
if (consumeOption(cliArgs, "--passphrase")) {
|
|
3371
3807
|
throw new Error("The --passphrase option is not supported in CNOS 1.4. Use env, keychain, or prompt-based auth.");
|
|
3372
3808
|
}
|
|
@@ -3462,9 +3898,9 @@ async function runSecret(argsOrPath, options = {}) {
|
|
|
3462
3898
|
}
|
|
3463
3899
|
|
|
3464
3900
|
// src/commands/use.ts
|
|
3465
|
-
import
|
|
3901
|
+
import path21 from "path";
|
|
3466
3902
|
async function runUse(args = [], options = {}) {
|
|
3467
|
-
const root =
|
|
3903
|
+
const root = path21.resolve(options.root ?? process.cwd());
|
|
3468
3904
|
const action = args[0];
|
|
3469
3905
|
const hasUpdates = Boolean(options.workspace || options.profile || options.globalRoot);
|
|
3470
3906
|
if (action === "show" || !action && !hasUpdates) {
|
|
@@ -3501,7 +3937,7 @@ async function runValidate(options = {}) {
|
|
|
3501
3937
|
// package.json
|
|
3502
3938
|
var package_default = {
|
|
3503
3939
|
name: "@kitsy/cnos-cli",
|
|
3504
|
-
version: "1.
|
|
3940
|
+
version: "1.7.0",
|
|
3505
3941
|
description: "CLI entry point and developer tooling for CNOS.",
|
|
3506
3942
|
type: "module",
|
|
3507
3943
|
main: "./dist/index.js",
|
|
@@ -3556,7 +3992,7 @@ function runVersion() {
|
|
|
3556
3992
|
}
|
|
3557
3993
|
|
|
3558
3994
|
// src/commands/value.ts
|
|
3559
|
-
import
|
|
3995
|
+
import path22 from "path";
|
|
3560
3996
|
function normalizeValueCommand(args) {
|
|
3561
3997
|
const [actionOrPath, ...tail] = args;
|
|
3562
3998
|
if (!actionOrPath) {
|
|
@@ -3580,7 +4016,7 @@ async function runValue(argsOrPath, options = {}) {
|
|
|
3580
4016
|
const args = Array.isArray(argsOrPath) ? argsOrPath : [argsOrPath];
|
|
3581
4017
|
const { action, tail } = normalizeValueCommand(args);
|
|
3582
4018
|
const cliArgs = [...options.cliArgs ?? []];
|
|
3583
|
-
const root =
|
|
4019
|
+
const root = path22.resolve(options.root ?? process.cwd());
|
|
3584
4020
|
if (action === "list") {
|
|
3585
4021
|
const prefix = consumeOption(cliArgs, "--prefix");
|
|
3586
4022
|
const entries = await listConfigEntries("value", {
|
|
@@ -3591,16 +4027,24 @@ async function runValue(argsOrPath, options = {}) {
|
|
|
3591
4027
|
if (options.json) {
|
|
3592
4028
|
return printJson(entries);
|
|
3593
4029
|
}
|
|
3594
|
-
return entries.map((entry) => `${entry.key}=${printValue(entry.value)}`).join("\n");
|
|
4030
|
+
return entries.map((entry) => `${entry.key}=${printValue(entry.value)}${entry.derived ? " (derived)" : ""}`).join("\n");
|
|
3595
4031
|
}
|
|
3596
4032
|
if (action === "set") {
|
|
3597
4033
|
const valuePath = tail[0] ?? "app.name";
|
|
3598
|
-
const
|
|
4034
|
+
const derive = consumeFlag(cliArgs, "--derive");
|
|
4035
|
+
const expr = consumeOption(cliArgs, "--expr");
|
|
4036
|
+
const deriveArg = derive && !expr && cliArgs[0] && !cliArgs[0].startsWith("--") ? cliArgs.shift() : void 0;
|
|
4037
|
+
const rawValue = derive ? "" : tail[1] ?? "";
|
|
4038
|
+
const deriveExpression = derive ? expr ?? tail[1] ?? deriveArg ?? "" : void 0;
|
|
3599
4039
|
const target = consumeOption(cliArgs, "--target") ?? "local";
|
|
3600
4040
|
const result = await defineValue("value", valuePath, rawValue, {
|
|
3601
4041
|
...options,
|
|
3602
4042
|
cliArgs,
|
|
3603
|
-
target
|
|
4043
|
+
target,
|
|
4044
|
+
...deriveExpression !== void 0 ? {
|
|
4045
|
+
deriveExpression,
|
|
4046
|
+
deriveExprMode: Boolean(expr)
|
|
4047
|
+
} : {}
|
|
3604
4048
|
});
|
|
3605
4049
|
if (options.json) {
|
|
3606
4050
|
return printJson({
|
|
@@ -3656,6 +4100,7 @@ async function buildRunEnvironment(options) {
|
|
|
3656
4100
|
const prefix = consumeOption(cliArgs, "--prefix");
|
|
3657
4101
|
const runtime = await createRuntimeService({
|
|
3658
4102
|
...options,
|
|
4103
|
+
cacheMode: "dev",
|
|
3659
4104
|
cliArgs
|
|
3660
4105
|
});
|
|
3661
4106
|
const authenticatedSecrets = isAuthenticated ? Object.fromEntries(
|
|
@@ -3697,12 +4142,14 @@ async function startWatchLoop(options) {
|
|
|
3697
4142
|
const root = options.root ?? process.cwd();
|
|
3698
4143
|
let current = await buildRunEnvironment({
|
|
3699
4144
|
...options,
|
|
4145
|
+
cacheMode: "dev",
|
|
3700
4146
|
cliArgs
|
|
3701
4147
|
});
|
|
3702
4148
|
let child = !isSignal ? spawnWatchedChild(command, root, current.env) : void 0;
|
|
3703
4149
|
let closed = false;
|
|
3704
4150
|
const watcher = await startGraphWatchLoop({
|
|
3705
4151
|
...options,
|
|
4152
|
+
cacheMode: "dev",
|
|
3706
4153
|
cliArgs,
|
|
3707
4154
|
debounceMs,
|
|
3708
4155
|
async onChange(payload) {
|
|
@@ -3711,6 +4158,7 @@ async function startWatchLoop(options) {
|
|
|
3711
4158
|
}
|
|
3712
4159
|
current = await buildRunEnvironment({
|
|
3713
4160
|
...options,
|
|
4161
|
+
cacheMode: "dev",
|
|
3714
4162
|
cliArgs
|
|
3715
4163
|
});
|
|
3716
4164
|
if (isSignal) {
|
|
@@ -3764,12 +4212,12 @@ async function runWatch(command, options = {}) {
|
|
|
3764
4212
|
}
|
|
3765
4213
|
|
|
3766
4214
|
// src/commands/workspace.ts
|
|
3767
|
-
import { cp, mkdir as mkdir6, rename, rm as
|
|
3768
|
-
import
|
|
3769
|
-
import { loadManifest as
|
|
4215
|
+
import { cp, mkdir as mkdir6, rename, rm as rm5, stat as stat2, writeFile as writeFile9, readFile as readFile6 } from "fs/promises";
|
|
4216
|
+
import path23 from "path";
|
|
4217
|
+
import { loadManifest as loadManifest8, parseYaml as parseYaml5, stringifyYaml as stringifyYaml7 } from "@kitsy/cnos/internal";
|
|
3770
4218
|
async function exists(targetPath) {
|
|
3771
4219
|
try {
|
|
3772
|
-
await
|
|
4220
|
+
await stat2(targetPath);
|
|
3773
4221
|
return true;
|
|
3774
4222
|
} catch {
|
|
3775
4223
|
return false;
|
|
@@ -3779,22 +4227,22 @@ async function copyIfExists(source, target) {
|
|
|
3779
4227
|
if (!await exists(source)) {
|
|
3780
4228
|
return;
|
|
3781
4229
|
}
|
|
3782
|
-
await mkdir6(
|
|
4230
|
+
await mkdir6(path23.dirname(target), { recursive: true });
|
|
3783
4231
|
await cp(source, target, { recursive: true, force: true });
|
|
3784
4232
|
}
|
|
3785
4233
|
async function mergeWorkspaceRootsIntoStandalone(targetCnosRoot, sourceRoots) {
|
|
3786
4234
|
for (const sourceRoot of sourceRoots) {
|
|
3787
4235
|
for (const folderName of ["values", "secrets", "env", "profiles"]) {
|
|
3788
4236
|
await copyIfExists(
|
|
3789
|
-
|
|
3790
|
-
|
|
4237
|
+
path23.join(sourceRoot, folderName),
|
|
4238
|
+
path23.join(targetCnosRoot, folderName)
|
|
3791
4239
|
);
|
|
3792
4240
|
}
|
|
3793
4241
|
}
|
|
3794
4242
|
}
|
|
3795
4243
|
async function writeCnosrc(packageRoot, config) {
|
|
3796
4244
|
await writeFile9(
|
|
3797
|
-
|
|
4245
|
+
path23.join(packageRoot, ".cnosrc.yml"),
|
|
3798
4246
|
stringifyYaml7({
|
|
3799
4247
|
root: config.root,
|
|
3800
4248
|
...config.workspace ? { workspace: config.workspace } : {}
|
|
@@ -3810,17 +4258,17 @@ function createDetachedManifest(rawManifest) {
|
|
|
3810
4258
|
return next;
|
|
3811
4259
|
}
|
|
3812
4260
|
async function runDetach(packageRoot, options = {}) {
|
|
3813
|
-
const loaded = await
|
|
4261
|
+
const loaded = await loadManifest8({ cwd: packageRoot });
|
|
3814
4262
|
if (!loaded.anchorPath || !loaded.anchoredWorkspace) {
|
|
3815
4263
|
throw new Error("workspace detach requires a package-local .cnosrc.yml with a workspace binding");
|
|
3816
4264
|
}
|
|
3817
|
-
const targetCnosRoot =
|
|
4265
|
+
const targetCnosRoot = path23.join(packageRoot, ".cnos");
|
|
3818
4266
|
const force = consumeFlag([...options.cliArgs ?? []], "--force");
|
|
3819
4267
|
if (await exists(targetCnosRoot) && !force) {
|
|
3820
4268
|
throw new Error(`Refusing to detach because ${displayPath(targetCnosRoot, packageRoot)} already exists. Use --force to overwrite.`);
|
|
3821
4269
|
}
|
|
3822
4270
|
if (force) {
|
|
3823
|
-
await
|
|
4271
|
+
await rm5(targetCnosRoot, { recursive: true, force: true });
|
|
3824
4272
|
}
|
|
3825
4273
|
const runtime = await createRuntimeService({
|
|
3826
4274
|
...options,
|
|
@@ -3831,11 +4279,11 @@ async function runDetach(packageRoot, options = {}) {
|
|
|
3831
4279
|
await mkdir6(targetCnosRoot, { recursive: true });
|
|
3832
4280
|
await mergeWorkspaceRootsIntoStandalone(targetCnosRoot, localRoots);
|
|
3833
4281
|
await writeFile9(
|
|
3834
|
-
|
|
4282
|
+
path23.join(targetCnosRoot, "cnos.yml"),
|
|
3835
4283
|
stringifyYaml7(createDetachedManifest(loaded.rawManifest)),
|
|
3836
4284
|
"utf8"
|
|
3837
4285
|
);
|
|
3838
|
-
const relativeRoot =
|
|
4286
|
+
const relativeRoot = path23.relative(packageRoot, loaded.manifestRoot).replace(/\\/g, "/");
|
|
3839
4287
|
const marker = {
|
|
3840
4288
|
detachedFrom: relativeRoot || ".",
|
|
3841
4289
|
detachedWorkspace: loaded.anchoredWorkspace,
|
|
@@ -3845,7 +4293,7 @@ async function runDetach(packageRoot, options = {}) {
|
|
|
3845
4293
|
workspace: loaded.anchoredWorkspace
|
|
3846
4294
|
}
|
|
3847
4295
|
};
|
|
3848
|
-
await writeFile9(
|
|
4296
|
+
await writeFile9(path23.join(targetCnosRoot, ".detached"), stringifyYaml7(marker), "utf8");
|
|
3849
4297
|
await writeCnosrc(packageRoot, { root: "./.cnos" });
|
|
3850
4298
|
if (options.json) {
|
|
3851
4299
|
return printJson({
|
|
@@ -3859,8 +4307,8 @@ async function runDetach(packageRoot, options = {}) {
|
|
|
3859
4307
|
async function runAttach(packageRoot, options = {}) {
|
|
3860
4308
|
const cliArgs = [...options.cliArgs ?? []];
|
|
3861
4309
|
const force = consumeFlag(cliArgs, "--force");
|
|
3862
|
-
const childCnosRoot =
|
|
3863
|
-
const markerPath =
|
|
4310
|
+
const childCnosRoot = path23.join(packageRoot, ".cnos");
|
|
4311
|
+
const markerPath = path23.join(childCnosRoot, ".detached");
|
|
3864
4312
|
if (!await exists(markerPath)) {
|
|
3865
4313
|
throw new Error("workspace attach requires a detached package with .cnos/.detached");
|
|
3866
4314
|
}
|
|
@@ -3868,19 +4316,24 @@ async function runAttach(packageRoot, options = {}) {
|
|
|
3868
4316
|
if (!marker?.originalCnosrc?.root || !marker.detachedWorkspace) {
|
|
3869
4317
|
throw new Error("Invalid .detached marker");
|
|
3870
4318
|
}
|
|
3871
|
-
const parentManifestRoot =
|
|
3872
|
-
const parentLoaded = await
|
|
4319
|
+
const parentManifestRoot = path23.resolve(packageRoot, marker.originalCnosrc.root);
|
|
4320
|
+
const parentLoaded = await loadManifest8({ root: parentManifestRoot });
|
|
4321
|
+
if (parentLoaded.rootResolution.readOnly) {
|
|
4322
|
+
throw new Error(
|
|
4323
|
+
`Cannot attach workspace because the parent CNOS root is remote and read-only (${parentLoaded.rootResolution.rootUri}).`
|
|
4324
|
+
);
|
|
4325
|
+
}
|
|
3873
4326
|
const workspaceId = marker.originalCnosrc.workspace ?? marker.detachedWorkspace;
|
|
3874
|
-
const parentWorkspaceRoot =
|
|
4327
|
+
const parentWorkspaceRoot = path23.join(parentLoaded.manifestRoot, "workspaces", workspaceId);
|
|
3875
4328
|
if (await exists(parentWorkspaceRoot) && !force) {
|
|
3876
4329
|
throw new Error(`workspace "${workspaceId}" already exists in parent root. Use --force to overwrite.`);
|
|
3877
4330
|
}
|
|
3878
4331
|
if (force) {
|
|
3879
|
-
await
|
|
4332
|
+
await rm5(parentWorkspaceRoot, { recursive: true, force: true });
|
|
3880
4333
|
}
|
|
3881
4334
|
await mkdir6(parentWorkspaceRoot, { recursive: true });
|
|
3882
4335
|
for (const folderName of ["values", "secrets", "env", "profiles"]) {
|
|
3883
|
-
await copyIfExists(
|
|
4336
|
+
await copyIfExists(path23.join(childCnosRoot, folderName), path23.join(parentWorkspaceRoot, folderName));
|
|
3884
4337
|
}
|
|
3885
4338
|
const rawManifest = parentLoaded.rawManifest;
|
|
3886
4339
|
const workspaces = rawManifest.workspaces ?? {};
|
|
@@ -3888,9 +4341,9 @@ async function runAttach(packageRoot, options = {}) {
|
|
|
3888
4341
|
items[workspaceId] = items[workspaceId] ?? {};
|
|
3889
4342
|
workspaces.items = items;
|
|
3890
4343
|
rawManifest.workspaces = workspaces;
|
|
3891
|
-
await writeFile9(
|
|
3892
|
-
const archivePath =
|
|
3893
|
-
await
|
|
4344
|
+
await writeFile9(path23.join(parentLoaded.manifestRoot, "cnos.yml"), stringifyYaml7(rawManifest), "utf8");
|
|
4345
|
+
const archivePath = path23.join(packageRoot, ".cnos.detached.bak");
|
|
4346
|
+
await rm5(archivePath, { recursive: true, force: true });
|
|
3894
4347
|
await rename(childCnosRoot, archivePath);
|
|
3895
4348
|
await writeCnosrc(packageRoot, {
|
|
3896
4349
|
root: marker.originalCnosrc.root,
|
|
@@ -3909,7 +4362,7 @@ async function runAttach(packageRoot, options = {}) {
|
|
|
3909
4362
|
async function runWorkspace(args = [], options = {}) {
|
|
3910
4363
|
const [action] = args;
|
|
3911
4364
|
const cliArgs = [...options.cliArgs ?? []];
|
|
3912
|
-
const packageRoot =
|
|
4365
|
+
const packageRoot = path23.resolve(consumeOption(cliArgs, "--package-root") ?? options.root ?? process.cwd());
|
|
3913
4366
|
switch (action) {
|
|
3914
4367
|
case "detach":
|
|
3915
4368
|
return runDetach(packageRoot, { ...options, cliArgs });
|
|
@@ -3934,6 +4387,9 @@ function resolveHelpTopic(command, args) {
|
|
|
3934
4387
|
if (command === "build" && args[0] && ["env", "server", "browser", "public"].includes(args[0])) {
|
|
3935
4388
|
return normalizeHelpTopic([command, args[0]]);
|
|
3936
4389
|
}
|
|
4390
|
+
if (command === "cache" && args[0] && ["list", "clear", "refresh"].includes(args[0])) {
|
|
4391
|
+
return normalizeHelpTopic([command, args[0]]);
|
|
4392
|
+
}
|
|
3937
4393
|
if (command === "dev" && args[0] === "env") {
|
|
3938
4394
|
return normalizeHelpTopic([command, args[0]]);
|
|
3939
4395
|
}
|
|
@@ -3983,6 +4439,9 @@ async function main(argv) {
|
|
|
3983
4439
|
...options.globalRoot ? {
|
|
3984
4440
|
globalRoot: options.globalRoot
|
|
3985
4441
|
} : {},
|
|
4442
|
+
...typeof options.cacheTtlSeconds === "number" ? {
|
|
4443
|
+
cacheTtlSeconds: options.cacheTtlSeconds
|
|
4444
|
+
} : {},
|
|
3986
4445
|
...options.json ? {
|
|
3987
4446
|
json: true
|
|
3988
4447
|
} : {},
|
|
@@ -4074,6 +4533,10 @@ async function main(argv) {
|
|
|
4074
4533
|
return;
|
|
4075
4534
|
case "build":
|
|
4076
4535
|
process.stdout.write(`${await runBuild(args[0], runtimeOptions)}
|
|
4536
|
+
`);
|
|
4537
|
+
return;
|
|
4538
|
+
case "cache":
|
|
4539
|
+
process.stdout.write(`${await runCache(args, runtimeOptions)}
|
|
4077
4540
|
`);
|
|
4078
4541
|
return;
|
|
4079
4542
|
case "dev":
|