@kitsy/cnos 1.9.2 → 1.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/dist/build/index.cjs +523 -80
  2. package/dist/build/index.d.cts +1 -1
  3. package/dist/build/index.d.ts +1 -1
  4. package/dist/build/index.js +13 -15
  5. package/dist/{chunk-6QQPHDUI.js → chunk-2DMCB3PK.js} +1 -1
  6. package/dist/{chunk-LURQ4LAK.js → chunk-5JGNRADB.js} +1 -1
  7. package/dist/{chunk-2JBA2LXU.js → chunk-DPC2BV3S.js} +35 -6
  8. package/dist/{chunk-7JZO6XN3.js → chunk-KJ57PF47.js} +1 -1
  9. package/dist/{chunk-CPGRRZLP.js → chunk-NFGPS7VJ.js} +8 -8
  10. package/dist/{chunk-A2WG3ZKW.js → chunk-NU25VFA2.js} +1 -1
  11. package/dist/{chunk-L7JVECPE.js → chunk-RNTTPI5S.js} +1 -1
  12. package/dist/{chunk-NVFACB64.js → chunk-T3E57MSQ.js} +1 -1
  13. package/dist/{chunk-7KVM5PUW.js → chunk-WPB4HB2K.js} +478 -61
  14. package/dist/{chunk-QK7BMU47.js → chunk-XGK6DXQL.js} +157 -37
  15. package/dist/configure/index.cjs +521 -76
  16. package/dist/configure/index.d.cts +3 -3
  17. package/dist/configure/index.d.ts +3 -3
  18. package/dist/configure/index.js +8 -8
  19. package/dist/{core-zDTUSVx9.d.cts → core-BW8SLnRx.d.cts} +46 -7
  20. package/dist/{core-zDTUSVx9.d.ts → core-BW8SLnRx.d.ts} +46 -7
  21. package/dist/{envNaming-EFzezmB3.d.cts → envNaming-1rk7BR0e.d.cts} +1 -1
  22. package/dist/{envNaming-BkorOKW_.d.ts → envNaming-CjL28IeH.d.ts} +1 -1
  23. package/dist/index.cjs +672 -108
  24. package/dist/index.d.cts +2 -2
  25. package/dist/index.d.ts +2 -2
  26. package/dist/index.js +10 -10
  27. package/dist/internal.cjs +378 -54
  28. package/dist/internal.d.cts +32 -4
  29. package/dist/internal.d.ts +32 -4
  30. package/dist/internal.js +141 -23
  31. package/dist/plugin/basic-schema.cjs +13 -3
  32. package/dist/plugin/basic-schema.d.cts +1 -1
  33. package/dist/plugin/basic-schema.d.ts +1 -1
  34. package/dist/plugin/basic-schema.js +2 -2
  35. package/dist/plugin/cli-args.cjs +4 -1
  36. package/dist/plugin/cli-args.d.cts +1 -1
  37. package/dist/plugin/cli-args.d.ts +1 -1
  38. package/dist/plugin/cli-args.js +2 -2
  39. package/dist/plugin/dotenv.cjs +40 -8
  40. package/dist/plugin/dotenv.d.cts +2 -2
  41. package/dist/plugin/dotenv.d.ts +2 -2
  42. package/dist/plugin/dotenv.js +2 -2
  43. package/dist/plugin/env-export.cjs +5 -2
  44. package/dist/plugin/env-export.d.cts +2 -2
  45. package/dist/plugin/env-export.d.ts +2 -2
  46. package/dist/plugin/env-export.js +2 -2
  47. package/dist/plugin/filesystem.cjs +13 -10
  48. package/dist/plugin/filesystem.d.cts +1 -1
  49. package/dist/plugin/filesystem.d.ts +1 -1
  50. package/dist/plugin/filesystem.js +2 -2
  51. package/dist/plugin/process-env.cjs +4 -1
  52. package/dist/plugin/process-env.d.cts +2 -2
  53. package/dist/plugin/process-env.d.ts +2 -2
  54. package/dist/plugin/process-env.js +2 -2
  55. package/dist/runtime/index.cjs +672 -108
  56. package/dist/runtime/index.d.cts +13 -6
  57. package/dist/runtime/index.d.ts +13 -6
  58. package/dist/runtime/index.js +10 -10
  59. package/dist/{toPublicEnv-Ds1DRwCX.d.cts → toPublicEnv-CZzpvhGg.d.cts} +1 -1
  60. package/dist/{toPublicEnv-CT265rzS.d.ts → toPublicEnv-CmydGcxg.d.ts} +1 -1
  61. package/package.json +1 -1
