@kitsy/cnos-cli 0.0.1 → 1.0.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 +903 -99
- package/package.json +2 -3
package/dist/index.js
CHANGED
|
@@ -7,8 +7,50 @@ var OPTION_KEYS = {
|
|
|
7
7
|
"--profile": "profile",
|
|
8
8
|
"--global-root": "globalRoot"
|
|
9
9
|
};
|
|
10
|
-
var COMMAND_OPTION_KEYS_WITH_VALUE = /* @__PURE__ */ new Set([
|
|
11
|
-
|
|
10
|
+
var COMMAND_OPTION_KEYS_WITH_VALUE = /* @__PURE__ */ new Set([
|
|
11
|
+
"--format",
|
|
12
|
+
"--framework",
|
|
13
|
+
"--prefix",
|
|
14
|
+
"--target",
|
|
15
|
+
"--to",
|
|
16
|
+
"--provider",
|
|
17
|
+
"--passphrase",
|
|
18
|
+
"--inherit"
|
|
19
|
+
]);
|
|
20
|
+
var COMMAND_FLAG_KEYS = /* @__PURE__ */ new Set(["--flatten", "--public", "--local", "--remote", "--ref"]);
|
|
21
|
+
function normalizeCommand(argv) {
|
|
22
|
+
const [command = "doctor", ...rest] = argv;
|
|
23
|
+
const resource = rest[0];
|
|
24
|
+
const remaining = rest.slice(1);
|
|
25
|
+
if ((command === "create" || command === "add") && resource === "profile") {
|
|
26
|
+
return ["profile", "create", ...remaining];
|
|
27
|
+
}
|
|
28
|
+
if ((command === "delete" || command === "remove") && resource === "profile") {
|
|
29
|
+
return ["profile", "delete", ...remaining];
|
|
30
|
+
}
|
|
31
|
+
if (command === "list" && resource === "profile") {
|
|
32
|
+
return ["profile", "list", ...remaining];
|
|
33
|
+
}
|
|
34
|
+
if ((command === "create" || command === "add") && resource === "secret") {
|
|
35
|
+
return ["secret", "set", ...remaining];
|
|
36
|
+
}
|
|
37
|
+
if ((command === "delete" || command === "remove") && resource === "secret") {
|
|
38
|
+
return ["secret", "delete", ...remaining];
|
|
39
|
+
}
|
|
40
|
+
if (command === "list" && resource === "secret") {
|
|
41
|
+
return ["secret", "list", ...remaining];
|
|
42
|
+
}
|
|
43
|
+
if ((command === "create" || command === "add") && resource === "value") {
|
|
44
|
+
return ["value", "set", ...remaining];
|
|
45
|
+
}
|
|
46
|
+
if ((command === "delete" || command === "remove") && resource === "value") {
|
|
47
|
+
return ["value", "delete", ...remaining];
|
|
48
|
+
}
|
|
49
|
+
if (command === "list" && resource === "value") {
|
|
50
|
+
return ["value", "list", ...remaining];
|
|
51
|
+
}
|
|
52
|
+
return [command, ...rest];
|
|
53
|
+
}
|
|
12
54
|
function setOption(options, key, value) {
|
|
13
55
|
options[key] = value;
|
|
14
56
|
}
|
|
@@ -23,7 +65,8 @@ function parseArgs(argv) {
|
|
|
23
65
|
passthrough: []
|
|
24
66
|
};
|
|
25
67
|
}
|
|
26
|
-
const
|
|
68
|
+
const normalizedArgv = normalizeCommand(argv);
|
|
69
|
+
const [command = "doctor", ...rest] = normalizedArgv;
|
|
27
70
|
const options = {};
|
|
28
71
|
const args = [];
|
|
29
72
|
const cliArgs = [];
|
|
@@ -128,15 +171,24 @@ function printJson(value) {
|
|
|
128
171
|
}
|
|
129
172
|
|
|
130
173
|
// src/services/writes.ts
|
|
131
|
-
import {
|
|
174
|
+
import { randomUUID } from "crypto";
|
|
175
|
+
import { mkdir, readFile, rm, writeFile } from "fs/promises";
|
|
132
176
|
import path from "path";
|
|
133
|
-
import {
|
|
177
|
+
import {
|
|
178
|
+
parseYaml,
|
|
179
|
+
resolveConfigDocumentPath,
|
|
180
|
+
resolveSecretStoreRoot,
|
|
181
|
+
stringifyYaml,
|
|
182
|
+
writeLocalSecret
|
|
183
|
+
} from "@kitsy/cnos/internal";
|
|
134
184
|
|
|
135
185
|
// src/services/runtime.ts
|
|
136
186
|
import { createCnos } from "@kitsy/cnos";
|
|
137
187
|
async function createRuntimeService(options = {}) {
|
|
138
188
|
const createOptions = {
|
|
139
|
-
|
|
189
|
+
...options.root ? {
|
|
190
|
+
root: options.root
|
|
191
|
+
} : {},
|
|
140
192
|
...options.workspace ? {
|
|
141
193
|
workspace: options.workspace
|
|
142
194
|
} : {},
|
|
@@ -181,8 +233,38 @@ function parseScalarValue(rawValue) {
|
|
|
181
233
|
return rawValue;
|
|
182
234
|
}
|
|
183
235
|
}
|
|
184
|
-
|
|
185
|
-
const
|
|
236
|
+
function unsetNestedValue(target, pathSegments) {
|
|
237
|
+
const [head, ...tail] = pathSegments;
|
|
238
|
+
if (!head || !(head in target)) {
|
|
239
|
+
return false;
|
|
240
|
+
}
|
|
241
|
+
if (tail.length === 0) {
|
|
242
|
+
delete target[head];
|
|
243
|
+
return true;
|
|
244
|
+
}
|
|
245
|
+
const nextValue = target[head];
|
|
246
|
+
if (!nextValue || typeof nextValue !== "object" || Array.isArray(nextValue)) {
|
|
247
|
+
return false;
|
|
248
|
+
}
|
|
249
|
+
const deleted = unsetNestedValue(nextValue, tail);
|
|
250
|
+
if (deleted && Object.keys(nextValue).length === 0) {
|
|
251
|
+
delete target[head];
|
|
252
|
+
}
|
|
253
|
+
return deleted;
|
|
254
|
+
}
|
|
255
|
+
function isSecretReference(value) {
|
|
256
|
+
return Boolean(
|
|
257
|
+
value && typeof value === "object" && !Array.isArray(value) && typeof value.provider === "string" && typeof value.ref === "string"
|
|
258
|
+
);
|
|
259
|
+
}
|
|
260
|
+
async function readYamlDocument(filePath) {
|
|
261
|
+
try {
|
|
262
|
+
return parseYaml(await readFile(filePath, "utf8")) ?? {};
|
|
263
|
+
} catch {
|
|
264
|
+
return {};
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
function getSelectedWorkspaceRoot(options, runtime) {
|
|
186
268
|
const target = options.target ?? "local";
|
|
187
269
|
const workspaceRoot = runtime.graph.workspace.workspaceRoots.find(
|
|
188
270
|
(entry) => entry.scope === target && entry.workspaceId === runtime.graph.workspace.workspaceId
|
|
@@ -193,18 +275,27 @@ async function defineValue(namespace, configPath, rawValue, options = {}) {
|
|
|
193
275
|
if (target === "global" && !runtime.manifest.workspaces.global.allowWrite) {
|
|
194
276
|
throw new Error("Global writes require workspaces.global.allowWrite: true");
|
|
195
277
|
}
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
278
|
+
return workspaceRoot.path;
|
|
279
|
+
}
|
|
280
|
+
async function defineValue(namespace, configPath, rawValue, options = {}) {
|
|
281
|
+
if (namespace === "secret") {
|
|
282
|
+
const secret = await setSecret(configPath, rawValue, {
|
|
283
|
+
...options,
|
|
284
|
+
mode: options.mode ?? "local"
|
|
285
|
+
});
|
|
286
|
+
return {
|
|
287
|
+
filePath: secret.filePath,
|
|
288
|
+
value: {
|
|
289
|
+
provider: secret.provider,
|
|
290
|
+
ref: secret.ref
|
|
291
|
+
}
|
|
292
|
+
};
|
|
207
293
|
}
|
|
294
|
+
const runtime = await createRuntimeService(options);
|
|
295
|
+
const workspaceRoot = getSelectedWorkspaceRoot(options, runtime);
|
|
296
|
+
const profile = options.profile ?? runtime.graph.profile;
|
|
297
|
+
const filePath = resolveConfigDocumentPath(workspaceRoot, namespace, configPath, profile);
|
|
298
|
+
const document = await readYamlDocument(filePath);
|
|
208
299
|
const parsedValue = parseScalarValue(rawValue);
|
|
209
300
|
setNestedValue(document, configPath.split("."), parsedValue);
|
|
210
301
|
await mkdir(path.dirname(filePath), { recursive: true });
|
|
@@ -214,15 +305,118 @@ async function defineValue(namespace, configPath, rawValue, options = {}) {
|
|
|
214
305
|
value: parsedValue
|
|
215
306
|
};
|
|
216
307
|
}
|
|
308
|
+
async function setSecret(configPath, rawValue, options = {}) {
|
|
309
|
+
const runtime = await createRuntimeService(options);
|
|
310
|
+
const workspaceRoot = getSelectedWorkspaceRoot(options, runtime);
|
|
311
|
+
const profile = options.profile ?? runtime.graph.profile;
|
|
312
|
+
const filePath = resolveConfigDocumentPath(workspaceRoot, "secret", configPath, profile);
|
|
313
|
+
const document = await readYamlDocument(filePath);
|
|
314
|
+
const mode = options.mode ?? "local";
|
|
315
|
+
let reference;
|
|
316
|
+
let storePath;
|
|
317
|
+
if (mode === "local") {
|
|
318
|
+
const passphrase = options.passphrase ?? options.processEnv?.CNOS_SECRET_PASSPHRASE ?? process.env.CNOS_SECRET_PASSPHRASE;
|
|
319
|
+
if (!passphrase) {
|
|
320
|
+
throw new Error("Local secret writes require --passphrase or CNOS_SECRET_PASSPHRASE");
|
|
321
|
+
}
|
|
322
|
+
const profileSegment = profile && profile !== "base" ? profile : "base";
|
|
323
|
+
const ref = [
|
|
324
|
+
runtime.manifest.project.name,
|
|
325
|
+
runtime.graph.workspace.workspaceId,
|
|
326
|
+
profileSegment,
|
|
327
|
+
...configPath.split("."),
|
|
328
|
+
randomUUID()
|
|
329
|
+
].map((segment) => segment.replace(/[^A-Za-z0-9._-]+/g, "-")).join("/");
|
|
330
|
+
storePath = await writeLocalSecret(resolveSecretStoreRoot(options.processEnv), ref, rawValue, passphrase);
|
|
331
|
+
reference = {
|
|
332
|
+
provider: "local",
|
|
333
|
+
ref
|
|
334
|
+
};
|
|
335
|
+
} else {
|
|
336
|
+
reference = {
|
|
337
|
+
provider: options.provider ?? (mode === "ref" ? "ref" : "remote"),
|
|
338
|
+
ref: rawValue
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
setNestedValue(document, configPath.split("."), reference);
|
|
342
|
+
await mkdir(path.dirname(filePath), { recursive: true });
|
|
343
|
+
await writeFile(filePath, stringifyYaml(document), "utf8");
|
|
344
|
+
return {
|
|
345
|
+
filePath,
|
|
346
|
+
provider: reference.provider,
|
|
347
|
+
ref: reference.ref,
|
|
348
|
+
...storePath ? { storePath } : {}
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
async function deleteSecret(configPath, options = {}) {
|
|
352
|
+
const runtime = await createRuntimeService(options);
|
|
353
|
+
const workspaceRoot = getSelectedWorkspaceRoot(options, runtime);
|
|
354
|
+
const profile = options.profile ?? runtime.graph.profile;
|
|
355
|
+
const filePath = resolveConfigDocumentPath(workspaceRoot, "secret", configPath, profile);
|
|
356
|
+
const document = await readYamlDocument(filePath);
|
|
357
|
+
const metadata = runtime.graph.entries.get(`secret.${configPath}`)?.winner.metadata;
|
|
358
|
+
const deleted = unsetNestedValue(document, configPath.split("."));
|
|
359
|
+
if (!deleted) {
|
|
360
|
+
return {
|
|
361
|
+
filePath,
|
|
362
|
+
deleted: false
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
await writeFile(filePath, stringifyYaml(document), "utf8");
|
|
366
|
+
let removedStore;
|
|
367
|
+
const secretRef = metadata?.secretRef;
|
|
368
|
+
if (isSecretReference(secretRef) && secretRef.provider === "local") {
|
|
369
|
+
const storePath = path.join(resolveSecretStoreRoot(options.processEnv), "store", ...secretRef.ref.split("/")).concat(".json");
|
|
370
|
+
await rm(storePath, { force: true });
|
|
371
|
+
removedStore = storePath;
|
|
372
|
+
}
|
|
373
|
+
return {
|
|
374
|
+
filePath,
|
|
375
|
+
deleted: true,
|
|
376
|
+
...removedStore ? { removedStore } : {}
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
async function deleteValue(namespace, configPath, options = {}) {
|
|
380
|
+
if (namespace === "secret") {
|
|
381
|
+
return deleteSecret(configPath, options);
|
|
382
|
+
}
|
|
383
|
+
const runtime = await createRuntimeService(options);
|
|
384
|
+
const workspaceRoot = getSelectedWorkspaceRoot(options, runtime);
|
|
385
|
+
const profile = options.profile ?? runtime.graph.profile;
|
|
386
|
+
const filePath = resolveConfigDocumentPath(workspaceRoot, namespace, configPath, profile);
|
|
387
|
+
const document = await readYamlDocument(filePath);
|
|
388
|
+
const deleted = unsetNestedValue(document, configPath.split("."));
|
|
389
|
+
if (!deleted) {
|
|
390
|
+
return {
|
|
391
|
+
filePath,
|
|
392
|
+
deleted: false
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
await writeFile(filePath, stringifyYaml(document), "utf8");
|
|
396
|
+
return {
|
|
397
|
+
filePath,
|
|
398
|
+
deleted: true
|
|
399
|
+
};
|
|
400
|
+
}
|
|
217
401
|
|
|
218
402
|
// src/commands/define.ts
|
|
219
403
|
async function runDefine(namespace, configPath, rawValue, options = {}) {
|
|
220
404
|
const cliArgs = [...options.cliArgs ?? []];
|
|
221
405
|
const target = consumeOption(cliArgs, "--target") ?? "local";
|
|
406
|
+
const local = consumeFlag(cliArgs, "--local");
|
|
407
|
+
const remote = consumeFlag(cliArgs, "--remote");
|
|
408
|
+
const ref = consumeFlag(cliArgs, "--ref");
|
|
409
|
+
const provider = consumeOption(cliArgs, "--provider");
|
|
410
|
+
const passphrase = consumeOption(cliArgs, "--passphrase");
|
|
222
411
|
const result = await defineValue(namespace, configPath, rawValue, {
|
|
223
412
|
...options,
|
|
224
413
|
cliArgs,
|
|
225
|
-
target
|
|
414
|
+
target,
|
|
415
|
+
...namespace === "secret" ? {
|
|
416
|
+
mode: local ? "local" : remote ? "remote" : ref ? "ref" : "local",
|
|
417
|
+
...provider ? { provider } : {},
|
|
418
|
+
...passphrase ? { passphrase } : {}
|
|
419
|
+
} : {}
|
|
226
420
|
});
|
|
227
421
|
if (options.json) {
|
|
228
422
|
return printJson({
|
|
@@ -237,7 +431,7 @@ async function runDefine(namespace, configPath, rawValue, options = {}) {
|
|
|
237
431
|
}
|
|
238
432
|
|
|
239
433
|
// src/commands/diff.ts
|
|
240
|
-
import { flattenObject } from "@kitsy/cnos
|
|
434
|
+
import { flattenObject } from "@kitsy/cnos/internal";
|
|
241
435
|
function flattenRuntime(runtime) {
|
|
242
436
|
return {
|
|
243
437
|
...Object.fromEntries(
|
|
@@ -278,7 +472,7 @@ import { readFile as readFile2 } from "fs/promises";
|
|
|
278
472
|
import path2 from "path";
|
|
279
473
|
|
|
280
474
|
// src/services/validation.ts
|
|
281
|
-
import { validateRuntime } from "@kitsy/cnos
|
|
475
|
+
import { validateRuntime } from "@kitsy/cnos/internal";
|
|
282
476
|
async function createValidationSummary(options = {}) {
|
|
283
477
|
const runtime = await createRuntimeService(options);
|
|
284
478
|
const summary = await validateRuntime(runtime);
|
|
@@ -292,11 +486,14 @@ async function createValidationSummary(options = {}) {
|
|
|
292
486
|
async function checkGitignore(root) {
|
|
293
487
|
const gitignorePath = path2.join(root, ".gitignore");
|
|
294
488
|
const expected = [
|
|
295
|
-
"cnos/
|
|
296
|
-
"cnos/
|
|
297
|
-
"cnos/
|
|
298
|
-
"
|
|
299
|
-
"
|
|
489
|
+
".cnos/env/.env",
|
|
490
|
+
".cnos/env/.env.*",
|
|
491
|
+
"!.cnos/env/.env.example",
|
|
492
|
+
"!.cnos/env/.env.*.example",
|
|
493
|
+
".cnos/workspaces/*/env/.env",
|
|
494
|
+
".cnos/workspaces/*/env/.env.*",
|
|
495
|
+
"!.cnos/workspaces/*/env/.env.example",
|
|
496
|
+
"!.cnos/workspaces/*/env/.env.*.example"
|
|
300
497
|
];
|
|
301
498
|
try {
|
|
302
499
|
const content = await readFile2(gitignorePath, "utf8");
|
|
@@ -448,14 +645,14 @@ var COMMANDS = [
|
|
|
448
645
|
id: "init",
|
|
449
646
|
summary: "Scaffold a workspace-aware CNOS tree in the current project.",
|
|
450
647
|
usage: "cnos init [--workspace <id>] [--root <path>] [--json]",
|
|
451
|
-
description: "Creates cnos/cnos.yml, .cnos-workspace.yml,
|
|
648
|
+
description: "Creates .cnos/cnos.yml, optional .cnos-workspace.yml, config folders, and .gitignore entries without overwriting existing files.",
|
|
452
649
|
examples: ["cnos init", "cnos init --workspace api", "cnos init --root ./apps/api --workspace api --json"]
|
|
453
650
|
},
|
|
454
651
|
{
|
|
455
652
|
id: "onboard",
|
|
456
653
|
summary: "Onboard an existing repo into CNOS and import root dotenv files.",
|
|
457
654
|
usage: "cnos onboard [--workspace <id>] [--root <path>] [--move] [--json]",
|
|
458
|
-
description: "Scaffolds the CNOS workspace tree and imports root-level .env, .env.<profile>, and .env.*.example files into cnos/workspaces/<workspace>/env.",
|
|
655
|
+
description: "Scaffolds the CNOS workspace tree and imports root-level .env, .env.<profile>, and .env.*.example files into .cnos/workspaces/<workspace>/env.",
|
|
459
656
|
options: [
|
|
460
657
|
{
|
|
461
658
|
flag: "--move",
|
|
@@ -480,37 +677,98 @@ var COMMANDS = [
|
|
|
480
677
|
},
|
|
481
678
|
{
|
|
482
679
|
id: "value",
|
|
483
|
-
summary: "Read
|
|
484
|
-
usage: "cnos value <path> [global-options]",
|
|
485
|
-
description: "Reads value.<path> from the selected workspace and profile.",
|
|
680
|
+
summary: "Read, write, list, and delete values.",
|
|
681
|
+
usage: "cnos value [get <path> | set <path> <value> | list | delete <path>] [options] [global-options]",
|
|
682
|
+
description: "Reads and manages value.<path> entries from the selected workspace and profile.",
|
|
486
683
|
arguments: [
|
|
487
684
|
{
|
|
488
685
|
name: "path",
|
|
489
|
-
description: "Value path without the value. namespace prefix."
|
|
490
|
-
|
|
686
|
+
description: "Value path without the value. namespace prefix."
|
|
687
|
+
}
|
|
688
|
+
],
|
|
689
|
+
options: [
|
|
690
|
+
{
|
|
691
|
+
flag: "--target <local|global>",
|
|
692
|
+
description: "Choose whether writes land in the local project workspace or the configured global root."
|
|
693
|
+
},
|
|
694
|
+
{
|
|
695
|
+
flag: "--prefix <path>",
|
|
696
|
+
description: "Filter value list output to keys that begin with this logical path or key prefix."
|
|
491
697
|
}
|
|
492
698
|
],
|
|
493
|
-
examples: [
|
|
699
|
+
examples: [
|
|
700
|
+
"cnos value app.name",
|
|
701
|
+
"cnos value set server.port 3000",
|
|
702
|
+
"cnos add value app.name demo",
|
|
703
|
+
"cnos value list --prefix app."
|
|
704
|
+
]
|
|
705
|
+
},
|
|
706
|
+
{
|
|
707
|
+
id: "value set",
|
|
708
|
+
summary: "Write a value.",
|
|
709
|
+
usage: "cnos value set <path> <value> [--target <local|global>] [global-options]",
|
|
710
|
+
description: "Writes a deterministic value document into the selected workspace or explicit global target.",
|
|
711
|
+
examples: ["cnos value set app.name demo", "cnos add value server.port 3000 --target global"]
|
|
712
|
+
},
|
|
713
|
+
{
|
|
714
|
+
id: "value list",
|
|
715
|
+
summary: "List resolved values.",
|
|
716
|
+
usage: "cnos value list [--prefix <path>] [global-options]",
|
|
717
|
+
description: "Lists resolved value keys for the selected workspace and profile.",
|
|
718
|
+
examples: ["cnos value list", "cnos value list --prefix app."]
|
|
719
|
+
},
|
|
720
|
+
{
|
|
721
|
+
id: "value delete",
|
|
722
|
+
summary: "Delete a value entry.",
|
|
723
|
+
usage: "cnos value delete <path> [--target <local|global>] [global-options]",
|
|
724
|
+
description: "Deletes a value from the selected workspace or explicit global target.",
|
|
725
|
+
examples: ["cnos value delete app.name", "cnos remove value server.port"]
|
|
494
726
|
},
|
|
495
727
|
{
|
|
496
728
|
id: "secret",
|
|
497
|
-
summary: "Read
|
|
498
|
-
usage: "cnos secret <path> [global-options]",
|
|
499
|
-
description: "Reads secret
|
|
729
|
+
summary: "Read, write, list, and delete secrets.",
|
|
730
|
+
usage: "cnos secret [get <path> | set <path> <value> | list | delete <path>] [options] [global-options]",
|
|
731
|
+
description: "Reads resolved secrets, writes secret refs plus external local-secret material, and manages secret entries for the selected workspace and profile.",
|
|
500
732
|
arguments: [
|
|
501
733
|
{
|
|
502
734
|
name: "path",
|
|
503
|
-
description: "Secret path without the secret. namespace prefix."
|
|
504
|
-
required: true
|
|
735
|
+
description: "Secret path without the secret. namespace prefix."
|
|
505
736
|
}
|
|
506
737
|
],
|
|
507
|
-
|
|
738
|
+
options: [
|
|
739
|
+
{
|
|
740
|
+
flag: "--local",
|
|
741
|
+
description: "Store encrypted secret material under ~/.cnos/secrets and write a local ref into the repo."
|
|
742
|
+
},
|
|
743
|
+
{
|
|
744
|
+
flag: "--remote",
|
|
745
|
+
description: "Write a remote secret reference into the repo."
|
|
746
|
+
},
|
|
747
|
+
{
|
|
748
|
+
flag: "--ref",
|
|
749
|
+
description: "Write a generic secret reference into the repo."
|
|
750
|
+
},
|
|
751
|
+
{
|
|
752
|
+
flag: "--provider <name>",
|
|
753
|
+
description: "Provider name for --remote or --ref secret writes."
|
|
754
|
+
},
|
|
755
|
+
{
|
|
756
|
+
flag: "--passphrase <value>",
|
|
757
|
+
description: "Passphrase used to encrypt local secret material when --local is selected."
|
|
758
|
+
}
|
|
759
|
+
],
|
|
760
|
+
examples: [
|
|
761
|
+
"cnos secret app.token",
|
|
762
|
+
"cnos secret set app.token super-secret --local --passphrase dev-pass",
|
|
763
|
+
"cnos add secret app.token APP_TOKEN --ref --provider env",
|
|
764
|
+
"cnos secret list --workspace agents"
|
|
765
|
+
]
|
|
508
766
|
},
|
|
509
767
|
{
|
|
510
768
|
id: "define",
|
|
511
769
|
summary: "Write a value or secret into the selected workspace.",
|
|
512
770
|
usage: "cnos define <value|secret> <path> <rawValue> [--target <local|global>] [global-options]",
|
|
513
|
-
description: "Writes deterministic YAML into the selected workspace. Global writes require allowWrite and an explicit --target global flag.",
|
|
771
|
+
description: "Writes deterministic YAML into the selected workspace. Secret writes default to secure local-secret storage plus a repo ref. Global writes require allowWrite and an explicit --target global flag.",
|
|
514
772
|
arguments: [
|
|
515
773
|
{
|
|
516
774
|
name: "namespace",
|
|
@@ -536,9 +794,99 @@ var COMMANDS = [
|
|
|
536
794
|
],
|
|
537
795
|
examples: [
|
|
538
796
|
"cnos define value server.port 3000 --workspace api",
|
|
539
|
-
"cnos define secret app.token super-secret --workspace api
|
|
797
|
+
"cnos define secret app.token super-secret --workspace api"
|
|
540
798
|
]
|
|
541
799
|
},
|
|
800
|
+
{
|
|
801
|
+
id: "use",
|
|
802
|
+
summary: "Persist repo-local CLI defaults such as workspace and profile.",
|
|
803
|
+
usage: "cnos use [--workspace <id>] [--profile <name>] [--global-root <path>] [--root <path>] [--json]",
|
|
804
|
+
description: "Writes .cnos-workspace.yml so future CLI invocations can omit repeated workspace/profile/global-root flags.",
|
|
805
|
+
examples: ["cnos use --workspace api --profile stage", "cnos use --global-root ~/.cnos"]
|
|
806
|
+
},
|
|
807
|
+
{
|
|
808
|
+
id: "list",
|
|
809
|
+
summary: "List resolved config entries.",
|
|
810
|
+
usage: "cnos list [value|secret|meta|all] [--prefix <path>] [global-options]",
|
|
811
|
+
description: "Lists resolved config entries across one namespace or the full effective graph, with optional key-prefix filtering.",
|
|
812
|
+
options: [
|
|
813
|
+
{
|
|
814
|
+
flag: "--namespace <value|secret|meta|all>",
|
|
815
|
+
description: "Explicit namespace selector when not using a positional namespace argument."
|
|
816
|
+
},
|
|
817
|
+
{
|
|
818
|
+
flag: "--prefix <path>",
|
|
819
|
+
description: "Filter list output to entries whose logical keys begin with this prefix."
|
|
820
|
+
}
|
|
821
|
+
],
|
|
822
|
+
examples: ["cnos list", "cnos list value --prefix app.", "cnos list --namespace secret"]
|
|
823
|
+
},
|
|
824
|
+
{
|
|
825
|
+
id: "profile",
|
|
826
|
+
summary: "Manage CNOS profiles.",
|
|
827
|
+
usage: "cnos profile [create <name> | list | use <name> | delete <name>] [options] [global-options]",
|
|
828
|
+
description: "Creates and lists explicit profiles and stores the active repo-local profile selection for CLI usage.",
|
|
829
|
+
options: [
|
|
830
|
+
{
|
|
831
|
+
flag: "--inherit <name>",
|
|
832
|
+
description: "Parent profile to extend when creating a profile."
|
|
833
|
+
}
|
|
834
|
+
],
|
|
835
|
+
examples: [
|
|
836
|
+
"cnos profile create stage --inherit base",
|
|
837
|
+
"cnos profile list",
|
|
838
|
+
"cnos profile use stage"
|
|
839
|
+
]
|
|
840
|
+
},
|
|
841
|
+
{
|
|
842
|
+
id: "profile create",
|
|
843
|
+
summary: "Create a profile definition.",
|
|
844
|
+
usage: "cnos profile create <name> [--inherit <name>] [--root <path>] [--json]",
|
|
845
|
+
description: "Creates .cnos/profiles/<name>.yml for an explicit profile overlay.",
|
|
846
|
+
examples: ["cnos profile create stage --inherit base"]
|
|
847
|
+
},
|
|
848
|
+
{
|
|
849
|
+
id: "profile list",
|
|
850
|
+
summary: "List available profiles.",
|
|
851
|
+
usage: "cnos profile list [--root <path>] [--json]",
|
|
852
|
+
description: "Lists the base profile plus any explicit profile definition files in .cnos/profiles.",
|
|
853
|
+
examples: ["cnos profile list"]
|
|
854
|
+
},
|
|
855
|
+
{
|
|
856
|
+
id: "profile use",
|
|
857
|
+
summary: "Persist the active profile for this repo.",
|
|
858
|
+
usage: "cnos profile use <name> [--root <path>] [--json]",
|
|
859
|
+
description: "Writes the selected profile into .cnos-workspace.yml.",
|
|
860
|
+
examples: ["cnos profile use stage"]
|
|
861
|
+
},
|
|
862
|
+
{
|
|
863
|
+
id: "profile delete",
|
|
864
|
+
summary: "Delete a profile definition.",
|
|
865
|
+
usage: "cnos profile delete <name> [--root <path>] [--json]",
|
|
866
|
+
description: "Deletes .cnos/profiles/<name>.yml.",
|
|
867
|
+
examples: ["cnos profile delete stage"]
|
|
868
|
+
},
|
|
869
|
+
{
|
|
870
|
+
id: "secret set",
|
|
871
|
+
summary: "Write a secret securely.",
|
|
872
|
+
usage: "cnos secret set <path> <value> [--local|--remote|--ref] [--provider <name>] [--passphrase <value>] [global-options]",
|
|
873
|
+
description: "Writes a secret reference into the repo and, when --local is used, stores encrypted secret material outside the repo under ~/.cnos/secrets.",
|
|
874
|
+
examples: ["cnos secret set app.token super-secret --local --passphrase dev-pass"]
|
|
875
|
+
},
|
|
876
|
+
{
|
|
877
|
+
id: "secret list",
|
|
878
|
+
summary: "List resolved secrets.",
|
|
879
|
+
usage: "cnos secret list [global-options]",
|
|
880
|
+
description: "Lists resolved secret keys for the selected workspace and profile.",
|
|
881
|
+
examples: ["cnos secret list --workspace api"]
|
|
882
|
+
},
|
|
883
|
+
{
|
|
884
|
+
id: "secret delete",
|
|
885
|
+
summary: "Delete a secret reference.",
|
|
886
|
+
usage: "cnos secret delete <path> [--target <local|global>] [global-options]",
|
|
887
|
+
description: "Deletes the secret reference from the repo and removes local encrypted material when the secret used the local provider.",
|
|
888
|
+
examples: ["cnos secret delete app.token"]
|
|
889
|
+
},
|
|
542
890
|
{
|
|
543
891
|
id: "inspect",
|
|
544
892
|
summary: "Inspect the winning value and provenance for a key.",
|
|
@@ -572,7 +920,11 @@ var COMMANDS = [
|
|
|
572
920
|
required: true
|
|
573
921
|
}
|
|
574
922
|
],
|
|
575
|
-
examples: [
|
|
923
|
+
examples: [
|
|
924
|
+
"cnos export env",
|
|
925
|
+
"cnos export env --public --framework vite --workspace api",
|
|
926
|
+
"cnos export env --public --framework next --workspace webapp"
|
|
927
|
+
]
|
|
576
928
|
},
|
|
577
929
|
{
|
|
578
930
|
id: "export env",
|
|
@@ -586,7 +938,7 @@ var COMMANDS = [
|
|
|
586
938
|
},
|
|
587
939
|
{
|
|
588
940
|
flag: "--framework <name>",
|
|
589
|
-
description: "Apply framework-specific public env conventions such as vite or
|
|
941
|
+
description: "Apply framework-specific public env conventions such as vite, next, or nuxt."
|
|
590
942
|
},
|
|
591
943
|
{
|
|
592
944
|
flag: "--prefix <prefix>",
|
|
@@ -678,6 +1030,24 @@ var COMMANDS = [
|
|
|
678
1030
|
examples: ["cnos help-ai --format json", "cnos help-ai export env --format json"]
|
|
679
1031
|
}
|
|
680
1032
|
];
|
|
1033
|
+
var INTEGRATIONS = [
|
|
1034
|
+
{
|
|
1035
|
+
id: "vite",
|
|
1036
|
+
packageName: "@kitsy/cnos-vite",
|
|
1037
|
+
entrypoint: "@kitsy/cnos-vite",
|
|
1038
|
+
summary: "Inject CNOS public env into Vite define replacements and import.meta.env.",
|
|
1039
|
+
usage: 'import { createCnosVitePlugin } from "@kitsy/cnos-vite"',
|
|
1040
|
+
examples: ["cnos export env --public --framework vite", "vite.config.ts -> plugins: [createCnosVitePlugin()]"]
|
|
1041
|
+
},
|
|
1042
|
+
{
|
|
1043
|
+
id: "next",
|
|
1044
|
+
packageName: "@kitsy/cnos-next",
|
|
1045
|
+
entrypoint: "@kitsy/cnos-next",
|
|
1046
|
+
summary: "Merge CNOS public env into next.config.* using the NEXT_PUBLIC_ convention.",
|
|
1047
|
+
usage: 'import { withCnosNext } from "@kitsy/cnos-next"',
|
|
1048
|
+
examples: ["cnos export env --public --framework next", "next.config.mjs -> export default withCnosNext({})"]
|
|
1049
|
+
}
|
|
1050
|
+
];
|
|
681
1051
|
var HELP_DOCUMENT = {
|
|
682
1052
|
name: "cnos",
|
|
683
1053
|
summary: "Workspace-aware configuration runtime and CLI for local, global, and promoted environment data.",
|
|
@@ -685,7 +1055,14 @@ var HELP_DOCUMENT = {
|
|
|
685
1055
|
description: "CNOS resolves one active workspace per invocation, layers local and optional global config roots, and exposes read, write, export, dump, validation, and diagnostics commands.",
|
|
686
1056
|
globalOptions: GLOBAL_OPTIONS,
|
|
687
1057
|
commands: COMMANDS,
|
|
688
|
-
|
|
1058
|
+
integrations: INTEGRATIONS,
|
|
1059
|
+
examples: [
|
|
1060
|
+
"cnos use --profile stage",
|
|
1061
|
+
"cnos doctor --workspace api",
|
|
1062
|
+
"cnos export env --public --framework vite",
|
|
1063
|
+
"cnos export env --public --framework next",
|
|
1064
|
+
"cnos help-ai --format json"
|
|
1065
|
+
]
|
|
689
1066
|
};
|
|
690
1067
|
function normalizeHelpTopic(parts) {
|
|
691
1068
|
const cleaned = parts.filter((part) => part.length > 0);
|
|
@@ -739,6 +1116,11 @@ function formatRootHelp() {
|
|
|
739
1116
|
"Commands",
|
|
740
1117
|
...HELP_DOCUMENT.commands.filter((command) => !command.id.includes(" ")).map((command) => ` ${command.id.padEnd(12, " ")} ${command.summary}`),
|
|
741
1118
|
"",
|
|
1119
|
+
"Framework integrations",
|
|
1120
|
+
...HELP_DOCUMENT.integrations.map(
|
|
1121
|
+
(integration) => ` ${integration.id.padEnd(12, " ")} ${integration.packageName} via ${integration.entrypoint}`
|
|
1122
|
+
),
|
|
1123
|
+
"",
|
|
742
1124
|
...formatOptions("Global options", HELP_DOCUMENT.globalOptions),
|
|
743
1125
|
"",
|
|
744
1126
|
"Examples",
|
|
@@ -766,6 +1148,7 @@ function runHelpAi(topic, cliArgs = []) {
|
|
|
766
1148
|
cli: HELP_DOCUMENT.name,
|
|
767
1149
|
summary: HELP_DOCUMENT.summary,
|
|
768
1150
|
globalOptions: HELP_DOCUMENT.globalOptions,
|
|
1151
|
+
integrations: HELP_DOCUMENT.integrations,
|
|
769
1152
|
command
|
|
770
1153
|
} : {
|
|
771
1154
|
cli: HELP_DOCUMENT.name,
|
|
@@ -773,7 +1156,8 @@ function runHelpAi(topic, cliArgs = []) {
|
|
|
773
1156
|
usage: HELP_DOCUMENT.usage,
|
|
774
1157
|
description: HELP_DOCUMENT.description,
|
|
775
1158
|
globalOptions: HELP_DOCUMENT.globalOptions,
|
|
776
|
-
commands: HELP_DOCUMENT.commands
|
|
1159
|
+
commands: HELP_DOCUMENT.commands,
|
|
1160
|
+
integrations: HELP_DOCUMENT.integrations
|
|
777
1161
|
};
|
|
778
1162
|
if (topic && !command) {
|
|
779
1163
|
throw new Error(`Unknown help topic: ${topic}`);
|
|
@@ -794,25 +1178,32 @@ import path4 from "path";
|
|
|
794
1178
|
import { mkdir as mkdir2, readFile as readFile3, writeFile as writeFile2 } from "fs/promises";
|
|
795
1179
|
import path3 from "path";
|
|
796
1180
|
function scaffoldManifest(projectName, workspace) {
|
|
797
|
-
|
|
1181
|
+
const lines = [
|
|
798
1182
|
"version: 1",
|
|
799
1183
|
"project:",
|
|
800
1184
|
` name: ${projectName}`,
|
|
801
|
-
"workspaces:",
|
|
802
|
-
` default: ${workspace}`,
|
|
803
|
-
" global:",
|
|
804
|
-
" enabled: false",
|
|
805
|
-
" allowWrite: false",
|
|
806
|
-
" items:",
|
|
807
|
-
` ${workspace}: {}`,
|
|
808
1185
|
"profiles:",
|
|
809
|
-
" default:
|
|
1186
|
+
" default: base",
|
|
810
1187
|
"envMapping:",
|
|
811
1188
|
" convention: SCREAMING_SNAKE",
|
|
812
1189
|
"public:",
|
|
813
1190
|
" promote: []",
|
|
814
1191
|
""
|
|
815
|
-
]
|
|
1192
|
+
];
|
|
1193
|
+
if (workspace) {
|
|
1194
|
+
lines.splice(
|
|
1195
|
+
4,
|
|
1196
|
+
0,
|
|
1197
|
+
"workspaces:",
|
|
1198
|
+
` default: ${workspace}`,
|
|
1199
|
+
" global:",
|
|
1200
|
+
" enabled: false",
|
|
1201
|
+
" allowWrite: false",
|
|
1202
|
+
" items:",
|
|
1203
|
+
` ${workspace}: {}`
|
|
1204
|
+
);
|
|
1205
|
+
}
|
|
1206
|
+
return lines.join("\n");
|
|
816
1207
|
}
|
|
817
1208
|
async function ensureFile(filePath, content) {
|
|
818
1209
|
try {
|
|
@@ -826,11 +1217,14 @@ async function ensureFile(filePath, content) {
|
|
|
826
1217
|
async function ensureGitignore(root) {
|
|
827
1218
|
const gitignorePath = path3.join(root, ".gitignore");
|
|
828
1219
|
const requiredEntries = [
|
|
829
|
-
"cnos/
|
|
830
|
-
"cnos/
|
|
831
|
-
"cnos/
|
|
832
|
-
"
|
|
833
|
-
"
|
|
1220
|
+
".cnos/env/.env",
|
|
1221
|
+
".cnos/env/.env.*",
|
|
1222
|
+
"!.cnos/env/.env.example",
|
|
1223
|
+
"!.cnos/env/.env.*.example",
|
|
1224
|
+
".cnos/workspaces/*/env/.env",
|
|
1225
|
+
".cnos/workspaces/*/env/.env.*",
|
|
1226
|
+
"!.cnos/workspaces/*/env/.env.example",
|
|
1227
|
+
"!.cnos/workspaces/*/env/.env.*.example"
|
|
834
1228
|
];
|
|
835
1229
|
let current = "";
|
|
836
1230
|
try {
|
|
@@ -849,28 +1243,34 @@ async function ensureGitignore(root) {
|
|
|
849
1243
|
return true;
|
|
850
1244
|
}
|
|
851
1245
|
async function scaffoldWorkspace(root, workspace) {
|
|
852
|
-
const cnosRoot = path3.join(root, "cnos");
|
|
853
|
-
const workspaceRoot = path3.join(cnosRoot, "workspaces", workspace);
|
|
1246
|
+
const cnosRoot = path3.join(root, ".cnos");
|
|
1247
|
+
const workspaceRoot = workspace ? path3.join(cnosRoot, "workspaces", workspace) : cnosRoot;
|
|
854
1248
|
const createdPaths = [];
|
|
855
1249
|
await mkdir2(path3.join(workspaceRoot, "profiles"), { recursive: true });
|
|
856
|
-
await mkdir2(path3.join(workspaceRoot, "values"
|
|
857
|
-
await mkdir2(path3.join(workspaceRoot, "secrets"
|
|
1250
|
+
await mkdir2(path3.join(workspaceRoot, "values"), { recursive: true });
|
|
1251
|
+
await mkdir2(path3.join(workspaceRoot, "secrets"), { recursive: true });
|
|
858
1252
|
await mkdir2(path3.join(workspaceRoot, "env"), { recursive: true });
|
|
859
|
-
|
|
1253
|
+
const relativePaths = workspace ? [
|
|
860
1254
|
["workspaces", workspace, "profiles", ".gitkeep"],
|
|
861
|
-
["workspaces", workspace, "values", "
|
|
862
|
-
["workspaces", workspace, "secrets", "
|
|
1255
|
+
["workspaces", workspace, "values", ".gitkeep"],
|
|
1256
|
+
["workspaces", workspace, "secrets", ".gitkeep"],
|
|
863
1257
|
["workspaces", workspace, "env", ".gitkeep"]
|
|
864
|
-
]
|
|
1258
|
+
] : [
|
|
1259
|
+
["profiles", ".gitkeep"],
|
|
1260
|
+
["values", ".gitkeep"],
|
|
1261
|
+
["secrets", ".gitkeep"],
|
|
1262
|
+
["env", ".gitkeep"]
|
|
1263
|
+
];
|
|
1264
|
+
for (const relativePath of relativePaths) {
|
|
865
1265
|
const filePath = path3.join(cnosRoot, ...relativePath);
|
|
866
1266
|
if (await ensureFile(filePath, "")) {
|
|
867
1267
|
createdPaths.push(path3.relative(root, filePath).replace(/\\/g, "/"));
|
|
868
1268
|
}
|
|
869
1269
|
}
|
|
870
1270
|
if (await ensureFile(path3.join(cnosRoot, "cnos.yml"), scaffoldManifest(path3.basename(root), workspace))) {
|
|
871
|
-
createdPaths.push("cnos/cnos.yml");
|
|
1271
|
+
createdPaths.push(".cnos/cnos.yml");
|
|
872
1272
|
}
|
|
873
|
-
if (await ensureFile(path3.join(root, ".cnos-workspace.yml"), `workspace: ${workspace}
|
|
1273
|
+
if (workspace && await ensureFile(path3.join(root, ".cnos-workspace.yml"), `workspace: ${workspace}
|
|
874
1274
|
globalRoot: ~/.cnos
|
|
875
1275
|
`)) {
|
|
876
1276
|
createdPaths.push(".cnos-workspace.yml");
|
|
@@ -880,7 +1280,7 @@ globalRoot: ~/.cnos
|
|
|
880
1280
|
}
|
|
881
1281
|
return {
|
|
882
1282
|
root,
|
|
883
|
-
workspace,
|
|
1283
|
+
...workspace ? { workspace } : {},
|
|
884
1284
|
created: createdPaths
|
|
885
1285
|
};
|
|
886
1286
|
}
|
|
@@ -888,12 +1288,14 @@ globalRoot: ~/.cnos
|
|
|
888
1288
|
// src/commands/init.ts
|
|
889
1289
|
async function runInit(options = {}) {
|
|
890
1290
|
const root = path4.resolve(options.root ?? process.cwd());
|
|
891
|
-
const
|
|
892
|
-
const result = await scaffoldWorkspace(root, workspace);
|
|
1291
|
+
const result = await scaffoldWorkspace(root, options.workspace);
|
|
893
1292
|
if (options.json) {
|
|
894
1293
|
return printJson(result);
|
|
895
1294
|
}
|
|
896
|
-
|
|
1295
|
+
if (result.workspace) {
|
|
1296
|
+
return `initialized CNOS workspace ${result.workspace} at ${root}`;
|
|
1297
|
+
}
|
|
1298
|
+
return `initialized CNOS project at ${root}`;
|
|
897
1299
|
}
|
|
898
1300
|
|
|
899
1301
|
// src/format/printInspect.ts
|
|
@@ -928,8 +1330,76 @@ async function runInspect(key, options = {}) {
|
|
|
928
1330
|
return printInspect(inspectResult);
|
|
929
1331
|
}
|
|
930
1332
|
|
|
1333
|
+
// src/format/printValue.ts
|
|
1334
|
+
function printValue(value, json = false) {
|
|
1335
|
+
if (json) {
|
|
1336
|
+
return printJson(value);
|
|
1337
|
+
}
|
|
1338
|
+
if (typeof value === "string") {
|
|
1339
|
+
return value;
|
|
1340
|
+
}
|
|
1341
|
+
if (value === void 0 || value === null || typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") {
|
|
1342
|
+
return String(value);
|
|
1343
|
+
}
|
|
1344
|
+
return printJson(value);
|
|
1345
|
+
}
|
|
1346
|
+
|
|
1347
|
+
// src/services/listing.ts
|
|
1348
|
+
import { flattenObject as flattenObject2 } from "@kitsy/cnos/internal";
|
|
1349
|
+
async function listConfigEntries(namespace, options = {}) {
|
|
1350
|
+
const runtime = await createRuntimeService(options);
|
|
1351
|
+
const namespaces = namespace === "all" ? ["value", "secret", "meta"] : [namespace];
|
|
1352
|
+
const entries = [];
|
|
1353
|
+
const prefix = options.prefix?.trim();
|
|
1354
|
+
for (const currentNamespace of namespaces) {
|
|
1355
|
+
const projected = flattenObject2(runtime.toNamespace(currentNamespace));
|
|
1356
|
+
for (const [path10, value] of Object.entries(projected)) {
|
|
1357
|
+
const key = `${currentNamespace}.${path10}`;
|
|
1358
|
+
if (prefix && !key.startsWith(prefix) && !path10.startsWith(prefix)) {
|
|
1359
|
+
continue;
|
|
1360
|
+
}
|
|
1361
|
+
entries.push({
|
|
1362
|
+
key,
|
|
1363
|
+
value
|
|
1364
|
+
});
|
|
1365
|
+
}
|
|
1366
|
+
}
|
|
1367
|
+
return entries.sort((left, right) => left.key.localeCompare(right.key));
|
|
1368
|
+
}
|
|
1369
|
+
|
|
1370
|
+
// src/commands/list.ts
|
|
1371
|
+
function normalizeNamespace(value) {
|
|
1372
|
+
if (!value || value === "all") {
|
|
1373
|
+
return "all";
|
|
1374
|
+
}
|
|
1375
|
+
if (value === "values" || value === "value") {
|
|
1376
|
+
return "value";
|
|
1377
|
+
}
|
|
1378
|
+
if (value === "secrets" || value === "secret") {
|
|
1379
|
+
return "secret";
|
|
1380
|
+
}
|
|
1381
|
+
if (value === "meta") {
|
|
1382
|
+
return "meta";
|
|
1383
|
+
}
|
|
1384
|
+
throw new Error(`Unsupported list namespace: ${value}`);
|
|
1385
|
+
}
|
|
1386
|
+
async function runList(args = [], options = {}) {
|
|
1387
|
+
const cliArgs = [...options.cliArgs ?? []];
|
|
1388
|
+
const namespace = normalizeNamespace(args[0] ?? consumeOption(cliArgs, "--namespace"));
|
|
1389
|
+
const prefix = consumeOption(cliArgs, "--prefix");
|
|
1390
|
+
const entries = await listConfigEntries(namespace, {
|
|
1391
|
+
...options,
|
|
1392
|
+
cliArgs,
|
|
1393
|
+
...prefix ? { prefix } : {}
|
|
1394
|
+
});
|
|
1395
|
+
if (options.json) {
|
|
1396
|
+
return printJson(entries);
|
|
1397
|
+
}
|
|
1398
|
+
return entries.map((entry) => `${entry.key}=${printValue(entry.value)}`).join("\n");
|
|
1399
|
+
}
|
|
1400
|
+
|
|
931
1401
|
// src/commands/onboard.ts
|
|
932
|
-
import { copyFile, readdir, rm } from "fs/promises";
|
|
1402
|
+
import { copyFile, readdir, rm as rm2 } from "fs/promises";
|
|
933
1403
|
import path5 from "path";
|
|
934
1404
|
var ROOT_ENV_FILE_PATTERN = /^\.env(?:\.[A-Za-z0-9_-]+)*(?:\.example)?$/;
|
|
935
1405
|
async function listRootEnvFiles(root) {
|
|
@@ -945,7 +1415,7 @@ async function runOnboard(options = {}) {
|
|
|
945
1415
|
throw new Error(`Unsupported onboard arguments: ${cliArgs.join(" ")}`);
|
|
946
1416
|
}
|
|
947
1417
|
const scaffold = await scaffoldWorkspace(root, workspace);
|
|
948
|
-
const envRoot = path5.join(root, "cnos", "workspaces", workspace, "env");
|
|
1418
|
+
const envRoot = path5.join(root, ".cnos", "workspaces", workspace, "env");
|
|
949
1419
|
const rootFiles = await listRootEnvFiles(root);
|
|
950
1420
|
const imported = [];
|
|
951
1421
|
const skipped = [];
|
|
@@ -956,7 +1426,7 @@ async function runOnboard(options = {}) {
|
|
|
956
1426
|
await copyFile(sourcePath, targetPath);
|
|
957
1427
|
imported.push(path5.relative(root, targetPath).replace(/\\/g, "/"));
|
|
958
1428
|
if (move) {
|
|
959
|
-
await
|
|
1429
|
+
await rm2(sourcePath);
|
|
960
1430
|
}
|
|
961
1431
|
} catch {
|
|
962
1432
|
skipped.push(fileName);
|
|
@@ -975,21 +1445,169 @@ async function runOnboard(options = {}) {
|
|
|
975
1445
|
}
|
|
976
1446
|
const importedCount = imported.length;
|
|
977
1447
|
const skippedSuffix = skipped.length > 0 ? ` (${skipped.length} skipped)` : "";
|
|
978
|
-
return `onboarded ${workspace} at ${root}; imported ${importedCount} root env files into cnos/workspaces/${workspace}/env using ${result.mode}${skippedSuffix}`;
|
|
1448
|
+
return `onboarded ${workspace} at ${root}; imported ${importedCount} root env files into .cnos/workspaces/${workspace}/env using ${result.mode}${skippedSuffix}`;
|
|
979
1449
|
}
|
|
980
1450
|
|
|
981
|
-
// src/
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
1451
|
+
// src/commands/profile.ts
|
|
1452
|
+
import path8 from "path";
|
|
1453
|
+
|
|
1454
|
+
// src/services/context.ts
|
|
1455
|
+
import { readFile as readFile4, writeFile as writeFile3 } from "fs/promises";
|
|
1456
|
+
import path6 from "path";
|
|
1457
|
+
import { parseYaml as parseYaml2, stringifyYaml as stringifyYaml2 } from "@kitsy/cnos/internal";
|
|
1458
|
+
async function loadCliContext(root = process.cwd()) {
|
|
1459
|
+
const filePath = path6.join(path6.resolve(root), ".cnos-workspace.yml");
|
|
1460
|
+
try {
|
|
1461
|
+
const source = await readFile4(filePath, "utf8");
|
|
1462
|
+
const parsed = parseYaml2(source);
|
|
1463
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
1464
|
+
return {};
|
|
1465
|
+
}
|
|
1466
|
+
return parsed;
|
|
1467
|
+
} catch {
|
|
1468
|
+
return {};
|
|
985
1469
|
}
|
|
986
|
-
|
|
987
|
-
|
|
1470
|
+
}
|
|
1471
|
+
async function saveCliContext(options = {}) {
|
|
1472
|
+
const root = path6.resolve(options.root ?? process.cwd());
|
|
1473
|
+
const filePath = path6.join(root, ".cnos-workspace.yml");
|
|
1474
|
+
const current = await loadCliContext(root);
|
|
1475
|
+
const next = {
|
|
1476
|
+
...current.workspace ? { workspace: current.workspace } : {},
|
|
1477
|
+
...current.profile ? { profile: current.profile } : {},
|
|
1478
|
+
...current.globalRoot ? { globalRoot: current.globalRoot } : {},
|
|
1479
|
+
...options.workspace ? { workspace: options.workspace } : {},
|
|
1480
|
+
...options.profile ? { profile: options.profile } : {},
|
|
1481
|
+
...options.globalRoot ? { globalRoot: options.globalRoot } : {}
|
|
1482
|
+
};
|
|
1483
|
+
await writeFile3(filePath, stringifyYaml2(next), "utf8");
|
|
1484
|
+
return {
|
|
1485
|
+
filePath,
|
|
1486
|
+
context: next
|
|
1487
|
+
};
|
|
1488
|
+
}
|
|
1489
|
+
|
|
1490
|
+
// src/services/profiles.ts
|
|
1491
|
+
import { mkdir as mkdir3, readdir as readdir2, readFile as readFile5, rm as rm3, writeFile as writeFile4 } from "fs/promises";
|
|
1492
|
+
import path7 from "path";
|
|
1493
|
+
import { parseYaml as parseYaml3, stringifyYaml as stringifyYaml3 } from "@kitsy/cnos/internal";
|
|
1494
|
+
async function createProfileDefinition(root = process.cwd(), profile, inherit) {
|
|
1495
|
+
const filePath = path7.join(path7.resolve(root), ".cnos", "profiles", `${profile}.yml`);
|
|
1496
|
+
await mkdir3(path7.dirname(filePath), { recursive: true });
|
|
1497
|
+
const document = inherit && inherit !== "base" ? {
|
|
1498
|
+
name: profile,
|
|
1499
|
+
extends: [inherit]
|
|
1500
|
+
} : {
|
|
1501
|
+
name: profile
|
|
1502
|
+
};
|
|
1503
|
+
await writeFile4(filePath, stringifyYaml3(document), "utf8");
|
|
1504
|
+
return {
|
|
1505
|
+
filePath,
|
|
1506
|
+
profile,
|
|
1507
|
+
...inherit ? { inherit } : {}
|
|
1508
|
+
};
|
|
1509
|
+
}
|
|
1510
|
+
async function listProfiles(root = process.cwd()) {
|
|
1511
|
+
const profilesRoot = path7.join(path7.resolve(root), ".cnos", "profiles");
|
|
1512
|
+
try {
|
|
1513
|
+
const entries = await readdir2(profilesRoot, { withFileTypes: true });
|
|
1514
|
+
const discovered = /* @__PURE__ */ new Set(["base"]);
|
|
1515
|
+
for (const entry of entries) {
|
|
1516
|
+
if (entry.isFile() && entry.name.endsWith(".yml")) {
|
|
1517
|
+
discovered.add(entry.name.replace(/\.yml$/, ""));
|
|
1518
|
+
}
|
|
1519
|
+
}
|
|
1520
|
+
return [...discovered].sort((left, right) => left.localeCompare(right));
|
|
1521
|
+
} catch {
|
|
1522
|
+
return ["base"];
|
|
988
1523
|
}
|
|
989
|
-
|
|
990
|
-
|
|
1524
|
+
}
|
|
1525
|
+
async function deleteProfileDefinition(root = process.cwd(), profile) {
|
|
1526
|
+
const filePath = path7.join(path7.resolve(root), ".cnos", "profiles", `${profile}.yml`);
|
|
1527
|
+
try {
|
|
1528
|
+
await rm3(filePath);
|
|
1529
|
+
return {
|
|
1530
|
+
filePath,
|
|
1531
|
+
deleted: true
|
|
1532
|
+
};
|
|
1533
|
+
} catch {
|
|
1534
|
+
return {
|
|
1535
|
+
filePath,
|
|
1536
|
+
deleted: false
|
|
1537
|
+
};
|
|
1538
|
+
}
|
|
1539
|
+
}
|
|
1540
|
+
async function readProfileDefinition(root = process.cwd(), profile = "base") {
|
|
1541
|
+
if (profile === "base") {
|
|
1542
|
+
return {
|
|
1543
|
+
name: "base"
|
|
1544
|
+
};
|
|
1545
|
+
}
|
|
1546
|
+
const filePath = path7.join(path7.resolve(root), ".cnos", "profiles", `${profile}.yml`);
|
|
1547
|
+
try {
|
|
1548
|
+
return parseYaml3(await readFile5(filePath, "utf8")) ?? void 0;
|
|
1549
|
+
} catch {
|
|
1550
|
+
return void 0;
|
|
991
1551
|
}
|
|
992
|
-
|
|
1552
|
+
}
|
|
1553
|
+
|
|
1554
|
+
// src/commands/profile.ts
|
|
1555
|
+
function normalizeProfileAction(args) {
|
|
1556
|
+
const [action = "list", ...tail] = args;
|
|
1557
|
+
if (["create", "list", "use", "delete", "remove"].includes(action)) {
|
|
1558
|
+
return {
|
|
1559
|
+
action: action === "remove" ? "delete" : action,
|
|
1560
|
+
tail
|
|
1561
|
+
};
|
|
1562
|
+
}
|
|
1563
|
+
return {
|
|
1564
|
+
action: "list",
|
|
1565
|
+
tail: args
|
|
1566
|
+
};
|
|
1567
|
+
}
|
|
1568
|
+
async function runProfile(args, options = {}) {
|
|
1569
|
+
const { action, tail } = normalizeProfileAction(args);
|
|
1570
|
+
const root = path8.resolve(options.root ?? process.cwd());
|
|
1571
|
+
const cliArgs = [...options.cliArgs ?? []];
|
|
1572
|
+
if (action === "create") {
|
|
1573
|
+
const profile = tail[0] ?? "stage";
|
|
1574
|
+
const inherit = consumeOption(cliArgs, "--inherit");
|
|
1575
|
+
const result = await createProfileDefinition(root, profile, inherit);
|
|
1576
|
+
if (options.json) {
|
|
1577
|
+
return printJson(result);
|
|
1578
|
+
}
|
|
1579
|
+
return `created profile ${profile} at ${result.filePath}`;
|
|
1580
|
+
}
|
|
1581
|
+
if (action === "use") {
|
|
1582
|
+
const profile = tail[0] ?? "base";
|
|
1583
|
+
const result = await saveCliContext({
|
|
1584
|
+
root,
|
|
1585
|
+
profile
|
|
1586
|
+
});
|
|
1587
|
+
if (options.json) {
|
|
1588
|
+
return printJson(result);
|
|
1589
|
+
}
|
|
1590
|
+
return `active profile set to ${profile} in ${result.filePath}`;
|
|
1591
|
+
}
|
|
1592
|
+
if (action === "delete") {
|
|
1593
|
+
const profile = tail[0] ?? "base";
|
|
1594
|
+
const result = await deleteProfileDefinition(root, profile);
|
|
1595
|
+
if (options.json) {
|
|
1596
|
+
return printJson(result);
|
|
1597
|
+
}
|
|
1598
|
+
return result.deleted ? `deleted profile ${profile}` : `profile ${profile} was not found`;
|
|
1599
|
+
}
|
|
1600
|
+
const profiles = await listProfiles(root);
|
|
1601
|
+
if (options.json) {
|
|
1602
|
+
const details = await Promise.all(
|
|
1603
|
+
profiles.map(async (profile) => ({
|
|
1604
|
+
profile,
|
|
1605
|
+
definition: await readProfileDefinition(root, profile)
|
|
1606
|
+
}))
|
|
1607
|
+
);
|
|
1608
|
+
return printJson(details);
|
|
1609
|
+
}
|
|
1610
|
+
return profiles.join("\n");
|
|
993
1611
|
}
|
|
994
1612
|
|
|
995
1613
|
// src/commands/read.ts
|
|
@@ -1050,21 +1668,113 @@ async function runCommand(command, options = {}) {
|
|
|
1050
1668
|
}
|
|
1051
1669
|
|
|
1052
1670
|
// src/commands/secret.ts
|
|
1053
|
-
|
|
1671
|
+
function isSecretRef(value) {
|
|
1672
|
+
return Boolean(
|
|
1673
|
+
value && typeof value === "object" && !Array.isArray(value) && typeof value.provider === "string" && typeof value.ref === "string"
|
|
1674
|
+
);
|
|
1675
|
+
}
|
|
1676
|
+
function normalizeSecretCommand(args) {
|
|
1677
|
+
const [actionOrPath, ...tail] = args;
|
|
1678
|
+
if (!actionOrPath) {
|
|
1679
|
+
return {
|
|
1680
|
+
action: "list",
|
|
1681
|
+
tail: []
|
|
1682
|
+
};
|
|
1683
|
+
}
|
|
1684
|
+
if (["get", "set", "create", "add", "list", "delete", "remove"].includes(actionOrPath)) {
|
|
1685
|
+
return {
|
|
1686
|
+
action: actionOrPath === "remove" ? "delete" : actionOrPath === "create" || actionOrPath === "add" ? "set" : actionOrPath,
|
|
1687
|
+
tail
|
|
1688
|
+
};
|
|
1689
|
+
}
|
|
1690
|
+
return {
|
|
1691
|
+
action: "get",
|
|
1692
|
+
tail: args
|
|
1693
|
+
};
|
|
1694
|
+
}
|
|
1695
|
+
async function runSecret(argsOrPath, options = {}) {
|
|
1696
|
+
const args = Array.isArray(argsOrPath) ? argsOrPath : [argsOrPath];
|
|
1697
|
+
const { action, tail } = normalizeSecretCommand(args);
|
|
1698
|
+
const cliArgs = [...options.cliArgs ?? []];
|
|
1699
|
+
if (action === "list") {
|
|
1700
|
+
const runtime2 = await createRuntimeService(options);
|
|
1701
|
+
const secrets = runtime2.toNamespace("secret");
|
|
1702
|
+
if (options.json) {
|
|
1703
|
+
return printJson(secrets);
|
|
1704
|
+
}
|
|
1705
|
+
return Object.entries(secrets).sort(([left], [right]) => left.localeCompare(right)).map(([key, value2]) => `${key}=${printValue(value2)}`).join("\n");
|
|
1706
|
+
}
|
|
1707
|
+
if (action === "set") {
|
|
1708
|
+
const secretPath = tail[0];
|
|
1709
|
+
const rawValue = tail[1] ?? "";
|
|
1710
|
+
const local = consumeFlag(cliArgs, "--local");
|
|
1711
|
+
const remote = consumeFlag(cliArgs, "--remote");
|
|
1712
|
+
const ref = consumeFlag(cliArgs, "--ref");
|
|
1713
|
+
const target = consumeOption(cliArgs, "--target") ?? "local";
|
|
1714
|
+
const provider = consumeOption(cliArgs, "--provider");
|
|
1715
|
+
const passphrase = consumeOption(cliArgs, "--passphrase");
|
|
1716
|
+
const mode = local ? "local" : remote ? "remote" : ref ? "ref" : "local";
|
|
1717
|
+
const result = await setSecret(secretPath ?? "app.token", rawValue, {
|
|
1718
|
+
...options,
|
|
1719
|
+
cliArgs,
|
|
1720
|
+
target,
|
|
1721
|
+
mode,
|
|
1722
|
+
...provider ? { provider } : {},
|
|
1723
|
+
...passphrase ? { passphrase } : {}
|
|
1724
|
+
});
|
|
1725
|
+
if (options.json) {
|
|
1726
|
+
return printJson(result);
|
|
1727
|
+
}
|
|
1728
|
+
return `set secret.${secretPath} via ${result.provider} in ${result.filePath}`;
|
|
1729
|
+
}
|
|
1730
|
+
if (action === "delete") {
|
|
1731
|
+
const secretPath = tail[0];
|
|
1732
|
+
const target = consumeOption(cliArgs, "--target") ?? "local";
|
|
1733
|
+
const result = await deleteSecret(secretPath ?? "app.token", {
|
|
1734
|
+
...options,
|
|
1735
|
+
cliArgs,
|
|
1736
|
+
target
|
|
1737
|
+
});
|
|
1738
|
+
if (options.json) {
|
|
1739
|
+
return printJson(result);
|
|
1740
|
+
}
|
|
1741
|
+
return result.deleted ? `deleted secret.${secretPath} from ${result.filePath}` : `no secret.${secretPath} found in ${result.filePath}`;
|
|
1742
|
+
}
|
|
1054
1743
|
const runtime = await createRuntimeService(options);
|
|
1055
|
-
const value = runtime.secret(
|
|
1744
|
+
const value = runtime.secret(tail[0] ?? "app.token");
|
|
1056
1745
|
if (value === void 0) {
|
|
1057
|
-
throw new Error(`Missing CNOS secret path: ${
|
|
1746
|
+
throw new Error(`Missing CNOS secret path: ${tail[0] ?? "app.token"}`);
|
|
1747
|
+
}
|
|
1748
|
+
if (isSecretRef(value)) {
|
|
1749
|
+
throw new Error(
|
|
1750
|
+
`Secret ${tail[0] ?? "app.token"} is stored as an unresolved ${value.provider} reference. Provide the required provider context to resolve it.`
|
|
1751
|
+
);
|
|
1058
1752
|
}
|
|
1059
1753
|
if (options.json) {
|
|
1060
1754
|
return printJson({
|
|
1061
|
-
key: `secret.${
|
|
1755
|
+
key: `secret.${tail[0] ?? "app.token"}`,
|
|
1062
1756
|
value
|
|
1063
1757
|
});
|
|
1064
1758
|
}
|
|
1065
1759
|
return printValue(value);
|
|
1066
1760
|
}
|
|
1067
1761
|
|
|
1762
|
+
// src/commands/use.ts
|
|
1763
|
+
import path9 from "path";
|
|
1764
|
+
async function runUse(options = {}) {
|
|
1765
|
+
const root = path9.resolve(options.root ?? process.cwd());
|
|
1766
|
+
const result = await saveCliContext({
|
|
1767
|
+
root,
|
|
1768
|
+
...options.workspace ? { workspace: options.workspace } : {},
|
|
1769
|
+
...options.profile ? { profile: options.profile } : {},
|
|
1770
|
+
...options.globalRoot ? { globalRoot: options.globalRoot } : {}
|
|
1771
|
+
});
|
|
1772
|
+
if (options.json) {
|
|
1773
|
+
return printJson(result);
|
|
1774
|
+
}
|
|
1775
|
+
return `updated CLI context in ${result.filePath}`;
|
|
1776
|
+
}
|
|
1777
|
+
|
|
1068
1778
|
// src/commands/validate.ts
|
|
1069
1779
|
async function runValidate(options = {}) {
|
|
1070
1780
|
const { summary } = await createValidationSummary(options);
|
|
@@ -1078,15 +1788,82 @@ async function runValidate(options = {}) {
|
|
|
1078
1788
|
}
|
|
1079
1789
|
|
|
1080
1790
|
// src/commands/value.ts
|
|
1081
|
-
|
|
1791
|
+
function normalizeValueCommand(args) {
|
|
1792
|
+
const [actionOrPath, ...tail] = args;
|
|
1793
|
+
if (!actionOrPath) {
|
|
1794
|
+
return {
|
|
1795
|
+
action: "list",
|
|
1796
|
+
tail: []
|
|
1797
|
+
};
|
|
1798
|
+
}
|
|
1799
|
+
if (["get", "set", "create", "add", "list", "delete", "remove"].includes(actionOrPath)) {
|
|
1800
|
+
return {
|
|
1801
|
+
action: actionOrPath === "remove" ? "delete" : actionOrPath === "create" || actionOrPath === "add" ? "set" : actionOrPath,
|
|
1802
|
+
tail
|
|
1803
|
+
};
|
|
1804
|
+
}
|
|
1805
|
+
return {
|
|
1806
|
+
action: "get",
|
|
1807
|
+
tail: args
|
|
1808
|
+
};
|
|
1809
|
+
}
|
|
1810
|
+
async function runValue(argsOrPath, options = {}) {
|
|
1811
|
+
const args = Array.isArray(argsOrPath) ? argsOrPath : [argsOrPath];
|
|
1812
|
+
const { action, tail } = normalizeValueCommand(args);
|
|
1813
|
+
const cliArgs = [...options.cliArgs ?? []];
|
|
1814
|
+
if (action === "list") {
|
|
1815
|
+
const prefix = consumeOption(cliArgs, "--prefix");
|
|
1816
|
+
const entries = await listConfigEntries("value", {
|
|
1817
|
+
...options,
|
|
1818
|
+
cliArgs,
|
|
1819
|
+
...prefix ? { prefix } : {}
|
|
1820
|
+
});
|
|
1821
|
+
if (options.json) {
|
|
1822
|
+
return printJson(entries);
|
|
1823
|
+
}
|
|
1824
|
+
return entries.map((entry) => `${entry.key}=${printValue(entry.value)}`).join("\n");
|
|
1825
|
+
}
|
|
1826
|
+
if (action === "set") {
|
|
1827
|
+
const valuePath = tail[0] ?? "app.name";
|
|
1828
|
+
const rawValue = tail[1] ?? "";
|
|
1829
|
+
const target = consumeOption(cliArgs, "--target") ?? "local";
|
|
1830
|
+
const result = await defineValue("value", valuePath, rawValue, {
|
|
1831
|
+
...options,
|
|
1832
|
+
cliArgs,
|
|
1833
|
+
target
|
|
1834
|
+
});
|
|
1835
|
+
if (options.json) {
|
|
1836
|
+
return printJson({
|
|
1837
|
+
namespace: "value",
|
|
1838
|
+
path: valuePath,
|
|
1839
|
+
target,
|
|
1840
|
+
filePath: result.filePath,
|
|
1841
|
+
value: result.value
|
|
1842
|
+
});
|
|
1843
|
+
}
|
|
1844
|
+
return `set value.${valuePath} in ${result.filePath}`;
|
|
1845
|
+
}
|
|
1846
|
+
if (action === "delete") {
|
|
1847
|
+
const valuePath = tail[0] ?? "app.name";
|
|
1848
|
+
const target = consumeOption(cliArgs, "--target") ?? "local";
|
|
1849
|
+
const result = await deleteValue("value", valuePath, {
|
|
1850
|
+
...options,
|
|
1851
|
+
cliArgs,
|
|
1852
|
+
target
|
|
1853
|
+
});
|
|
1854
|
+
if (options.json) {
|
|
1855
|
+
return printJson(result);
|
|
1856
|
+
}
|
|
1857
|
+
return result.deleted ? `deleted value.${valuePath} from ${result.filePath}` : `no value.${valuePath} found in ${result.filePath}`;
|
|
1858
|
+
}
|
|
1082
1859
|
const runtime = await createRuntimeService(options);
|
|
1083
|
-
const value = runtime.value(
|
|
1860
|
+
const value = runtime.value(tail[0] ?? "app.name");
|
|
1084
1861
|
if (value === void 0) {
|
|
1085
|
-
throw new Error(`Missing CNOS value path: ${
|
|
1862
|
+
throw new Error(`Missing CNOS value path: ${tail[0] ?? "app.name"}`);
|
|
1086
1863
|
}
|
|
1087
1864
|
if (options.json) {
|
|
1088
1865
|
return printJson({
|
|
1089
|
-
key: `value.${
|
|
1866
|
+
key: `value.${tail[0] ?? "app.name"}`,
|
|
1090
1867
|
value
|
|
1091
1868
|
});
|
|
1092
1869
|
}
|
|
@@ -1101,6 +1878,21 @@ function resolveHelpTopic(command, args) {
|
|
|
1101
1878
|
if (command === "export" && args[0] === "env") {
|
|
1102
1879
|
return normalizeHelpTopic([command, args[0]]);
|
|
1103
1880
|
}
|
|
1881
|
+
if (command === "secret" && args[0] && ["set", "create", "add", "list", "delete", "remove"].includes(args[0])) {
|
|
1882
|
+
return normalizeHelpTopic([
|
|
1883
|
+
command,
|
|
1884
|
+
args[0] === "remove" ? "delete" : args[0] === "create" || args[0] === "add" ? "set" : args[0]
|
|
1885
|
+
]);
|
|
1886
|
+
}
|
|
1887
|
+
if (command === "value" && args[0] && ["set", "create", "add", "list", "delete", "remove"].includes(args[0])) {
|
|
1888
|
+
return normalizeHelpTopic([
|
|
1889
|
+
command,
|
|
1890
|
+
args[0] === "remove" ? "delete" : args[0] === "create" || args[0] === "add" ? "set" : args[0]
|
|
1891
|
+
]);
|
|
1892
|
+
}
|
|
1893
|
+
if (command === "profile" && args[0] && ["create", "list", "use", "delete", "remove"].includes(args[0])) {
|
|
1894
|
+
return normalizeHelpTopic([command, args[0] === "remove" ? "delete" : args[0]]);
|
|
1895
|
+
}
|
|
1104
1896
|
return normalizeHelpTopic([command]);
|
|
1105
1897
|
}
|
|
1106
1898
|
async function main(argv) {
|
|
@@ -1152,11 +1944,23 @@ async function main(argv) {
|
|
|
1152
1944
|
`);
|
|
1153
1945
|
return;
|
|
1154
1946
|
case "value":
|
|
1155
|
-
process.stdout.write(`${await runValue(args
|
|
1947
|
+
process.stdout.write(`${await runValue(args.length > 0 ? args : ["app.name"], runtimeOptions)}
|
|
1156
1948
|
`);
|
|
1157
1949
|
return;
|
|
1158
1950
|
case "secret":
|
|
1159
|
-
process.stdout.write(`${await runSecret(args
|
|
1951
|
+
process.stdout.write(`${await runSecret(args.length > 0 ? args : ["app.token"], runtimeOptions)}
|
|
1952
|
+
`);
|
|
1953
|
+
return;
|
|
1954
|
+
case "use":
|
|
1955
|
+
process.stdout.write(`${await runUse(runtimeOptions)}
|
|
1956
|
+
`);
|
|
1957
|
+
return;
|
|
1958
|
+
case "profile":
|
|
1959
|
+
process.stdout.write(`${await runProfile(args, runtimeOptions)}
|
|
1960
|
+
`);
|
|
1961
|
+
return;
|
|
1962
|
+
case "list":
|
|
1963
|
+
process.stdout.write(`${await runList(args, runtimeOptions)}
|
|
1160
1964
|
`);
|
|
1161
1965
|
return;
|
|
1162
1966
|
case "define":
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kitsy/cnos-cli",
|
|
3
|
-
"version": "0.0
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"description": "CLI entry point and developer tooling for CNOS.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -36,8 +36,7 @@
|
|
|
36
36
|
"access": "public"
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"@kitsy/cnos": "0.0
|
|
40
|
-
"@kitsy/cnos-core": "0.0.1"
|
|
39
|
+
"@kitsy/cnos": "1.0.0"
|
|
41
40
|
},
|
|
42
41
|
"scripts": {
|
|
43
42
|
"build": "tsup src/index.ts --format esm --dts",
|