@kitsy/cnos 0.0.1 → 1.0.1

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.
Files changed (47) hide show
  1. package/README.md +3 -1
  2. package/dist/chunk-44JOQPSN.js +109 -0
  3. package/dist/chunk-ASZ7I3JJ.js +35 -0
  4. package/dist/chunk-CGTFH4QQ.js +49 -0
  5. package/dist/chunk-GGYIRIGU.js +83 -0
  6. package/dist/chunk-H65FPTDM.js +23 -0
  7. package/dist/chunk-K2T4R5WH.js +1565 -0
  8. package/dist/chunk-KG6OZX5C.js +202 -0
  9. package/dist/envNaming-BrOk5ndZ.d.cts +8 -0
  10. package/dist/envNaming-DCaNdnrF.d.ts +8 -0
  11. package/dist/index.cjs +1942 -28
  12. package/dist/index.d.cts +7 -3
  13. package/dist/index.d.ts +7 -3
  14. package/dist/index.js +112 -23
  15. package/dist/internal.cjs +288 -0
  16. package/dist/internal.d.cts +20 -0
  17. package/dist/internal.d.ts +20 -0
  18. package/dist/internal.js +18 -0
  19. package/dist/plugin/basic-schema.cjs +214 -3
  20. package/dist/plugin/basic-schema.d.cts +5 -6
  21. package/dist/plugin/basic-schema.d.ts +5 -6
  22. package/dist/plugin/basic-schema.js +7 -2
  23. package/dist/plugin/cli-args.cjs +132 -3
  24. package/dist/plugin/cli-args.d.cts +12 -1
  25. package/dist/plugin/cli-args.d.ts +12 -1
  26. package/dist/plugin/cli-args.js +11 -2
  27. package/dist/plugin/dotenv.cjs +212 -3
  28. package/dist/plugin/dotenv.d.cts +8 -1
  29. package/dist/plugin/dotenv.d.ts +8 -1
  30. package/dist/plugin/dotenv.js +11 -2
  31. package/dist/plugin/env-export.cjs +222 -3
  32. package/dist/plugin/env-export.d.cts +7 -1
  33. package/dist/plugin/env-export.d.ts +7 -1
  34. package/dist/plugin/env-export.js +14 -2
  35. package/dist/plugin/filesystem.cjs +320 -3
  36. package/dist/plugin/filesystem.d.cts +17 -1
  37. package/dist/plugin/filesystem.d.ts +17 -1
  38. package/dist/plugin/filesystem.js +17 -2
  39. package/dist/plugin/process-env.cjs +126 -3
  40. package/dist/plugin/process-env.d.cts +7 -1
  41. package/dist/plugin/process-env.d.ts +7 -1
  42. package/dist/plugin/process-env.js +9 -2
  43. package/dist/plugin-BVNEHj19.d.cts +309 -0
  44. package/dist/plugin-BVNEHj19.d.ts +309 -0
  45. package/dist/toPublicEnv-Dd152fFy.d.cts +7 -0
  46. package/dist/toPublicEnv-Gwz3xTK0.d.ts +7 -0
  47. package/package.json +15 -16