package/dist/internal.cjs CHANGED
@@ -40,6 +40,7 @@ __export(internal_exports, {
40
40
  clearAllVaultSessionKeys: () => clearAllVaultSessionKeys,
41
41
  clearVaultSessionKey: () => clearVaultSessionKey,
42
42
  compareSchemaToGraph: () => compareSchemaToGraph,
43
+ compareSpecToGraph: () => compareSpecToGraph,
43
44
  createRemoteRootCacheKey: () => createRemoteRootCacheKey,
44
45
  createSecretVault: () => createSecretVault,
45
46
  createSecretVaultProvider: () => createSecretVaultProvider,
@@ -1033,6 +1034,134 @@ function resolveConfigDocumentPath(workspaceRoot, namespace, configPath, profile
1033
1034
  return import_node_path5.default.resolve(namespaceRoot, fileName);
1034
1035
  }
1035
1036
 
1037
+ // ../core/src/spec/normalizeSpecRule.ts
1038
+ var ALLOWED_TYPES = /* @__PURE__ */ new Set(["string", "number", "boolean", "object", "array"]);
1039
+ var SECRET_FORBIDDEN_FIELDS = ["default", "examples", "enum"];
1040
+ function hasOwn(target, key) {
1041
+ return Object.prototype.hasOwnProperty.call(target, key);
1042
+ }
1043
+ function normalizeOptionalString(value, fieldName, logicalKey) {
1044
+ if (value === void 0) {
1045
+ return void 0;
1046
+ }
1047
+ if (typeof value !== "string") {
1048
+ throw new CnosManifestError(`Invalid schema rule for ${logicalKey}: "${fieldName}" must be a string.`);
1049
+ }
1050
+ const nextValue = value.trim();
1051
+ return nextValue.length > 0 ? nextValue : void 0;
1052
+ }
1053
+ function normalizeStringArray(value, fieldName, logicalKey) {
1054
+ if (value === void 0) {
1055
+ return void 0;
1056
+ }
1057
+ if (!Array.isArray(value)) {
1058
+ throw new CnosManifestError(`Invalid schema rule for ${logicalKey}: "${fieldName}" must be an array.`);
1059
+ }
1060
+ const nextValue = value.map((entry) => {
1061
+ if (typeof entry !== "string") {
1062
+ throw new CnosManifestError(
1063
+ `Invalid schema rule for ${logicalKey}: "${fieldName}" entries must be strings.`
1064
+ );
1065
+ }
1066
+ return entry.trim();
1067
+ }).filter(Boolean);
1068
+ return nextValue.length > 0 ? nextValue : void 0;
1069
+ }
1070
+ function normalizeUnknownArray(value, fieldName, logicalKey) {
1071
+ if (value === void 0) {
1072
+ return void 0;
1073
+ }
1074
+ if (!Array.isArray(value)) {
1075
+ throw new CnosManifestError(`Invalid schema rule for ${logicalKey}: "${fieldName}" must be an array.`);
1076
+ }
1077
+ return value.length > 0 ? value : void 0;
1078
+ }
1079
+ function assertValidPatternRegex(pattern, logicalKey) {
1080
+ try {
1081
+ void new RegExp(pattern);
1082
+ } catch (error) {
1083
+ const reason = error instanceof Error ? error.message : String(error);
1084
+ throw new CnosManifestError(
1085
+ `Invalid schema rule for ${logicalKey}: "pattern" must be a valid regex (${reason}).`
1086
+ );
1087
+ }
1088
+ }
1089
+ function assertSecretRuleSafety(logicalKey, rule) {
1090
+ if (!logicalKey.startsWith("secret.")) {
1091
+ return;
1092
+ }
1093
+ const offendingFields = SECRET_FORBIDDEN_FIELDS.filter((field) => hasOwn(rule, field));
1094
+ if (offendingFields.length === 0) {
1095
+ return;
1096
+ }
1097
+ throw new CnosManifestError(
1098
+ `Invalid schema rule for ${logicalKey}: secret specs cannot include ${offendingFields.join(", ")}. Store secret values in the vault, not schema metadata. Remove ${offendingFields.map((field) => `schema.${logicalKey}.${field}`).join(", ")} to continue.`
1099
+ );
1100
+ }
1101
+ function normalizeSpecRule(logicalKey, rule) {
1102
+ if (!rule || typeof rule !== "object" || Array.isArray(rule)) {
1103
+ throw new CnosManifestError(`Invalid schema rule for ${logicalKey}: expected an object.`);
1104
+ }
1105
+ const candidate = rule;
1106
+ assertSecretRuleSafety(logicalKey, candidate);
1107
+ const normalized = {};
1108
+ if (candidate.type !== void 0) {
1109
+ if (typeof candidate.type !== "string" || !ALLOWED_TYPES.has(candidate.type)) {
1110
+ throw new CnosManifestError(`Invalid schema rule for ${logicalKey}: unsupported type "${String(candidate.type)}".`);
1111
+ }
1112
+ normalized.type = candidate.type;
1113
+ }
1114
+ if (candidate.required !== void 0) {
1115
+ if (typeof candidate.required !== "boolean") {
1116
+ throw new CnosManifestError(`Invalid schema rule for ${logicalKey}: "required" must be a boolean.`);
1117
+ }
1118
+ normalized.required = candidate.required;
1119
+ }
1120
+ if (hasOwn(candidate, "default")) {
1121
+ normalized.default = candidate.default;
1122
+ }
1123
+ const normalizedEnum = normalizeUnknownArray(candidate.enum, "enum", logicalKey);
1124
+ if (normalizedEnum !== void 0) {
1125
+ normalized.enum = normalizedEnum;
1126
+ }
1127
+ const normalizedPattern = normalizeOptionalString(candidate.pattern, "pattern", logicalKey);
1128
+ if (normalizedPattern !== void 0) {
1129
+ assertValidPatternRegex(normalizedPattern, logicalKey);
1130
+ normalized.pattern = normalizedPattern;
1131
+ }
1132
+ const normalizedSummary = normalizeOptionalString(candidate.summary, "summary", logicalKey);
1133
+ if (normalizedSummary !== void 0) {
1134
+ normalized.summary = normalizedSummary;
1135
+ }
1136
+ const normalizedDescription = normalizeOptionalString(candidate.description, "description", logicalKey);
1137
+ if (normalizedDescription !== void 0) {
1138
+ normalized.description = normalizedDescription;
1139
+ }
1140
+ const normalizedExamples = normalizeUnknownArray(candidate.examples, "examples", logicalKey);
1141
+ if (normalizedExamples !== void 0) {
1142
+ normalized.examples = normalizedExamples;
1143
+ }
1144
+ const normalizedUsedBy = normalizeStringArray(candidate.usedBy, "usedBy", logicalKey);
1145
+ if (normalizedUsedBy !== void 0) {
1146
+ normalized.usedBy = normalizedUsedBy;
1147
+ }
1148
+ if (candidate.deprecated !== void 0) {
1149
+ if (typeof candidate.deprecated !== "boolean") {
1150
+ throw new CnosManifestError(`Invalid schema rule for ${logicalKey}: "deprecated" must be a boolean.`);
1151
+ }
1152
+ normalized.deprecated = candidate.deprecated;
1153
+ }
1154
+ const normalizedDeprecationMessage = normalizeOptionalString(
1155
+ candidate.deprecationMessage,
1156
+ "deprecationMessage",
1157
+ logicalKey
1158
+ );
1159
+ if (normalizedDeprecationMessage !== void 0) {
1160
+ normalized.deprecationMessage = normalizedDeprecationMessage;
1161
+ }
1162
+ return normalized;
1163
+ }
1164
+
1036
1165
  // ../core/src/manifest/normalizeManifest.ts
1037
1166
  var DEFAULT_RESOLVE_FROM = ["cli.profile", "env.CNOS_PROFILE", "default"];
1038
1167
  var DEFAULT_LOADERS = [
@@ -1166,11 +1295,19 @@ function normalizeVaults(vaults) {
1166
1295
  throw new CnosManifestError(`Vault "${name}" requires a provider`);
1167
1296
  }
1168
1297
  const normalizedAuth = normalizeVaultAuth(name, provider, definition.auth);
1169
- const normalizedMapping = Object.fromEntries(
1170
- Object.entries(definition.mapping ?? {}).filter(
1171
- (entry) => typeof entry[0] === "string" && typeof entry[1] === "string"
1172
- ).map(([envVar, logicalRef]) => [envVar.trim(), logicalRef.trim()]).filter(([envVar, logicalRef]) => envVar.length > 0 && logicalRef.length > 0)
1173
- );
1298
+ const normalizedMapping = normalizeVaultMapping(definition.mapping);
1299
+ const fallback = (definition.fallback ?? []).map((entry, index) => {
1300
+ const fallbackProvider = entry.provider?.trim();
1301
+ if (!fallbackProvider) {
1302
+ throw new CnosManifestError(`Vault "${name}" fallback ${index + 1} requires a provider`);
1303
+ }
1304
+ const fallbackMapping = normalizeVaultMapping(entry.mapping);
1305
+ return {
1306
+ provider: fallbackProvider,
1307
+ auth: normalizeVaultAuth(name, fallbackProvider, entry.auth),
1308
+ ...Object.keys(fallbackMapping).length > 0 ? { mapping: fallbackMapping } : {}
1309
+ };
1310
+ });
1174
1311
  return [
1175
1312
  name,
1176
1313
  {
@@ -1178,12 +1315,20 @@ function normalizeVaults(vaults) {
1178
1315
  auth: normalizedAuth,
1179
1316
  ...Object.keys(normalizedMapping).length > 0 ? {
1180
1317
  mapping: normalizedMapping
1181
- } : {}
1318
+ } : {},
1319
+ ...fallback.length > 0 ? { fallback } : {}
1182
1320
  }
1183
1321
  ];
1184
1322
  })
1185
1323
  );
