@kitsy/cnos-cli 1.8.0 → 1.8.2
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 +530 -127
- package/package.json +3 -2
package/dist/index.js
CHANGED
|
@@ -12,6 +12,7 @@ var COMMAND_OPTION_KEYS_WITH_VALUE = /* @__PURE__ */ new Set([
|
|
|
12
12
|
"--format",
|
|
13
13
|
"--framework",
|
|
14
14
|
"--prefix",
|
|
15
|
+
"--mode",
|
|
15
16
|
"--scan",
|
|
16
17
|
"--target",
|
|
17
18
|
"--to",
|
|
@@ -24,7 +25,12 @@ var COMMAND_OPTION_KEYS_WITH_VALUE = /* @__PURE__ */ new Set([
|
|
|
24
25
|
"--set",
|
|
25
26
|
"--debounce",
|
|
26
27
|
"--expr",
|
|
27
|
-
"--extends"
|
|
28
|
+
"--extends",
|
|
29
|
+
"--workspaces",
|
|
30
|
+
"--env",
|
|
31
|
+
"--yaml",
|
|
32
|
+
"--toml",
|
|
33
|
+
"--config"
|
|
28
34
|
]);
|
|
29
35
|
var COMMAND_FLAG_KEYS = /* @__PURE__ */ new Set([
|
|
30
36
|
"--flatten",
|
|
@@ -44,7 +50,8 @@ var COMMAND_FLAG_KEYS = /* @__PURE__ */ new Set([
|
|
|
44
50
|
"--rewrite",
|
|
45
51
|
"--signal",
|
|
46
52
|
"--derive",
|
|
47
|
-
"--
|
|
53
|
+
"--materialize",
|
|
54
|
+
"--source-only"
|
|
48
55
|
]);
|
|
49
56
|
function normalizeCommand(argv) {
|
|
50
57
|
const [command = "doctor", ...rest] = argv;
|
|
@@ -160,6 +167,14 @@ function parseArgs(argv) {
|
|
|
160
167
|
passthrough.push(token);
|
|
161
168
|
continue;
|
|
162
169
|
}
|
|
170
|
+
if (command === "onboard" && token === "--json") {
|
|
171
|
+
const nextValue = rest[index + 1];
|
|
172
|
+
if (nextValue && !nextValue.startsWith("--")) {
|
|
173
|
+
cliArgs.push(token, nextValue);
|
|
174
|
+
index += 1;
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
163
178
|
if (token === "--json") {
|
|
164
179
|
options.json = true;
|
|
165
180
|
continue;
|
|
@@ -773,6 +788,8 @@ async function defineValue(namespace, configPath, rawValue, options = {}) {
|
|
|
773
788
|
const derivedValue = normalizeDerivedValue(options.deriveExpression, options.deriveExprMode ?? false);
|
|
774
789
|
validateParsedDerivation(runtime.manifest, parseDerivation(derivedValue));
|
|
775
790
|
parsedValue = derivedValue;
|
|
791
|
+
} else if (Object.hasOwn(options, "parsedValue")) {
|
|
792
|
+
parsedValue = options.parsedValue;
|
|
776
793
|
} else {
|
|
777
794
|
parsedValue = parseScalarValue(rawValue);
|
|
778
795
|
}
|
|
@@ -795,7 +812,7 @@ async function setSecret(configPath, rawValue, options = {}) {
|
|
|
795
812
|
if (!vaultDefinition) {
|
|
796
813
|
throw new Error(`Unknown vault "${vault}". Create it first with cnos vault create ${vault}.`);
|
|
797
814
|
}
|
|
798
|
-
const mode = options.mode ?? (vaultDefinition.provider === "local" ? "local" : vaultDefinition.provider === "github-secrets" ? "ref" : "remote");
|
|
815
|
+
const mode = options.mode ?? (vaultDefinition.provider === "local" ? "local" : vaultDefinition.provider === "github-secrets" || vaultDefinition.provider === "environment" ? "ref" : "remote");
|
|
799
816
|
let reference;
|
|
800
817
|
if (mode === "local") {
|
|
801
818
|
const auth = await resolveVaultAuth(vault, vaultDefinition, options.processEnv ?? process.env);
|
|
@@ -1578,23 +1595,65 @@ var COMMANDS = [
|
|
|
1578
1595
|
},
|
|
1579
1596
|
{
|
|
1580
1597
|
id: "init",
|
|
1581
|
-
summary: "Scaffold a
|
|
1582
|
-
usage: "cnos init [--workspace <
|
|
1583
|
-
description: "Creates .cnos/cnos.yml, .cnosrc.yml,
|
|
1584
|
-
examples: [
|
|
1598
|
+
summary: "Scaffold a CNOS project in regular mode or workspace mode.",
|
|
1599
|
+
usage: "cnos init [--mode <regular|workspace>] [--workspaces <csv>] [--root <path>] [--json]",
|
|
1600
|
+
description: "Creates .cnos/cnos.yml, .cnosrc.yml, config folders, and .gitignore entries without overwriting existing files. Regular mode is the default; workspace mode creates base plus optional child workspaces.",
|
|
1601
|
+
examples: [
|
|
1602
|
+
"cnos init",
|
|
1603
|
+
"cnos init --mode workspace",
|
|
1604
|
+
"cnos init --mode workspace --workspaces api,web,agents",
|
|
1605
|
+
"cnos init --root ./apps/api --json"
|
|
1606
|
+
]
|
|
1585
1607
|
},
|
|
1586
1608
|
{
|
|
1587
1609
|
id: "onboard",
|
|
1588
|
-
summary: "
|
|
1589
|
-
usage: "cnos onboard [--workspace <id>] [--
|
|
1590
|
-
description: "
|
|
1610
|
+
summary: "Import existing env or config sources into CNOS and propose value.* mappings.",
|
|
1611
|
+
usage: "cnos onboard [--workspace <id>] [--env <path>|--yaml <path>|--json <path>|--toml <path>|--config <path>] [--materialize|--source-only] [--prefix <path>] [--move] [--root <path>] [--json]",
|
|
1612
|
+
description: "Auto-discovers root .env* files by default, copies them into CNOS source storage, prints proposed value.* mappings, and can materialize those mappings into CNOS values. In workspace mode, imports land in the selected workspace; otherwise they land in the implicit base layer.",
|
|
1591
1613
|
options: [
|
|
1614
|
+
{
|
|
1615
|
+
flag: "--env <path>",
|
|
1616
|
+
description: "Import one dotenv file instead of auto-discovering root .env* files."
|
|
1617
|
+
},
|
|
1618
|
+
{
|
|
1619
|
+
flag: "--yaml <path>",
|
|
1620
|
+
description: "Import one YAML config file and flatten it into value.* keys."
|
|
1621
|
+
},
|
|
1622
|
+
{
|
|
1623
|
+
flag: "--json <path>",
|
|
1624
|
+
description: "Import one JSON config file and flatten it into value.* keys."
|
|
1625
|
+
},
|
|
1626
|
+
{
|
|
1627
|
+
flag: "--toml <path>",
|
|
1628
|
+
description: "Import one TOML config file and flatten it into value.* keys."
|
|
1629
|
+
},
|
|
1630
|
+
{
|
|
1631
|
+
flag: "--config <path>",
|
|
1632
|
+
description: "Import one config file using extension-based format detection."
|
|
1633
|
+
},
|
|
1634
|
+
{
|
|
1635
|
+
flag: "--materialize",
|
|
1636
|
+
description: "Write the proposed value.* mappings without prompting."
|
|
1637
|
+
},
|
|
1638
|
+
{
|
|
1639
|
+
flag: "--source-only",
|
|
1640
|
+
description: "Copy the source file(s) into CNOS storage but skip value materialization."
|
|
1641
|
+
},
|
|
1642
|
+
{
|
|
1643
|
+
flag: "--prefix <path>",
|
|
1644
|
+
description: "Scope imported keys under value.<prefix>.*."
|
|
1645
|
+
},
|
|
1592
1646
|
{
|
|
1593
1647
|
flag: "--move",
|
|
1594
|
-
description: "Move the
|
|
1648
|
+
description: "Move the source files into CNOS instead of leaving the originals in place."
|
|
1595
1649
|
}
|
|
1596
1650
|
],
|
|
1597
|
-
examples: [
|
|
1651
|
+
examples: [
|
|
1652
|
+
"cnos onboard",
|
|
1653
|
+
"cnos onboard --env .env.production --materialize",
|
|
1654
|
+
"cnos onboard --yaml config/app.yml --prefix app",
|
|
1655
|
+
"cnos onboard --workspace api --config config/api.toml"
|
|
1656
|
+
]
|
|
1598
1657
|
},
|
|
1599
1658
|
{
|
|
1600
1659
|
id: "codegen",
|
|
@@ -1747,7 +1806,7 @@ var COMMANDS = [
|
|
|
1747
1806
|
"cnos vault create local-dev",
|
|
1748
1807
|
"cnos vault auth local-dev",
|
|
1749
1808
|
"cnos secret set app.token super-secret --vault local-dev",
|
|
1750
|
-
"cnos vault create github-ci --provider
|
|
1809
|
+
"cnos vault create github-ci --provider environment --no-passphrase",
|
|
1751
1810
|
"cnos secret set app.token APP_TOKEN --vault github-ci"
|
|
1752
1811
|
]
|
|
1753
1812
|
},
|
|
@@ -1755,21 +1814,21 @@ var COMMANDS = [
|
|
|
1755
1814
|
id: "vault",
|
|
1756
1815
|
summary: "Manage manifest-defined secret vaults.",
|
|
1757
1816
|
usage: "cnos vault [create <name> | list | remove <name>] [options] [global-options]",
|
|
1758
|
-
description: "Creates, lists, and removes vault definitions in .cnos/cnos.yml. Local vaults use encrypted material under ~/.cnos/secrets, while
|
|
1817
|
+
description: "Creates, lists, and removes vault definitions in .cnos/cnos.yml. Local vaults use encrypted material under ~/.cnos/secrets, while environment-backed vaults resolve from process.env in CI and cloud runtimes. github-secrets remains a compatibility alias.",
|
|
1759
1818
|
options: [
|
|
1760
1819
|
{
|
|
1761
|
-
flag: "--provider <local|github-secrets>",
|
|
1820
|
+
flag: "--provider <local|environment|github-secrets>",
|
|
1762
1821
|
description: "Vault provider. Defaults to local."
|
|
1763
1822
|
},
|
|
1764
1823
|
{
|
|
1765
1824
|
flag: "--no-passphrase",
|
|
1766
|
-
description: "Allowed for passwordless providers such as
|
|
1825
|
+
description: "Allowed for passwordless providers such as environment-backed vaults."
|
|
1767
1826
|
}
|
|
1768
1827
|
],
|
|
1769
1828
|
examples: [
|
|
1770
1829
|
"cnos vault create local-dev",
|
|
1771
1830
|
"cnos vault auth local-dev",
|
|
1772
|
-
"cnos vault create github-ci --provider
|
|
1831
|
+
"cnos vault create github-ci --provider environment --no-passphrase",
|
|
1773
1832
|
"cnos vault list",
|
|
1774
1833
|
"cnos vault remove local-dev"
|
|
1775
1834
|
]
|
|
@@ -1777,11 +1836,11 @@ var COMMANDS = [
|
|
|
1777
1836
|
{
|
|
1778
1837
|
id: "vault create",
|
|
1779
1838
|
summary: "Create a manifest-defined vault.",
|
|
1780
|
-
usage: "cnos vault create <name> [--provider <local|github-secrets>] [--no-passphrase] [global-options]",
|
|
1839
|
+
usage: "cnos vault create <name> [--provider <local|environment|github-secrets>] [--no-passphrase] [global-options]",
|
|
1781
1840
|
description: "Creates a vault definition in .cnos/cnos.yml and, for local vaults, initializes the encrypted store under ~/.cnos/secrets. CNOS prompts for a passphrase when one is not already available from env or keychain.",
|
|
1782
1841
|
examples: [
|
|
1783
1842
|
"cnos vault create local-dev",
|
|
1784
|
-
"cnos vault create
|
|
1843
|
+
"cnos vault create firebase-prod --provider environment --no-passphrase"
|
|
1785
1844
|
]
|
|
1786
1845
|
},
|
|
1787
1846
|
{
|
|
@@ -1954,12 +2013,12 @@ var COMMANDS = [
|
|
|
1954
2013
|
id: "secret set",
|
|
1955
2014
|
summary: "Write a secret securely.",
|
|
1956
2015
|
usage: "cnos secret set <path> <value> [--local|--remote|--ref] [--vault <name>] [--provider <name>] [global-options]",
|
|
1957
|
-
description: "Writes a secret reference into the repo. When a local vault is selected, CNOS stores encrypted secret material outside the repo under ~/.cnos/secrets/vaults/<vault>; when
|
|
2016
|
+
description: "Writes a secret reference into the repo. When a local vault is selected, CNOS stores encrypted secret material outside the repo under ~/.cnos/secrets/vaults/<vault>; when an environment-backed vault is selected, CNOS writes an env-backed ref for CI or cloud runtimes.",
|
|
1958
2017
|
examples: [
|
|
1959
2018
|
"cnos vault create db",
|
|
1960
2019
|
"cnos vault auth db",
|
|
1961
2020
|
"cnos secret set app.token super-secret --vault db",
|
|
1962
|
-
"cnos vault create github-ci --provider
|
|
2021
|
+
"cnos vault create github-ci --provider environment --no-passphrase",
|
|
1963
2022
|
"cnos secret set app.token APP_TOKEN --vault github-ci"
|
|
1964
2023
|
]
|
|
1965
2024
|
},
|
|
@@ -2217,25 +2276,32 @@ var COMMANDS = [
|
|
|
2217
2276
|
{
|
|
2218
2277
|
id: "workspace",
|
|
2219
2278
|
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: "
|
|
2279
|
+
usage: "cnos workspace <enable|add|list|remove|scaffold|attach|detach> [options] [global-options]",
|
|
2280
|
+
description: "Enables workspace mode for flat CNOS projects, adds and removes manifest workspaces, scaffolds package anchors, and handles detach/attach flows for independent child packages.",
|
|
2222
2281
|
examples: [
|
|
2223
2282
|
"cnos workspace list",
|
|
2283
|
+
"cnos workspace enable",
|
|
2224
2284
|
"cnos workspace add travel --package-root apps/travel --extends base",
|
|
2225
|
-
"cnos workspace add main --onboard-current",
|
|
2226
2285
|
"cnos workspace remove gallery",
|
|
2227
2286
|
"cnos workspace detach --package-root apps/travel"
|
|
2228
2287
|
]
|
|
2229
2288
|
},
|
|
2289
|
+
{
|
|
2290
|
+
id: "workspace enable",
|
|
2291
|
+
summary: "Convert a flat regular-mode CNOS root into workspace mode with base.",
|
|
2292
|
+
usage: "cnos workspace enable [global-options]",
|
|
2293
|
+
description: "Moves .cnos/values, .cnos/secrets, .cnos/env, and .cnos/profiles into .cnos/workspaces/base, adds a workspaces block to cnos.yml, and updates the root anchor to workspace: base.",
|
|
2294
|
+
examples: ["cnos workspace enable"]
|
|
2295
|
+
},
|
|
2230
2296
|
{
|
|
2231
2297
|
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>] [--
|
|
2234
|
-
description: "Creates .cnos/workspaces/<id>, updates cnos.yml, writes a .cnosrc.yml anchor at the selected package root,
|
|
2298
|
+
summary: "Add a child workspace to the manifest and scaffold its on-disk layout.",
|
|
2299
|
+
usage: "cnos workspace add <id> [--package-root <path>] [--extends <workspace|none>] [--force] [global-options]",
|
|
2300
|
+
description: "Creates .cnos/workspaces/<id>, updates cnos.yml, and writes a .cnosrc.yml anchor at the selected package root. When a base workspace exists, CNOS defaults new child workspaces to extends: [base] unless --extends or --extends none is provided.",
|
|
2235
2301
|
examples: [
|
|
2236
2302
|
"cnos workspace add travel --package-root apps/travel --extends base",
|
|
2237
2303
|
"cnos workspace add insights --package-root apps/insights",
|
|
2238
|
-
"cnos workspace add
|
|
2304
|
+
"cnos workspace add api --extends none"
|
|
2239
2305
|
]
|
|
2240
2306
|
},
|
|
2241
2307
|
{
|
|
@@ -2551,32 +2617,41 @@ import path10 from "path";
|
|
|
2551
2617
|
// src/services/scaffold.ts
|
|
2552
2618
|
import { mkdir as mkdir4, readFile as readFile3, writeFile as writeFile4 } from "fs/promises";
|
|
2553
2619
|
import path9 from "path";
|
|
2554
|
-
function scaffoldManifest(projectName,
|
|
2620
|
+
function scaffoldManifest(projectName, options = {}) {
|
|
2621
|
+
const mode = options.mode ?? "regular";
|
|
2622
|
+
const baseWorkspace = options.workspace ?? "base";
|
|
2623
|
+
const workspaceIds = mode === "workspace" ? [baseWorkspace, ...(options.workspaces ?? []).filter((id) => id !== baseWorkspace)] : [];
|
|
2555
2624
|
const lines = [
|
|
2556
2625
|
"version: 1",
|
|
2557
2626
|
"project:",
|
|
2558
|
-
` name: ${projectName}
|
|
2559
|
-
"profiles:",
|
|
2560
|
-
" default: base",
|
|
2561
|
-
"envMapping:",
|
|
2562
|
-
" convention: SCREAMING_SNAKE",
|
|
2563
|
-
"public:",
|
|
2564
|
-
" promote: []",
|
|
2565
|
-
""
|
|
2627
|
+
` name: ${projectName}`
|
|
2566
2628
|
];
|
|
2567
|
-
if (workspace) {
|
|
2568
|
-
lines.
|
|
2569
|
-
4,
|
|
2570
|
-
0,
|
|
2629
|
+
if (mode === "workspace") {
|
|
2630
|
+
lines.push(
|
|
2571
2631
|
"workspaces:",
|
|
2572
|
-
` default: ${
|
|
2632
|
+
` default: ${baseWorkspace}`,
|
|
2573
2633
|
" global:",
|
|
2574
2634
|
" enabled: false",
|
|
2575
2635
|
" allowWrite: false",
|
|
2576
2636
|
" items:",
|
|
2577
|
-
` ${
|
|
2637
|
+
` ${baseWorkspace}: {}`
|
|
2578
2638
|
);
|
|
2639
|
+
for (const workspaceId of workspaceIds) {
|
|
2640
|
+
if (workspaceId === baseWorkspace) {
|
|
2641
|
+
continue;
|
|
2642
|
+
}
|
|
2643
|
+
lines.push(` ${workspaceId}:`, " extends: [base]");
|
|
2644
|
+
}
|
|
2579
2645
|
}
|
|
2646
|
+
lines.push(
|
|
2647
|
+
"profiles:",
|
|
2648
|
+
" default: local",
|
|
2649
|
+
"envMapping:",
|
|
2650
|
+
" convention: SCREAMING_SNAKE",
|
|
2651
|
+
"public:",
|
|
2652
|
+
" promote: []",
|
|
2653
|
+
""
|
|
2654
|
+
);
|
|
2580
2655
|
return lines.join("\n");
|
|
2581
2656
|
}
|
|
2582
2657
|
async function ensureFile(filePath, content) {
|
|
@@ -2650,18 +2725,31 @@ workspace: ${workspace}
|
|
|
2650
2725
|
` : "root: ./.cnos\n"
|
|
2651
2726
|
);
|
|
2652
2727
|
}
|
|
2653
|
-
async function
|
|
2728
|
+
async function scaffoldProject(root, options = {}) {
|
|
2729
|
+
const mode = options.mode ?? "regular";
|
|
2730
|
+
const baseWorkspace = options.workspace ?? "base";
|
|
2731
|
+
const childWorkspaces = mode === "workspace" ? (options.workspaces ?? []).filter((workspaceId) => workspaceId !== baseWorkspace) : [];
|
|
2654
2732
|
const cnosRoot = path9.join(root, ".cnos");
|
|
2655
|
-
const createdPaths =
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2733
|
+
const createdPaths = [];
|
|
2734
|
+
if (mode === "workspace") {
|
|
2735
|
+
createdPaths.push(
|
|
2736
|
+
...(await ensureWorkspaceLayout(cnosRoot, baseWorkspace)).map((entry) => entry.replace(/^\.cnos\//, ".cnos/"))
|
|
2737
|
+
);
|
|
2738
|
+
for (const workspaceId of childWorkspaces) {
|
|
2739
|
+
createdPaths.push(
|
|
2740
|
+
...(await ensureWorkspaceLayout(cnosRoot, workspaceId)).map((entry) => entry.replace(/^\.cnos\//, ".cnos/"))
|
|
2741
|
+
);
|
|
2742
|
+
}
|
|
2743
|
+
} else {
|
|
2744
|
+
createdPaths.push(...(await ensureWorkspaceLayout(cnosRoot)).map((entry) => entry.replace(/^\.cnos\//, ".cnos/")));
|
|
2745
|
+
}
|
|
2746
|
+
if (await ensureFile(path9.join(cnosRoot, "cnos.yml"), scaffoldManifest(path9.basename(root), options))) {
|
|
2659
2747
|
createdPaths.push(".cnos/cnos.yml");
|
|
2660
2748
|
}
|
|
2661
|
-
if (await ensureCnosrc(root, workspace)) {
|
|
2749
|
+
if (await ensureCnosrc(root, mode === "workspace" ? baseWorkspace : void 0)) {
|
|
2662
2750
|
createdPaths.push(".cnosrc.yml");
|
|
2663
2751
|
}
|
|
2664
|
-
if (workspace && await ensureFile(path9.join(root, ".cnos-workspace.yml"), `workspace: ${
|
|
2752
|
+
if (mode === "workspace" && await ensureFile(path9.join(root, ".cnos-workspace.yml"), `workspace: ${baseWorkspace}
|
|
2665
2753
|
globalRoot: ~/.cnos
|
|
2666
2754
|
`)) {
|
|
2667
2755
|
createdPaths.push(".cnos-workspace.yml");
|
|
@@ -2671,20 +2759,42 @@ globalRoot: ~/.cnos
|
|
|
2671
2759
|
}
|
|
2672
2760
|
return {
|
|
2673
2761
|
root,
|
|
2674
|
-
|
|
2762
|
+
mode,
|
|
2763
|
+
...mode === "workspace" ? { workspace: baseWorkspace, workspaces: [baseWorkspace, ...childWorkspaces] } : {},
|
|
2675
2764
|
created: createdPaths
|
|
2676
2765
|
};
|
|
2677
2766
|
}
|
|
2678
2767
|
|
|
2679
2768
|
// src/commands/init.ts
|
|
2769
|
+
function parseWorkspaceList(value) {
|
|
2770
|
+
if (!value) {
|
|
2771
|
+
return [];
|
|
2772
|
+
}
|
|
2773
|
+
return value.split(",").map((entry) => entry.trim()).filter(Boolean);
|
|
2774
|
+
}
|
|
2680
2775
|
async function runInit(options = {}) {
|
|
2681
2776
|
const root = path10.resolve(options.root ?? process.cwd());
|
|
2682
|
-
const
|
|
2777
|
+
const cliArgs = [...options.cliArgs ?? []];
|
|
2778
|
+
const modeOption = consumeOption(cliArgs, "--mode");
|
|
2779
|
+
const workspacesOption = consumeOption(cliArgs, "--workspaces");
|
|
2780
|
+
if (cliArgs.length > 0) {
|
|
2781
|
+
throw new Error(`Unsupported init arguments: ${cliArgs.join(" ")}`);
|
|
2782
|
+
}
|
|
2783
|
+
const mode = modeOption === void 0 ? options.workspace ? "workspace" : "regular" : modeOption === "workspace" || modeOption === "regular" ? modeOption : void 0;
|
|
2784
|
+
if (!mode) {
|
|
2785
|
+
throw new Error(`Invalid value for --mode: ${modeOption}. Use "regular" or "workspace".`);
|
|
2786
|
+
}
|
|
2787
|
+
const workspaces = parseWorkspaceList(workspacesOption);
|
|
2788
|
+
const result = await scaffoldProject(root, {
|
|
2789
|
+
mode,
|
|
2790
|
+
...mode === "workspace" ? { workspace: options.workspace ?? "base", workspaces } : {}
|
|
2791
|
+
});
|
|
2683
2792
|
if (options.json) {
|
|
2684
2793
|
return printJson(result);
|
|
2685
2794
|
}
|
|
2686
|
-
if (result.workspace) {
|
|
2687
|
-
|
|
2795
|
+
if (result.mode === "workspace" && result.workspace) {
|
|
2796
|
+
const suffix = result.workspaces && result.workspaces.length > 1 ? ` (${result.workspaces.slice(1).join(", ")} extends ${result.workspace})` : "";
|
|
2797
|
+
return `initialized CNOS workspace project at ${root} with base workspace ${result.workspace}${suffix}`;
|
|
2688
2798
|
}
|
|
2689
2799
|
return `initialized CNOS project at ${root}`;
|
|
2690
2800
|
}
|
|
@@ -3083,67 +3193,289 @@ async function runNamespace(namespace, args = [], options = {}) {
|
|
|
3083
3193
|
}
|
|
3084
3194
|
|
|
3085
3195
|
// src/commands/onboard.ts
|
|
3086
|
-
import { copyFile, readdir as readdir3, rm as rm2 } from "fs/promises";
|
|
3196
|
+
import { copyFile, mkdir as mkdir5, readdir as readdir3, rm as rm2, stat as stat2, readFile as readFile4 } from "fs/promises";
|
|
3087
3197
|
import path13 from "path";
|
|
3198
|
+
import readline from "readline/promises";
|
|
3199
|
+
import { loadManifest as loadManifest5, parseYaml as parseYaml3 } from "@kitsy/cnos/internal";
|
|
3200
|
+
import { parse as parseToml } from "smol-toml";
|
|
3088
3201
|
var ROOT_ENV_FILE_PATTERN = /^\.env(?:\.[A-Za-z0-9_-]+)*(?:\.example)?$/;
|
|
3202
|
+
var SECRET_LIKE_PATTERN = /(secret|token|password|passwd|private|api[_-]?key|client[_-]?secret|dsn)/i;
|
|
3203
|
+
async function exists(targetPath) {
|
|
3204
|
+
try {
|
|
3205
|
+
await stat2(targetPath);
|
|
3206
|
+
return true;
|
|
3207
|
+
} catch {
|
|
3208
|
+
return false;
|
|
3209
|
+
}
|
|
3210
|
+
}
|
|
3089
3211
|
async function listRootEnvFiles(root) {
|
|
3090
3212
|
const entries = await readdir3(root, { withFileTypes: true });
|
|
3091
3213
|
return entries.filter((entry) => entry.isFile() && ROOT_ENV_FILE_PATTERN.test(entry.name)).map((entry) => entry.name).sort((left, right) => left.localeCompare(right));
|
|
3092
3214
|
}
|
|
3215
|
+
function normalizePathSegment(value) {
|
|
3216
|
+
return value.trim().replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/[^A-Za-z0-9_-]+/g, "_").replace(/^_+|_+$/g, "").toLowerCase();
|
|
3217
|
+
}
|
|
3218
|
+
function toLogicalPath(sourceKey, prefixSegments) {
|
|
3219
|
+
const derivedSegments = sourceKey.split(/[._-]+/).map((segment) => normalizePathSegment(segment)).filter(Boolean);
|
|
3220
|
+
return [...prefixSegments, ...derivedSegments].join(".");
|
|
3221
|
+
}
|
|
3222
|
+
function toWarning(sourceKey) {
|
|
3223
|
+
return SECRET_LIKE_PATTERN.test(sourceKey) ? "looks like a secret" : void 0;
|
|
3224
|
+
}
|
|
3225
|
+
function createProposedMapping(source, pathKey, value, warning) {
|
|
3226
|
+
return {
|
|
3227
|
+
source,
|
|
3228
|
+
key: `value.${pathKey}`,
|
|
3229
|
+
path: pathKey,
|
|
3230
|
+
value,
|
|
3231
|
+
...warning ? { warning } : {}
|
|
3232
|
+
};
|
|
3233
|
+
}
|
|
3234
|
+
function parseEnv(content) {
|
|
3235
|
+
const values = {};
|
|
3236
|
+
for (const rawLine of content.split(/\r?\n/)) {
|
|
3237
|
+
const line = rawLine.trim();
|
|
3238
|
+
if (!line || line.startsWith("#")) {
|
|
3239
|
+
continue;
|
|
3240
|
+
}
|
|
3241
|
+
const separator = line.indexOf("=");
|
|
3242
|
+
if (separator <= 0) {
|
|
3243
|
+
continue;
|
|
3244
|
+
}
|
|
3245
|
+
const key = line.slice(0, separator).trim();
|
|
3246
|
+
let value = line.slice(separator + 1).trim();
|
|
3247
|
+
if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
|
|
3248
|
+
value = value.slice(1, -1);
|
|
3249
|
+
}
|
|
3250
|
+
values[key] = value;
|
|
3251
|
+
}
|
|
3252
|
+
return values;
|
|
3253
|
+
}
|
|
3254
|
+
function flattenStructured(value, prefixSegments, currentKey = []) {
|
|
3255
|
+
if (Array.isArray(value) || value === null || typeof value !== "object") {
|
|
3256
|
+
const pathKey = [...prefixSegments, ...currentKey.map((segment) => normalizePathSegment(segment)).filter(Boolean)].join(".");
|
|
3257
|
+
const sourceKey = currentKey.join(".");
|
|
3258
|
+
return pathKey ? [
|
|
3259
|
+
createProposedMapping(sourceKey, pathKey, value, sourceKey ? toWarning(sourceKey) : void 0)
|
|
3260
|
+
] : [];
|
|
3261
|
+
}
|
|
3262
|
+
return Object.entries(value).flatMap(
|
|
3263
|
+
([key, nested]) => flattenStructured(nested, prefixSegments, [...currentKey, key])
|
|
3264
|
+
);
|
|
3265
|
+
}
|
|
3266
|
+
async function parseSource(input, prefixSegments) {
|
|
3267
|
+
const content = await readFile4(input.filePath, "utf8");
|
|
3268
|
+
switch (input.kind) {
|
|
3269
|
+
case "env":
|
|
3270
|
+
return Object.entries(parseEnv(content)).map(([sourceKey, value]) => {
|
|
3271
|
+
const logicalPath = toLogicalPath(sourceKey, prefixSegments);
|
|
3272
|
+
return createProposedMapping(sourceKey, logicalPath, value, toWarning(sourceKey));
|
|
3273
|
+
});
|
|
3274
|
+
case "yaml":
|
|
3275
|
+
return flattenStructured(parseYaml3(content), prefixSegments);
|
|
3276
|
+
case "json":
|
|
3277
|
+
return flattenStructured(JSON.parse(content), prefixSegments);
|
|
3278
|
+
case "toml":
|
|
3279
|
+
return flattenStructured(parseToml(content), prefixSegments);
|
|
3280
|
+
}
|
|
3281
|
+
}
|
|
3282
|
+
function detectKindFromPath(filePath) {
|
|
3283
|
+
const ext = path13.extname(filePath).toLowerCase();
|
|
3284
|
+
switch (ext) {
|
|
3285
|
+
case ".env":
|
|
3286
|
+
return "env";
|
|
3287
|
+
case ".yaml":
|
|
3288
|
+
case ".yml":
|
|
3289
|
+
return "yaml";
|
|
3290
|
+
case ".json":
|
|
3291
|
+
return "json";
|
|
3292
|
+
case ".toml":
|
|
3293
|
+
return "toml";
|
|
3294
|
+
default:
|
|
3295
|
+
throw new Error(`Unsupported config format for ${filePath}. Use --env, --yaml, --json, --toml, or --config with a supported extension.`);
|
|
3296
|
+
}
|
|
3297
|
+
}
|
|
3298
|
+
function buildPrefixSegments(prefix) {
|
|
3299
|
+
if (!prefix) {
|
|
3300
|
+
return [];
|
|
3301
|
+
}
|
|
3302
|
+
return prefix.split(".").map((segment) => normalizePathSegment(segment)).filter(Boolean);
|
|
3303
|
+
}
|
|
3304
|
+
function formatProposals(proposed) {
|
|
3305
|
+
if (proposed.length === 0) {
|
|
3306
|
+
return ["No value mappings were discovered."];
|
|
3307
|
+
}
|
|
3308
|
+
return proposed.map((entry) => {
|
|
3309
|
+
const renderedValue = typeof entry.value === "string" ? JSON.stringify(entry.value) : JSON.stringify(entry.value);
|
|
3310
|
+
return ` ${entry.source || entry.path} -> ${entry.key} = ${renderedValue}${entry.warning ? ` [${entry.warning}]` : ""}`;
|
|
3311
|
+
});
|
|
3312
|
+
}
|
|
3313
|
+
async function promptForMaterialize() {
|
|
3314
|
+
const rl = readline.createInterface({
|
|
3315
|
+
input: process.stdin,
|
|
3316
|
+
output: process.stdout
|
|
3317
|
+
});
|
|
3318
|
+
try {
|
|
3319
|
+
const answer = (await rl.question("Materialize these values into value.*? [Y/n] ")).trim().toLowerCase();
|
|
3320
|
+
return answer === "" || answer === "y" || answer === "yes";
|
|
3321
|
+
} finally {
|
|
3322
|
+
rl.close();
|
|
3323
|
+
}
|
|
3324
|
+
}
|
|
3325
|
+
function isInteractive(options) {
|
|
3326
|
+
const processEnv = options.processEnv ?? process.env;
|
|
3327
|
+
return !processEnv.CI && process.stdin.isTTY && process.stdout.isTTY;
|
|
3328
|
+
}
|
|
3329
|
+
function resolveSourceInputs(root, cliArgs) {
|
|
3330
|
+
const envFile = consumeOption(cliArgs, "--env");
|
|
3331
|
+
const yamlFile = consumeOption(cliArgs, "--yaml");
|
|
3332
|
+
const jsonFile = consumeOption(cliArgs, "--json");
|
|
3333
|
+
const tomlFile = consumeOption(cliArgs, "--toml");
|
|
3334
|
+
const configFile = consumeOption(cliArgs, "--config");
|
|
3335
|
+
const explicit = [
|
|
3336
|
+
envFile ? { kind: "env", filePath: envFile } : void 0,
|
|
3337
|
+
yamlFile ? { kind: "yaml", filePath: yamlFile } : void 0,
|
|
3338
|
+
jsonFile ? { kind: "json", filePath: jsonFile } : void 0,
|
|
3339
|
+
tomlFile ? { kind: "toml", filePath: tomlFile } : void 0,
|
|
3340
|
+
configFile ? { kind: detectKindFromPath(configFile), filePath: configFile } : void 0
|
|
3341
|
+
].filter(Boolean);
|
|
3342
|
+
if (explicit.length > 1) {
|
|
3343
|
+
throw new Error("Use only one explicit source flag per onboard invocation.");
|
|
3344
|
+
}
|
|
3345
|
+
if (explicit.length === 1) {
|
|
3346
|
+
const source = explicit[0];
|
|
3347
|
+
if (!source) {
|
|
3348
|
+
return [];
|
|
3349
|
+
}
|
|
3350
|
+
const resolvedPath = path13.resolve(root, source.filePath);
|
|
3351
|
+
return [
|
|
3352
|
+
{
|
|
3353
|
+
kind: source.kind,
|
|
3354
|
+
filePath: resolvedPath,
|
|
3355
|
+
displayName: path13.basename(resolvedPath)
|
|
3356
|
+
}
|
|
3357
|
+
];
|
|
3358
|
+
}
|
|
3359
|
+
return [];
|
|
3360
|
+
}
|
|
3093
3361
|
async function runOnboard(options = {}) {
|
|
3094
3362
|
const root = path13.resolve(options.root ?? process.cwd());
|
|
3095
|
-
const workspace = options.workspace ?? path13.basename(root);
|
|
3096
3363
|
const cliArgs = [...options.cliArgs ?? []];
|
|
3097
3364
|
const move = consumeFlag(cliArgs, "--move");
|
|
3365
|
+
const materialize = consumeFlag(cliArgs, "--materialize");
|
|
3366
|
+
const sourceOnly = consumeFlag(cliArgs, "--source-only");
|
|
3367
|
+
const prefix = consumeOption(cliArgs, "--prefix");
|
|
3368
|
+
if (materialize && sourceOnly) {
|
|
3369
|
+
throw new Error("Use either --materialize or --source-only, not both.");
|
|
3370
|
+
}
|
|
3371
|
+
const explicitSources = resolveSourceInputs(root, cliArgs);
|
|
3098
3372
|
if (cliArgs.length > 0) {
|
|
3099
3373
|
throw new Error(`Unsupported onboard arguments: ${cliArgs.join(" ")}`);
|
|
3100
3374
|
}
|
|
3101
|
-
|
|
3102
|
-
const
|
|
3103
|
-
|
|
3375
|
+
let scaffolded = [];
|
|
3376
|
+
const manifestPath = path13.join(root, ".cnos", "cnos.yml");
|
|
3377
|
+
if (!await exists(manifestPath)) {
|
|
3378
|
+
const scaffold = await scaffoldProject(root, {
|
|
3379
|
+
mode: options.workspace && options.workspace !== "base" ? "workspace" : "regular",
|
|
3380
|
+
...options.workspace && options.workspace !== "base" ? { workspaces: [options.workspace] } : {}
|
|
3381
|
+
});
|
|
3382
|
+
scaffolded = scaffold.created;
|
|
3383
|
+
}
|
|
3384
|
+
const loaded = await loadManifest5({
|
|
3385
|
+
root,
|
|
3386
|
+
...options.processEnv ? { processEnv: options.processEnv } : {}
|
|
3387
|
+
});
|
|
3388
|
+
const isWorkspaceMode = Object.keys(loaded.manifest.workspaces.items).length > 0;
|
|
3389
|
+
const selectedWorkspace = isWorkspaceMode ? options.workspace ?? (loaded.manifest.workspaces.items.base ? "base" : loaded.manifest.workspaces.default ?? "base") : "base";
|
|
3390
|
+
if (isWorkspaceMode && !loaded.manifest.workspaces.items[selectedWorkspace]) {
|
|
3391
|
+
throw new Error(`Workspace "${selectedWorkspace}" does not exist in this CNOS root.`);
|
|
3392
|
+
}
|
|
3393
|
+
if (!isWorkspaceMode && options.workspace && options.workspace !== "base") {
|
|
3394
|
+
throw new Error("This repo is still in regular mode. Run `cnos workspace enable` before onboarding into a child workspace.");
|
|
3395
|
+
}
|
|
3396
|
+
const envRoot = isWorkspaceMode ? path13.join(root, ".cnos", "workspaces", selectedWorkspace, "env") : path13.join(root, ".cnos", "env");
|
|
3397
|
+
await mkdir5(envRoot, { recursive: true });
|
|
3398
|
+
const rootFiles = explicitSources.length > 0 ? explicitSources : (await listRootEnvFiles(root)).map((fileName) => ({
|
|
3399
|
+
kind: "env",
|
|
3400
|
+
filePath: path13.join(root, fileName),
|
|
3401
|
+
displayName: fileName
|
|
3402
|
+
}));
|
|
3104
3403
|
const imported = [];
|
|
3105
3404
|
const skipped = [];
|
|
3106
|
-
|
|
3107
|
-
|
|
3108
|
-
|
|
3405
|
+
const prefixSegments = buildPrefixSegments(prefix);
|
|
3406
|
+
const proposed = [];
|
|
3407
|
+
for (const source of rootFiles) {
|
|
3408
|
+
const targetPath = path13.join(envRoot, source.displayName);
|
|
3109
3409
|
try {
|
|
3110
|
-
await copyFile(
|
|
3410
|
+
await copyFile(source.filePath, targetPath);
|
|
3111
3411
|
imported.push(path13.relative(root, targetPath).replace(/\\/g, "/"));
|
|
3112
3412
|
if (move) {
|
|
3113
|
-
await rm2(
|
|
3413
|
+
await rm2(source.filePath);
|
|
3114
3414
|
}
|
|
3115
3415
|
} catch {
|
|
3116
|
-
skipped.push(
|
|
3416
|
+
skipped.push(source.displayName);
|
|
3417
|
+
continue;
|
|
3418
|
+
}
|
|
3419
|
+
proposed.push(...await parseSource(source, prefixSegments));
|
|
3420
|
+
}
|
|
3421
|
+
const shouldMaterialize = materialize || (!sourceOnly && isInteractive(options) && proposed.length > 0 ? await promptForMaterialize() : false);
|
|
3422
|
+
const materialized = [];
|
|
3423
|
+
if (shouldMaterialize) {
|
|
3424
|
+
for (const entry of proposed) {
|
|
3425
|
+
await defineValue("value", entry.path, String(entry.value ?? ""), {
|
|
3426
|
+
root,
|
|
3427
|
+
...isWorkspaceMode ? { workspace: selectedWorkspace } : {},
|
|
3428
|
+
parsedValue: entry.value
|
|
3429
|
+
});
|
|
3430
|
+
materialized.push(entry.key);
|
|
3117
3431
|
}
|
|
3118
3432
|
}
|
|
3119
3433
|
const result = {
|
|
3120
3434
|
root,
|
|
3121
|
-
workspace,
|
|
3122
|
-
|
|
3435
|
+
workspace: selectedWorkspace,
|
|
3436
|
+
mode: move ? "move" : "copy",
|
|
3437
|
+
storageMode: isWorkspaceMode ? "workspace" : "regular",
|
|
3438
|
+
scaffolded,
|
|
3123
3439
|
imported,
|
|
3124
3440
|
skipped,
|
|
3125
|
-
|
|
3441
|
+
proposed,
|
|
3442
|
+
materialized
|
|
3126
3443
|
};
|
|
3127
3444
|
if (options.json) {
|
|
3128
3445
|
return printJson(result);
|
|
3129
3446
|
}
|
|
3130
|
-
const
|
|
3131
|
-
|
|
3132
|
-
|
|
3447
|
+
const lines = [
|
|
3448
|
+
`onboarded ${selectedWorkspace} at ${root}`,
|
|
3449
|
+
`Imported ${imported.length} source file(s) into ${path13.relative(root, envRoot).replace(/\\/g, "/") || ".cnos/env"} using ${result.mode}.`,
|
|
3450
|
+
"",
|
|
3451
|
+
`Discovered ${proposed.length} proposed value mapping(s):`,
|
|
3452
|
+
...formatProposals(proposed)
|
|
3453
|
+
];
|
|
3454
|
+
if (!shouldMaterialize && !sourceOnly && !isInteractive(options) && proposed.length > 0) {
|
|
3455
|
+
lines.push("", "Non-interactive mode detected; defaulted to source-only. Re-run with --materialize to write value.* keys.");
|
|
3456
|
+
} else if (shouldMaterialize) {
|
|
3457
|
+
lines.push("", `Materialized ${materialized.length} value key(s).`);
|
|
3458
|
+
} else if (sourceOnly) {
|
|
3459
|
+
lines.push("", "Skipped value materialization because --source-only was set.");
|
|
3460
|
+
}
|
|
3461
|
+
if (skipped.length > 0) {
|
|
3462
|
+
lines.push("", `Skipped ${skipped.length} source file(s): ${skipped.join(", ")}`);
|
|
3463
|
+
}
|
|
3464
|
+
return lines.join("\n");
|
|
3133
3465
|
}
|
|
3134
3466
|
|
|
3135
3467
|
// src/commands/profile.ts
|
|
3136
3468
|
import path16 from "path";
|
|
3137
3469
|
|
|
3138
3470
|
// src/services/context.ts
|
|
3139
|
-
import { readFile as
|
|
3471
|
+
import { readFile as readFile5, writeFile as writeFile5 } from "fs/promises";
|
|
3140
3472
|
import path14 from "path";
|
|
3141
|
-
import { parseYaml as
|
|
3473
|
+
import { parseYaml as parseYaml4, stringifyYaml as stringifyYaml3 } from "@kitsy/cnos/internal";
|
|
3142
3474
|
async function loadCliContext(root = process.cwd()) {
|
|
3143
3475
|
const filePath = path14.join(path14.resolve(root), ".cnos-workspace.yml");
|
|
3144
3476
|
try {
|
|
3145
|
-
const source = await
|
|
3146
|
-
const parsed =
|
|
3477
|
+
const source = await readFile5(filePath, "utf8");
|
|
3478
|
+
const parsed = parseYaml4(source);
|
|
3147
3479
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
3148
3480
|
return {};
|
|
3149
3481
|
}
|
|
@@ -3172,21 +3504,21 @@ async function saveCliContext(options = {}) {
|
|
|
3172
3504
|
}
|
|
3173
3505
|
|
|
3174
3506
|
// src/services/profiles.ts
|
|
3175
|
-
import { mkdir as
|
|
3507
|
+
import { mkdir as mkdir6, readdir as readdir4, readFile as readFile6, rm as rm3, writeFile as writeFile6 } from "fs/promises";
|
|
3176
3508
|
import path15 from "path";
|
|
3177
|
-
import { loadManifest as
|
|
3509
|
+
import { loadManifest as loadManifest6, parseYaml as parseYaml5, stringifyYaml as stringifyYaml4 } from "@kitsy/cnos/internal";
|
|
3178
3510
|
async function resolveProfilesRoot(root = process.cwd()) {
|
|
3179
3511
|
try {
|
|
3180
|
-
const loadedManifest = await
|
|
3512
|
+
const loadedManifest = await loadManifest6({ root });
|
|
3181
3513
|
return path15.join(loadedManifest.manifestRoot, "profiles");
|
|
3182
3514
|
} catch {
|
|
3183
|
-
const loadedManifest = await
|
|
3515
|
+
const loadedManifest = await loadManifest6({ cwd: root });
|
|
3184
3516
|
return path15.join(loadedManifest.manifestRoot, "profiles");
|
|
3185
3517
|
}
|
|
3186
3518
|
}
|
|
3187
3519
|
async function createProfileDefinition(root = process.cwd(), profile, inherit, options = {}) {
|
|
3188
3520
|
const filePath = path15.join(await resolveProfilesRoot(root), `${profile}.yml`);
|
|
3189
|
-
await
|
|
3521
|
+
await mkdir6(path15.dirname(filePath), { recursive: true });
|
|
3190
3522
|
const document = options.noInherit ? {
|
|
3191
3523
|
name: profile,
|
|
3192
3524
|
activate: {
|
|
@@ -3246,7 +3578,7 @@ async function readProfileDefinition(root = process.cwd(), profile = "base") {
|
|
|
3246
3578
|
}
|
|
3247
3579
|
const filePath = path15.join(await resolveProfilesRoot(root), `${profile}.yml`);
|
|
3248
3580
|
try {
|
|
3249
|
-
return
|
|
3581
|
+
return parseYaml5(await readFile6(filePath, "utf8")) ?? void 0;
|
|
3250
3582
|
} catch {
|
|
3251
3583
|
return void 0;
|
|
3252
3584
|
}
|
|
@@ -3326,7 +3658,7 @@ import path17 from "path";
|
|
|
3326
3658
|
import { writeFile as writeFile7 } from "fs/promises";
|
|
3327
3659
|
import {
|
|
3328
3660
|
ensureProjectionAllowed,
|
|
3329
|
-
loadManifest as
|
|
3661
|
+
loadManifest as loadManifest7,
|
|
3330
3662
|
stringifyYaml as stringifyYaml5
|
|
3331
3663
|
} from "@kitsy/cnos/internal";
|
|
3332
3664
|
function normalizeTarget(value) {
|
|
@@ -3355,7 +3687,7 @@ async function runPromote(args = [], options = {}) {
|
|
|
3355
3687
|
throw new Error("promote --to env requires --as <ENV_VAR>");
|
|
3356
3688
|
}
|
|
3357
3689
|
}
|
|
3358
|
-
const loadedManifest = await
|
|
3690
|
+
const loadedManifest = await loadManifest7({
|
|
3359
3691
|
...options.root ? { root: options.root } : {},
|
|
3360
3692
|
...options.cwd ? { cwd: options.cwd } : {},
|
|
3361
3693
|
...options.processEnv ? { processEnv: options.processEnv } : {},
|
|
@@ -3522,7 +3854,7 @@ import {
|
|
|
3522
3854
|
createSecretVault,
|
|
3523
3855
|
deriveVaultKey,
|
|
3524
3856
|
listLocalSecrets,
|
|
3525
|
-
loadManifest as
|
|
3857
|
+
loadManifest as loadManifest8,
|
|
3526
3858
|
listSecretVaults,
|
|
3527
3859
|
readVaultMetadata,
|
|
3528
3860
|
resolveSecretStoreRoot as resolveSecretStoreRoot2,
|
|
@@ -3561,7 +3893,7 @@ async function createVaultDefinition(name, options = {}) {
|
|
|
3561
3893
|
if (provider === "local" && (options.noPassphrase ?? false)) {
|
|
3562
3894
|
throw new Error("Local vaults cannot be passwordless.");
|
|
3563
3895
|
}
|
|
3564
|
-
const loadedManifest = await
|
|
3896
|
+
const loadedManifest = await loadManifest8({
|
|
3565
3897
|
...options.root ? { root: options.root } : {},
|
|
3566
3898
|
...options.cwd ? { cwd: options.cwd } : {},
|
|
3567
3899
|
...options.processEnv ? { processEnv: options.processEnv } : {},
|
|
@@ -3598,7 +3930,7 @@ async function createVaultDefinition(name, options = {}) {
|
|
|
3598
3930
|
};
|
|
3599
3931
|
}
|
|
3600
3932
|
async function listVaultDefinitions(options = {}) {
|
|
3601
|
-
const loadedManifest = await
|
|
3933
|
+
const loadedManifest = await loadManifest8({
|
|
3602
3934
|
...options.root ? { root: options.root } : {},
|
|
3603
3935
|
...options.cwd ? { cwd: options.cwd } : {},
|
|
3604
3936
|
...options.processEnv ? { processEnv: options.processEnv } : {},
|
|
@@ -3619,7 +3951,7 @@ async function listVaultDefinitions(options = {}) {
|
|
|
3619
3951
|
async function removeVaultDefinition(name, options = {}) {
|
|
3620
3952
|
await assertWritableConfigRoot(`remove vault ${name}`, options);
|
|
3621
3953
|
const vault = name.trim() || "default";
|
|
3622
|
-
const loadedManifest = await
|
|
3954
|
+
const loadedManifest = await loadManifest8({
|
|
3623
3955
|
...options.root ? { root: options.root } : {},
|
|
3624
3956
|
...options.cwd ? { cwd: options.cwd } : {},
|
|
3625
3957
|
...options.processEnv ? { processEnv: options.processEnv } : {},
|
|
@@ -3665,7 +3997,7 @@ async function listLocalStoreVaults(options = {}) {
|
|
|
3665
3997
|
}
|
|
3666
3998
|
async function authenticateVault(name, options = {}) {
|
|
3667
3999
|
const vault = name.trim() || "default";
|
|
3668
|
-
const loadedManifest = await
|
|
4000
|
+
const loadedManifest = await loadManifest8({
|
|
3669
4001
|
...options.root ? { root: options.root } : {},
|
|
3670
4002
|
...options.cwd ? { cwd: options.cwd } : {},
|
|
3671
4003
|
...options.processEnv ? { processEnv: options.processEnv } : {},
|
|
@@ -3986,7 +4318,7 @@ async function runValidate(options = {}) {
|
|
|
3986
4318
|
// package.json
|
|
3987
4319
|
var package_default = {
|
|
3988
4320
|
name: "@kitsy/cnos-cli",
|
|
3989
|
-
version: "1.8.
|
|
4321
|
+
version: "1.8.2",
|
|
3990
4322
|
description: "CLI entry point and developer tooling for CNOS.",
|
|
3991
4323
|
type: "module",
|
|
3992
4324
|
main: "./dist/index.js",
|
|
@@ -4022,7 +4354,8 @@ var package_default = {
|
|
|
4022
4354
|
access: "public"
|
|
4023
4355
|
},
|
|
4024
4356
|
dependencies: {
|
|
4025
|
-
"@kitsy/cnos": "workspace:*"
|
|
4357
|
+
"@kitsy/cnos": "workspace:*",
|
|
4358
|
+
"smol-toml": "^1.4.2"
|
|
4026
4359
|
},
|
|
4027
4360
|
scripts: {
|
|
4028
4361
|
build: "tsup src/index.ts --format esm --dts",
|
|
@@ -4261,34 +4594,34 @@ async function runWatch(command, options = {}) {
|
|
|
4261
4594
|
}
|
|
4262
4595
|
|
|
4263
4596
|
// src/commands/workspace.ts
|
|
4264
|
-
import { cp, mkdir as
|
|
4597
|
+
import { cp, mkdir as mkdir7, readdir as readdir5, readFile as readFile7, rename, rm as rm5, stat as stat3, writeFile as writeFile9 } from "fs/promises";
|
|
4265
4598
|
import path23 from "path";
|
|
4266
|
-
import { loadManifest as
|
|
4267
|
-
async function
|
|
4599
|
+
import { loadManifest as loadManifest9, parseYaml as parseYaml6, stringifyYaml as stringifyYaml7 } from "@kitsy/cnos/internal";
|
|
4600
|
+
async function exists2(targetPath) {
|
|
4268
4601
|
try {
|
|
4269
|
-
await
|
|
4602
|
+
await stat3(targetPath);
|
|
4270
4603
|
return true;
|
|
4271
4604
|
} catch {
|
|
4272
4605
|
return false;
|
|
4273
4606
|
}
|
|
4274
4607
|
}
|
|
4275
4608
|
async function copyIfExists(source, target) {
|
|
4276
|
-
if (!await
|
|
4609
|
+
if (!await exists2(source)) {
|
|
4277
4610
|
return;
|
|
4278
4611
|
}
|
|
4279
|
-
await
|
|
4612
|
+
await mkdir7(path23.dirname(target), { recursive: true });
|
|
4280
4613
|
await cp(source, target, { recursive: true, force: true });
|
|
4281
4614
|
}
|
|
4282
4615
|
async function moveIfExists(source, target, force = false) {
|
|
4283
|
-
if (!await
|
|
4616
|
+
if (!await exists2(source)) {
|
|
4284
4617
|
return false;
|
|
4285
4618
|
}
|
|
4286
4619
|
if (force) {
|
|
4287
4620
|
await rm5(target, { recursive: true, force: true });
|
|
4288
|
-
} else if (await
|
|
4621
|
+
} else if (await exists2(target)) {
|
|
4289
4622
|
throw new Error(`Refusing to overwrite existing path ${target}. Use --force to replace it.`);
|
|
4290
4623
|
}
|
|
4291
|
-
await
|
|
4624
|
+
await mkdir7(path23.dirname(target), { recursive: true });
|
|
4292
4625
|
await rename(source, target);
|
|
4293
4626
|
return true;
|
|
4294
4627
|
}
|
|
@@ -4332,13 +4665,16 @@ function splitExtends(value) {
|
|
|
4332
4665
|
if (!value) {
|
|
4333
4666
|
return void 0;
|
|
4334
4667
|
}
|
|
4668
|
+
if (value.trim() === "none") {
|
|
4669
|
+
return [];
|
|
4670
|
+
}
|
|
4335
4671
|
const items = value.split(",").map((entry) => entry.trim()).filter(Boolean);
|
|
4336
4672
|
return items.length > 0 ? items : void 0;
|
|
4337
4673
|
}
|
|
4338
4674
|
async function hasDirectConfigData(cnosRoot) {
|
|
4339
4675
|
for (const folderName of ["values", "secrets", "env", "profiles"]) {
|
|
4340
4676
|
const folder = path23.join(cnosRoot, folderName);
|
|
4341
|
-
if (!await
|
|
4677
|
+
if (!await exists2(folder)) {
|
|
4342
4678
|
continue;
|
|
4343
4679
|
}
|
|
4344
4680
|
const entries = await readdir5(folder, { withFileTypes: true });
|
|
@@ -4348,14 +4684,39 @@ async function hasDirectConfigData(cnosRoot) {
|
|
|
4348
4684
|
}
|
|
4349
4685
|
return false;
|
|
4350
4686
|
}
|
|
4687
|
+
async function updateRootAnchorToWorkspace(packageRoot, workspaceId) {
|
|
4688
|
+
const anchorPath = path23.join(packageRoot, ".cnosrc.yml");
|
|
4689
|
+
const current = await exists2(anchorPath) ? parseYaml6(await readFile7(anchorPath, "utf8")) : void 0;
|
|
4690
|
+
await writeFile9(
|
|
4691
|
+
anchorPath,
|
|
4692
|
+
stringifyYaml7({
|
|
4693
|
+
root: typeof current?.root === "string" ? current.root : "./.cnos",
|
|
4694
|
+
workspace: workspaceId
|
|
4695
|
+
}),
|
|
4696
|
+
"utf8"
|
|
4697
|
+
);
|
|
4698
|
+
}
|
|
4699
|
+
async function updateWorkspaceContext(packageRoot, workspaceId) {
|
|
4700
|
+
const workspacePath = path23.join(packageRoot, ".cnos-workspace.yml");
|
|
4701
|
+
const current = await exists2(workspacePath) ? parseYaml6(await readFile7(workspacePath, "utf8")) : void 0;
|
|
4702
|
+
await writeFile9(
|
|
4703
|
+
workspacePath,
|
|
4704
|
+
stringifyYaml7({
|
|
4705
|
+
workspace: workspaceId,
|
|
4706
|
+
...typeof current?.profile === "string" ? { profile: current.profile } : {},
|
|
4707
|
+
...typeof current?.globalRoot === "string" ? { globalRoot: current.globalRoot } : { globalRoot: "~/.cnos" }
|
|
4708
|
+
}),
|
|
4709
|
+
"utf8"
|
|
4710
|
+
);
|
|
4711
|
+
}
|
|
4351
4712
|
async function runDetach(packageRoot, options = {}) {
|
|
4352
|
-
const loaded = await
|
|
4713
|
+
const loaded = await loadManifest9({ cwd: packageRoot });
|
|
4353
4714
|
if (!loaded.anchorPath || !loaded.anchoredWorkspace) {
|
|
4354
4715
|
throw new Error("workspace detach requires a package-local .cnosrc.yml with a workspace binding");
|
|
4355
4716
|
}
|
|
4356
4717
|
const targetCnosRoot = path23.join(packageRoot, ".cnos");
|
|
4357
4718
|
const force = consumeFlag([...options.cliArgs ?? []], "--force");
|
|
4358
|
-
if (await
|
|
4719
|
+
if (await exists2(targetCnosRoot) && !force) {
|
|
4359
4720
|
throw new Error(`Refusing to detach because ${displayPath(targetCnosRoot, packageRoot)} already exists. Use --force to overwrite.`);
|
|
4360
4721
|
}
|
|
4361
4722
|
if (force) {
|
|
@@ -4367,7 +4728,7 @@ async function runDetach(packageRoot, options = {}) {
|
|
|
4367
4728
|
workspace: loaded.anchoredWorkspace
|
|
4368
4729
|
});
|
|
4369
4730
|
const localRoots = runtime.graph.workspace.workspaceRoots.filter((root) => root.scope === "local").map((root) => root.path);
|
|
4370
|
-
await
|
|
4731
|
+
await mkdir7(targetCnosRoot, { recursive: true });
|
|
4371
4732
|
await mergeWorkspaceRootsIntoStandalone(targetCnosRoot, localRoots);
|
|
4372
4733
|
await writeFile9(
|
|
4373
4734
|
path23.join(targetCnosRoot, "cnos.yml"),
|
|
@@ -4400,15 +4761,15 @@ async function runAttach(packageRoot, options = {}) {
|
|
|
4400
4761
|
const force = consumeFlag(cliArgs, "--force");
|
|
4401
4762
|
const childCnosRoot = path23.join(packageRoot, ".cnos");
|
|
4402
4763
|
const markerPath = path23.join(childCnosRoot, ".detached");
|
|
4403
|
-
if (!await
|
|
4764
|
+
if (!await exists2(markerPath)) {
|
|
4404
4765
|
throw new Error("workspace attach requires a detached package with .cnos/.detached");
|
|
4405
4766
|
}
|
|
4406
|
-
const marker =
|
|
4767
|
+
const marker = parseYaml6(await readFile7(markerPath, "utf8"));
|
|
4407
4768
|
if (!marker?.originalCnosrc?.root || !marker.detachedWorkspace) {
|
|
4408
4769
|
throw new Error("Invalid .detached marker");
|
|
4409
4770
|
}
|
|
4410
4771
|
const parentManifestRoot = path23.resolve(packageRoot, marker.originalCnosrc.root);
|
|
4411
|
-
const parentLoaded = await
|
|
4772
|
+
const parentLoaded = await loadManifest9({ root: parentManifestRoot });
|
|
4412
4773
|
if (parentLoaded.rootResolution.readOnly) {
|
|
4413
4774
|
throw new Error(
|
|
4414
4775
|
`Cannot attach workspace because the parent CNOS root is remote and read-only (${parentLoaded.rootResolution.rootUri}).`
|
|
@@ -4416,13 +4777,13 @@ async function runAttach(packageRoot, options = {}) {
|
|
|
4416
4777
|
}
|
|
4417
4778
|
const workspaceId = marker.originalCnosrc.workspace ?? marker.detachedWorkspace;
|
|
4418
4779
|
const parentWorkspaceRoot = path23.join(parentLoaded.manifestRoot, "workspaces", workspaceId);
|
|
4419
|
-
if (await
|
|
4780
|
+
if (await exists2(parentWorkspaceRoot) && !force) {
|
|
4420
4781
|
throw new Error(`workspace "${workspaceId}" already exists in parent root. Use --force to overwrite.`);
|
|
4421
4782
|
}
|
|
4422
4783
|
if (force) {
|
|
4423
4784
|
await rm5(parentWorkspaceRoot, { recursive: true, force: true });
|
|
4424
4785
|
}
|
|
4425
|
-
await
|
|
4786
|
+
await mkdir7(parentWorkspaceRoot, { recursive: true });
|
|
4426
4787
|
for (const folderName of ["values", "secrets", "env", "profiles"]) {
|
|
4427
4788
|
await copyIfExists(path23.join(childCnosRoot, folderName), path23.join(parentWorkspaceRoot, folderName));
|
|
4428
4789
|
}
|
|
@@ -4448,7 +4809,7 @@ async function runAttach(packageRoot, options = {}) {
|
|
|
4448
4809
|
return `attached workspace ${workspaceId} to ${displayPath(parentLoaded.manifestRoot, packageRoot)}`;
|
|
4449
4810
|
}
|
|
4450
4811
|
async function runList2(manifestCwd, options = {}) {
|
|
4451
|
-
const loaded = await
|
|
4812
|
+
const loaded = await loadManifest9({
|
|
4452
4813
|
...options.root ? { root: options.root } : {},
|
|
4453
4814
|
cwd: manifestCwd,
|
|
4454
4815
|
...options.processEnv ? { processEnv: options.processEnv } : {}
|
|
@@ -4476,15 +4837,66 @@ async function runList2(manifestCwd, options = {}) {
|
|
|
4476
4837
|
return `${entry.id}${tags.length > 0 ? ` (${tags.join(", ")})` : ""}`;
|
|
4477
4838
|
}).join("\n");
|
|
4478
4839
|
}
|
|
4840
|
+
async function runEnable(manifestCwd, packageRoot, options = {}) {
|
|
4841
|
+
const cliArgs = [...options.cliArgs ?? []];
|
|
4842
|
+
if (cliArgs.length > 0) {
|
|
4843
|
+
throw new Error(`Unsupported workspace arguments: ${cliArgs.join(" ")}`);
|
|
4844
|
+
}
|
|
4845
|
+
const loaded = await loadManifest9({
|
|
4846
|
+
...options.root ? { root: options.root } : {},
|
|
4847
|
+
cwd: manifestCwd,
|
|
4848
|
+
...options.processEnv ? { processEnv: options.processEnv } : {}
|
|
4849
|
+
});
|
|
4850
|
+
if (loaded.rootResolution.readOnly) {
|
|
4851
|
+
throw new Error(
|
|
4852
|
+
`Cannot enable workspace mode because the active CNOS root is remote and read-only (${loaded.rootResolution.rootUri}). Clone the config repo and edit it directly.`
|
|
4853
|
+
);
|
|
4854
|
+
}
|
|
4855
|
+
const rawManifest = structuredClone(loaded.rawManifest);
|
|
4856
|
+
const rawWorkspaces = rawManifest.workspaces ?? {};
|
|
4857
|
+
const rawItems = rawWorkspaces.items ?? {};
|
|
4858
|
+
if (Object.keys(rawItems).length > 0) {
|
|
4859
|
+
throw new Error("This CNOS root is already in workspace mode.");
|
|
4860
|
+
}
|
|
4861
|
+
const cnosRoot = loaded.manifestRoot;
|
|
4862
|
+
const baseWorkspaceRoot = path23.join(cnosRoot, "workspaces", "base");
|
|
4863
|
+
if (await exists2(baseWorkspaceRoot)) {
|
|
4864
|
+
throw new Error("Cannot enable workspace mode because .cnos/workspaces/base already exists.");
|
|
4865
|
+
}
|
|
4866
|
+
const moved = [];
|
|
4867
|
+
for (const folderName of ["values", "secrets", "env", "profiles"]) {
|
|
4868
|
+
if (await moveIfExists(path23.join(cnosRoot, folderName), path23.join(baseWorkspaceRoot, folderName))) {
|
|
4869
|
+
moved.push(folderName);
|
|
4870
|
+
}
|
|
4871
|
+
}
|
|
4872
|
+
await ensureWorkspaceLayout(cnosRoot, "base");
|
|
4873
|
+
rawWorkspaces.default = "base";
|
|
4874
|
+
rawWorkspaces.items = {
|
|
4875
|
+
base: {}
|
|
4876
|
+
};
|
|
4877
|
+
rawManifest.workspaces = rawWorkspaces;
|
|
4878
|
+
await writeFile9(path23.join(cnosRoot, "cnos.yml"), stringifyYaml7(rawManifest), "utf8");
|
|
4879
|
+
await updateRootAnchorToWorkspace(packageRoot, "base");
|
|
4880
|
+
await updateWorkspaceContext(packageRoot, "base");
|
|
4881
|
+
await ensureGitignore(path23.dirname(cnosRoot));
|
|
4882
|
+
if (options.json) {
|
|
4883
|
+
return printJson({
|
|
4884
|
+
root: path23.dirname(cnosRoot),
|
|
4885
|
+
workspace: "base",
|
|
4886
|
+
moved
|
|
4887
|
+
});
|
|
4888
|
+
}
|
|
4889
|
+
const movedSummary = moved.length > 0 ? `; moved ${moved.join(", ")} into .cnos/workspaces/base` : "";
|
|
4890
|
+
return `enabled workspace mode at ${displayPath(path23.dirname(cnosRoot), packageRoot)} with base workspace${movedSummary}`;
|
|
4891
|
+
}
|
|
4479
4892
|
async function runAddOrScaffold(action, workspaceId, manifestCwd, packageRoot, options = {}) {
|
|
4480
4893
|
const cliArgs = [...options.cliArgs ?? []];
|
|
4481
4894
|
const extendsOption = splitExtends(consumeOption(cliArgs, "--extends"));
|
|
4482
|
-
const onboardCurrent = consumeFlag(cliArgs, "--onboard-current");
|
|
4483
4895
|
const force = consumeFlag(cliArgs, "--force");
|
|
4484
4896
|
if (cliArgs.length > 0) {
|
|
4485
4897
|
throw new Error(`Unsupported workspace arguments: ${cliArgs.join(" ")}`);
|
|
4486
4898
|
}
|
|
4487
|
-
const loaded = await
|
|
4899
|
+
const loaded = await loadManifest9({
|
|
4488
4900
|
...options.root ? { root: options.root } : {},
|
|
4489
4901
|
cwd: manifestCwd,
|
|
4490
4902
|
...options.processEnv ? { processEnv: options.processEnv } : {}
|
|
@@ -4501,9 +4913,9 @@ async function runAddOrScaffold(action, workspaceId, manifestCwd, packageRoot, o
|
|
|
4501
4913
|
const rawItems = rawWorkspaces.items ?? {};
|
|
4502
4914
|
const isWorkspaceMode = Object.keys(rawItems).length > 0;
|
|
4503
4915
|
const directConfigPresent = await hasDirectConfigData(cnosRoot);
|
|
4504
|
-
if (!isWorkspaceMode
|
|
4916
|
+
if (!isWorkspaceMode || directConfigPresent) {
|
|
4505
4917
|
throw new Error(
|
|
4506
|
-
"This CNOS root is
|
|
4918
|
+
"This CNOS root is not ready for child workspaces yet. Run `cnos workspace enable` first to convert the flat project into workspace mode."
|
|
4507
4919
|
);
|
|
4508
4920
|
}
|
|
4509
4921
|
if (rawItems[workspaceId] && !force) {
|
|
@@ -4515,26 +4927,15 @@ async function runAddOrScaffold(action, workspaceId, manifestCwd, packageRoot, o
|
|
|
4515
4927
|
rawWorkspaces.default = rawWorkspaces.default ?? workspaceId;
|
|
4516
4928
|
rawManifest.workspaces = rawWorkspaces;
|
|
4517
4929
|
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
4930
|
const created = await ensureWorkspaceLayout(cnosRoot, workspaceId);
|
|
4527
4931
|
await writeFile9(path23.join(cnosRoot, "cnos.yml"), stringifyYaml7(rawManifest), "utf8");
|
|
4528
4932
|
await ensureGitignore(path23.dirname(cnosRoot));
|
|
4529
4933
|
await writeAnchor(packageRoot, cnosRoot, workspaceId);
|
|
4530
|
-
await
|
|
4531
|
-
globalRoot: ~/.cnos
|
|
4532
|
-
`);
|
|
4934
|
+
await updateWorkspaceContext(packageRoot, workspaceId);
|
|
4533
4935
|
const result = {
|
|
4534
4936
|
workspace: workspaceId,
|
|
4535
4937
|
root: path23.dirname(cnosRoot),
|
|
4536
4938
|
packageRoot,
|
|
4537
|
-
onboarded: onboardCurrent,
|
|
4538
4939
|
created
|
|
4539
4940
|
};
|
|
4540
4941
|
if (options.json) {
|
|
@@ -4549,7 +4950,7 @@ async function runRemove(workspaceId, manifestCwd, options = {}) {
|
|
|
4549
4950
|
if (cliArgs.length > 0) {
|
|
4550
4951
|
throw new Error(`Unsupported workspace arguments: ${cliArgs.join(" ")}`);
|
|
4551
4952
|
}
|
|
4552
|
-
const loaded = await
|
|
4953
|
+
const loaded = await loadManifest9({
|
|
4553
4954
|
...options.root ? { root: options.root } : {},
|
|
4554
4955
|
cwd: manifestCwd,
|
|
4555
4956
|
...options.processEnv ? { processEnv: options.processEnv } : {}
|
|
@@ -4591,6 +4992,8 @@ async function runWorkspace(args = [], options = {}) {
|
|
|
4591
4992
|
return runAttach(packageRoot, { ...options, cliArgs: baseCliArgs });
|
|
4592
4993
|
case "detach":
|
|
4593
4994
|
return runDetach(packageRoot, { ...options, cliArgs: baseCliArgs });
|
|
4995
|
+
case "enable":
|
|
4996
|
+
return runEnable(manifestCwd, packageRoot, { ...options, cliArgs: baseCliArgs });
|
|
4594
4997
|
case "list":
|
|
4595
4998
|
return runList2(manifestCwd, options);
|
|
4596
4999
|
case "add":
|
|
@@ -4634,7 +5037,7 @@ function resolveHelpTopic(command, args) {
|
|
|
4634
5037
|
if (command === "dev" && args[0] === "env") {
|
|
4635
5038
|
return normalizeHelpTopic([command, args[0]]);
|
|
4636
5039
|
}
|
|
4637
|
-
if (command === "workspace" && args[0] && ["attach", "detach", "add", "list", "remove", "delete", "scaffold"].includes(args[0])) {
|
|
5040
|
+
if (command === "workspace" && args[0] && ["attach", "detach", "add", "list", "remove", "delete", "scaffold", "enable"].includes(args[0])) {
|
|
4638
5041
|
return normalizeHelpTopic([command, args[0] === "delete" ? "remove" : args[0]]);
|
|
4639
5042
|
}
|
|
4640
5043
|
if (command === "vault" && args[0] && ["create", "add", "list", "delete", "remove"].includes(args[0])) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kitsy/cnos-cli",
|
|
3
|
-
"version": "1.8.
|
|
3
|
+
"version": "1.8.2",
|
|
4
4
|
"description": "CLI entry point and developer tooling for CNOS.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -36,7 +36,8 @@
|
|
|
36
36
|
"access": "public"
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"
|
|
39
|
+
"smol-toml": "^1.4.2",
|
|
40
|
+
"@kitsy/cnos": "1.8.2"
|
|
40
41
|
},
|
|
41
42
|
"scripts": {
|
|
42
43
|
"build": "tsup src/index.ts --format esm --dts",
|