@@ -0,0 +1,1565 @@
1
+ // ../core/src/utils/path.ts
2
+ import { access } from "fs/promises";
3
+ import os from "os";
4
+ import path from "path";
5
+
6
+ // ../core/src/errors.ts
7
+ var CnosError = class extends Error {
8
+ constructor(message) {
9
+ super(message);
10
+ this.name = new.target.name;
11
+ }
12
+ };
13
+ var CnosManifestError = class extends CnosError {
14
+ constructor(message, manifestPath) {
15
+ super(manifestPath ? `${message} (${manifestPath})` : message);
16
+ this.manifestPath = manifestPath;
17
+ }
18
+ manifestPath;
19
+ };
20
+ var CnosKeyNotFoundError = class extends CnosError {
21
+ constructor(key) {
22
+ super(`Missing required CNOS config key: ${key}`);
23
+ this.key = key;
24
+ }
25
+ key;
26
+ };
27
+
28
+ // ../core/src/utils/path.ts
29
+ var PRIMARY_CNOS_DIR = ".cnos";
30
+ var LEGACY_CNOS_DIR = "cnos";
31
+ async function exists(filePath) {
32
+ try {
33
+ await access(filePath);
34
+ return true;
35
+ } catch {
36
+ return false;
37
+ }
38
+ }
39
+ async function resolveCnosRoot(root = process.cwd()) {
40
+ const basePath = path.resolve(root);
41
+ const candidates = [
42
+ path.join(basePath, PRIMARY_CNOS_DIR),
43
+ path.join(basePath, LEGACY_CNOS_DIR),
44
+ basePath
45
+ ];
46
+ for (const candidate of candidates) {
47
+ if (await exists(path.join(candidate, "cnos.yml"))) {
48
+ return candidate;
49
+ }
50
+ }
51
+ throw new CnosManifestError(
52
+ `Could not locate .cnos/cnos.yml or cnos/cnos.yml from root: ${basePath}`
53
+ );
54
+ }
55
+ async function resolveManifestRoot(root = process.cwd()) {
56
+ return resolveCnosRoot(root);
57
+ }
58
+ function interpolatePathTemplate(template, tokens) {
59
+ return Object.entries(tokens).reduce(
60
+ (result, [token, value]) => result.replaceAll(`{${token}}`, value),
61
+ template
62
+ );
63
+ }
64
+ function expandHomePath(targetPath) {
65
+ if (targetPath === "~") {
66
+ return os.homedir();
67
+ }
68
+ if (targetPath.startsWith("~/") || targetPath.startsWith("~\\")) {
69
+ return path.join(os.homedir(), targetPath.slice(2));
70
+ }
71
+ return targetPath;
72
+ }
73
+ function stripWorkspaceTemplatePrefix(template) {
74
+ const normalized = template.replace(/\\/g, "/").replace(/^\.\//, "");
75
+ const marker = "workspaces/{workspace}";
76
+ if (normalized === marker) {
77
+ return ".";
78
+ }
79
+ if (normalized.startsWith(`${marker}/`)) {
80
+ return normalized.slice(marker.length + 1);
81
+ }
82
+ return template;
83
+ }
84
+ function resolveWorkspaceScopedPath(workspaceRoot, template, tokens) {
85
+ const relativeTemplate = stripWorkspaceTemplatePrefix(template);
86
+ const interpolated = interpolatePathTemplate(relativeTemplate, tokens);
87
+ return path.resolve(workspaceRoot, interpolated);
88
+ }
89
+ function resolveNamespaceDirectory(workspaceRoot, namespace, profile) {
90
+ const rootFolder = namespace === "value" ? "values" : "secrets";
91
+ if (profile && profile !== "base") {
92
+ return path.resolve(workspaceRoot, "profiles", profile, rootFolder);
93
+ }
94
+ return path.resolve(workspaceRoot, rootFolder);
95
+ }
96
+ function resolveConfigDocumentPath(workspaceRoot, namespace, configPath, profile) {
97
+ const namespaceRoot = resolveNamespaceDirectory(workspaceRoot, namespace, profile);
98
+ const fileName = `${configPath.split(".").shift() ?? "app"}.yml`;
99
+ return path.resolve(namespaceRoot, fileName);
100
+ }
101
+ function toPortablePath(targetPath) {
102
+ return targetPath.replace(/\\/g, "/");
103
+ }
104
+ function joinConfigPath(...parts) {
105
+ return parts.flatMap((part) => part.split(".")).map((part) => part.trim()).filter(Boolean).join(".");
106
+ }
107
+ function toLogicalKey(namespace, valuePath) {
108
+ return `${namespace}.${joinConfigPath(valuePath)}`;
109
+ }
110
+ function stripNamespace(key) {
111
+ return key.split(".").slice(1).join(".");
112
+ }
113
+
114
+ // ../core/src/utils/yaml.ts
115
+ import { parse, stringify } from "yaml";
116
+ function parseYaml(source) {
117
+ return parse(source);
118
+ }
119
+ function stringifyYaml(value) {
120
+ return stringify(value);
121
+ }
122
+
123
+ // ../core/src/utils/envNaming.ts
124
+ function normalizeMappingConfig(config = {}) {
125
+ return {
126
+ convention: config.convention,
127
+ explicit: config.explicit ?? {}
128
+ };
129
+ }
130
+ function toScreamingSnakeSegment(segment) {
131
+ return segment.replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/[^A-Za-z0-9]+/g, "_").replace(/_+/g, "_").replace(/^_+|_+$/g, "").toUpperCase();
132
+ }
133
+ function toScreamingSnake(path8) {
134
+ return path8.split(".").map((segment) => toScreamingSnakeSegment(segment)).filter(Boolean).join("_");
135
+ }
136
+ function fromScreamingSnake(path8) {
137
+ return path8.split("_").map((segment) => segment.trim().toLowerCase()).filter(Boolean).join(".");
138
+ }
139
+ function logicalKeyToEnvVar(key, config = {}) {
140
+ const normalized = normalizeMappingConfig(config);
141
+ const explicitEntry = Object.entries(normalized.explicit).find(([, logicalKey]) => logicalKey === key);
142
+ if (explicitEntry) {
143
+ return explicitEntry[0];
144
+ }
145
+ if (normalized.convention !== "SCREAMING_SNAKE") {
146
+ return void 0;
147
+ }
148
+ if (key.startsWith("value.")) {
149
+ return toScreamingSnake(key.slice("value.".length));
150
+ }
151
+ if (key.startsWith("secret.")) {
152
+ return `SECRET_${toScreamingSnake(key.slice("secret.".length))}`;
153
+ }
154
+ return void 0;
155
+ }
156
+ function envVarToLogicalKey(envVar, config = {}) {
157
+ const normalized = normalizeMappingConfig(config);
158
+ const explicitMatch = normalized.explicit[envVar];
159
+ if (explicitMatch) {
160
+ return explicitMatch;
161
+ }
162
+ if (normalized.convention !== "SCREAMING_SNAKE") {
163
+ return void 0;
164
+ }
165
+ if (envVar.startsWith("SECRET_")) {
166
+ const stripped = envVar.slice("SECRET_".length);
167
+ if (!stripped) {
168
+ return void 0;
169
+ }
170
+ return `secret.${fromScreamingSnake(stripped)}`;
171
+ }
172
+ if (!/^[A-Z][A-Z0-9_]*$/.test(envVar)) {
173
+ return void 0;
174
+ }
175
+ return `value.${fromScreamingSnake(envVar)}`;
176
+ }
177
+
178
+ // ../core/src/runtime/toEnv.ts
179
+ function fallbackLogicalKeyToEnvVar(key) {
180
+ if (key.startsWith("value.")) {
181
+ return key.slice("value.".length).replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/[^A-Za-z0-9]+/g, "_").replace(/_+/g, "_").replace(/^_+|_+$/g, "").toUpperCase();
182
+ }
183
+ if (key.startsWith("secret.")) {
184
+ const normalized = key.slice("secret.".length).replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/[^A-Za-z0-9]+/g, "_").replace(/_+/g, "_").replace(/^_+|_+$/g, "").toUpperCase();
185
+ return `SECRET_${normalized}`;
186
+ }
187
+ return key.replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/[^A-Za-z0-9]+/g, "_").replace(/_+/g, "_").replace(/^_+|_+$/g, "").toUpperCase();
188
+ }
189
+ function normalizeEnvValue(value) {
190
+ if (value === void 0 || value === null) {
191
+ return "";
192
+ }
193
+ if (typeof value === "string") {
194
+ return value;
195
+ }
196
+ if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") {
197
+ return String(value);
198
+ }
199
+ return JSON.stringify(value);
200
+ }
201
+ function toEnv(graph, manifest, options = {}) {
202
+ const includeSecrets = options.includeSecrets ?? true;
203
+ const output = {};
204
+ const resolvedEntries = Array.from(graph.entries.values()).sort(
205
+ (left, right) => left.key.localeCompare(right.key)
206
+ );
207
+ for (const entry of resolvedEntries) {
208
+ if (entry.namespace === "meta") {
209
+ continue;
210
+ }
211
+ if (!includeSecrets && entry.namespace === "secret") {
212
+ continue;
213
+ }
214
+ const envVar = logicalKeyToEnvVar(entry.key, manifest.envMapping) ?? fallbackLogicalKeyToEnvVar(entry.key);
215
+ output[envVar] = normalizeEnvValue(entry.value);
216
+ }
217
+ return output;
218
+ }
219
+
220
+ // ../core/src/runtime/toPublicEnv.ts
221
+ function fallbackValueEnvVar(key) {
222
+ return key.replace(/^value\./, "").replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/[^A-Za-z0-9]+/g, "_").replace(/_+/g, "_").replace(/^_+|_+$/g, "").toUpperCase();
223
+ }
224
+ function normalizeEnvValue2(value) {
225
+ if (value === void 0 || value === null) {
226
+ return "";
227
+ }
228
+ if (typeof value === "string") {
229
+ return value;
230
+ }
231
+ if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") {
232
+ return String(value);
233
+ }
234
+ return JSON.stringify(value);
235
+ }
236
+ function resolvePublicPrefix(manifest, options) {
237
+ if (options.prefix) {
238
+ return options.prefix;
239
+ }
240
+ if (!options.framework) {
241
+ return "";
242
+ }
243
+ const configuredPrefix = manifest.public.frameworks[options.framework];
244
+ if (!configuredPrefix) {
245
+ throw new CnosManifestError(`Unknown public framework prefix: ${options.framework}`);
246
+ }
247
+ return configuredPrefix;
248
+ }
249
+ function ensurePublicPromotionKey(key) {
250
+ if (!key.startsWith("value.")) {
251
+ throw new CnosManifestError(`public.promote may only contain value.* keys: ${key}`);
252
+ }
253
+ }
254
+ function toPublicEnv(graph, manifest, options = {}) {
255
+ const prefix = resolvePublicPrefix(manifest, options);
256
+ const output = {};
257
+ const promotions = [...manifest.public.promote].sort((left, right) => left.localeCompare(right));
258
+ for (const key of promotions) {
259
+ ensurePublicPromotionKey(key);
260
+ const resolved = graph.entries.get(key);
261
+ if (!resolved) {
262
+ continue;
263
+ }
264
+ const baseEnvVar = logicalKeyToEnvVar(key, manifest.envMapping) ?? fallbackValueEnvVar(key);
265
+ const envVar = prefix && !baseEnvVar.startsWith(prefix) ? `${prefix}${baseEnvVar}` : baseEnvVar;
266
+ output[envVar] = normalizeEnvValue2(resolved.value);
267
+ }
268
+ return output;
269
+ }
270
+
271
+ // ../core/src/runtime/dump.ts
272
+ import { mkdir, writeFile } from "fs/promises";
273
+ import path2 from "path";
274
+
275
+ // ../core/src/runtime/projection.ts
276
+ function setNestedValue(target, pathSegments, value) {
277
+ const [head, ...tail] = pathSegments;
278
+ if (!head) {
279
+ return;
280
+ }
281
+ if (tail.length === 0) {
282
+ target[head] = value;
283
+ return;
284
+ }
285
+ const current = target[head];
286
+ const nextTarget = current && typeof current === "object" && !Array.isArray(current) ? current : {};
287
+ target[head] = nextTarget;
288
+ setNestedValue(nextTarget, tail, value);
289
+ }
290
+ function toNamespaceObject(graph, namespace) {
291
+ const output = {};
292
+ const resolvedEntries = Array.from(graph.entries.values()).sort(
293
+ (left, right) => left.key.localeCompare(right.key)
294
+ );
295
+ for (const entry of resolvedEntries) {
296
+ if (namespace && entry.namespace !== namespace) {
297
+ continue;
298
+ }
299
+ const valuePath = namespace ? stripNamespace(entry.key) : entry.key;
300
+ setNestedValue(output, valuePath.split("."), entry.value);
301
+ }
302
+ return output;
303
+ }
304
+
305
+ // ../core/src/runtime/dump.ts
306
+ function buildDumpFiles(graph, options = {}) {
307
+ const basePath = options.flatten ? "" : path2.posix.join("workspaces", graph.workspace.workspaceId);
308
+ const values = toNamespaceObject(graph, "value");
309
+ const secrets = toNamespaceObject(graph, "secret");
310
+ const files = [];
311
+ if (Object.keys(values).length > 0) {
312
+ files.push({
313
+ path: path2.posix.join(basePath, "values", graph.profile, "app.yml"),
314
+ namespace: "value",
315
+ content: stringifyYaml(values)
316
+ });
317
+ }
318
+ if (Object.keys(secrets).length > 0) {
319
+ files.push({
320
+ path: path2.posix.join(basePath, "secrets", graph.profile, "app.yml"),
321
+ namespace: "secret",
322
+ content: stringifyYaml(secrets)
323
+ });
324
+ }
325
+ return files.sort((left, right) => left.path.localeCompare(right.path));
326
+ }
327
+ function planDump(graph, options = {}) {
328
+ return {
329
+ workspaceId: graph.workspace.workspaceId,
330
+ profile: graph.profile,
331
+ flatten: options.flatten ?? false,
332
+ files: buildDumpFiles(graph, options)
333
+ };
334
+ }
335
+ async function writeDump(graph, options) {
336
+ const root = path2.resolve(options.to);
337
+ const plan = planDump(graph, options);
338
+ for (const file of plan.files) {
339
+ const destination = path2.join(root, file.path);
340
+ await mkdir(path2.dirname(destination), { recursive: true });
341
+ await writeFile(destination, file.content, "utf8");
342
+ }
343
+ return {
344
+ ...plan,
345
+ root
346
+ };
347
+ }
348
+
349
+ // ../core/src/utils/flatten.ts
350
+ function flattenObject(value, prefix = "") {
351
+ return Object.entries(value).reduce((accumulator, [key, nestedValue]) => {
352
+ const nextKey = prefix ? `${prefix}.${key}` : key;
353
+ if (nestedValue && typeof nestedValue === "object" && !Array.isArray(nestedValue)) {
354
+ Object.assign(accumulator, flattenObject(nestedValue, nextKey));
355
+ return accumulator;
356
+ }
357
+ accumulator[nextKey] = nestedValue;
358
+ return accumulator;
359
+ }, {});
360
+ }
361
+
362
+ // ../core/src/utils/secretStore.ts
363
+ import { createCipheriv, createDecipheriv, randomBytes, scryptSync } from "crypto";
364
+ import { mkdir as mkdir2, readFile, writeFile as writeFile2 } from "fs/promises";
365
+ import path3 from "path";
366
+ function isObject(value) {
367
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
368
+ }
369
+ function isSecretReference(value) {
370
+ return isObject(value) && typeof value.provider === "string" && value.provider.trim().length > 0 && typeof value.ref === "string" && value.ref.trim().length > 0 && Object.keys(value).every((key) => ["provider", "ref"].includes(key));
371
+ }
372
+ function resolveSecretStoreRoot(processEnv = process.env) {
373
+ return path3.resolve(expandHomePath(processEnv.CNOS_SECRET_HOME ?? "~/.cnos/secrets"));
374
+ }
375
+ function resolveSecretStoreFile(storeRoot, ref) {
376
+ return path3.join(storeRoot, "store", ...ref.split("/")).concat(".json");
377
+ }
378
+ function deriveKey(passphrase, salt) {
379
+ return scryptSync(passphrase, salt, 32);
380
+ }
381
+ function encryptDocument(value, passphrase) {
382
+ const salt = randomBytes(16);
383
+ const iv = randomBytes(12);
384
+ const key = deriveKey(passphrase, salt);
385
+ const cipher = createCipheriv("aes-256-gcm", key, iv);
386
+ const ciphertext = Buffer.concat([cipher.update(value, "utf8"), cipher.final()]);
387
+ const tag = cipher.getAuthTag();
388
+ return {
389
+ version: 1,
390
+ algorithm: "aes-256-gcm",
391
+ salt: salt.toString("base64"),
392
+ iv: iv.toString("base64"),
393
+ tag: tag.toString("base64"),
394
+ ciphertext: ciphertext.toString("base64")
395
+ };
396
+ }
397
+ function decryptDocument(document, passphrase) {
398
+ const salt = Buffer.from(document.salt, "base64");
399
+ const iv = Buffer.from(document.iv, "base64");
400
+ const tag = Buffer.from(document.tag, "base64");
401
+ const ciphertext = Buffer.from(document.ciphertext, "base64");
402
+ const key = deriveKey(passphrase, salt);
403
+ const decipher = createDecipheriv("aes-256-gcm", key, iv);
404
+ decipher.setAuthTag(tag);
405
+ const plaintext = Buffer.concat([decipher.update(ciphertext), decipher.final()]);
406
+ return plaintext.toString("utf8");
407
+ }
408
+ async function writeLocalSecret(storeRoot, ref, value, passphrase) {
409
+ const filePath = resolveSecretStoreFile(storeRoot, ref);
410
+ await mkdir2(path3.dirname(filePath), { recursive: true });
411
+ await writeFile2(filePath, JSON.stringify(encryptDocument(value, passphrase), null, 2), "utf8");
412
+ return filePath;
413
+ }
414
+ async function readLocalSecret(storeRoot, ref, passphrase) {
415
+ if (!passphrase) {
416
+ throw new CnosManifestError(
417
+ `Missing CNOS secret passphrase for local secret ref "${ref}". Set CNOS_SECRET_PASSPHRASE or pass processEnv explicitly.`
418
+ );
419
+ }
420
+ const filePath = resolveSecretStoreFile(storeRoot, ref);
421
+ const source = await readFile(filePath, "utf8");
422
+ const document = JSON.parse(source);
423
+ if (document.version !== 1 || document.algorithm !== "aes-256-gcm" || typeof document.salt !== "string" || typeof document.iv !== "string" || typeof document.tag !== "string" || typeof document.ciphertext !== "string") {
424
+ throw new CnosManifestError("Invalid local secret document", filePath);
425
+ }
426
+ return decryptDocument(document, passphrase);
427
+ }
428
+
429
+ // ../core/src/validation/envMapping.ts
430
+ function fallbackLogicalKeyToEnvVar2(key) {
431
+ return key.replace(/^(value|secret)\./, "").replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/[^A-Za-z0-9]+/g, "_").replace(/_+/g, "_").replace(/^_+|_+$/g, "").toUpperCase();
432
+ }
433
+ function validateEnvMappingCollisions(manifest, graph) {
434
+ const candidates = /* @__PURE__ */ new Set([
435
+ ...Object.values(manifest.envMapping.explicit),
436
+ ...manifest.public.promote,
437
+ ...Object.keys(manifest.schema),
438
+ ...graph ? Array.from(graph.entries.keys()) : []
439
+ ]);
440
+ const collisions = /* @__PURE__ */ new Map();
441
+ for (const key of candidates) {
442
+ if (key.startsWith("meta.")) {
443
+ continue;
444
+ }
445
+ const envVar = logicalKeyToEnvVar(key, manifest.envMapping) ?? (key.startsWith("value.") || key.startsWith("secret.") ? fallbackLogicalKeyToEnvVar2(key) : void 0);
446
+ if (!envVar) {
447
+ continue;
448
+ }
449
+ const keys = collisions.get(envVar) ?? [];
450
+ keys.push(key);
451
+ collisions.set(envVar, keys);
452
+ }
453
+ return Array.from(collisions.entries()).filter(([, keys]) => new Set(keys).size > 1).map(([envVar, keys]) => ({
454
+ code: "env-mapping.collision",
455
+ message: `Multiple logical keys map to env var ${envVar}: ${Array.from(new Set(keys)).join(", ")}`
456
+ }));
457
+ }
458
+
459
+ // ../core/src/validation/publicSafety.ts
460
+ function validatePublicSafety(manifest) {
461
+ return manifest.public.promote.filter((key) => !key.startsWith("value.")).map((key) => ({
462
+ code: "public.invalid-promotion",
463
+ key,
464
+ message: `public.promote may only include value.* keys: ${key}`
465
+ }));
466
+ }
467
+
468
+ // ../core/src/validation/workspaceSafety.ts
469
+ function validateWorkspaceSafety(manifest, graph) {
470
+ const issues = [];
471
+ const localRoot = graph.workspace.workspaceRoots.find(
472
+ (entry) => entry.scope === "local" && entry.workspaceId === graph.workspace.workspaceId
473
+ );
474
+ if (!localRoot) {
475
+ issues.push({
476
+ code: "workspace.missing-local-root",
477
+ message: `Missing local workspace root for ${graph.workspace.workspaceId}`
478
+ });
479
+ }
480
+ if (manifest.workspaces.global.allowWrite && !manifest.workspaces.global.enabled) {
481
+ issues.push({
482
+ code: "workspace.global-write-policy",
483
+ message: "workspaces.global.allowWrite requires workspaces.global.enabled: true"
484
+ });
485
+ }
486
+ return issues;
487
+ }
488
+
489
+ // ../core/src/validation/validateRuntime.ts
490
+ async function validateRuntime(runtime) {
491
+ const validatorPlugins = runtime.plugins.filter(
492
+ (plugin) => plugin.kind === "validator"
493
+ );
494
+ const pluginResults = await Promise.all(
495
+ validatorPlugins.map(
496
+ (plugin) => plugin.validate(runtime.graph, {
497
+ manifest: runtime.manifest,
498
+ schema: runtime.manifest.schema
499
+ })
500
+ )
501
+ );
502
+ const builtInResults = [
503
+ {
504
+ pluginId: "public-safety",
505
+ valid: true,
506
+ issues: validatePublicSafety(runtime.manifest)
507
+ },
508
+ {
509
+ pluginId: "env-mapping",
510
+ valid: true,
511
+ issues: validateEnvMappingCollisions(runtime.manifest, runtime.graph)
512
+ },
513
+ {
514
+ pluginId: "workspace-safety",
515
+ valid: true,
516
+ issues: validateWorkspaceSafety(runtime.manifest, runtime.graph)
517
+ }
518
+ ].map((result) => ({
519
+ ...result,
520
+ valid: result.issues.length === 0
521
+ }));
522
+ const results = [...pluginResults, ...builtInResults];
523
+ const issues = results.flatMap((result) => result.issues);
524
+ return {
525
+ valid: issues.length === 0,
526
+ issues,
527
+ results
528
+ };
529
+ }
530
+
531
+ // ../core/src/runtime/inspect.ts
532
+ function inspectValue(graph, key) {
533
+ const entry = graph.entries.get(key);
534
+ if (!entry) {
535
+ throw new CnosKeyNotFoundError(key);
536
+ }
537
+ return {
538
+ key: entry.key,
539
+ value: entry.value,
540
+ namespace: entry.namespace,
541
+ profile: graph.profile,
542
+ profileSource: graph.profileSource,
543
+ workspace: {
544
+ id: graph.workspace.workspaceId,
545
+ source: graph.workspace.workspaceSource,
546
+ chain: graph.workspace.workspaceChain
547
+ },
548
+ winner: {
549
+ sourceId: entry.winner.sourceId,
550
+ pluginId: entry.winner.pluginId,
551
+ workspaceId: entry.winner.workspaceId,
552
+ ...entry.winner.origin ? { origin: entry.winner.origin } : {}
553
+ },
554
+ overridden: entry.overridden.map((override) => ({
555
+ sourceId: override.sourceId,
556
+ pluginId: override.pluginId,
557
+ workspaceId: override.workspaceId,
558
+ value: override.value,
559
+ ...override.origin ? { origin: override.origin } : {}
560
+ }))
561
+ };
562
+ }
563
+
564
+ // ../core/src/inspectors/provenance.ts
565
+ function createProvenanceInspector() {
566
+ return {
567
+ id: "provenance",
568
+ kind: "inspector",
569
+ async inspect(key, graph) {
570
+ return inspectValue(graph, key);
571
+ }
572
+ };
573
+ }
574
+
575
+ // ../core/src/manifest/loadManifest.ts
576
+ import { readFile as readFile2 } from "fs/promises";
577
+ import path4 from "path";
578
+
579
+ // ../core/src/manifest/normalizeManifest.ts
580
+ var DEFAULT_RESOLVE_FROM = ["cli.profile", "env.CNOS_PROFILE", "default"];
581
+ var DEFAULT_LOADERS = [
582
+ "filesystem-values",
583
+ "filesystem-secrets",
584
+ "dotenv",
585
+ "process-env",
586
+ "cli-args"
587
+ ];
588
+ var DEFAULT_VALIDATORS = ["basic-schema"];
589
+ var DEFAULT_EXPORTERS = ["env", "public-env"];
590
+ var DEFAULT_INSPECTORS = ["provenance"];
591
+ var DEFAULT_FRAMEWORK_PREFIXES = {
592
+ next: "NEXT_PUBLIC_",
593
+ vite: "VITE_",
594
+ nuxt: "NUXT_PUBLIC_"
595
+ };
596
+ function validateResolveFrom(resolveFrom) {
597
+ const validValues = ["cli.profile", "env.CNOS_PROFILE", "default"];
598
+ for (const entry of resolveFrom) {
599
+ if (!validValues.includes(entry)) {
600
+ throw new CnosManifestError(`Unsupported profiles.resolveFrom entry: ${entry}`);
601
+ }
602
+ }
603
+ return resolveFrom;
604
+ }
605
+ function normalizeWorkspaceItems(items) {
606
+ return Object.fromEntries(
607
+ Object.entries(items ?? {}).map(([workspaceId, item]) => [
608
+ workspaceId,
609
+ {
610
+ extends: Array.isArray(item?.extends) ? item.extends.map((entry) => entry.trim()).filter(Boolean) : item?.extends ? [item.extends.trim()].filter(Boolean) : [],
611
+ ...item?.globalId?.trim() ? { globalId: item.globalId.trim() } : {}
612
+ }
613
+ ])
614
+ );
615
+ }
616
+ function normalizeManifest(manifest) {
617
+ const version = manifest.version ?? 1;
618
+ if (version !== 1) {
619
+ throw new CnosManifestError(`Unsupported CNOS manifest version: ${version}`);
620
+ }
621
+ const projectName = manifest.project?.name?.trim();
622
+ if (!projectName) {
623
+ throw new CnosManifestError("Manifest requires project.name");
624
+ }
625
+ const defaultProfile = manifest.profiles?.default?.trim() || "base";
626
+ const workspaceItems = normalizeWorkspaceItems(manifest.workspaces?.items);
627
+ const resolveFrom = validateResolveFrom(manifest.profiles?.resolveFrom ?? DEFAULT_RESOLVE_FROM);
628
+ const filesystemValues = {
629
+ root: "./",
630
+ format: "yaml",
631
+ ...manifest.sources?.["filesystem-values"] ?? {}
632
+ };
633
+ const filesystemSecrets = {
634
+ root: "./",
635
+ format: "yaml",
636
+ ...manifest.sources?.["filesystem-secrets"] ?? {}
637
+ };
638
+ const dotenv = {
639
+ root: "./env",
640
+ ...manifest.sources?.dotenv ?? {}
641
+ };
642
+ return {
643
+ version: 1,
644
+ project: {
645
+ name: projectName
646
+ },
647
+ workspaces: {
648
+ ...manifest.workspaces?.default?.trim() ? {
649
+ default: manifest.workspaces.default.trim()
650
+ } : {},
651
+ global: {
652
+ enabled: manifest.workspaces?.global?.enabled ?? false,
653
+ ...manifest.workspaces?.global?.root?.trim() ? {
654
+ root: manifest.workspaces.global.root.trim()
655
+ } : {},
656
+ allowWrite: manifest.workspaces?.global?.allowWrite ?? false
657
+ },
658
+ items: workspaceItems
659
+ },
660
+ profiles: {
661
+ default: defaultProfile,
662
+ resolveFrom
663
+ },
664
+ plugins: {
665
+ loaders: manifest.plugins?.loaders ?? DEFAULT_LOADERS,
666
+ resolver: manifest.plugins?.resolver ?? "profile-aware",
667
+ validators: manifest.plugins?.validators ?? DEFAULT_VALIDATORS,
668
+ exporters: manifest.plugins?.exporters ?? DEFAULT_EXPORTERS,
669
+ inspectors: manifest.plugins?.inspectors ?? DEFAULT_INSPECTORS
670
+ },
671
+ sources: {
672
+ ...manifest.sources ?? {},
673
+ "filesystem-values": filesystemValues,
674
+ "filesystem-secrets": filesystemSecrets,
675
+ dotenv
676
+ },
677
+ resolution: {
678
+ precedence: manifest.resolution?.precedence ?? [
679
+ "filesystem-values",
680
+ "filesystem-secrets",
681
+ "dotenv",
682
+ "process-env",
683
+ "cli-args"
684
+ ],
685
+ arrayPolicy: manifest.resolution?.arrayPolicy ?? "replace"
686
+ },
687
+ envMapping: {
688
+ ...manifest.envMapping?.convention ? {
689
+ convention: manifest.envMapping.convention
690
+ } : {},
691
+ explicit: manifest.envMapping?.explicit ?? {}
692
+ },
693
+ public: {
694
+ promote: manifest.public?.promote ?? [],
695
+ frameworks: {
696
+ ...DEFAULT_FRAMEWORK_PREFIXES,
697
+ ...manifest.public?.frameworks ?? {}
698
+ }
699
+ },
700
+ writePolicy: {
701
+ define: {
702
+ defaultProfile: manifest.writePolicy?.define?.defaultProfile ?? defaultProfile,
703
+ targets: {
704
+ value: manifest.writePolicy?.define?.targets?.value ?? "./values/app.yml",
705
+ secret: manifest.writePolicy?.define?.targets?.secret ?? "./secrets/app.yml"
706
+ }
707
+ }
708
+ },
709
+ schema: manifest.schema ?? {}
710
+ };
711
+ }
712
+
713
+ // ../core/src/manifest/loadManifest.ts
714
+ async function loadManifest(options = {}) {
715
+ const manifestRoot = await resolveManifestRoot(options.root);
716
+ const manifestPath = path4.join(manifestRoot, "cnos.yml");
717
+ let source;
718
+ try {
719
+ source = await readFile2(manifestPath, "utf8");
720
+ } catch {
721
+ throw new CnosManifestError("Unable to read CNOS manifest", manifestPath);
722
+ }
723
+ const rawManifest = parseYaml(source);
724
+ if (!rawManifest || typeof rawManifest !== "object") {
725
+ throw new CnosManifestError("CNOS manifest must be a YAML object", manifestPath);
726
+ }
727
+ return {
728
+ manifestRoot,
729
+ repoRoot: path4.dirname(manifestRoot),
730
+ manifestPath,
731
+ manifest: normalizeManifest(rawManifest),
732
+ rawManifest
733
+ };
734
+ }
735
+
736
+ // ../core/src/manifest/loadWorkspaceFile.ts
737
+ import { readFile as readFile3 } from "fs/promises";
738
+ import path5 from "path";
739
+ async function loadWorkspaceFile(repoRoot) {
740
+ const workspaceFilePath = path5.join(repoRoot, ".cnos-workspace.yml");
741
+ try {
742
+ const source = await readFile3(workspaceFilePath, "utf8");
743
+ const parsed = parseYaml(source);
744
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
745
+ throw new CnosManifestError(".cnos-workspace.yml must be a YAML object", workspaceFilePath);
746
+ }
747
+ const config = parsed;
748
+ return {
749
+ path: workspaceFilePath,
750
+ config: {
751
+ ...config.workspace ? { workspace: config.workspace.trim() } : {},
752
+ ...config.profile ? { profile: config.profile.trim() } : {},
753
+ ...config.globalRoot ? { globalRoot: config.globalRoot.trim() } : {}
754
+ }
755
+ };
756
+ } catch (error) {
757
+ if (error.code === "ENOENT") {
758
+ return void 0;
759
+ }
760
+ throw error;
761
+ }
762
+ }
763
+
764
+ // ../core/src/profiles/expandProfileChain.ts
765
+ import { access as access2, readFile as readFile4 } from "fs/promises";
766
+ import path6 from "path";
767
+ async function fileExists(targetPath) {
768
+ try {
769
+ await access2(targetPath);
770
+ return true;
771
+ } catch {
772
+ return false;
773
+ }
774
+ }
775
+ function normalizeProfileDefinition(profileName, rawDefinition, filePath) {
776
+ const normalizeActivationLayer = (entry, namespace) => {
777
+ const normalized = entry.trim();
778
+ if (!normalized) {
779
+ return normalized;
780
+ }
781
+ if (normalized.includes("/") || normalized.includes("\\") || normalized.startsWith(".")) {
782
+ return normalized.replace(/\\/g, "/");
783
+ }
784
+ return `${namespace}/${normalized}`;
785
+ };
786
+ return {
787
+ name: rawDefinition?.name?.trim() || profileName,
788
+ extends: Array.isArray(rawDefinition?.extends) ? rawDefinition.extends.map((entry) => entry.trim()).filter(Boolean) : rawDefinition?.extends ? [rawDefinition.extends.trim()].filter(Boolean) : [],
789
+ activate: {
790
+ values: rawDefinition?.activate?.values?.map((entry) => normalizeActivationLayer(entry, "values")).filter(Boolean) ?? [],
791
+ secrets: rawDefinition?.activate?.secrets?.map((entry) => normalizeActivationLayer(entry, "secrets")).filter(Boolean) ?? [],
792
+ envFiles: rawDefinition?.activate?.envFiles?.map((entry) => entry.trim()).filter(Boolean) ?? []
793
+ },
794
+ ...filePath ? { filePath } : {}
795
+ };
796
+ }
797
+ async function loadProfileDefinition(profileName, options) {
798
+ const workspaceRoots = options.workspace?.workspaceRoots ?? [];
799
+ if (workspaceRoots.length === 0) {
800
+ return normalizeProfileDefinition(profileName, void 0);
801
+ }
802
+ for (const workspaceRoot of [...workspaceRoots].reverse()) {
803
+ const profilePath = path6.join(workspaceRoot.path, "profiles", `${profileName}.yml`);
804
+ if (!await fileExists(profilePath)) {
805
+ continue;
806
+ }
807
+ const document = await readFile4(profilePath, "utf8");
808
+ const parsed = parseYaml(document);
809
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
810
+ throw new CnosManifestError("Profile definition must be a YAML object", profilePath);
811
+ }
812
+ const definition = normalizeProfileDefinition(
813
+ profileName,
814
+ parsed,
815
+ options.manifestRoot ? toPortablePath(path6.relative(path6.dirname(options.manifestRoot), profilePath)) : toPortablePath(profilePath)
816
+ );
817
+ if (definition.name !== profileName) {
818
+ throw new CnosManifestError(
819
+ `Profile file name mismatch: expected "${profileName}" but found "${definition.name}"`,
820
+ profilePath
821
+ );
822
+ }
823
+ return definition;
824
+ }
825
+ return normalizeProfileDefinition(profileName, void 0);
826
+ }
827
+ function pushUnique(target, values) {
828
+ for (const value of values) {
829
+ if (!target.includes(value)) {
830
+ target.push(value);
831
+ }
832
+ }
833
+ }
834
+ function buildFallbackActivation(activeProfile, orderedProfiles) {
835
+ const overlayProfiles = orderedProfiles.filter((profile) => profile !== "base");
836
+ return {
837
+ values: [
838
+ "values",
839
+ ...activeProfile !== "base" ? ["values/base"] : [],
840
+ ...overlayProfiles.flatMap((profile) => [`profiles/${profile}/values`, `values/${profile}`])
841
+ ],
842
+ secrets: [
843
+ "secrets",
844
+ ...overlayProfiles.flatMap((profile) => [`profiles/${profile}/secrets`, `secrets/${profile}`])
845
+ ],
846
+ envFiles: activeProfile === "base" ? [".env"] : [".env", `.env.${activeProfile}`]
847
+ };
848
+ }
849
+ async function expandProfileChain(activeProfile, options = {}) {
850
+ const visiting = /* @__PURE__ */ new Set();
851
+ const resolved = /* @__PURE__ */ new Set();
852
+ const orderedProfiles = [];
853
+ const definitions = /* @__PURE__ */ new Map();
854
+ const visit = async (profileName) => {
855
+ if (resolved.has(profileName)) {
856
+ return;
857
+ }
858
+ if (visiting.has(profileName)) {
859
+ throw new CnosManifestError(`Detected profile inheritance cycle involving "${profileName}"`);
860
+ }
861
+ visiting.add(profileName);
862
+ const definition = await loadProfileDefinition(profileName, options);
863
+ definitions.set(profileName, definition);
864
+ for (const parent of definition.extends) {
865
+ await visit(parent);
866
+ }
867
+ visiting.delete(profileName);
868
+ resolved.add(profileName);
869
+ orderedProfiles.push(profileName);
870
+ };
871
+ await visit(activeProfile);
872
+ const activation = {
873
+ values: [],
874
+ secrets: [],
875
+ envFiles: []
876
+ };
877
+ for (const profileName of orderedProfiles) {
878
+ const definition = definitions.get(profileName);
879
+ if (!definition) {
880
+ continue;
881
+ }
882
+ pushUnique(activation.values, definition.activate.values);
883
+ pushUnique(activation.secrets, definition.activate.secrets);
884
+ pushUnique(activation.envFiles, definition.activate.envFiles);
885
+ }
886
+ const fallback = buildFallbackActivation(activeProfile, orderedProfiles);
887
+ if (activation.values.length === 0) {
888
+ activation.values = fallback.values;
889
+ }
890
+ if (activation.secrets.length === 0) {
891
+ activation.secrets = fallback.secrets;
892
+ }
893
+ if (activation.envFiles.length === 0) {
894
+ activation.envFiles = fallback.envFiles;
895
+ }
896
+ return {
897
+ activeProfile,
898
+ profiles: orderedProfiles,
899
+ activation
900
+ };
901
+ }
902
+
903
+ // ../core/src/profiles/resolveActiveProfile.ts
904
+ function resolveActiveProfile(manifest, options = {}) {
905
+ for (const source of manifest.profiles.resolveFrom) {
906
+ if (source === "cli.profile" && options.profile) {
907
+ return {
908
+ profile: options.profile,
909
+ source: "cli"
910
+ };
911
+ }
912
+ if (source === "env.CNOS_PROFILE") {
913
+ if (options.workspaceFile?.profile) {
914
+ return {
915
+ profile: options.workspaceFile.profile,
916
+ source: "workspace-file"
917
+ };
918
+ }
919
+ const envProfile = options.processEnv?.CNOS_PROFILE;
920
+ if (envProfile) {
921
+ return {
922
+ profile: envProfile,
923
+ source: "env"
924
+ };
925
+ }
926
+ }
927
+ if (source === "default") {
928
+ return {
929
+ profile: manifest.profiles.default,
930
+ source: "manifest-default"
931
+ };
932
+ }
933
+ }
934
+ return {
935
+ profile: manifest.profiles.default,
936
+ source: "manifest-default"
937
+ };
938
+ }
939
+
940
+ // ../core/src/utils/deepMerge.ts
941
+ function isPlainObject(value) {
942
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
943
+ }
944
+ function deepMerge(base, override) {
945
+ const merged = { ...base };
946
+ for (const [key, value] of Object.entries(override)) {
947
+ const currentValue = merged[key];
948
+ if (isPlainObject(currentValue) && isPlainObject(value)) {
949
+ merged[key] = deepMerge(currentValue, value);
950
+ continue;
951
+ }
952
+ merged[key] = value;
953
+ }
954
+ return merged;
955
+ }
956
+
957
+ // ../core/src/resolvers/profileAwareResolver.ts
958
+ function mergeResolvedValue(currentValue, nextValue, arrayPolicy) {
959
+ if (Array.isArray(currentValue) && Array.isArray(nextValue)) {
960
+ if (arrayPolicy === "append") {
961
+ return [...currentValue, ...nextValue];
962
+ }
963
+ if (arrayPolicy === "unique-append") {
964
+ return [.../* @__PURE__ */ new Set([...currentValue, ...nextValue])];
965
+ }
966
+ return [...nextValue];
967
+ }
968
+ if (isPlainObject(currentValue) && isPlainObject(nextValue)) {
969
+ return deepMerge(currentValue, nextValue);
970
+ }
971
+ return nextValue;
972
+ }
973
+ function createProfileAwareResolver() {
974
+ return {
975
+ id: "profile-aware",
976
+ kind: "resolver",
977
+ async resolve(entries, context) {
978
+ const precedence = new Map(context.precedenceOrder.map((sourceId, index) => [sourceId, index]));
979
+ const sortedEntries = entries.map((entry, index) => ({
980
+ entry,
981
+ index,
982
+ precedence: precedence.get(entry.sourceId) ?? context.precedenceOrder.length
983
+ })).sort((left, right) => left.precedence - right.precedence || left.index - right.index);
984
+ const resolvedEntries = /* @__PURE__ */ new Map();
985
+ for (const { entry } of sortedEntries) {
986
+ const current = resolvedEntries.get(entry.key);
987
+ if (!current) {
988
+ resolvedEntries.set(entry.key, {
989
+ key: entry.key,
990
+ value: entry.value,
991
+ namespace: entry.namespace,
992
+ winner: entry,
993
+ overridden: []
994
+ });
995
+ continue;
996
+ }
997
+ resolvedEntries.set(entry.key, {
998
+ key: entry.key,
999
+ value: mergeResolvedValue(current.value, entry.value, context.manifest.resolution.arrayPolicy),
1000
+ namespace: current.namespace,
1001
+ winner: entry,
1002
+ overridden: [...current.overridden, current.winner]
1003
+ });
1004
+ }
1005
+ return {
1006
+ entries: resolvedEntries,
1007
+ profile: context.profile,
1008
+ resolvedAt: (/* @__PURE__ */ new Date()).toISOString(),
1009
+ profileSource: "manifest-default",
1010
+ workspace: context.workspace
1011
+ };
1012
+ }
1013
+ };
1014
+ }
1015
+
1016
+ // ../core/src/workspaces/resolveWorkspaceContext.ts
1017
+ import { access as access3 } from "fs/promises";
1018
+ import path7 from "path";
1019
+
1020
+ // ../core/src/workspaces/expandWorkspaceChain.ts
1021
+ function expandWorkspaceChain(workspaceId, items) {
1022
+ if (Object.keys(items).length === 0) {
1023
+ return [workspaceId];
1024
+ }
1025
+ if (!items[workspaceId]) {
1026
+ throw new CnosManifestError(`Unknown workspace "${workspaceId}"`);
1027
+ }
1028
+ const visiting = /* @__PURE__ */ new Set();
1029
+ const resolved = /* @__PURE__ */ new Set();
1030
+ const chain = [];
1031
+ const visit = (currentWorkspaceId) => {
1032
+ if (resolved.has(currentWorkspaceId)) {
1033
+ return;
1034
+ }
1035
+ if (visiting.has(currentWorkspaceId)) {
1036
+ throw new CnosManifestError(`Detected workspace inheritance cycle involving "${currentWorkspaceId}"`);
1037
+ }
1038
+ const item = items[currentWorkspaceId];
1039
+ if (!item) {
1040
+ throw new CnosManifestError(`Unknown workspace "${currentWorkspaceId}"`);
1041
+ }
1042
+ visiting.add(currentWorkspaceId);
1043
+ for (const parentWorkspaceId of item.extends) {
1044
+ visit(parentWorkspaceId);
1045
+ }
1046
+ visiting.delete(currentWorkspaceId);
1047
+ resolved.add(currentWorkspaceId);
1048
+ chain.push(currentWorkspaceId);
1049
+ };
1050
+ visit(workspaceId);
1051
+ return chain;
1052
+ }
1053
+
1054
+ // ../core/src/workspaces/resolveWorkspaceContext.ts
1055
+ async function exists2(targetPath) {
1056
+ try {
1057
+ await access3(targetPath);
1058
+ return true;
1059
+ } catch {
1060
+ return false;
1061
+ }
1062
+ }
1063
+ async function resolveLocalWorkspaceRoot(manifestRoot, workspaceId) {
1064
+ const workspaceRoot = path7.join(manifestRoot, "workspaces", workspaceId);
1065
+ if (await exists2(workspaceRoot)) {
1066
+ return workspaceRoot;
1067
+ }
1068
+ const legacyMarkers = ["values", "secrets", "env", "profiles"].map(
1069
+ (segment) => path7.join(manifestRoot, segment)
1070
+ );
1071
+ if ((await Promise.all(legacyMarkers.map((marker) => exists2(marker)))).some(Boolean)) {
1072
+ return manifestRoot;
1073
+ }
1074
+ return workspaceRoot;
1075
+ }
1076
+ function resolveWorkspaceSelection(manifest, workspaceFile, workspaceOption) {
1077
+ if (workspaceOption) {
1078
+ return {
1079
+ workspaceId: workspaceOption,
1080
+ source: "cli"
1081
+ };
1082
+ }
1083
+ if (workspaceFile?.workspace) {
1084
+ return {
1085
+ workspaceId: workspaceFile.workspace,
1086
+ source: "workspace-file"
1087
+ };
1088
+ }
1089
+ if (manifest.workspaces.default) {
1090
+ return {
1091
+ workspaceId: manifest.workspaces.default,
1092
+ source: "manifest-default"
1093
+ };
1094
+ }
1095
+ if (Object.keys(manifest.workspaces.items).length === 0) {
1096
+ return {
1097
+ workspaceId: "default",
1098
+ source: "implicit"
1099
+ };
1100
+ }
1101
+ throw new CnosManifestError(
1102
+ "Workspace selection requires --workspace, .cnos-workspace.yml, or workspaces.default when workspaces.items are defined"
1103
+ );
1104
+ }
1105
+ function resolveGlobalRoot(manifest, workspaceFile, options) {
1106
+ if (!manifest.workspaces.global.enabled) {
1107
+ return {};
1108
+ }
1109
+ if (options.globalRoot) {
1110
+ return {
1111
+ value: path7.resolve(expandHomePath(options.globalRoot)),
1112
+ source: "cli"
1113
+ };
1114
+ }
1115
+ if (workspaceFile?.globalRoot) {
1116
+ return {
1117
+ value: path7.resolve(expandHomePath(workspaceFile.globalRoot)),
1118
+ source: "workspace-file"
1119
+ };
1120
+ }
1121
+ if (manifest.workspaces.global.root) {
1122
+ return {
1123
+ value: path7.resolve(expandHomePath(manifest.workspaces.global.root)),
1124
+ source: "manifest"
1125
+ };
1126
+ }
1127
+ const cnosHome = options.processEnv?.CNOS_HOME;
1128
+ if (cnosHome) {
1129
+ return {
1130
+ value: path7.resolve(expandHomePath(cnosHome)),
1131
+ source: "CNOS_HOME"
1132
+ };
1133
+ }
1134
+ return {};
1135
+ }
1136
+ async function resolveWorkspaceContext(manifest, options) {
1137
+ const selectedWorkspace = resolveWorkspaceSelection(manifest, options.workspaceFile, options.workspace);
1138
+ const workspaceChain = expandWorkspaceChain(selectedWorkspace.workspaceId, manifest.workspaces.items);
1139
+ const globalRoot = resolveGlobalRoot(manifest, options.workspaceFile, options);
1140
+ const workspaceRoots = [];
1141
+ if (globalRoot.value) {
1142
+ for (const chainWorkspaceId of workspaceChain) {
1143
+ const globalWorkspaceId = manifest.workspaces.items[chainWorkspaceId]?.globalId ?? chainWorkspaceId;
1144
+ workspaceRoots.push({
1145
+ scope: "global",
1146
+ workspaceId: chainWorkspaceId,
1147
+ path: path7.join(globalRoot.value, "workspaces", globalWorkspaceId)
1148
+ });
1149
+ }
1150
+ }
1151
+ for (const chainWorkspaceId of workspaceChain) {
1152
+ workspaceRoots.push({
1153
+ scope: "local",
1154
+ workspaceId: chainWorkspaceId,
1155
+ path: await resolveLocalWorkspaceRoot(options.manifestRoot, chainWorkspaceId)
1156
+ });
1157
+ }
1158
+ return {
1159
+ workspaceId: selectedWorkspace.workspaceId,
1160
+ workspaceSource: selectedWorkspace.source,
1161
+ ...globalRoot.value ? { globalRoot: globalRoot.value } : {},
1162
+ ...globalRoot.source ? { globalRootSource: globalRoot.source } : {},
1163
+ workspaceChain,
1164
+ workspaceRoots
1165
+ };
1166
+ }
1167
+
1168
+ // ../core/src/validation/basicSchema.ts
1169
+ function describeValueType(value) {
1170
+ if (Array.isArray(value)) {
1171
+ return "array";
1172
+ }
1173
+ if (value === null) {
1174
+ return "null";
1175
+ }
1176
+ return typeof value;
1177
+ }
1178
+ function coerceBoolean(value) {
1179
+ if (value === "true") {
1180
+ return true;
1181
+ }
1182
+ if (value === "false") {
1183
+ return false;
1184
+ }
1185
+ return void 0;
1186
+ }
1187
+ function coerceValue(value, rule) {
1188
+ if (typeof value !== "string" || !rule.type) {
1189
+ return value;
1190
+ }
1191
+ switch (rule.type) {
1192
+ case "number": {
1193
+ const parsed = Number(value);
1194
+ return Number.isNaN(parsed) ? value : parsed;
1195
+ }
1196
+ case "boolean":
1197
+ return coerceBoolean(value) ?? value;
1198
+ case "object":
1199
+ case "array": {
1200
+ try {
1201
+ const parsed = JSON.parse(value);
1202
+ if (rule.type === "array" && Array.isArray(parsed)) {
1203
+ return parsed;
1204
+ }
1205
+ if (rule.type === "object" && parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
1206
+ return parsed;
1207
+ }
1208
+ return value;
1209
+ } catch {
1210
+ return value;
1211
+ }
1212
+ }
1213
+ default:
1214
+ return value;
1215
+ }
1216
+ }
1217
+ function matchesType(value, type) {
1218
+ if (!type) {
1219
+ return true;
1220
+ }
1221
+ switch (type) {
1222
+ case "array":
1223
+ return Array.isArray(value);
1224
+ case "object":
1225
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
1226
+ default:
1227
+ return typeof value === type;
1228
+ }
1229
+ }
1230
+ function enumMatches(value, allowed) {
1231
+ const serialized = JSON.stringify(value);
1232
+ return allowed.some((candidate) => JSON.stringify(candidate) === serialized);
1233
+ }
1234
+ function applySchemaRules(graph, schema) {
1235
+ const nextEntries = new Map(graph.entries);
1236
+ const issues = [];
1237
+ for (const [key, rule] of Object.entries(schema).sort(([left], [right]) => left.localeCompare(right))) {
1238
+ const resolvedEntry = nextEntries.get(key);
1239
+ if (!resolvedEntry) {
1240
+ if (rule.default !== void 0) {
1241
+ const defaultEntry = {
1242
+ key,
1243
+ value: rule.default,
1244
+ namespace: key.startsWith("secret.") ? "secret" : key.startsWith("meta.") ? "meta" : "value",
1245
+ sourceId: "schema-default",
1246
+ pluginId: "basic-schema",
1247
+ workspaceId: graph.workspace.workspaceId,
1248
+ metadata: {
1249
+ schemaDefault: true
1250
+ }
1251
+ };
1252
+ nextEntries.set(key, {
1253
+ key,
1254
+ value: rule.default,
1255
+ namespace: defaultEntry.namespace,
1256
+ winner: defaultEntry,
1257
+ overridden: []
1258
+ });
1259
+ continue;
1260
+ }
1261
+ if (rule.required) {
1262
+ issues.push({
1263
+ code: "schema.required",
1264
+ key,
1265
+ message: `Missing required config key: ${key}`
1266
+ });
1267
+ }
1268
+ continue;
1269
+ }
1270
+ const coercedValue = coerceValue(resolvedEntry.value, rule);
1271
+ const nextResolvedEntry = coercedValue === resolvedEntry.value ? resolvedEntry : {
1272
+ ...resolvedEntry,
1273
+ value: coercedValue
1274
+ };
1275
+ if (!matchesType(coercedValue, rule.type)) {
1276
+ issues.push({
1277
+ code: "schema.type",
1278
+ key,
1279
+ message: `Config key ${key} expected type ${rule.type} but got ${describeValueType(coercedValue)}`
1280
+ });
1281
+ }
1282
+ if (rule.enum && !enumMatches(coercedValue, rule.enum)) {
1283
+ issues.push({
1284
+ code: "schema.enum",
1285
+ key,
1286
+ message: `Config key ${key} must be one of ${rule.enum.map((entry) => JSON.stringify(entry)).join(", ")}`
1287
+ });
1288
+ }
1289
+ if (rule.pattern) {
1290
+ if (typeof coercedValue !== "string") {
1291
+ issues.push({
1292
+ code: "schema.pattern",
1293
+ key,
1294
+ message: `Config key ${key} must be a string to match pattern ${rule.pattern}`
1295
+ });
1296
+ } else if (!new RegExp(rule.pattern).test(coercedValue)) {
1297
+ issues.push({
1298
+ code: "schema.pattern",
1299
+ key,
1300
+ message: `Config key ${key} does not match pattern ${rule.pattern}`
1301
+ });
1302
+ }
1303
+ }
1304
+ nextEntries.set(key, nextResolvedEntry);
1305
+ }
1306
+ return {
1307
+ graph: {
1308
+ ...graph,
1309
+ entries: nextEntries
1310
+ },
1311
+ issues
1312
+ };
1313
+ }
1314
+
1315
+ // ../core/src/orchestrator/pipeline.ts
1316
+ async function runPipeline(options) {
1317
+ const collectedEntries = await Promise.all(
1318
+ options.plugins.map(
1319
+ (plugin) => plugin.load({
1320
+ manifestConfig: {
1321
+ ...options.manifest.sources[plugin.id] ?? {},
1322
+ envMapping: options.manifest.envMapping
1323
+ },
1324
+ profile: options.profile,
1325
+ profileChain: options.profileChain,
1326
+ profileActivation: options.profileActivation,
1327
+ manifestRoot: options.manifestRoot,
1328
+ workspace: options.workspace,
1329
+ ...options.cliArgs ? { cliArgs: options.cliArgs } : {},
1330
+ ...options.processEnv ? { processEnv: options.processEnv } : {}
1331
+ })
1332
+ )
1333
+ );
1334
+ return collectedEntries.flat();
1335
+ }
1336
+
1337
+ // ../core/src/runtime/read.ts
1338
+ function readValue(graph, key) {
1339
+ return graph.entries.get(key)?.value;
1340
+ }
1341
+
1342
+ // ../core/src/runtime/readOr.ts
1343
+ function readOrValue(graph, key, fallback) {
1344
+ const value = readValue(graph, key);
1345
+ return value === void 0 ? fallback : value;
1346
+ }
1347
+
1348
+ // ../core/src/runtime/require.ts
1349
+ function requireValue(graph, key) {
1350
+ const value = readValue(graph, key);
1351
+ if (value === void 0) {
1352
+ throw new CnosKeyNotFoundError(key);
1353
+ }
1354
+ return value;
1355
+ }
1356
+
1357
+ // ../core/src/orchestrator/runtime.ts
1358
+ function createRuntime(manifest, graph, plugins = []) {
1359
+ return {
1360
+ manifest,
1361
+ plugins,
1362
+ graph,
1363
+ read(key) {
1364
+ return readValue(graph, key);
1365
+ },
1366
+ require(key) {
1367
+ return requireValue(graph, key);
1368
+ },
1369
+ readOr(key, fallback) {
1370
+ return readOrValue(graph, key, fallback);
1371
+ },
1372
+ value(path8) {
1373
+ return readValue(graph, toLogicalKey("value", path8));
1374
+ },
1375
+ secret(path8) {
1376
+ return readValue(graph, toLogicalKey("secret", path8));
1377
+ },
1378
+ meta(path8) {
1379
+ return readValue(graph, toLogicalKey("meta", path8));
1380
+ },
1381
+ inspect(key) {
1382
+ return inspectValue(graph, key);
1383
+ },
1384
+ toObject() {
1385
+ return toNamespaceObject(graph);
1386
+ },
1387
+ toNamespace(namespace) {
1388
+ return toNamespaceObject(graph, namespace);
1389
+ },
1390
+ toEnv(options) {
1391
+ return toEnv(graph, manifest, options);
1392
+ },
1393
+ toPublicEnv(options) {
1394
+ return toPublicEnv(graph, manifest, options);
1395
+ }
1396
+ };
1397
+ }
1398
+
1399
+ // ../core/src/orchestrator/createCnos.ts
1400
+ function buildMetaEntries(graph, cnosVersion) {
1401
+ return [
1402
+ {
1403
+ key: "meta.profile",
1404
+ value: graph.profile,
1405
+ namespace: "meta",
1406
+ sourceId: "profile-resolver",
1407
+ pluginId: "core",
1408
+ workspaceId: graph.workspace.workspaceId
1409
+ },
1410
+ {
1411
+ key: "meta.cnos.version",
1412
+ value: cnosVersion ?? "0.0.0-dev",
1413
+ namespace: "meta",
1414
+ sourceId: "core",
1415
+ pluginId: "core",
1416
+ workspaceId: graph.workspace.workspaceId
1417
+ },
1418
+ {
1419
+ key: "meta.resolved.at",
1420
+ value: graph.resolvedAt,
1421
+ namespace: "meta",
1422
+ sourceId: "core",
1423
+ pluginId: "core",
1424
+ workspaceId: graph.workspace.workspaceId
1425
+ },
1426
+ {
1427
+ key: "meta.resolved.from",
1428
+ value: graph.profileSource,
1429
+ namespace: "meta",
1430
+ sourceId: "profile-resolver",
1431
+ pluginId: "core",
1432
+ workspaceId: graph.workspace.workspaceId
1433
+ },
1434
+ {
1435
+ key: "meta.workspace",
1436
+ value: graph.workspace.workspaceId,
1437
+ namespace: "meta",
1438
+ sourceId: "workspace-resolver",
1439
+ pluginId: "core",
1440
+ workspaceId: graph.workspace.workspaceId
1441
+ },
1442
+ {
1443
+ key: "meta.workspace.source",
1444
+ value: graph.workspace.workspaceSource,
1445
+ namespace: "meta",
1446
+ sourceId: "workspace-resolver",
1447
+ pluginId: "core",
1448
+ workspaceId: graph.workspace.workspaceId
1449
+ },
1450
+ {
1451
+ key: "meta.workspace.chain",
1452
+ value: graph.workspace.workspaceChain,
1453
+ namespace: "meta",
1454
+ sourceId: "workspace-resolver",
1455
+ pluginId: "core",
1456
+ workspaceId: graph.workspace.workspaceId
1457
+ },
1458
+ {
1459
+ key: "meta.globalRoot",
1460
+ value: graph.workspace.globalRoot ?? null,
1461
+ namespace: "meta",
1462
+ sourceId: "workspace-resolver",
1463
+ pluginId: "core",
1464
+ workspaceId: graph.workspace.workspaceId
1465
+ },
1466
+ {
1467
+ key: "meta.global.enabled",
1468
+ value: Boolean(graph.workspace.globalRoot),
1469
+ namespace: "meta",
1470
+ sourceId: "workspace-resolver",
1471
+ pluginId: "core",
1472
+ workspaceId: graph.workspace.workspaceId
1473
+ }
1474
+ ];
1475
+ }
1476
+ function appendMetaEntries(graph, cnosVersion) {
1477
+ const nextEntries = new Map(graph.entries);
1478
+ for (const entry of buildMetaEntries(graph, cnosVersion)) {
1479
+ nextEntries.set(entry.key, {
1480
+ key: entry.key,
1481
+ value: entry.value,
1482
+ namespace: entry.namespace,
1483
+ winner: entry,
1484
+ overridden: []
1485
+ });
1486
+ }
1487
+ return {
1488
+ ...graph,
1489
+ entries: nextEntries
1490
+ };
1491
+ }
1492
+ async function createCnos(options = {}) {
1493
+ const loadedManifest = await loadManifest(options.root ? { root: options.root } : {});
1494
+ const workspaceFile = await loadWorkspaceFile(loadedManifest.repoRoot);
1495
+ const workspace = await resolveWorkspaceContext(loadedManifest.manifest, {
1496
+ manifestRoot: loadedManifest.manifestRoot,
1497
+ ...workspaceFile ? { workspaceFile: workspaceFile.config } : {},
1498
+ ...options.workspace ? { workspace: options.workspace } : {},
1499
+ ...options.globalRoot ? { globalRoot: options.globalRoot } : {},
1500
+ ...options.processEnv ? { processEnv: options.processEnv } : {}
1501
+ });
1502
+ const activeProfile = resolveActiveProfile(loadedManifest.manifest, {
1503
+ ...options.profile ? { profile: options.profile } : {},
1504
+ ...workspaceFile ? { workspaceFile: workspaceFile.config } : {},
1505
+ ...options.processEnv ? { processEnv: options.processEnv } : {}
1506
+ });
1507
+ const profileChain = await expandProfileChain(activeProfile.profile, {
1508
+ manifestRoot: loadedManifest.manifestRoot,
1509
+ workspace
1510
+ });
1511
+ const plugins = options.plugins ?? [];
1512
+ const loaderPlugins = plugins.filter((plugin) => plugin.kind === "loader");
1513
+ const resolverPlugin = plugins.find((plugin) => plugin.kind === "resolver") ?? createProfileAwareResolver();
1514
+ const entries = await runPipeline({
1515
+ manifestRoot: loadedManifest.manifestRoot,
1516
+ manifest: loadedManifest.manifest,
1517
+ profile: activeProfile.profile,
1518
+ profileChain: profileChain.profiles,
1519
+ profileActivation: profileChain.activation,
1520
+ workspace,
1521
+ plugins: loaderPlugins,
1522
+ ...options.cliArgs ? { cliArgs: options.cliArgs } : {},
1523
+ ...options.processEnv ? { processEnv: options.processEnv } : {}
1524
+ });
1525
+ const graph = await resolverPlugin.resolve(entries, {
1526
+ manifest: loadedManifest.manifest,
1527
+ profile: activeProfile.profile,
1528
+ profileChain: profileChain.profiles,
1529
+ precedenceOrder: loadedManifest.manifest.resolution.precedence,
1530
+ workspace
1531
+ });
1532
+ const schemaApplied = applySchemaRules(graph, loadedManifest.manifest.schema);
1533
+ return createRuntime(
1534
+ loadedManifest.manifest,
1535
+ appendMetaEntries({
1536
+ ...schemaApplied.graph,
1537
+ profileSource: activeProfile.source
1538
+ }, options.cnosVersion),
1539
+ plugins
1540
+ );
1541
+ }
1542
+
1543
+ export {
1544
+ CnosManifestError,
1545
+ createProvenanceInspector,
1546
+ resolveWorkspaceScopedPath,
1547
+ resolveConfigDocumentPath,
1548
+ toPortablePath,
1549
+ joinConfigPath,
1550
+ parseYaml,
1551
+ stringifyYaml,
1552
+ applySchemaRules,
1553
+ envVarToLogicalKey,
1554
+ toEnv,
1555
+ toPublicEnv,
1556
+ createCnos,
1557
+ planDump,
1558
+ writeDump,
1559
+ flattenObject,
1560
+ isSecretReference,
1561
+ resolveSecretStoreRoot,
1562
+ writeLocalSecret,
1563
+ readLocalSecret,
1564
+ validateRuntime
1565
+ };