1186
1324
  }
1325
+ function normalizeVaultMapping(mapping) {
1326
+ return Object.fromEntries(
1327
+ Object.entries(mapping ?? {}).filter(
1328
+ (entry) => typeof entry[0] === "string" && typeof entry[1] === "string"
1329
+ ).map(([envVar, logicalRef]) => [envVar.trim(), logicalRef.trim()]).filter(([envVar, logicalRef]) => envVar.length > 0 && logicalRef.length > 0)
1330
+ );
1331
+ }
1187
1332
  function normalizeAuthSources(value) {
1188
1333
  if (!value || typeof value !== "object" || Array.isArray(value)) {
1189
1334
  return void 0;
@@ -1231,6 +1376,14 @@ function normalizeVaultAuth(vaultName, provider, auth) {
1231
1376
  ...auth?.config ? { config: auth.config } : {}
1232
1377
  };
1233
1378
  }
1379
+ function normalizeSchema(schema) {
1380
+ return Object.fromEntries(
1381
+ Object.entries(schema ?? {}).map(([logicalKey, rule]) => [
1382
+ logicalKey,
1383
+ normalizeSpecRule(logicalKey, rule)
1384
+ ])
1385
+ );
1386
+ }
1234
1387
  function normalizeManifest(manifest) {
1235
1388
  const version = manifest.version ?? 1;
1236
1389
  if (version !== 1) {
@@ -1328,7 +1481,7 @@ function normalizeManifest(manifest) {
1328
1481
  }
1329
1482
  }
1330
1483
  },
1331
- schema: manifest.schema ?? {}
1484
+ schema: normalizeSchema(manifest.schema)
1332
1485
  };
