@clef-sh/core 0.1.10 → 0.1.11-beta.62

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.mjs CHANGED
@@ -695,9 +695,21 @@ var ManifestParser = class {
695
695
  }
696
696
  };
697
697
 
698
+ // src/manifest/io.ts
699
+ import * as fs2 from "fs";
700
+ import * as path from "path";
701
+ import * as YAML2 from "yaml";
702
+ function readManifestYaml(repoRoot) {
703
+ const raw = fs2.readFileSync(path.join(repoRoot, CLEF_MANIFEST_FILENAME), "utf-8");
704
+ return YAML2.parse(raw);
705
+ }
706
+ function writeManifestYaml(repoRoot, doc) {
707
+ fs2.writeFileSync(path.join(repoRoot, CLEF_MANIFEST_FILENAME), YAML2.stringify(doc), "utf-8");
708
+ }
709
+
698
710
  // src/scanner/index.ts
699
- import * as fs3 from "fs";
700
- import * as path2 from "path";
711
+ import * as fs4 from "fs";
712
+ import * as path3 from "path";
701
713
 
702
714
  // src/scanner/patterns.ts
703
715
  var PATTERNS = [
@@ -757,12 +769,12 @@ function matchPatterns(line, lineNumber, filePath) {
757
769
  }
758
770
 
759
771
  // src/scanner/ignore.ts
760
- import * as fs2 from "fs";
761
- import * as path from "path";
772
+ import * as fs3 from "fs";
773
+ import * as path2 from "path";
762
774
  function loadIgnoreRules(repoRoot) {
763
- const ignorePath = path.join(repoRoot, ".clefignore");
775
+ const ignorePath = path2.join(repoRoot, ".clefignore");
764
776
  try {
765
- const content = fs2.readFileSync(ignorePath, "utf-8");
777
+ const content = fs3.readFileSync(ignorePath, "utf-8");
766
778
  return parseIgnoreContent(content);
767
779
  } catch {
768
780
  return { files: [], patterns: [], paths: [] };
@@ -845,9 +857,9 @@ var ScanRunner = class {
845
857
  for (const ns of manifest.namespaces) {
846
858
  for (const env of manifest.environments) {
847
859
  const relPath = manifest.file_pattern.replace("{namespace}", ns.name).replace("{environment}", env.name);
848
- const absPath = path2.join(repoRoot, relPath);
849
- if (fs3.existsSync(absPath)) {
850
- const content = fs3.readFileSync(absPath, "utf-8");
860
+ const absPath = path3.join(repoRoot, relPath);
861
+ if (fs4.existsSync(absPath)) {
862
+ const content = fs4.readFileSync(absPath, "utf-8");
851
863
  if (!content.includes("sops:") && !content.includes('"sops"')) {
852
864
  unencryptedMatrixFiles.push(relPath);
853
865
  }
@@ -863,8 +875,8 @@ var ScanRunner = class {
863
875
  filesToScan = await this.getAllTrackedFiles(repoRoot);
864
876
  }
865
877
  for (const relFile of filesToScan) {
866
- const absFile = path2.isAbsolute(relFile) ? relFile : path2.join(repoRoot, relFile);
867
- const relPath = path2.relative(repoRoot, absFile).replace(/\\/g, "/");
878
+ const absFile = path3.isAbsolute(relFile) ? relFile : path3.join(repoRoot, relFile);
879
+ const relPath = path3.relative(repoRoot, absFile).replace(/\\/g, "/");
868
880
  if (this.shouldAlwaysSkip(relPath)) {
869
881
  filesSkipped++;
870
882
  continue;
@@ -873,13 +885,13 @@ var ScanRunner = class {
873
885
  filesSkipped++;
874
886
  continue;
875
887
  }
876
- if (!fs3.existsSync(absFile)) {
888
+ if (!fs4.existsSync(absFile)) {
877
889
  filesSkipped++;
878
890
  continue;
879
891
  }
880
892
  let stat;
881
893
  try {
882
- stat = fs3.statSync(absFile);
894
+ stat = fs4.statSync(absFile);
883
895
  } catch {
884
896
  filesSkipped++;
885
897
  continue;
@@ -893,7 +905,7 @@ var ScanRunner = class {
893
905
  continue;
894
906
  }
895
907
  filesScanned++;
896
- const content = fs3.readFileSync(absFile, "utf-8");
908
+ const content = fs4.readFileSync(absFile, "utf-8");
897
909
  const lines = content.split("\n");
898
910
  for (let i = 0; i < lines.length; i++) {
899
911
  const line = lines[i];
@@ -935,10 +947,10 @@ var ScanRunner = class {
935
947
  }
936
948
  isBinary(filePath) {
937
949
  try {
938
- const fd = fs3.openSync(filePath, "r");
950
+ const fd = fs4.openSync(filePath, "r");
939
951
  const buf = Buffer.alloc(512);
940
- const bytesRead = fs3.readSync(fd, buf, 0, 512, 0);
941
- fs3.closeSync(fd);
952
+ const bytesRead = fs4.readSync(fd, buf, 0, 512, 0);
953
+ fs4.closeSync(fd);
942
954
  for (let i = 0; i < bytesRead; i++) {
943
955
  if (buf[i] === 0) return true;
944
956
  }
@@ -978,13 +990,13 @@ var ScanRunner = class {
978
990
  async getFilesInPaths(repoRoot, paths) {
979
991
  const files = [];
980
992
  for (const p of paths) {
981
- const absPath = path2.isAbsolute(p) ? p : path2.join(repoRoot, p);
982
- if (!fs3.existsSync(absPath)) continue;
983
- const stat = fs3.statSync(absPath);
993
+ const absPath = path3.isAbsolute(p) ? p : path3.join(repoRoot, p);
994
+ if (!fs4.existsSync(absPath)) continue;
995
+ const stat = fs4.statSync(absPath);
984
996
  if (stat.isDirectory()) {
985
997
  files.push(...this.walkDir(absPath, repoRoot));
986
998
  } else {
987
- files.push(path2.relative(repoRoot, absPath).replace(/\\/g, "/"));
999
+ files.push(path3.relative(repoRoot, absPath).replace(/\\/g, "/"));
988
1000
  }
989
1001
  }
990
1002
  return files;
@@ -1000,13 +1012,13 @@ var ScanRunner = class {
1000
1012
  const files = [];
1001
1013
  let entries;
1002
1014
  try {
1003
- entries = fs3.readdirSync(dir, { withFileTypes: true });
1015
+ entries = fs4.readdirSync(dir, { withFileTypes: true });
1004
1016
  } catch {
1005
1017
  return files;
1006
1018
  }
1007
1019
  for (const entry of entries) {
1008
- const fullPath = path2.join(dir, entry.name);
1009
- const relPath = path2.relative(repoRoot, fullPath).replace(/\\/g, "/");
1020
+ const fullPath = path3.join(dir, entry.name);
1021
+ const relPath = path3.relative(repoRoot, fullPath).replace(/\\/g, "/");
1010
1022
  if (entry.isDirectory()) {
1011
1023
  if (!ALWAYS_SKIP_DIRS.includes(entry.name)) {
1012
1024
  files.push(...this.walkDir(fullPath, repoRoot));
@@ -1020,29 +1032,29 @@ var ScanRunner = class {
1020
1032
  };
1021
1033
 
1022
1034
  // src/matrix/manager.ts
1023
- import * as fs6 from "fs";
1024
- import * as path4 from "path";
1025
- import * as YAML4 from "yaml";
1035
+ import * as fs7 from "fs";
1036
+ import * as path5 from "path";
1037
+ import * as YAML5 from "yaml";
1026
1038
 
1027
1039
  // src/pending/metadata.ts
1028
- import * as fs4 from "fs";
1029
- import * as path3 from "path";
1040
+ import * as fs5 from "fs";
1041
+ import * as path4 from "path";
1030
1042
  import * as crypto from "crypto";
1031
- import * as YAML2 from "yaml";
1043
+ import * as YAML3 from "yaml";
1032
1044
  function metadataPath(encryptedFilePath) {
1033
- const dir = path3.dirname(encryptedFilePath);
1034
- const base = path3.basename(encryptedFilePath).replace(/\.enc\.(yaml|json)$/, "");
1035
- return path3.join(dir, `${base}.clef-meta.yaml`);
1045
+ const dir = path4.dirname(encryptedFilePath);
1046
+ const base = path4.basename(encryptedFilePath).replace(/\.enc\.(yaml|json)$/, "");
1047
+ return path4.join(dir, `${base}.clef-meta.yaml`);
1036
1048
  }
1037
1049
  var HEADER_COMMENT = "# Managed by Clef. Do not edit manually.\n";
1038
1050
  async function loadMetadata(filePath) {
1039
1051
  const metaPath = metadataPath(filePath);
1040
1052
  try {
1041
- if (!fs4.existsSync(metaPath)) {
1053
+ if (!fs5.existsSync(metaPath)) {
1042
1054
  return { version: 1, pending: [] };
1043
1055
  }
1044
- const content = fs4.readFileSync(metaPath, "utf-8");
1045
- const parsed = YAML2.parse(content);
1056
+ const content = fs5.readFileSync(metaPath, "utf-8");
1057
+ const parsed = YAML3.parse(content);
1046
1058
  if (!parsed || !Array.isArray(parsed.pending)) {
1047
1059
  return { version: 1, pending: [] };
1048
1060
  }
@@ -1060,9 +1072,9 @@ async function loadMetadata(filePath) {
1060
1072
  }
1061
1073
  async function saveMetadata(filePath, metadata) {
1062
1074
  const metaPath = metadataPath(filePath);
1063
- const dir = path3.dirname(metaPath);
1064
- if (!fs4.existsSync(dir)) {
1065
- fs4.mkdirSync(dir, { recursive: true });
1075
+ const dir = path4.dirname(metaPath);
1076
+ if (!fs5.existsSync(dir)) {
1077
+ fs5.mkdirSync(dir, { recursive: true });
1066
1078
  }
1067
1079
  const data = {
1068
1080
  version: metadata.version,
@@ -1072,7 +1084,7 @@ async function saveMetadata(filePath, metadata) {
1072
1084
  setBy: p.setBy
1073
1085
  }))
1074
1086
  };
1075
- fs4.writeFileSync(metaPath, HEADER_COMMENT + YAML2.stringify(data), "utf-8");
1087
+ fs5.writeFileSync(metaPath, HEADER_COMMENT + YAML3.stringify(data), "utf-8");
1076
1088
  }
1077
1089
  async function markPending(filePath, keys, setBy) {
1078
1090
  const metadata = await loadMetadata(filePath);
@@ -1113,12 +1125,12 @@ async function markPendingWithRetry(filePath, keys, setBy, retryDelayMs = 200) {
1113
1125
  }
1114
1126
 
1115
1127
  // src/sops/keys.ts
1116
- import * as fs5 from "fs";
1117
- import * as YAML3 from "yaml";
1128
+ import * as fs6 from "fs";
1129
+ import * as YAML4 from "yaml";
1118
1130
  function readSopsKeyNames(filePath) {
1119
1131
  try {
1120
- const raw = fs5.readFileSync(filePath, "utf-8");
1121
- const parsed = YAML3.parse(raw);
1132
+ const raw = fs6.readFileSync(filePath, "utf-8");
1133
+ const parsed = YAML4.parse(raw);
1122
1134
  if (parsed === null || parsed === void 0 || typeof parsed !== "object") return null;
1123
1135
  return Object.keys(parsed).filter((k) => k !== "sops");
1124
1136
  } catch {
@@ -1140,12 +1152,12 @@ var MatrixManager = class {
1140
1152
  for (const ns of manifest.namespaces) {
1141
1153
  for (const env of manifest.environments) {
1142
1154
  const relativePath = manifest.file_pattern.replace("{namespace}", ns.name).replace("{environment}", env.name);
1143
- const filePath = path4.join(repoRoot, relativePath);
1155
+ const filePath = path5.join(repoRoot, relativePath);
1144
1156
  cells.push({
1145
1157
  namespace: ns.name,
1146
1158
  environment: env.name,
1147
1159
  filePath,
1148
- exists: fs6.existsSync(filePath)
1160
+ exists: fs7.existsSync(filePath)
1149
1161
  });
1150
1162
  }
1151
1163
  }
@@ -1168,9 +1180,9 @@ var MatrixManager = class {
1168
1180
  * @param manifest - Parsed manifest used to determine the encryption backend.
1169
1181
  */
1170
1182
  async scaffoldCell(cell, sopsClient, manifest) {
1171
- const dir = path4.dirname(cell.filePath);
1172
- if (!fs6.existsSync(dir)) {
1173
- fs6.mkdirSync(dir, { recursive: true });
1183
+ const dir = path5.dirname(cell.filePath);
1184
+ if (!fs7.existsSync(dir)) {
1185
+ fs7.mkdirSync(dir, { recursive: true });
1174
1186
  }
1175
1187
  await sopsClient.encrypt(cell.filePath, {}, manifest, cell.environment);
1176
1188
  }
@@ -1246,8 +1258,8 @@ var MatrixManager = class {
1246
1258
  */
1247
1259
  readLastModified(filePath) {
1248
1260
  try {
1249
- const raw = fs6.readFileSync(filePath, "utf-8");
1250
- const parsed = YAML4.parse(raw);
1261
+ const raw = fs7.readFileSync(filePath, "utf-8");
1262
+ const parsed = YAML5.parse(raw);
1251
1263
  const sops = parsed?.sops;
1252
1264
  if (sops?.lastmodified) return new Date(String(sops.lastmodified));
1253
1265
  return null;
@@ -1268,8 +1280,8 @@ var MatrixManager = class {
1268
1280
  };
1269
1281
 
1270
1282
  // src/schema/validator.ts
1271
- import * as fs7 from "fs";
1272
- import * as YAML5 from "yaml";
1283
+ import * as fs8 from "fs";
1284
+ import * as YAML6 from "yaml";
1273
1285
  var SchemaValidator = class {
1274
1286
  /**
1275
1287
  * Read and parse a YAML schema file from disk.
@@ -1281,13 +1293,13 @@ var SchemaValidator = class {
1281
1293
  loadSchema(filePath) {
1282
1294
  let raw;
1283
1295
  try {
1284
- raw = fs7.readFileSync(filePath, "utf-8");
1296
+ raw = fs8.readFileSync(filePath, "utf-8");
1285
1297
  } catch {
1286
1298
  throw new SchemaLoadError(`Could not read schema file at '${filePath}'.`, filePath);
1287
1299
  }
1288
1300
  let parsed;
1289
1301
  try {
1290
- parsed = YAML5.parse(raw);
1302
+ parsed = YAML6.parse(raw);
1291
1303
  } catch {
1292
1304
  throw new SchemaLoadError(`Schema file '${filePath}' contains invalid YAML.`, filePath);
1293
1305
  }
@@ -1415,7 +1427,7 @@ var SchemaValidator = class {
1415
1427
  };
1416
1428
 
1417
1429
  // src/diff/engine.ts
1418
- import * as path5 from "path";
1430
+ import * as path6 from "path";
1419
1431
  var DiffEngine = class {
1420
1432
  /**
1421
1433
  * Compare two in-memory value maps and produce a sorted diff result.
@@ -1472,11 +1484,11 @@ var DiffEngine = class {
1472
1484
  * @throws {@link SopsDecryptionError} If either file cannot be decrypted.
1473
1485
  */
1474
1486
  async diffFiles(namespace, envA, envB, manifest, sopsClient, repoRoot) {
1475
- const fileA = path5.join(
1487
+ const fileA = path6.join(
1476
1488
  repoRoot,
1477
1489
  manifest.file_pattern.replace("{namespace}", namespace).replace("{environment}", envA)
1478
1490
  );
1479
- const fileB = path5.join(
1491
+ const fileB = path6.join(
1480
1492
  repoRoot,
1481
1493
  manifest.file_pattern.replace("{namespace}", namespace).replace("{environment}", envB)
1482
1494
  );
@@ -1489,7 +1501,7 @@ var DiffEngine = class {
1489
1501
  };
1490
1502
 
1491
1503
  // src/bulk/ops.ts
1492
- import * as path6 from "path";
1504
+ import * as path7 from "path";
1493
1505
  var BulkOps = class {
1494
1506
  /**
1495
1507
  * Set a key to different values in multiple environments at once.
@@ -1508,7 +1520,7 @@ var BulkOps = class {
1508
1520
  if (!(env.name in values)) {
1509
1521
  continue;
1510
1522
  }
1511
- const filePath = path6.join(
1523
+ const filePath = path7.join(
1512
1524
  repoRoot,
1513
1525
  manifest.file_pattern.replace("{namespace}", namespace).replace("{environment}", env.name)
1514
1526
  );
@@ -1542,7 +1554,7 @@ Successfully updated ${Object.keys(values).length - errors.length} environment(s
1542
1554
  async deleteAcrossEnvironments(namespace, key, manifest, sopsClient, repoRoot) {
1543
1555
  const errors = [];
1544
1556
  for (const env of manifest.environments) {
1545
- const filePath = path6.join(
1557
+ const filePath = path7.join(
1546
1558
  repoRoot,
1547
1559
  manifest.file_pattern.replace("{namespace}", namespace).replace("{environment}", env.name)
1548
1560
  );
@@ -1588,8 +1600,8 @@ ${details}`
1588
1600
  };
1589
1601
 
1590
1602
  // src/git/integration.ts
1591
- import * as fs8 from "fs";
1592
- import * as path7 from "path";
1603
+ import * as fs9 from "fs";
1604
+ import * as path8 from "path";
1593
1605
  var PRE_COMMIT_HOOK = `#!/bin/sh
1594
1606
  # Clef pre-commit hook \u2014 blocks commits of files missing SOPS encryption metadata
1595
1607
  # and scans staged files for plaintext secrets.
@@ -1796,15 +1808,15 @@ var GitIntegration = class {
1796
1808
  cwd: repoRoot
1797
1809
  });
1798
1810
  const gitConfig = configResult.exitCode === 0 && configResult.stdout.trim().length > 0;
1799
- const attrFilePath = path7.join(repoRoot, ".gitattributes");
1800
- const attrContent = fs8.existsSync(attrFilePath) ? fs8.readFileSync(attrFilePath, "utf-8") : "";
1811
+ const attrFilePath = path8.join(repoRoot, ".gitattributes");
1812
+ const attrContent = fs9.existsSync(attrFilePath) ? fs9.readFileSync(attrFilePath, "utf-8") : "";
1801
1813
  const gitattributes = attrContent.includes("merge=sops");
1802
1814
  return { gitConfig, gitattributes };
1803
1815
  }
1804
1816
  async ensureGitattributes(repoRoot) {
1805
- const attrPath = path7.join(repoRoot, ".gitattributes");
1817
+ const attrPath = path8.join(repoRoot, ".gitattributes");
1806
1818
  const mergeRule = "*.enc.yaml merge=sops\n*.enc.json merge=sops";
1807
- const existing = fs8.existsSync(attrPath) ? fs8.readFileSync(attrPath, "utf-8") : "";
1819
+ const existing = fs9.existsSync(attrPath) ? fs9.readFileSync(attrPath, "utf-8") : "";
1808
1820
  if (existing.includes("merge=sops")) {
1809
1821
  return;
1810
1822
  }
@@ -1831,7 +1843,7 @@ ${mergeRule}
1831
1843
  * @throws {@link GitOperationError} On failure.
1832
1844
  */
1833
1845
  async installPreCommitHook(repoRoot) {
1834
- const hookPath = path7.join(repoRoot, ".git", "hooks", "pre-commit");
1846
+ const hookPath = path8.join(repoRoot, ".git", "hooks", "pre-commit");
1835
1847
  const result = await this.runner.run("tee", [hookPath], {
1836
1848
  stdin: PRE_COMMIT_HOOK,
1837
1849
  cwd: repoRoot
@@ -1852,18 +1864,18 @@ ${mergeRule}
1852
1864
  };
1853
1865
 
1854
1866
  // src/sops/client.ts
1855
- import * as fs11 from "fs";
1867
+ import * as fs12 from "fs";
1856
1868
  import * as net from "net";
1857
1869
  import { randomBytes as randomBytes2 } from "crypto";
1858
- import * as YAML6 from "yaml";
1870
+ import * as YAML7 from "yaml";
1859
1871
 
1860
1872
  // src/sops/resolver.ts
1861
- import * as fs10 from "fs";
1862
- import * as path9 from "path";
1873
+ import * as fs11 from "fs";
1874
+ import * as path10 from "path";
1863
1875
 
1864
1876
  // src/sops/bundled.ts
1865
- import * as fs9 from "fs";
1866
- import * as path8 from "path";
1877
+ import * as fs10 from "fs";
1878
+ import * as path9 from "path";
1867
1879
  function tryBundled() {
1868
1880
  const platform = process.platform;
1869
1881
  const arch = process.arch;
@@ -1875,9 +1887,9 @@ function tryBundled() {
1875
1887
  const binName = platform === "win32" ? "sops.exe" : "sops";
1876
1888
  try {
1877
1889
  const packageMain = __require.resolve(`${packageName}/package.json`);
1878
- const packageDir = path8.dirname(packageMain);
1879
- const binPath = path8.join(packageDir, "bin", binName);
1880
- return fs9.existsSync(binPath) ? binPath : null;
1890
+ const packageDir = path9.dirname(packageMain);
1891
+ const binPath = path9.join(packageDir, "bin", binName);
1892
+ return fs10.existsSync(binPath) ? binPath : null;
1881
1893
  } catch {
1882
1894
  return null;
1883
1895
  }
@@ -1885,7 +1897,7 @@ function tryBundled() {
1885
1897
 
1886
1898
  // src/sops/resolver.ts
1887
1899
  function validateSopsPath(candidate) {
1888
- if (!path9.isAbsolute(candidate)) {
1900
+ if (!path10.isAbsolute(candidate)) {
1889
1901
  throw new Error(`CLEF_SOPS_PATH must be an absolute path, got '${candidate}'.`);
1890
1902
  }
1891
1903
  const segments = candidate.split(/[/\\]/);
@@ -1901,7 +1913,7 @@ function resolveSopsPath() {
1901
1913
  const envPath = process.env.CLEF_SOPS_PATH?.trim();
1902
1914
  if (envPath) {
1903
1915
  validateSopsPath(envPath);
1904
- if (!fs10.existsSync(envPath)) {
1916
+ if (!fs11.existsSync(envPath)) {
1905
1917
  throw new Error(`CLEF_SOPS_PATH points to '${envPath}' but the file does not exist.`);
1906
1918
  }
1907
1919
  cached = { path: envPath, source: "env" };
@@ -2109,7 +2121,7 @@ var SopsClient = class {
2109
2121
  }
2110
2122
  let parsed;
2111
2123
  try {
2112
- parsed = YAML6.parse(result.stdout) ?? {};
2124
+ parsed = YAML7.parse(result.stdout) ?? {};
2113
2125
  } catch {
2114
2126
  throw new SopsDecryptionError(
2115
2127
  `Decrypted content of '${filePath}' is not valid YAML.`,
@@ -2136,7 +2148,7 @@ var SopsClient = class {
2136
2148
  async encrypt(filePath, values, manifest, environment) {
2137
2149
  await assertSops(this.runner, this.sopsCommand);
2138
2150
  const fmt = formatFromPath(filePath);
2139
- const content = fmt === "json" ? JSON.stringify(values, null, 2) : YAML6.stringify(values);
2151
+ const content = fmt === "json" ? JSON.stringify(values, null, 2) : YAML7.stringify(values);
2140
2152
  const args = this.buildEncryptArgs(filePath, manifest, environment);
2141
2153
  const env = this.buildSopsEnv();
2142
2154
  let inputArg;
@@ -2180,7 +2192,7 @@ var SopsClient = class {
2180
2192
  );
2181
2193
  }
2182
2194
  try {
2183
- fs11.writeFileSync(filePath, result.stdout);
2195
+ fs12.writeFileSync(filePath, result.stdout);
2184
2196
  } catch {
2185
2197
  throw new SopsEncryptionError(`Failed to write encrypted data to '${filePath}'.`, filePath);
2186
2198
  }
@@ -2296,7 +2308,7 @@ var SopsClient = class {
2296
2308
  if (!this.ageKey && !this.ageKeyFile) return "key-not-found";
2297
2309
  let keyContent;
2298
2310
  try {
2299
- keyContent = this.ageKey ?? fs11.readFileSync(this.ageKeyFile, "utf-8");
2311
+ keyContent = this.ageKey ?? fs12.readFileSync(this.ageKeyFile, "utf-8");
2300
2312
  } catch {
2301
2313
  return "key-not-found";
2302
2314
  }
@@ -2313,7 +2325,7 @@ var SopsClient = class {
2313
2325
  parseMetadataFromFile(filePath) {
2314
2326
  let content;
2315
2327
  try {
2316
- content = fs11.readFileSync(filePath, "utf-8");
2328
+ content = fs12.readFileSync(filePath, "utf-8");
2317
2329
  } catch {
2318
2330
  throw new SopsDecryptionError(
2319
2331
  `Could not read file '${filePath}' to extract SOPS metadata.`,
@@ -2322,7 +2334,7 @@ var SopsClient = class {
2322
2334
  }
2323
2335
  let parsed;
2324
2336
  try {
2325
- parsed = YAML6.parse(content);
2337
+ parsed = YAML7.parse(content);
2326
2338
  } catch {
2327
2339
  throw new SopsDecryptionError(
2328
2340
  `File '${filePath}' is not valid YAML. Cannot extract SOPS metadata.`,
@@ -2417,7 +2429,7 @@ var SopsClient = class {
2417
2429
  };
2418
2430
 
2419
2431
  // src/lint/runner.ts
2420
- import * as path10 from "path";
2432
+ import * as path11 from "path";
2421
2433
  var LintRunner = class {
2422
2434
  constructor(matrixManager, schemaValidator, sopsClient) {
2423
2435
  this.matrixManager = matrixManager;
@@ -2517,7 +2529,7 @@ var LintRunner = class {
2517
2529
  }
2518
2530
  const ns = manifest.namespaces.find((n) => n.name === cell.namespace);
2519
2531
  if (ns?.schema) {
2520
- const schemaPath = path10.join(repoRoot, ns.schema);
2532
+ const schemaPath = path11.join(repoRoot, ns.schema);
2521
2533
  try {
2522
2534
  const schema = this.schemaValidator.loadSchema(schemaPath);
2523
2535
  const result = this.schemaValidator.validate(decrypted.values, schema);
@@ -2750,14 +2762,14 @@ Use 'clef exec' to inject secrets directly into a process, or 'clef export --for
2750
2762
  };
2751
2763
 
2752
2764
  // src/import/index.ts
2753
- import * as path12 from "path";
2765
+ import * as path13 from "path";
2754
2766
 
2755
2767
  // src/import/parsers.ts
2756
- import * as path11 from "path";
2757
- import * as YAML7 from "yaml";
2768
+ import * as path12 from "path";
2769
+ import * as YAML8 from "yaml";
2758
2770
  function detectFormat(filePath, content) {
2759
- const base = path11.basename(filePath);
2760
- const ext = path11.extname(filePath).toLowerCase();
2771
+ const base = path12.basename(filePath);
2772
+ const ext = path12.extname(filePath).toLowerCase();
2761
2773
  if (base === ".env" || base.startsWith(".env.")) {
2762
2774
  return "dotenv";
2763
2775
  }
@@ -2778,7 +2790,7 @@ function detectFormat(filePath, content) {
2778
2790
  } catch {
2779
2791
  }
2780
2792
  try {
2781
- const parsed = YAML7.parse(content);
2793
+ const parsed = YAML8.parse(content);
2782
2794
  if (parsed !== null && typeof parsed === "object" && !Array.isArray(parsed)) {
2783
2795
  return "yaml";
2784
2796
  }
@@ -2859,7 +2871,7 @@ function parseJson(content) {
2859
2871
  function parseYaml(content) {
2860
2872
  let parsed;
2861
2873
  try {
2862
- parsed = YAML7.parse(content);
2874
+ parsed = YAML8.parse(content);
2863
2875
  } catch (err) {
2864
2876
  throw new Error(`Invalid YAML: ${err.message}`);
2865
2877
  }
@@ -2893,7 +2905,7 @@ function parseYaml(content) {
2893
2905
  }
2894
2906
  return { pairs, format: "yaml", skipped, warnings };
2895
2907
  }
2896
- function parse8(content, format, filePath) {
2908
+ function parse9(content, format, filePath) {
2897
2909
  const resolved = format === "auto" ? detectFormat(filePath ?? "", content) : format;
2898
2910
  switch (resolved) {
2899
2911
  case "dotenv":
@@ -2922,11 +2934,11 @@ var ImportRunner = class {
2922
2934
  */
2923
2935
  async import(target, sourcePath, content, manifest, repoRoot, options) {
2924
2936
  const [ns, env] = target.split("/");
2925
- const filePath = path12.join(
2937
+ const filePath = path13.join(
2926
2938
  repoRoot,
2927
2939
  manifest.file_pattern.replace("{namespace}", ns).replace("{environment}", env)
2928
2940
  );
2929
- const parsed = parse8(content, options.format ?? "auto", sourcePath ?? "");
2941
+ const parsed = parse9(content, options.format ?? "auto", sourcePath ?? "");
2930
2942
  let candidates = Object.entries(parsed.pairs);
2931
2943
  if (options.prefix) {
2932
2944
  const prefix = options.prefix;
@@ -2980,9 +2992,8 @@ var ImportRunner = class {
2980
2992
  };
2981
2993
 
2982
2994
  // src/recipients/index.ts
2983
- import * as fs12 from "fs";
2984
- import * as path13 from "path";
2985
- import * as YAML8 from "yaml";
2995
+ import * as fs13 from "fs";
2996
+ import * as path14 from "path";
2986
2997
  function parseRecipientEntry(entry) {
2987
2998
  if (typeof entry === "string") {
2988
2999
  return { key: entry };
@@ -3003,15 +3014,6 @@ function toRecipient(entry) {
3003
3014
  ...entry.label ? { label: entry.label } : {}
3004
3015
  };
3005
3016
  }
3006
- function readManifestYaml(repoRoot) {
3007
- const manifestPath = path13.join(repoRoot, CLEF_MANIFEST_FILENAME);
3008
- const raw = fs12.readFileSync(manifestPath, "utf-8");
3009
- return YAML8.parse(raw);
3010
- }
3011
- function writeManifestYaml(repoRoot, doc) {
3012
- const manifestPath = path13.join(repoRoot, CLEF_MANIFEST_FILENAME);
3013
- fs12.writeFileSync(manifestPath, YAML8.stringify(doc), "utf-8");
3014
- }
3015
3017
  function getRecipientsArray(doc) {
3016
3018
  const sops = doc.sops;
3017
3019
  if (!sops) return [];
@@ -3110,8 +3112,8 @@ var RecipientManager = class {
3110
3112
  if (currentKeys.includes(normalizedKey)) {
3111
3113
  throw new Error(`Recipient '${keyPreview(normalizedKey)}' is already present.`);
3112
3114
  }
3113
- const manifestPath = path13.join(repoRoot, CLEF_MANIFEST_FILENAME);
3114
- const manifestBackup = fs12.readFileSync(manifestPath, "utf-8");
3115
+ const manifestPath = path14.join(repoRoot, CLEF_MANIFEST_FILENAME);
3116
+ const manifestBackup = fs13.readFileSync(manifestPath, "utf-8");
3115
3117
  const recipients = environment ? ensureEnvironmentRecipientsArray(doc, environment) : ensureRecipientsArray(doc);
3116
3118
  if (label) {
3117
3119
  recipients.push({ key: normalizedKey, label });
@@ -3126,16 +3128,16 @@ var RecipientManager = class {
3126
3128
  const fileBackups = /* @__PURE__ */ new Map();
3127
3129
  for (const cell of cells) {
3128
3130
  try {
3129
- fileBackups.set(cell.filePath, fs12.readFileSync(cell.filePath, "utf-8"));
3131
+ fileBackups.set(cell.filePath, fs13.readFileSync(cell.filePath, "utf-8"));
3130
3132
  await this.encryption.addRecipient(cell.filePath, normalizedKey);
3131
3133
  reEncryptedFiles.push(cell.filePath);
3132
3134
  } catch {
3133
3135
  failedFiles.push(cell.filePath);
3134
- fs12.writeFileSync(manifestPath, manifestBackup, "utf-8");
3136
+ fs13.writeFileSync(manifestPath, manifestBackup, "utf-8");
3135
3137
  for (const reEncryptedFile of reEncryptedFiles) {
3136
3138
  const backup = fileBackups.get(reEncryptedFile);
3137
3139
  if (backup) {
3138
- fs12.writeFileSync(reEncryptedFile, backup, "utf-8");
3140
+ fs13.writeFileSync(reEncryptedFile, backup, "utf-8");
3139
3141
  }
3140
3142
  }
3141
3143
  const restoredDoc = readManifestYaml(repoRoot);
@@ -3188,8 +3190,8 @@ var RecipientManager = class {
3188
3190
  throw new Error(`Recipient '${keyPreview(trimmedKey)}' is not in the manifest.`);
3189
3191
  }
3190
3192
  const removedEntry = parsed[matchIndex];
3191
- const manifestPath = path13.join(repoRoot, CLEF_MANIFEST_FILENAME);
3192
- const manifestBackup = fs12.readFileSync(manifestPath, "utf-8");
3193
+ const manifestPath = path14.join(repoRoot, CLEF_MANIFEST_FILENAME);
3194
+ const manifestBackup = fs13.readFileSync(manifestPath, "utf-8");
3193
3195
  const recipients = environment ? ensureEnvironmentRecipientsArray(doc, environment) : ensureRecipientsArray(doc);
3194
3196
  recipients.splice(matchIndex, 1);
3195
3197
  writeManifestYaml(repoRoot, doc);
@@ -3200,16 +3202,16 @@ var RecipientManager = class {
3200
3202
  const fileBackups = /* @__PURE__ */ new Map();
3201
3203
  for (const cell of cells) {
3202
3204
  try {
3203
- fileBackups.set(cell.filePath, fs12.readFileSync(cell.filePath, "utf-8"));
3205
+ fileBackups.set(cell.filePath, fs13.readFileSync(cell.filePath, "utf-8"));
3204
3206
  await this.encryption.removeRecipient(cell.filePath, trimmedKey);
3205
3207
  reEncryptedFiles.push(cell.filePath);
3206
3208
  } catch {
3207
3209
  failedFiles.push(cell.filePath);
3208
- fs12.writeFileSync(manifestPath, manifestBackup, "utf-8");
3210
+ fs13.writeFileSync(manifestPath, manifestBackup, "utf-8");
3209
3211
  for (const reEncryptedFile of reEncryptedFiles) {
3210
3212
  const backup = fileBackups.get(reEncryptedFile);
3211
3213
  if (backup) {
3212
- fs12.writeFileSync(reEncryptedFile, backup, "utf-8");
3214
+ fs13.writeFileSync(reEncryptedFile, backup, "utf-8");
3213
3215
  }
3214
3216
  }
3215
3217
  const restoredDoc = readManifestYaml(repoRoot);
@@ -3243,19 +3245,19 @@ var RecipientManager = class {
3243
3245
  };
3244
3246
 
3245
3247
  // src/recipients/requests.ts
3246
- import * as fs13 from "fs";
3247
- import * as path14 from "path";
3248
+ import * as fs14 from "fs";
3249
+ import * as path15 from "path";
3248
3250
  import * as YAML9 from "yaml";
3249
3251
  var REQUESTS_FILENAME = ".clef-requests.yaml";
3250
3252
  var HEADER_COMMENT2 = "# Pending recipient access requests. Approve with: clef recipients approve <label>\n";
3251
3253
  function requestsFilePath(repoRoot) {
3252
- return path14.join(repoRoot, REQUESTS_FILENAME);
3254
+ return path15.join(repoRoot, REQUESTS_FILENAME);
3253
3255
  }
3254
3256
  function loadRequests(repoRoot) {
3255
3257
  const filePath = requestsFilePath(repoRoot);
3256
3258
  try {
3257
- if (!fs13.existsSync(filePath)) return [];
3258
- const content = fs13.readFileSync(filePath, "utf-8");
3259
+ if (!fs14.existsSync(filePath)) return [];
3260
+ const content = fs14.readFileSync(filePath, "utf-8");
3259
3261
  const parsed = YAML9.parse(content);
3260
3262
  if (!parsed || !Array.isArray(parsed.requests)) return [];
3261
3263
  return parsed.requests.map((r) => ({
@@ -3272,7 +3274,7 @@ function saveRequests(repoRoot, requests) {
3272
3274
  const filePath = requestsFilePath(repoRoot);
3273
3275
  if (requests.length === 0) {
3274
3276
  try {
3275
- fs13.unlinkSync(filePath);
3277
+ fs14.unlinkSync(filePath);
3276
3278
  } catch {
3277
3279
  }
3278
3280
  return;
@@ -3288,7 +3290,7 @@ function saveRequests(repoRoot, requests) {
3288
3290
  return raw;
3289
3291
  })
3290
3292
  };
3291
- fs13.writeFileSync(filePath, HEADER_COMMENT2 + YAML9.stringify(data), "utf-8");
3293
+ fs14.writeFileSync(filePath, HEADER_COMMENT2 + YAML9.stringify(data), "utf-8");
3292
3294
  }
3293
3295
  function upsertRequest(repoRoot, key, label, environment) {
3294
3296
  const requests = loadRequests(repoRoot);
@@ -3324,7 +3326,7 @@ function findInList(requests, identifier) {
3324
3326
  }
3325
3327
 
3326
3328
  // src/drift/detector.ts
3327
- import * as path15 from "path";
3329
+ import * as path16 from "path";
3328
3330
  var DriftDetector = class {
3329
3331
  parser = new ManifestParser();
3330
3332
  matrix = new MatrixManager();
@@ -3337,8 +3339,8 @@ var DriftDetector = class {
3337
3339
  * @returns Drift result with any issues found.
3338
3340
  */
3339
3341
  detect(localRoot, remoteRoot, namespaceFilter) {
3340
- const localManifest = this.parser.parse(path15.join(localRoot, CLEF_MANIFEST_FILENAME));
3341
- const remoteManifest = this.parser.parse(path15.join(remoteRoot, CLEF_MANIFEST_FILENAME));
3342
+ const localManifest = this.parser.parse(path16.join(localRoot, CLEF_MANIFEST_FILENAME));
3343
+ const remoteManifest = this.parser.parse(path16.join(remoteRoot, CLEF_MANIFEST_FILENAME));
3342
3344
  const localCells = this.matrix.resolveMatrix(localManifest, localRoot);
3343
3345
  const remoteCells = this.matrix.resolveMatrix(remoteManifest, remoteRoot);
3344
3346
  const localEnvNames = localManifest.environments.map((e) => e.name);
@@ -3402,7 +3404,7 @@ var DriftDetector = class {
3402
3404
  };
3403
3405
 
3404
3406
  // src/report/generator.ts
3405
- import * as path16 from "path";
3407
+ import * as path17 from "path";
3406
3408
 
3407
3409
  // src/report/sanitizer.ts
3408
3410
  var ReportSanitizer = class {
@@ -3558,7 +3560,7 @@ var ReportGenerator = class {
3558
3560
  let manifest = null;
3559
3561
  try {
3560
3562
  const parser = new ManifestParser();
3561
- manifest = parser.parse(path16.join(repoRoot, "clef.yaml"));
3563
+ manifest = parser.parse(path17.join(repoRoot, "clef.yaml"));
3562
3564
  } catch {
3563
3565
  const emptyManifest = {
3564
3566
  manifestVersion: 0,
@@ -4036,9 +4038,9 @@ var SopsMergeDriver = class {
4036
4038
  };
4037
4039
 
4038
4040
  // src/service-identity/manager.ts
4039
- import * as fs14 from "fs";
4041
+ import * as fs15 from "fs";
4040
4042
  import * as os from "os";
4041
- import * as path17 from "path";
4043
+ import * as path18 from "path";
4042
4044
  import * as YAML10 from "yaml";
4043
4045
  var PartialRotationError = class extends Error {
4044
4046
  constructor(message, rotatedKeys) {
@@ -4090,8 +4092,8 @@ var ServiceIdentityManager = class {
4090
4092
  environments
4091
4093
  };
4092
4094
  await this.registerRecipients(definition, manifest, repoRoot);
4093
- const manifestPath = path17.join(repoRoot, CLEF_MANIFEST_FILENAME);
4094
- const raw = fs14.readFileSync(manifestPath, "utf-8");
4095
+ const manifestPath = path18.join(repoRoot, CLEF_MANIFEST_FILENAME);
4096
+ const raw = fs15.readFileSync(manifestPath, "utf-8");
4095
4097
  const doc = YAML10.parse(raw);
4096
4098
  if (!Array.isArray(doc.service_identities)) {
4097
4099
  doc.service_identities = [];
@@ -4102,13 +4104,13 @@ var ServiceIdentityManager = class {
4102
4104
  namespaces,
4103
4105
  environments
4104
4106
  });
4105
- const tmpCreate = path17.join(os.tmpdir(), `clef-manifest-${process.pid}-${Date.now()}.tmp`);
4107
+ const tmpCreate = path18.join(os.tmpdir(), `clef-manifest-${process.pid}-${Date.now()}.tmp`);
4106
4108
  try {
4107
- fs14.writeFileSync(tmpCreate, YAML10.stringify(doc), "utf-8");
4108
- fs14.renameSync(tmpCreate, manifestPath);
4109
+ fs15.writeFileSync(tmpCreate, YAML10.stringify(doc), "utf-8");
4110
+ fs15.renameSync(tmpCreate, manifestPath);
4109
4111
  } finally {
4110
4112
  try {
4111
- fs14.unlinkSync(tmpCreate);
4113
+ fs15.unlinkSync(tmpCreate);
4112
4114
  } catch {
4113
4115
  }
4114
4116
  }
@@ -4146,8 +4148,8 @@ var ServiceIdentityManager = class {
4146
4148
  } catch {
4147
4149
  }
4148
4150
  }
4149
- const manifestPath = path17.join(repoRoot, CLEF_MANIFEST_FILENAME);
4150
- const raw = fs14.readFileSync(manifestPath, "utf-8");
4151
+ const manifestPath = path18.join(repoRoot, CLEF_MANIFEST_FILENAME);
4152
+ const raw = fs15.readFileSync(manifestPath, "utf-8");
4151
4153
  const doc = YAML10.parse(raw);
4152
4154
  const identities = doc.service_identities;
4153
4155
  if (Array.isArray(identities)) {
@@ -4155,13 +4157,13 @@ var ServiceIdentityManager = class {
4155
4157
  (si) => si.name !== name
4156
4158
  );
4157
4159
  }
4158
- const tmp = path17.join(os.tmpdir(), `clef-manifest-${process.pid}-${Date.now()}.tmp`);
4160
+ const tmp = path18.join(os.tmpdir(), `clef-manifest-${process.pid}-${Date.now()}.tmp`);
4159
4161
  try {
4160
- fs14.writeFileSync(tmp, YAML10.stringify(doc), "utf-8");
4161
- fs14.renameSync(tmp, manifestPath);
4162
+ fs15.writeFileSync(tmp, YAML10.stringify(doc), "utf-8");
4163
+ fs15.renameSync(tmp, manifestPath);
4162
4164
  } finally {
4163
4165
  try {
4164
- fs14.unlinkSync(tmp);
4166
+ fs15.unlinkSync(tmp);
4165
4167
  } catch {
4166
4168
  }
4167
4169
  }
@@ -4176,8 +4178,8 @@ var ServiceIdentityManager = class {
4176
4178
  if (!identity) {
4177
4179
  throw new Error(`Service identity '${name}' not found.`);
4178
4180
  }
4179
- const manifestPath = path17.join(repoRoot, CLEF_MANIFEST_FILENAME);
4180
- const raw = fs14.readFileSync(manifestPath, "utf-8");
4181
+ const manifestPath = path18.join(repoRoot, CLEF_MANIFEST_FILENAME);
4182
+ const raw = fs15.readFileSync(manifestPath, "utf-8");
4181
4183
  const doc = YAML10.parse(raw);
4182
4184
  const identities = doc.service_identities;
4183
4185
  const siDoc = identities.find((si) => si.name === name);
@@ -4203,13 +4205,13 @@ var ServiceIdentityManager = class {
4203
4205
  envs[envName] = { kms: kmsConfig };
4204
4206
  identity.environments[envName] = { kms: kmsConfig };
4205
4207
  }
4206
- const tmp = path17.join(os.tmpdir(), `clef-manifest-${process.pid}-${Date.now()}.tmp`);
4208
+ const tmp = path18.join(os.tmpdir(), `clef-manifest-${process.pid}-${Date.now()}.tmp`);
4207
4209
  try {
4208
- fs14.writeFileSync(tmp, YAML10.stringify(doc), "utf-8");
4209
- fs14.renameSync(tmp, manifestPath);
4210
+ fs15.writeFileSync(tmp, YAML10.stringify(doc), "utf-8");
4211
+ fs15.renameSync(tmp, manifestPath);
4210
4212
  } finally {
4211
4213
  try {
4212
- fs14.unlinkSync(tmp);
4214
+ fs15.unlinkSync(tmp);
4213
4215
  } catch {
4214
4216
  }
4215
4217
  }
@@ -4245,8 +4247,8 @@ var ServiceIdentityManager = class {
4245
4247
  if (!identity) {
4246
4248
  throw new Error(`Service identity '${name}' not found.`);
4247
4249
  }
4248
- const manifestPath = path17.join(repoRoot, CLEF_MANIFEST_FILENAME);
4249
- const raw = fs14.readFileSync(manifestPath, "utf-8");
4250
+ const manifestPath = path18.join(repoRoot, CLEF_MANIFEST_FILENAME);
4251
+ const raw = fs15.readFileSync(manifestPath, "utf-8");
4250
4252
  const doc = YAML10.parse(raw);
4251
4253
  const identities = doc.service_identities;
4252
4254
  const siDoc = identities.find((si) => si.name === name);
@@ -4300,13 +4302,13 @@ var ServiceIdentityManager = class {
4300
4302
  }
4301
4303
  throw err;
4302
4304
  }
4303
- const tmpRotate = path17.join(os.tmpdir(), `clef-manifest-${process.pid}-${Date.now()}.tmp`);
4305
+ const tmpRotate = path18.join(os.tmpdir(), `clef-manifest-${process.pid}-${Date.now()}.tmp`);
4304
4306
  try {
4305
- fs14.writeFileSync(tmpRotate, YAML10.stringify(doc), "utf-8");
4306
- fs14.renameSync(tmpRotate, manifestPath);
4307
+ fs15.writeFileSync(tmpRotate, YAML10.stringify(doc), "utf-8");
4308
+ fs15.renameSync(tmpRotate, manifestPath);
4307
4309
  } finally {
4308
4310
  try {
4309
- fs14.unlinkSync(tmpRotate);
4311
+ fs15.unlinkSync(tmpRotate);
4310
4312
  } catch {
4311
4313
  }
4312
4314
  }
@@ -4427,8 +4429,8 @@ async function resolveIdentitySecrets(identityName, environment, manifest, repoR
4427
4429
  }
4428
4430
 
4429
4431
  // src/artifact/packer.ts
4430
- import * as fs15 from "fs";
4431
- import * as path18 from "path";
4432
+ import * as fs16 from "fs";
4433
+ import * as path19 from "path";
4432
4434
  import * as crypto3 from "crypto";
4433
4435
 
4434
4436
  // src/artifact/signer.ts
@@ -4603,9 +4605,9 @@ var ArtifactPacker = class {
4603
4605
  keys: Object.keys(resolved.values)
4604
4606
  };
4605
4607
  }
4606
- const outputDir = path18.dirname(config.outputPath);
4607
- if (!fs15.existsSync(outputDir)) {
4608
- fs15.mkdirSync(outputDir, { recursive: true });
4608
+ const outputDir = path19.dirname(config.outputPath);
4609
+ if (!fs16.existsSync(outputDir)) {
4610
+ fs16.mkdirSync(outputDir, { recursive: true });
4609
4611
  }
4610
4612
  if (config.ttl && config.ttl > 0) {
4611
4613
  artifact.expiresAt = new Date(Date.now() + config.ttl * 1e3).toISOString();
@@ -4624,8 +4626,8 @@ var ArtifactPacker = class {
4624
4626
  }
4625
4627
  const json = JSON.stringify(artifact, null, 2);
4626
4628
  const tmpOutput = `${config.outputPath}.tmp.${process.pid}`;
4627
- fs15.writeFileSync(tmpOutput, json, "utf-8");
4628
- fs15.renameSync(tmpOutput, config.outputPath);
4629
+ fs16.writeFileSync(tmpOutput, json, "utf-8");
4630
+ fs16.renameSync(tmpOutput, config.outputPath);
4629
4631
  return {
4630
4632
  outputPath: config.outputPath,
4631
4633
  namespaceCount: resolved.identity.namespaces.length,
@@ -4638,8 +4640,215 @@ var ArtifactPacker = class {
4638
4640
 
4639
4641
  // src/kms/types.ts
4640
4642
  var VALID_KMS_PROVIDERS = ["aws", "gcp", "azure"];
4643
+
4644
+ // src/migration/backend.ts
4645
+ import * as fs17 from "fs";
4646
+ import * as path20 from "path";
4647
+ import * as YAML11 from "yaml";
4648
+ var BACKEND_KEY_FIELDS = {
4649
+ age: void 0,
4650
+ awskms: "aws_kms_arn",
4651
+ gcpkms: "gcp_kms_resource_id",
4652
+ azurekv: "azure_kv_url",
4653
+ pgp: "pgp_fingerprint"
4654
+ };
4655
+ var ALL_KEY_FIELDS = Object.values(BACKEND_KEY_FIELDS).filter(
4656
+ (v) => v !== void 0
4657
+ );
4658
+ function metadataMatchesTarget(meta, target) {
4659
+ if (meta.backend !== target.backend) return false;
4660
+ if (!target.key) return true;
4661
+ return meta.recipients.includes(target.key);
4662
+ }
4663
+ var BackendMigrator = class {
4664
+ constructor(encryption, matrixManager) {
4665
+ this.encryption = encryption;
4666
+ this.matrixManager = matrixManager;
4667
+ }
4668
+ async migrate(manifest, repoRoot, options, callbacks, onProgress) {
4669
+ const { target, environment, dryRun, skipVerify } = options;
4670
+ if (environment) {
4671
+ const env = manifest.environments.find((e) => e.name === environment);
4672
+ if (!env) {
4673
+ throw new Error(`Environment '${environment}' not found in manifest.`);
4674
+ }
4675
+ }
4676
+ const allCells = this.matrixManager.resolveMatrix(manifest, repoRoot).filter((c) => c.exists);
4677
+ const targetCells = environment ? allCells.filter((c) => c.environment === environment) : allCells;
4678
+ if (targetCells.length === 0) {
4679
+ return {
4680
+ migratedFiles: [],
4681
+ skippedFiles: [],
4682
+ rolledBack: false,
4683
+ verifiedFiles: [],
4684
+ warnings: ["No encrypted files found to migrate."]
4685
+ };
4686
+ }
4687
+ const toMigrate = [];
4688
+ const skippedFiles = [];
4689
+ for (const cell of targetCells) {
4690
+ const meta = await this.encryption.getMetadata(cell.filePath);
4691
+ if (metadataMatchesTarget(meta, target)) {
4692
+ skippedFiles.push(cell.filePath);
4693
+ onProgress?.({
4694
+ type: "skip",
4695
+ file: cell.filePath,
4696
+ message: `${cell.namespace}/${cell.environment}: already on ${target.backend}, skipping`
4697
+ });
4698
+ } else {
4699
+ toMigrate.push(cell);
4700
+ }
4701
+ }
4702
+ if (toMigrate.length === 0) {
4703
+ return {
4704
+ migratedFiles: [],
4705
+ skippedFiles,
4706
+ rolledBack: false,
4707
+ verifiedFiles: [],
4708
+ warnings: ["All files already use the target backend and key. Nothing to migrate."]
4709
+ };
4710
+ }
4711
+ if (dryRun) {
4712
+ const warnings2 = [];
4713
+ for (const cell of toMigrate) {
4714
+ onProgress?.({
4715
+ type: "info",
4716
+ file: cell.filePath,
4717
+ message: `Would migrate ${cell.namespace}/${cell.environment} to ${target.backend}`
4718
+ });
4719
+ }
4720
+ if (environment) {
4721
+ warnings2.push(
4722
+ `Would add per-environment backend override for '${environment}' \u2192 ${target.backend}`
4723
+ );
4724
+ } else {
4725
+ warnings2.push(`Would update global default_backend \u2192 ${target.backend}`);
4726
+ }
4727
+ this.checkAgeRecipientsWarning(manifest, target, environment, warnings2);
4728
+ return {
4729
+ migratedFiles: [],
4730
+ skippedFiles,
4731
+ rolledBack: false,
4732
+ verifiedFiles: [],
4733
+ warnings: warnings2
4734
+ };
4735
+ }
4736
+ const manifestPath = path20.join(repoRoot, CLEF_MANIFEST_FILENAME);
4737
+ const manifestBackup = fs17.readFileSync(manifestPath, "utf-8");
4738
+ const sopsYamlPath = path20.join(repoRoot, ".sops.yaml");
4739
+ const sopsYamlBackup = fs17.existsSync(sopsYamlPath) ? fs17.readFileSync(sopsYamlPath, "utf-8") : void 0;
4740
+ const fileBackups = /* @__PURE__ */ new Map();
4741
+ const doc = readManifestYaml(repoRoot);
4742
+ this.updateManifestDoc(doc, target, environment);
4743
+ writeManifestYaml(repoRoot, doc);
4744
+ const updatedManifest = YAML11.parse(YAML11.stringify(doc));
4745
+ callbacks.regenerateSopsConfig();
4746
+ const migratedFiles = [];
4747
+ for (const cell of toMigrate) {
4748
+ try {
4749
+ fileBackups.set(cell.filePath, fs17.readFileSync(cell.filePath, "utf-8"));
4750
+ onProgress?.({
4751
+ type: "migrate",
4752
+ file: cell.filePath,
4753
+ message: `Migrating ${cell.namespace}/${cell.environment}...`
4754
+ });
4755
+ const decrypted = await this.encryption.decrypt(cell.filePath);
4756
+ await this.encryption.encrypt(
4757
+ cell.filePath,
4758
+ decrypted.values,
4759
+ updatedManifest,
4760
+ cell.environment
4761
+ );
4762
+ migratedFiles.push(cell.filePath);
4763
+ } catch (err) {
4764
+ this.rollback(manifestPath, manifestBackup, sopsYamlPath, sopsYamlBackup, fileBackups);
4765
+ const errorMsg = err instanceof Error ? err.message : String(err);
4766
+ onProgress?.({
4767
+ type: "warn",
4768
+ file: cell.filePath,
4769
+ message: `Migration failed: ${errorMsg}. All changes rolled back.`
4770
+ });
4771
+ return {
4772
+ migratedFiles: [],
4773
+ skippedFiles,
4774
+ rolledBack: true,
4775
+ error: `Failed on ${cell.namespace}/${cell.environment}: ${errorMsg}`,
4776
+ verifiedFiles: [],
4777
+ warnings: ["All changes have been rolled back."]
4778
+ };
4779
+ }
4780
+ }
4781
+ const verifiedFiles = [];
4782
+ const warnings = [];
4783
+ if (!skipVerify) {
4784
+ for (const cell of toMigrate) {
4785
+ try {
4786
+ onProgress?.({
4787
+ type: "verify",
4788
+ file: cell.filePath,
4789
+ message: `Verifying ${cell.namespace}/${cell.environment}...`
4790
+ });
4791
+ await this.encryption.decrypt(cell.filePath);
4792
+ verifiedFiles.push(cell.filePath);
4793
+ } catch (err) {
4794
+ const errorMsg = err instanceof Error ? err.message : String(err);
4795
+ warnings.push(
4796
+ `Verification failed for ${cell.namespace}/${cell.environment}: ${errorMsg}`
4797
+ );
4798
+ }
4799
+ }
4800
+ }
4801
+ this.checkAgeRecipientsWarning(manifest, target, environment, warnings);
4802
+ return { migratedFiles, skippedFiles, rolledBack: false, verifiedFiles, warnings };
4803
+ }
4804
+ // ── Private helpers ──────────────────────────────────────────────────
4805
+ updateManifestDoc(doc, target, environment) {
4806
+ const keyField = BACKEND_KEY_FIELDS[target.backend];
4807
+ if (environment) {
4808
+ const environments = doc.environments;
4809
+ const envDoc = environments.find(
4810
+ (e) => e.name === environment
4811
+ );
4812
+ const sopsOverride = { backend: target.backend };
4813
+ if (keyField && target.key) {
4814
+ sopsOverride[keyField] = target.key;
4815
+ }
4816
+ envDoc.sops = sopsOverride;
4817
+ } else {
4818
+ const sops = doc.sops;
4819
+ sops.default_backend = target.backend;
4820
+ for (const field of ALL_KEY_FIELDS) {
4821
+ delete sops[field];
4822
+ }
4823
+ if (keyField && target.key) {
4824
+ sops[keyField] = target.key;
4825
+ }
4826
+ }
4827
+ }
4828
+ rollback(manifestPath, manifestBackup, sopsYamlPath, sopsYamlBackup, fileBackups) {
4829
+ for (const [filePath, backup] of fileBackups) {
4830
+ fs17.writeFileSync(filePath, backup, "utf-8");
4831
+ }
4832
+ if (sopsYamlBackup !== void 0) {
4833
+ fs17.writeFileSync(sopsYamlPath, sopsYamlBackup, "utf-8");
4834
+ } else if (fs17.existsSync(sopsYamlPath)) {
4835
+ fs17.unlinkSync(sopsYamlPath);
4836
+ }
4837
+ fs17.writeFileSync(manifestPath, manifestBackup, "utf-8");
4838
+ }
4839
+ checkAgeRecipientsWarning(manifest, target, environment, warnings) {
4840
+ if (target.backend === "age") return;
4841
+ const hasRecipients = environment ? manifest.environments.find((e) => e.name === environment)?.recipients?.length : manifest.environments.some((e) => e.recipients?.length);
4842
+ if (hasRecipients) {
4843
+ warnings.push(
4844
+ "Per-environment age recipients are no longer used for encryption on the migrated environments. Consider removing them from clef.yaml if they are no longer needed."
4845
+ );
4846
+ }
4847
+ }
4848
+ };
4641
4849
  export {
4642
4850
  ArtifactPacker,
4851
+ BackendMigrator,
4643
4852
  BulkOps,
4644
4853
  CLEF_MANIFEST_FILENAME,
4645
4854
  CLEF_REPORT_SCHEMA_VERSION,
@@ -4702,11 +4911,12 @@ export {
4702
4911
  markResolved,
4703
4912
  matchPatterns,
4704
4913
  metadataPath,
4705
- parse8 as parse,
4914
+ parse9 as parse,
4706
4915
  parseDotenv,
4707
4916
  parseIgnoreContent,
4708
4917
  parseJson,
4709
4918
  parseYaml,
4919
+ readManifestYaml,
4710
4920
  redactValue,
4711
4921
  removeRequest as removeAccessRequest,
4712
4922
  requestsFilePath,
@@ -4724,6 +4934,7 @@ export {
4724
4934
  signKms,
4725
4935
  upsertRequest,
4726
4936
  validateAgePublicKey,
4727
- verifySignature
4937
+ verifySignature,
4938
+ writeManifestYaml
4728
4939
  };
4729
4940
  //# sourceMappingURL=index.mjs.map