@kitsy/cnos-cli 1.7.0 → 1.8.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 +278 -37
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -23,7 +23,8 @@ var COMMAND_OPTION_KEYS_WITH_VALUE = /* @__PURE__ */ new Set([
|
|
|
23
23
|
"--as",
|
|
24
24
|
"--set",
|
|
25
25
|
"--debounce",
|
|
26
|
-
"--expr"
|
|
26
|
+
"--expr",
|
|
27
|
+
"--extends"
|
|
27
28
|
]);
|
|
28
29
|
var COMMAND_FLAG_KEYS = /* @__PURE__ */ new Set([
|
|
29
30
|
"--flatten",
|
|
@@ -42,7 +43,8 @@ var COMMAND_FLAG_KEYS = /* @__PURE__ */ new Set([
|
|
|
42
43
|
"--apply",
|
|
43
44
|
"--rewrite",
|
|
44
45
|
"--signal",
|
|
45
|
-
"--derive"
|
|
46
|
+
"--derive",
|
|
47
|
+
"--onboard-current"
|
|
46
48
|
]);
|
|
47
49
|
function normalizeCommand(argv) {
|
|
48
50
|
const [command = "doctor", ...rest] = argv;
|
|
@@ -2214,10 +2216,48 @@ var COMMANDS = [
|
|
|
2214
2216
|
},
|
|
2215
2217
|
{
|
|
2216
2218
|
id: "workspace",
|
|
2217
|
-
summary: "
|
|
2218
|
-
usage: "cnos workspace <attach|detach> [options] [global-options]",
|
|
2219
|
-
description: "
|
|
2220
|
-
examples: [
|
|
2219
|
+
summary: "Manage workspace creation, listing, migration, and attach/detach flows.",
|
|
2220
|
+
usage: "cnos workspace <add|list|remove|scaffold|attach|detach> [options] [global-options]",
|
|
2221
|
+
description: "Adds and removes manifest workspaces, scaffolds package anchors, migrates single-root projects into workspace mode, and handles detach/attach flows for independent child packages.",
|
|
2222
|
+
examples: [
|
|
2223
|
+
"cnos workspace list",
|
|
2224
|
+
"cnos workspace add travel --package-root apps/travel --extends base",
|
|
2225
|
+
"cnos workspace add main --onboard-current",
|
|
2226
|
+
"cnos workspace remove gallery",
|
|
2227
|
+
"cnos workspace detach --package-root apps/travel"
|
|
2228
|
+
]
|
|
2229
|
+
},
|
|
2230
|
+
{
|
|
2231
|
+
id: "workspace add",
|
|
2232
|
+
summary: "Add a workspace to the manifest and scaffold its on-disk layout.",
|
|
2233
|
+
usage: "cnos workspace add <id> [--package-root <path>] [--extends <workspace>] [--onboard-current] [--force] [global-options]",
|
|
2234
|
+
description: "Creates .cnos/workspaces/<id>, updates cnos.yml, writes a .cnosrc.yml anchor at the selected package root, and optionally migrates an existing single-root .cnos tree into workspace mode with --onboard-current.",
|
|
2235
|
+
examples: [
|
|
2236
|
+
"cnos workspace add travel --package-root apps/travel --extends base",
|
|
2237
|
+
"cnos workspace add insights --package-root apps/insights",
|
|
2238
|
+
"cnos workspace add main --onboard-current"
|
|
2239
|
+
]
|
|
2240
|
+
},
|
|
2241
|
+
{
|
|
2242
|
+
id: "workspace scaffold",
|
|
2243
|
+
summary: "Scaffold a workspace and anchor without changing other runtime flows.",
|
|
2244
|
+
usage: "cnos workspace scaffold <id> [--package-root <path>] [--extends <workspace>] [--force] [global-options]",
|
|
2245
|
+
description: "Creates the workspace manifest entry, workspace folders, and package anchor for a new app or package. This is an alias-oriented workflow for teams that prefer scaffold wording over add.",
|
|
2246
|
+
examples: ["cnos workspace scaffold gallery --package-root apps/gallery --extends base"]
|
|
2247
|
+
},
|
|
2248
|
+
{
|
|
2249
|
+
id: "workspace list",
|
|
2250
|
+
summary: "List declared workspaces and their inheritance.",
|
|
2251
|
+
usage: "cnos workspace list [global-options]",
|
|
2252
|
+
description: "Shows the declared workspace ids, default workspace, and extends relationships from cnos.yml.",
|
|
2253
|
+
examples: ["cnos workspace list", "cnos workspace list --json"]
|
|
2254
|
+
},
|
|
2255
|
+
{
|
|
2256
|
+
id: "workspace remove",
|
|
2257
|
+
summary: "Remove a workspace from the manifest and delete its local workspace tree.",
|
|
2258
|
+
usage: "cnos workspace remove <id> [global-options]",
|
|
2259
|
+
description: "Deletes .cnos/workspaces/<id> and removes the workspace entry from cnos.yml. CNOS refuses to remove the current default workspace until you change workspaces.default.",
|
|
2260
|
+
examples: ["cnos workspace remove gallery", "cnos workspace remove insights --json"]
|
|
2221
2261
|
},
|
|
2222
2262
|
{
|
|
2223
2263
|
id: "workspace detach",
|
|
@@ -2576,8 +2616,7 @@ async function ensureGitignore(root) {
|
|
|
2576
2616
|
`, "utf8");
|
|
2577
2617
|
return true;
|
|
2578
2618
|
}
|
|
2579
|
-
async function
|
|
2580
|
-
const cnosRoot = path9.join(root, ".cnos");
|
|
2619
|
+
async function ensureWorkspaceLayout(cnosRoot, workspace) {
|
|
2581
2620
|
const workspaceRoot = workspace ? path9.join(cnosRoot, "workspaces", workspace) : cnosRoot;
|
|
2582
2621
|
const createdPaths = [];
|
|
2583
2622
|
await mkdir4(path9.join(workspaceRoot, "profiles"), { recursive: true });
|
|
@@ -2598,18 +2637,28 @@ async function scaffoldWorkspace(root, workspace) {
|
|
|
2598
2637
|
for (const relativePath of relativePaths) {
|
|
2599
2638
|
const filePath = path9.join(cnosRoot, ...relativePath);
|
|
2600
2639
|
if (await ensureFile(filePath, "")) {
|
|
2601
|
-
createdPaths.push(path9.relative(
|
|
2640
|
+
createdPaths.push(path9.relative(path9.dirname(cnosRoot), filePath).replace(/\\/g, "/"));
|
|
2602
2641
|
}
|
|
2603
2642
|
}
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
|
|
2643
|
+
return createdPaths;
|
|
2644
|
+
}
|
|
2645
|
+
async function ensureCnosrc(root, workspace) {
|
|
2646
|
+
return ensureFile(
|
|
2608
2647
|
path9.join(root, ".cnosrc.yml"),
|
|
2609
2648
|
workspace ? `root: ./.cnos
|
|
2610
2649
|
workspace: ${workspace}
|
|
2611
2650
|
` : "root: ./.cnos\n"
|
|
2612
|
-
)
|
|
2651
|
+
);
|
|
2652
|
+
}
|
|
2653
|
+
async function scaffoldWorkspace(root, workspace) {
|
|
2654
|
+
const cnosRoot = path9.join(root, ".cnos");
|
|
2655
|
+
const createdPaths = (await ensureWorkspaceLayout(cnosRoot, workspace)).map(
|
|
2656
|
+
(entry) => entry.replace(/^\.cnos\//, ".cnos/")
|
|
2657
|
+
);
|
|
2658
|
+
if (await ensureFile(path9.join(cnosRoot, "cnos.yml"), scaffoldManifest(path9.basename(root), workspace))) {
|
|
2659
|
+
createdPaths.push(".cnos/cnos.yml");
|
|
2660
|
+
}
|
|
2661
|
+
if (await ensureCnosrc(root, workspace)) {
|
|
2613
2662
|
createdPaths.push(".cnosrc.yml");
|
|
2614
2663
|
}
|
|
2615
2664
|
if (workspace && await ensureFile(path9.join(root, ".cnos-workspace.yml"), `workspace: ${workspace}
|
|
@@ -3937,7 +3986,7 @@ async function runValidate(options = {}) {
|
|
|
3937
3986
|
// package.json
|
|
3938
3987
|
var package_default = {
|
|
3939
3988
|
name: "@kitsy/cnos-cli",
|
|
3940
|
-
version: "1.
|
|
3989
|
+
version: "1.8.0",
|
|
3941
3990
|
description: "CLI entry point and developer tooling for CNOS.",
|
|
3942
3991
|
type: "module",
|
|
3943
3992
|
main: "./dist/index.js",
|
|
@@ -4212,7 +4261,7 @@ async function runWatch(command, options = {}) {
|
|
|
4212
4261
|
}
|
|
4213
4262
|
|
|
4214
4263
|
// src/commands/workspace.ts
|
|
4215
|
-
import { cp, mkdir as mkdir6, rename, rm as rm5, stat as stat2, writeFile as writeFile9
|
|
4264
|
+
import { cp, mkdir as mkdir6, readdir as readdir5, readFile as readFile6, rename, rm as rm5, stat as stat2, writeFile as writeFile9 } from "fs/promises";
|
|
4216
4265
|
import path23 from "path";
|
|
4217
4266
|
import { loadManifest as loadManifest8, parseYaml as parseYaml5, stringifyYaml as stringifyYaml7 } from "@kitsy/cnos/internal";
|
|
4218
4267
|
async function exists(targetPath) {
|
|
@@ -4230,22 +4279,34 @@ async function copyIfExists(source, target) {
|
|
|
4230
4279
|
await mkdir6(path23.dirname(target), { recursive: true });
|
|
4231
4280
|
await cp(source, target, { recursive: true, force: true });
|
|
4232
4281
|
}
|
|
4282
|
+
async function moveIfExists(source, target, force = false) {
|
|
4283
|
+
if (!await exists(source)) {
|
|
4284
|
+
return false;
|
|
4285
|
+
}
|
|
4286
|
+
if (force) {
|
|
4287
|
+
await rm5(target, { recursive: true, force: true });
|
|
4288
|
+
} else if (await exists(target)) {
|
|
4289
|
+
throw new Error(`Refusing to overwrite existing path ${target}. Use --force to replace it.`);
|
|
4290
|
+
}
|
|
4291
|
+
await mkdir6(path23.dirname(target), { recursive: true });
|
|
4292
|
+
await rename(source, target);
|
|
4293
|
+
return true;
|
|
4294
|
+
}
|
|
4233
4295
|
async function mergeWorkspaceRootsIntoStandalone(targetCnosRoot, sourceRoots) {
|
|
4234
4296
|
for (const sourceRoot of sourceRoots) {
|
|
4235
4297
|
for (const folderName of ["values", "secrets", "env", "profiles"]) {
|
|
4236
|
-
await copyIfExists(
|
|
4237
|
-
path23.join(sourceRoot, folderName),
|
|
4238
|
-
path23.join(targetCnosRoot, folderName)
|
|
4239
|
-
);
|
|
4298
|
+
await copyIfExists(path23.join(sourceRoot, folderName), path23.join(targetCnosRoot, folderName));
|
|
4240
4299
|
}
|
|
4241
4300
|
}
|
|
4242
4301
|
}
|
|
4243
|
-
async function
|
|
4302
|
+
async function writeAnchor(packageRoot, manifestRoot, workspace) {
|
|
4303
|
+
const relativeRoot = path23.relative(packageRoot, manifestRoot).replace(/\\/g, "/");
|
|
4304
|
+
const rootValue = relativeRoot.length === 0 ? "./.cnos" : relativeRoot.startsWith(".") ? relativeRoot : `./${relativeRoot}`;
|
|
4244
4305
|
await writeFile9(
|
|
4245
4306
|
path23.join(packageRoot, ".cnosrc.yml"),
|
|
4246
4307
|
stringifyYaml7({
|
|
4247
|
-
root:
|
|
4248
|
-
...
|
|
4308
|
+
root: rootValue,
|
|
4309
|
+
...workspace ? { workspace } : {}
|
|
4249
4310
|
}),
|
|
4250
4311
|
"utf8"
|
|
4251
4312
|
);
|
|
@@ -4257,6 +4318,36 @@ function createDetachedManifest(rawManifest) {
|
|
|
4257
4318
|
}
|
|
4258
4319
|
return next;
|
|
4259
4320
|
}
|
|
4321
|
+
function normalizeWorkspaceId(value) {
|
|
4322
|
+
const workspaceId = value?.trim();
|
|
4323
|
+
if (!workspaceId) {
|
|
4324
|
+
throw new Error("Workspace id is required");
|
|
4325
|
+
}
|
|
4326
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(workspaceId)) {
|
|
4327
|
+
throw new Error(`Invalid workspace id "${workspaceId}". Use letters, numbers, dot, underscore, or dash.`);
|
|
4328
|
+
}
|
|
4329
|
+
return workspaceId;
|
|
4330
|
+
}
|
|
4331
|
+
function splitExtends(value) {
|
|
4332
|
+
if (!value) {
|
|
4333
|
+
return void 0;
|
|
4334
|
+
}
|
|
4335
|
+
const items = value.split(",").map((entry) => entry.trim()).filter(Boolean);
|
|
4336
|
+
return items.length > 0 ? items : void 0;
|
|
4337
|
+
}
|
|
4338
|
+
async function hasDirectConfigData(cnosRoot) {
|
|
4339
|
+
for (const folderName of ["values", "secrets", "env", "profiles"]) {
|
|
4340
|
+
const folder = path23.join(cnosRoot, folderName);
|
|
4341
|
+
if (!await exists(folder)) {
|
|
4342
|
+
continue;
|
|
4343
|
+
}
|
|
4344
|
+
const entries = await readdir5(folder, { withFileTypes: true });
|
|
4345
|
+
if (entries.some((entry) => entry.name !== ".gitkeep")) {
|
|
4346
|
+
return true;
|
|
4347
|
+
}
|
|
4348
|
+
}
|
|
4349
|
+
return false;
|
|
4350
|
+
}
|
|
4260
4351
|
async function runDetach(packageRoot, options = {}) {
|
|
4261
4352
|
const loaded = await loadManifest8({ cwd: packageRoot });
|
|
4262
4353
|
if (!loaded.anchorPath || !loaded.anchoredWorkspace) {
|
|
@@ -4294,7 +4385,7 @@ async function runDetach(packageRoot, options = {}) {
|
|
|
4294
4385
|
}
|
|
4295
4386
|
};
|
|
4296
4387
|
await writeFile9(path23.join(targetCnosRoot, ".detached"), stringifyYaml7(marker), "utf8");
|
|
4297
|
-
await
|
|
4388
|
+
await writeFile9(path23.join(packageRoot, ".cnosrc.yml"), stringifyYaml7({ root: "./.cnos" }), "utf8");
|
|
4298
4389
|
if (options.json) {
|
|
4299
4390
|
return printJson({
|
|
4300
4391
|
packageRoot,
|
|
@@ -4335,7 +4426,7 @@ async function runAttach(packageRoot, options = {}) {
|
|
|
4335
4426
|
for (const folderName of ["values", "secrets", "env", "profiles"]) {
|
|
4336
4427
|
await copyIfExists(path23.join(childCnosRoot, folderName), path23.join(parentWorkspaceRoot, folderName));
|
|
4337
4428
|
}
|
|
4338
|
-
const rawManifest = parentLoaded.rawManifest;
|
|
4429
|
+
const rawManifest = structuredClone(parentLoaded.rawManifest);
|
|
4339
4430
|
const workspaces = rawManifest.workspaces ?? {};
|
|
4340
4431
|
const items = workspaces.items ?? {};
|
|
4341
4432
|
items[workspaceId] = items[workspaceId] ?? {};
|
|
@@ -4345,10 +4436,7 @@ async function runAttach(packageRoot, options = {}) {
|
|
|
4345
4436
|
const archivePath = path23.join(packageRoot, ".cnos.detached.bak");
|
|
4346
4437
|
await rm5(archivePath, { recursive: true, force: true });
|
|
4347
4438
|
await rename(childCnosRoot, archivePath);
|
|
4348
|
-
await
|
|
4349
|
-
root: marker.originalCnosrc.root,
|
|
4350
|
-
...workspaceId ? { workspace: workspaceId } : {}
|
|
4351
|
-
});
|
|
4439
|
+
await writeAnchor(packageRoot, parentLoaded.manifestRoot, workspaceId);
|
|
4352
4440
|
if (options.json) {
|
|
4353
4441
|
return printJson({
|
|
4354
4442
|
packageRoot,
|
|
@@ -4359,15 +4447,168 @@ async function runAttach(packageRoot, options = {}) {
|
|
|
4359
4447
|
}
|
|
4360
4448
|
return `attached workspace ${workspaceId} to ${displayPath(parentLoaded.manifestRoot, packageRoot)}`;
|
|
4361
4449
|
}
|
|
4362
|
-
async function
|
|
4363
|
-
const
|
|
4450
|
+
async function runList2(manifestCwd, options = {}) {
|
|
4451
|
+
const loaded = await loadManifest8({
|
|
4452
|
+
...options.root ? { root: options.root } : {},
|
|
4453
|
+
cwd: manifestCwd,
|
|
4454
|
+
...options.processEnv ? { processEnv: options.processEnv } : {}
|
|
4455
|
+
});
|
|
4456
|
+
const entries = Object.entries(loaded.manifest.workspaces.items).map(([id, config]) => ({
|
|
4457
|
+
id,
|
|
4458
|
+
extends: config.extends,
|
|
4459
|
+
default: loaded.manifest.workspaces.default === id,
|
|
4460
|
+
path: path23.join(loaded.manifestRoot, "workspaces", id)
|
|
4461
|
+
})).sort((left, right) => left.id.localeCompare(right.id));
|
|
4462
|
+
if (options.json) {
|
|
4463
|
+
return printJson({
|
|
4464
|
+
default: loaded.manifest.workspaces.default,
|
|
4465
|
+
workspaces: entries
|
|
4466
|
+
});
|
|
4467
|
+
}
|
|
4468
|
+
if (entries.length === 0) {
|
|
4469
|
+
return "no workspaces declared";
|
|
4470
|
+
}
|
|
4471
|
+
return entries.map((entry) => {
|
|
4472
|
+
const tags = [
|
|
4473
|
+
entry.default ? "default" : void 0,
|
|
4474
|
+
entry.extends.length > 0 ? `extends=${entry.extends.join(",")}` : void 0
|
|
4475
|
+
].filter(Boolean);
|
|
4476
|
+
return `${entry.id}${tags.length > 0 ? ` (${tags.join(", ")})` : ""}`;
|
|
4477
|
+
}).join("\n");
|
|
4478
|
+
}
|
|
4479
|
+
async function runAddOrScaffold(action, workspaceId, manifestCwd, packageRoot, options = {}) {
|
|
4364
4480
|
const cliArgs = [...options.cliArgs ?? []];
|
|
4365
|
-
const
|
|
4481
|
+
const extendsOption = splitExtends(consumeOption(cliArgs, "--extends"));
|
|
4482
|
+
const onboardCurrent = consumeFlag(cliArgs, "--onboard-current");
|
|
4483
|
+
const force = consumeFlag(cliArgs, "--force");
|
|
4484
|
+
if (cliArgs.length > 0) {
|
|
4485
|
+
throw new Error(`Unsupported workspace arguments: ${cliArgs.join(" ")}`);
|
|
4486
|
+
}
|
|
4487
|
+
const loaded = await loadManifest8({
|
|
4488
|
+
...options.root ? { root: options.root } : {},
|
|
4489
|
+
cwd: manifestCwd,
|
|
4490
|
+
...options.processEnv ? { processEnv: options.processEnv } : {}
|
|
4491
|
+
});
|
|
4492
|
+
if (loaded.rootResolution.readOnly) {
|
|
4493
|
+
throw new Error(
|
|
4494
|
+
`Cannot ${action} workspace because the active CNOS root is remote and read-only (${loaded.rootResolution.rootUri}). Clone the config repo and edit it directly.`
|
|
4495
|
+
);
|
|
4496
|
+
}
|
|
4497
|
+
const manifestRoot = loaded.manifestRoot;
|
|
4498
|
+
const cnosRoot = manifestRoot;
|
|
4499
|
+
const rawManifest = structuredClone(loaded.rawManifest);
|
|
4500
|
+
const rawWorkspaces = rawManifest.workspaces ?? {};
|
|
4501
|
+
const rawItems = rawWorkspaces.items ?? {};
|
|
4502
|
+
const isWorkspaceMode = Object.keys(rawItems).length > 0;
|
|
4503
|
+
const directConfigPresent = await hasDirectConfigData(cnosRoot);
|
|
4504
|
+
if (!isWorkspaceMode && directConfigPresent && !onboardCurrent) {
|
|
4505
|
+
throw new Error(
|
|
4506
|
+
"This CNOS root is in single-root mode and already has direct values/secrets/env/profiles data. Re-run with --onboard-current to migrate it into workspace mode."
|
|
4507
|
+
);
|
|
4508
|
+
}
|
|
4509
|
+
if (rawItems[workspaceId] && !force) {
|
|
4510
|
+
throw new Error(`workspace "${workspaceId}" already exists. Use --force to update its manifest entry and anchor.`);
|
|
4511
|
+
}
|
|
4512
|
+
const defaultExtends = extendsOption ?? (!["base", "root"].includes(workspaceId) && rawItems.base ? ["base"] : void 0);
|
|
4513
|
+
rawItems[workspaceId] = defaultExtends && defaultExtends.length > 0 ? { extends: defaultExtends } : {};
|
|
4514
|
+
rawWorkspaces.items = rawItems;
|
|
4515
|
+
rawWorkspaces.default = rawWorkspaces.default ?? workspaceId;
|
|
4516
|
+
rawManifest.workspaces = rawWorkspaces;
|
|
4517
|
+
const workspaceRoot = path23.join(cnosRoot, "workspaces", workspaceId);
|
|
4518
|
+
if (onboardCurrent) {
|
|
4519
|
+
if (isWorkspaceMode) {
|
|
4520
|
+
throw new Error("--onboard-current can only be used when the manifest is not already in workspace mode.");
|
|
4521
|
+
}
|
|
4522
|
+
for (const folderName of ["values", "secrets", "env", "profiles"]) {
|
|
4523
|
+
await moveIfExists(path23.join(cnosRoot, folderName), path23.join(workspaceRoot, folderName), force);
|
|
4524
|
+
}
|
|
4525
|
+
}
|
|
4526
|
+
const created = await ensureWorkspaceLayout(cnosRoot, workspaceId);
|
|
4527
|
+
await writeFile9(path23.join(cnosRoot, "cnos.yml"), stringifyYaml7(rawManifest), "utf8");
|
|
4528
|
+
await ensureGitignore(path23.dirname(cnosRoot));
|
|
4529
|
+
await writeAnchor(packageRoot, cnosRoot, workspaceId);
|
|
4530
|
+
await ensureFile(path23.join(packageRoot, ".cnos-workspace.yml"), `workspace: ${workspaceId}
|
|
4531
|
+
globalRoot: ~/.cnos
|
|
4532
|
+
`);
|
|
4533
|
+
const result = {
|
|
4534
|
+
workspace: workspaceId,
|
|
4535
|
+
root: path23.dirname(cnosRoot),
|
|
4536
|
+
packageRoot,
|
|
4537
|
+
onboarded: onboardCurrent,
|
|
4538
|
+
created
|
|
4539
|
+
};
|
|
4540
|
+
if (options.json) {
|
|
4541
|
+
return printJson(result);
|
|
4542
|
+
}
|
|
4543
|
+
const verb = action === "add" ? "added" : "scaffolded";
|
|
4544
|
+
return `${verb} workspace ${workspaceId} at ${displayPath(workspaceRoot, packageRoot)}`;
|
|
4545
|
+
}
|
|
4546
|
+
async function runRemove(workspaceId, manifestCwd, options = {}) {
|
|
4547
|
+
const cliArgs = [...options.cliArgs ?? []];
|
|
4548
|
+
consumeFlag(cliArgs, "--force");
|
|
4549
|
+
if (cliArgs.length > 0) {
|
|
4550
|
+
throw new Error(`Unsupported workspace arguments: ${cliArgs.join(" ")}`);
|
|
4551
|
+
}
|
|
4552
|
+
const loaded = await loadManifest8({
|
|
4553
|
+
...options.root ? { root: options.root } : {},
|
|
4554
|
+
cwd: manifestCwd,
|
|
4555
|
+
...options.processEnv ? { processEnv: options.processEnv } : {}
|
|
4556
|
+
});
|
|
4557
|
+
if (loaded.rootResolution.readOnly) {
|
|
4558
|
+
throw new Error(
|
|
4559
|
+
`Cannot remove workspace because the active CNOS root is remote and read-only (${loaded.rootResolution.rootUri}). Clone the config repo and edit it directly.`
|
|
4560
|
+
);
|
|
4561
|
+
}
|
|
4562
|
+
const rawManifest = structuredClone(loaded.rawManifest);
|
|
4563
|
+
const rawWorkspaces = rawManifest.workspaces ?? {};
|
|
4564
|
+
const rawItems = rawWorkspaces.items ?? {};
|
|
4565
|
+
if (!rawItems[workspaceId]) {
|
|
4566
|
+
throw new Error(`workspace "${workspaceId}" does not exist`);
|
|
4567
|
+
}
|
|
4568
|
+
if (rawWorkspaces.default === workspaceId) {
|
|
4569
|
+
throw new Error(`Cannot remove workspace "${workspaceId}" because it is the default workspace. Change workspaces.default first.`);
|
|
4570
|
+
}
|
|
4571
|
+
delete rawItems[workspaceId];
|
|
4572
|
+
rawWorkspaces.items = rawItems;
|
|
4573
|
+
rawManifest.workspaces = rawWorkspaces;
|
|
4574
|
+
await writeFile9(path23.join(loaded.manifestRoot, "cnos.yml"), stringifyYaml7(rawManifest), "utf8");
|
|
4575
|
+
await rm5(path23.join(loaded.manifestRoot, "workspaces", workspaceId), { recursive: true, force: true });
|
|
4576
|
+
if (options.json) {
|
|
4577
|
+
return printJson({
|
|
4578
|
+
workspace: workspaceId,
|
|
4579
|
+
removedFrom: loaded.manifestRoot
|
|
4580
|
+
});
|
|
4581
|
+
}
|
|
4582
|
+
return `removed workspace ${workspaceId}`;
|
|
4583
|
+
}
|
|
4584
|
+
async function runWorkspace(args = [], options = {}) {
|
|
4585
|
+
const [action, workspaceArg] = args;
|
|
4586
|
+
const baseCliArgs = [...options.cliArgs ?? []];
|
|
4587
|
+
const manifestCwd = path23.resolve(options.root ?? process.cwd());
|
|
4588
|
+
const packageRoot = path23.resolve(consumeOption(baseCliArgs, "--package-root") ?? options.root ?? process.cwd());
|
|
4366
4589
|
switch (action) {
|
|
4367
|
-
case "detach":
|
|
4368
|
-
return runDetach(packageRoot, { ...options, cliArgs });
|
|
4369
4590
|
case "attach":
|
|
4370
|
-
return runAttach(packageRoot, { ...options, cliArgs });
|
|
4591
|
+
return runAttach(packageRoot, { ...options, cliArgs: baseCliArgs });
|
|
4592
|
+
case "detach":
|
|
4593
|
+
return runDetach(packageRoot, { ...options, cliArgs: baseCliArgs });
|
|
4594
|
+
case "list":
|
|
4595
|
+
return runList2(manifestCwd, options);
|
|
4596
|
+
case "add":
|
|
4597
|
+
return runAddOrScaffold("add", normalizeWorkspaceId(workspaceArg), manifestCwd, packageRoot, {
|
|
4598
|
+
...options,
|
|
4599
|
+
cliArgs: baseCliArgs
|
|
4600
|
+
});
|
|
4601
|
+
case "scaffold":
|
|
4602
|
+
return runAddOrScaffold("scaffold", normalizeWorkspaceId(workspaceArg), manifestCwd, packageRoot, {
|
|
4603
|
+
...options,
|
|
4604
|
+
cliArgs: baseCliArgs
|
|
4605
|
+
});
|
|
4606
|
+
case "remove":
|
|
4607
|
+
case "delete":
|
|
4608
|
+
return runRemove(normalizeWorkspaceId(workspaceArg), manifestCwd, {
|
|
4609
|
+
...options,
|
|
4610
|
+
cliArgs: baseCliArgs
|
|
4611
|
+
});
|
|
4371
4612
|
default:
|
|
4372
4613
|
throw new Error(`Unsupported workspace action: ${action ?? "(missing)"}`);
|
|
4373
4614
|
}
|
|
@@ -4393,8 +4634,8 @@ function resolveHelpTopic(command, args) {
|
|
|
4393
4634
|
if (command === "dev" && args[0] === "env") {
|
|
4394
4635
|
return normalizeHelpTopic([command, args[0]]);
|
|
4395
4636
|
}
|
|
4396
|
-
if (command === "workspace" && args[0] && ["attach", "detach"].includes(args[0])) {
|
|
4397
|
-
return normalizeHelpTopic([command, args[0]]);
|
|
4637
|
+
if (command === "workspace" && args[0] && ["attach", "detach", "add", "list", "remove", "delete", "scaffold"].includes(args[0])) {
|
|
4638
|
+
return normalizeHelpTopic([command, args[0] === "delete" ? "remove" : args[0]]);
|
|
4398
4639
|
}
|
|
4399
4640
|
if (command === "vault" && args[0] && ["create", "add", "list", "delete", "remove"].includes(args[0])) {
|
|
4400
4641
|
return normalizeHelpTopic([command, args[0] === "delete" ? "remove" : args[0] === "add" ? "create" : args[0]]);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kitsy/cnos-cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.8.0",
|
|
4
4
|
"description": "CLI entry point and developer tooling for CNOS.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"access": "public"
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"@kitsy/cnos": "1.
|
|
39
|
+
"@kitsy/cnos": "1.8.0"
|
|
40
40
|
},
|
|
41
41
|
"scripts": {
|
|
42
42
|
"build": "tsup src/index.ts --format esm --dts",
|