1333
1486
  }
1334
1487
 
@@ -1499,7 +1652,7 @@ function isObject(value) {
1499
1652
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
1500
1653
  }
1501
1654
  function isSecretReference(value) {
1502
- return isObject(value) && typeof value.provider === "string" && value.provider.trim().length > 0 && typeof value.ref === "string" && value.ref.trim().length > 0 && (value.vault === void 0 && true || typeof value.vault === "string" && value.vault.trim().length > 0) && Object.keys(value).every((key) => ["provider", "ref", "vault"].includes(key));
1655
+ return isObject(value) && (value.provider === void 0 || typeof value.provider === "string" && value.provider.trim().length > 0) && typeof value.ref === "string" && value.ref.trim().length > 0 && (value.vault === void 0 && true || typeof value.vault === "string" && value.vault.trim().length > 0) && Object.keys(value).every((key) => ["provider", "ref", "vault"].includes(key));
1503
1656
  }
1504
1657
  function resolveSecretStoreRoot(processEnv = process.env) {
1505
1658
  return import_node_path11.default.resolve(expandHomePath(processEnv.CNOS_SECRET_HOME ?? "~/.cnos/secrets"));
@@ -1836,16 +1989,19 @@ async function removeLocalVaultFiles(storeRoot, vault = "default") {
1836
1989
  // ../core/src/secrets/auditLog.ts
1837
1990
  async function appendAuditEvent(event, processEnv = process.env) {
1838
1991
  const auditFile = processEnv.CNOS_AUDIT_FILE ?? import_node_path12.default.join(resolveSecretStoreRoot(processEnv), "audit", "access.log");
1839
- await (0, import_promises11.mkdir)(import_node_path12.default.dirname(auditFile), { recursive: true });
1840
- await (0, import_promises11.appendFile)(
1841
- auditFile,
1842
- `${JSON.stringify({
1843
- ts: (/* @__PURE__ */ new Date()).toISOString(),
1844
- ...event
1845
- })}
1992
+ try {
1993
+ await (0, import_promises11.mkdir)(import_node_path12.default.dirname(auditFile), { recursive: true });
1994
+ await (0, import_promises11.appendFile)(
1995
+ auditFile,
1996
+ `${JSON.stringify({
1997
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
1998
+ ...event
1999
+ })}
1846
2000
  `,
1847
- "utf8"
1848
- );
2001
+ "utf8"
2002
+ );
2003
+ } catch {
2004
+ }
1849
2005
  }
1850
2006
 
1851
2007
  // ../core/src/secrets/providers/environment.ts
@@ -1998,7 +2154,7 @@ var LocalSecretVaultProvider = class _LocalSecretVaultProvider {
1998
2154
  };
1999
2155
 
2000
2156
  // ../core/src/secrets/providers/registry.ts
2001
- function createSecretVaultProvider(vaultId, definition, processEnv) {
2157
+ function createSecretVaultProvider(vaultId, definition, processEnv, factories = []) {
2002
2158
  if (definition.provider === "local") {
2003
2159
  return new LocalSecretVaultProvider(vaultId, definition, processEnv);
2004
2160
  }
@@ -2008,9 +2164,16 @@ function createSecretVaultProvider(vaultId, definition, processEnv) {
2008
2164
  if (definition.provider === "github-secrets") {
2009
2165
  return new GithubSecretsVaultProvider(vaultId, definition, processEnv);
2010
2166
  }
2167
+ const factory = factories.find((candidate) => candidate.provider === definition.provider);
2168
+ if (factory) {
2169
+ return factory.create(vaultId, definition, processEnv);
2170
+ }
2011
2171
  throw new CnosManifestError(`Unsupported vault provider: ${definition.provider}`);
2012
2172
  }
2013
2173
 
2174
+ // ../core/src/secrets/resolveAuth.ts
2175
+ var import_promises12 = require("fs/promises");
2176
+
2014
2177
  // ../core/src/secrets/prompt.ts
2015
2178
  var import_node_readline = __toESM(require("readline"), 1);
2016
2179
  var import_node_stream = require("stream");
@@ -2051,6 +2214,23 @@ function toAuthError(vaultId, sources) {
2051
2214
  `Cannot authenticate to vault "${vaultId}". Tried: ${sources.join(", ")}. Set ${getVaultPassphraseEnvVar(vaultId)} or run cnos vault auth ${vaultId}.`
2052
2215
  );
2053
2216
  }
2217
+ async function resolveTokenFromSource(source, processEnv) {
2218
+ if (source.startsWith("env:")) {
2219
+ return processEnv[source.slice(4)] || void 0;
2220
+ }
2221
+ if (source.startsWith("file:")) {
2222
+ try {
2223
+ const value = await (0, import_promises12.readFile)(expandHomePath(source.slice("file:".length)), "utf8");
2224
+ return value.trim() || void 0;
2225
+ } catch {
2226
+ return void 0;
2227
+ }
2228
+ }
2229
+ if (source.startsWith("keychain:")) {
2230
+ return readKeychain(source.slice("keychain:".length));
2231
+ }
2232
+ return void 0;
2233
+ }
2054
2234
  async function resolveVaultAuth(vaultId, definition, processEnv = process.env) {
2055
2235
  const sessionKey = await resolveVaultSessionKey(vaultId, processEnv);
2056
2236
  if (sessionKey) {
@@ -2066,6 +2246,32 @@ async function resolveVaultAuth(vaultId, definition, processEnv = process.env) {
2066
2246
  ...definition.auth?.config ? { config: definition.auth.config } : {}
2067
2247
  };
2068
2248
  }
2249
+ if (definition.auth?.method === "iam") {
2250
+ return {
2251
+ method: "iam",
2252
+ ...definition.auth?.config ? { config: definition.auth.config } : {}
2253
+ };
2254
+ }
2255
+ if (definition.auth?.method === "environment") {
2256
+ return {
2257
+ method: "environment",
2258
+ ...definition.auth?.config ? { config: definition.auth.config } : {}
2259
+ };
2260
+ }
2261
+ const tokenSources = definition.auth?.token?.from ?? [];
2262
+ for (const source of tokenSources) {
2263
+ const token = await resolveTokenFromSource(source, processEnv);
2264
+ if (token) {
2265
+ return {
2266
+ token,
2267
+ method: "token",
2268
+ ...definition.auth?.config ? { config: definition.auth.config } : {}
2269
+ };
2270
+ }
2271
+ }
2272
+ if (definition.auth?.method === "token") {
2273
+ throw toAuthError(vaultId, [getVaultSessionKeyEnvVar(vaultId), ...tokenSources]);
2274
+ }
2069
2275
  const sources = definition.auth?.passphrase?.from ?? [getVaultPassphraseEnvVar(vaultId)];
2070
2276
  for (const source of sources) {
2071
2277
  if (source.startsWith("env:")) {
@@ -2114,7 +2320,7 @@ async function resolveVaultAuth(vaultId, definition, processEnv = process.env) {
2114
2320
  var import_node_crypto3 = require("crypto");
2115
2321
 
2116
2322
  // ../core/src/runtime/dump.ts
2117
- var import_promises12 = require("fs/promises");
2323
+ var import_promises13 = require("fs/promises");
2118
2324
  var import_node_path13 = __toESM(require("path"), 1);
2119
2325
 
2120
2326
  // ../core/src/utils/envNaming.ts
@@ -2516,7 +2722,7 @@ function generateCodegenContent(manifest, sourcePath, typeModuleImport = "./cnos
2516
2722
  }
2517
2723
 
2518
2724
  // src/codegen/writeOutput.ts
2519
- var import_promises13 = require("fs/promises");
2725
+ var import_promises14 = require("fs/promises");
2520
2726
  var import_node_path14 = __toESM(require("path"), 1);
2521
2727
  function stripTsExtension(filePath) {
2522
2728
  return filePath.replace(/(\.d)?\.[cm]?tsx?$/i, "").replace(/\.[cm]?jsx?$/i, "");
@@ -2536,10 +2742,10 @@ async function writeCodegenOutput(options = {}) {
2536
2742
  const outputRoot = loadedManifest.rootResolution.remote ? loadedManifest.consumerRoot : loadedManifest.repoRoot;
2537
2743
  const paths = resolveCodegenPaths(outputRoot, options.out);
2538
2744
  const generated = generateCodegenContent(loadedManifest.manifest, loadedManifest.manifestPath, paths.typeImportPath);
2539
- await (0, import_promises13.mkdir)(import_node_path14.default.dirname(paths.typesPath), { recursive: true });
2540
- await (0, import_promises13.mkdir)(import_node_path14.default.dirname(paths.runtimePath), { recursive: true });
2541
- await (0, import_promises13.writeFile)(paths.typesPath, generated.typesContent, "utf8");
2542
- await (0, import_promises13.writeFile)(paths.runtimePath, generated.runtimeContent, "utf8");
2745
+ await (0, import_promises14.mkdir)(import_node_path14.default.dirname(paths.typesPath), { recursive: true });
2746
+ await (0, import_promises14.mkdir)(import_node_path14.default.dirname(paths.runtimePath), { recursive: true });
2747
+ await (0, import_promises14.writeFile)(paths.typesPath, generated.typesContent, "utf8");
2748
+ await (0, import_promises14.writeFile)(paths.runtimePath, generated.runtimeContent, "utf8");
2543
2749
  return {
2544
2750
  manifestPath: loadedManifest.manifestPath,
2545
2751
  typesPath: paths.typesPath,
@@ -2580,7 +2786,7 @@ async function watchSchema(options = {}) {
2580
2786
  return watcher;
2581
2787
  }
2582
2788
 
2583
- // src/drift/compareSchemaToGraph.ts
2789
+ // src/spec/compareSpecToGraph.ts
2584
2790
  function describeValueType(value) {
2585
2791
  if (Array.isArray(value)) {
2586
2792
  return "array";
@@ -2603,6 +2809,17 @@ function matchesType(value, type) {
2603
2809
  return typeof value === type;
2604
2810
  }
2605
2811
  }
2812
+ function enumMatches(value, allowed) {
2813
+ const serialized = JSON.stringify(value);
2814
+ return allowed.some((candidate) => JSON.stringify(candidate) === serialized);
2815
+ }
2816
+ function matchesPattern(pattern, value) {
2817
+ try {
2818
+ return new RegExp(pattern).test(value);
2819
+ } catch {
2820
+ return false;
2821
+ }
2822
+ }
2606
2823
  function isSchemaDefault(entry) {
2607
2824
  return entry.winner.metadata?.schemaDefault === true;
2608
2825
  }
@@ -2612,34 +2829,53 @@ function shouldTrackKey(key) {
2612
2829
  function isTransientRuntimeSource(entry) {
2613
2830
  return entry.winner.sourceId === "process-env" || entry.winner.sourceId === "cli-args";
2614
2831
  }
2615
- function compareSchemaToGraph(runtime) {
2832
+ function buildSummary(issues) {
2833
+ return {
2834
+ missingRequired: issues.filter((issue) => issue.status === "missing_required").length,
2835
+ undeclared: issues.filter((issue) => issue.status === "undeclared").length,
2836
+ typeMismatch: issues.filter((issue) => issue.status === "type_mismatch").length,
2837
+ enumMismatch: issues.filter((issue) => issue.status === "enum_mismatch").length,
2838
+ patternMismatch: issues.filter((issue) => issue.status === "pattern_mismatch").length,
2839
+ defaultApplied: issues.filter((issue) => issue.status === "default_applied").length,
2840
+ deprecatedInUse: issues.filter((issue) => issue.status === "deprecated_in_use").length
2841
+ };
2842
+ }
2843
+ function compareSpecToGraph(runtime) {
2616
2844
  const schema = runtime.manifest.schema;
2617
- const missing = [];
2618
- const mismatches = [];
2619
- const defaultsApplied = [];
2845
+ const issues = [];
2620
2846
  for (const [key, rule] of Object.entries(schema).sort(([left], [right]) => left.localeCompare(right))) {
2621
2847
  const entry = runtime.graph.entries.get(key);
2848
+ const summary = rule.summary;
2622
2849
  if (!entry) {
2623
2850
  if (rule.required && rule.default === void 0) {
2624
- missing.push({
2851
+ issues.push({
2625
2852
  key,
2853
+ status: "missing_required",
2626
2854
  ...rule.type ? {
2627
2855
  expectedType: rule.type
2856
+ } : {},
2857
+ ...summary ? {
2858
+ summary
2628
2859
  } : {}
2629
2860
  });
2630
2861
  }
2631
2862
  continue;
2632
2863
  }
2633
2864
  if (isSchemaDefault(entry)) {
2634
- defaultsApplied.push({
2865
+ issues.push({
2635
2866
  key,
2636
- value: entry.value
2867
+ status: "default_applied",
2868
+ value: entry.value,
2869
+ ...summary ? {
2870
+ summary
2871
+ } : {}
2637
2872
  });
2638
2873
  }
2639
2874
  const actualValue = entry.winner.value;
2640
2875
  if (!matchesType(actualValue, rule.type)) {
2641
- mismatches.push({
2876
+ issues.push({
2642
2877
  key,
2878
+ status: "type_mismatch",
2643
2879
  ...rule.type ? {
2644
2880
  expectedType: rule.type
2645
2881
  } : {},
@@ -2647,26 +2883,113 @@ function compareSchemaToGraph(runtime) {
2647
2883
  value: actualValue,
2648
2884
  ...entry.winner.origin?.file ? {
2649
2885
  sourceFile: entry.winner.origin.file
2886
+ } : {},
2887
+ ...summary ? {
2888
+ summary
2889
+ } : {}
2890
+ });
2891
+ }
2892
+ if (rule.enum && !enumMatches(actualValue, rule.enum)) {
2893
+ issues.push({
2894
+ key,
2895
+ status: "enum_mismatch",
2896
+ value: actualValue,
2897
+ ...summary ? {
2898
+ summary
2899
+ } : {}
2900
+ });
2901
+ }
2902
+ if (rule.pattern) {
2903
+ if (typeof actualValue !== "string" || !matchesPattern(rule.pattern, actualValue)) {
2904
+ issues.push({
2905
+ key,
2906
+ status: "pattern_mismatch",
2907
+ value: actualValue,
2908
+ pattern: rule.pattern,
2909
+ ...summary ? {
2910
+ summary
2911
+ } : {}
2912
+ });
2913
+ }
2914
+ }
2915
+ if (rule.deprecated) {
2916
+ issues.push({
2917
+ key,
2918
+ status: "deprecated_in_use",
2919
+ value: actualValue,
2920
+ ...summary ? {
2921
+ summary
2650
2922
  } : {}
2651
2923
  });
2652
2924
  }
2653
2925
  }
2654
- const undeclared = Array.from(runtime.graph.entries.values()).filter(
2926
+ const undeclaredIssues = Array.from(runtime.graph.entries.values()).filter(
2655
2927
  (entry) => shouldTrackKey(entry.key) && !schema[entry.key] && !isSchemaDefault(entry) && !isTransientRuntimeSource(entry)
2656
- ).map((entry) => {
2657
- const issue = {
2658
- key: entry.key,
2659
- value: entry.winner.value,
2660
- actualType: describeValueType(entry.winner.value)
2661
- };
2662
- if (entry.winner.origin?.file) {
2663
- issue.sourceFile = entry.winner.origin.file;
2664
- }
2665
- return issue;
2666
- }).sort((left, right) => left.key.localeCompare(right.key));
2928
+ ).map((entry) => ({
2929
+ key: entry.key,
2930
+ status: "undeclared",
2931
+ value: entry.winner.value,
2932
+ actualType: describeValueType(entry.winner.value),
2933
+ ...entry.winner.origin?.file ? {
2934
+ sourceFile: entry.winner.origin.file
2935
+ } : {}
2936
+ })).sort((left, right) => left.key.localeCompare(right.key));
2937
+ const allIssues = [...issues, ...undeclaredIssues].sort((left, right) => left.key.localeCompare(right.key));
2667
2938
  return {
2668
2939
  profile: runtime.graph.profile,
2669
2940
  workspace: runtime.graph.workspace.workspaceId,
2941
+ summary: buildSummary(allIssues),
2942
+ issues: allIssues
2943
+ };
2944
+ }
2945
+
2946
+ // src/drift/compareSchemaToGraph.ts
2947
+ function compareSchemaToGraph(runtime) {
2948
+ const report = compareSpecToGraph(runtime);
2949
+ const missing = report.issues.filter((issue) => issue.status === "missing_required").map(
2950
+ (issue) => ({
2951
+ key: issue.key,
2952
+ ...issue.expectedType ? {
2953
+ expectedType: issue.expectedType
2954
+ } : {}
2955
+ })
2956
+ );
2957
+ const undeclared = report.issues.filter((issue) => issue.status === "undeclared").map(
2958
+ (issue) => ({
2959
+ key: issue.key,
2960
+ value: issue.value,
2961
+ ...issue.actualType ? {
2962
+ actualType: issue.actualType
2963
+ } : {},
2964
+ ...issue.sourceFile ? {
2965
+ sourceFile: issue.sourceFile
2966
+ } : {}
2967
+ })
2968
+ );
2969
+ const mismatches = report.issues.filter((issue) => issue.status === "type_mismatch").map(
2970
+ (issue) => ({
2971
+ key: issue.key,
2972
+ ...issue.expectedType ? {
2973
+ expectedType: issue.expectedType
2974
+ } : {},
2975
+ ...issue.actualType ? {
2976
+ actualType: issue.actualType
2977
+ } : {},
2978
+ value: issue.value,
2979
+ ...issue.sourceFile ? {
2980
+ sourceFile: issue.sourceFile
2981
+ } : {}
2982
+ })
2983
+ );
2984
+ const defaultsApplied = report.issues.filter((issue) => issue.status === "default_applied").map(
2985
+ (issue) => ({
2986
+ key: issue.key,
2987
+ value: issue.value
2988
+ })
2989
+ );
2990
+ return {
2991
+ profile: report.profile,
2992
+ workspace: report.workspace,
2670
2993
  missing,
2671
2994
  undeclared,
2672
2995
  mismatches,
@@ -2725,7 +3048,7 @@ function formatDriftReport(report) {
2725
3048
  }
2726
3049
 
2727
3050
  // src/migrate/applyManifest.ts
2728
- var import_promises14 = require("fs/promises");
3051
+ var import_promises15 = require("fs/promises");
2729
3052
  function sortRecord(record) {
2730
3053
  return Object.fromEntries(Object.entries(record).sort(([left], [right]) => left.localeCompare(right)));
2731
3054
  }
@@ -2760,7 +3083,7 @@ async function applyManifestMappings(proposals, root) {
2760
3083
  promote: Array.from(promoted).sort((left, right) => left.localeCompare(right))
2761
3084
  };
2762
3085
  }
2763
- await (0, import_promises14.writeFile)(loadedManifest.manifestPath, stringifyYaml(rawManifest), "utf8");
3086
+ await (0, import_promises15.writeFile)(loadedManifest.manifestPath, stringifyYaml(rawManifest), "utf8");
2764
3087
  return {
2765
3088
  manifestPath: loadedManifest.manifestPath,
2766
3089
  appliedMappings,
@@ -2795,7 +3118,7 @@ function proposeMapping(envVar) {
2795
3118
  }
2796
3119
 
2797
3120
  // src/migrate/rewriteSource.ts
2798
- var import_promises15 = require("fs/promises");
3121
+ var import_promises16 = require("fs/promises");
2799
3122
  function importStatementFor(kind) {
2800
3123
  return kind === "import-meta-env" ? "import cnos from '@kitsy/cnos/browser';" : "import cnos from '@kitsy/cnos';";
2801
3124
  }
@@ -2816,7 +3139,7 @@ async function rewriteSourceFiles(usages, proposals) {
2816
3139
  const backupFiles = [];
2817
3140
  const skippedUsages = [];
2818
3141
  for (const [filePath, fileUsages] of fileGroups.entries()) {
2819
- const original = await (0, import_promises15.readFile)(filePath, "utf8");
3142
+ const original = await (0, import_promises16.readFile)(filePath, "utf8");
2820
3143
  let nextSource = original;
2821
3144
  let changed = false;
2822
3145
  const importKinds = /* @__PURE__ */ new Set();
@@ -2843,7 +3166,7 @@ async function rewriteSourceFiles(usages, proposals) {
2843
3166
  continue;
2844
3167
  }
2845
3168
  const backupPath = `${filePath}.bak`;
2846
- await (0, import_promises15.copyFile)(filePath, backupPath);
3169
+ await (0, import_promises16.copyFile)(filePath, backupPath);
2847
3170
  backupFiles.push(backupPath);
2848
3171
  for (const kind of Array.from(importKinds)) {
2849
3172
  const importStatement = importStatementFor(kind);
@@ -2852,7 +3175,7 @@ async function rewriteSourceFiles(usages, proposals) {
2852
3175
  ${nextSource}`;
2853
3176
  }
2854
3177
  }
2855
- await (0, import_promises15.writeFile)(filePath, nextSource, "utf8");
3178
+ await (0, import_promises16.writeFile)(filePath, nextSource, "utf8");
2856
3179
  rewrittenFiles.push(filePath);
2857
3180
  }
2858
3181
  return {
@@ -2863,7 +3186,7 @@ ${nextSource}`;
2863
3186
  }
2864
3187
 
2865
3188
  // src/migrate/scanEnvUsage.ts
2866
- var import_promises16 = require("fs/promises");
3189
+ var import_promises17 = require("fs/promises");
2867
3190
  var import_node_path15 = __toESM(require("path"), 1);
2868
3191
  var SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx", ".mts", ".cts", ".mjs", ".cjs"]);
2869
3192
  var PROCESS_ENV_DOT = /process\.env\.([A-Z][A-Z0-9_]*)/g;
@@ -2871,7 +3194,7 @@ var PROCESS_ENV_BRACKET = /process\.env\[['"]([A-Z][A-Z0-9_]*)['"]\]/g;
2871
3194
  var IMPORT_META_ENV_DOT = /import\.meta\.env\.([A-Z][A-Z0-9_]*)/g;
2872
3195
  var IMPORT_META_ENV_BRACKET = /import\.meta\.env\[['"]([A-Z][A-Z0-9_]*)['"]\]/g;
2873
3196
  async function collectFiles(root) {
2874
- const entries = await (0, import_promises16.readdir)(root, { withFileTypes: true });
3197
+ const entries = await (0, import_promises17.readdir)(root, { withFileTypes: true });
2875
3198
  const files = [];
2876
3199
  for (const entry of entries) {
2877
3200
  const filePath = import_node_path15.default.join(root, entry.name);
@@ -2908,7 +3231,7 @@ async function scanEnvUsage(scanRoot) {
2908
3231
  const files = await collectFiles(scanRoot);
2909
3232
  const usages = [];
2910
3233
  for (const filePath of files) {
2911
- const source = await (0, import_promises16.readFile)(filePath, "utf8");
3234
+ const source = await (0, import_promises17.readFile)(filePath, "utf8");
2912
3235
  usages.push(...collectMatches(filePath, source, PROCESS_ENV_DOT, "process-env"));
2913
3236
  usages.push(...collectMatches(filePath, source, PROCESS_ENV_BRACKET, "process-env"));
2914
3237
  usages.push(...collectMatches(filePath, source, IMPORT_META_ENV_DOT, "import-meta-env"));
@@ -2978,6 +3301,7 @@ async function watchFiles(runtime, root) {
2978
3301
  clearAllVaultSessionKeys,
2979
3302
  clearVaultSessionKey,
2980
3303
  compareSchemaToGraph,
3304
+ compareSpecToGraph,
2981
3305
  createRemoteRootCacheKey,
2982
3306
  createSecretVault,
2983
3307
  createSecretVaultProvider,