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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -608,7 +608,7 @@ var require_age_encryption = __commonJS({
608
608
  };
609
609
  }
610
610
  // @__NO_SIDE_EFFECTS__
611
- function join16(separator = "") {
611
+ function join18(separator = "") {
612
612
  astr("join", separator);
613
613
  return {
614
614
  encode: (from) => {
@@ -738,9 +738,9 @@ var require_age_encryption = __commonJS({
738
738
  decode(s) {
739
739
  return decodeBase64Builtin(s, false);
740
740
  }
741
- } : /* @__PURE__ */ chain(/* @__PURE__ */ radix2(6), /* @__PURE__ */ alphabet("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"), /* @__PURE__ */ padding(6), /* @__PURE__ */ join16(""));
742
- var base64nopad = /* @__PURE__ */ chain(/* @__PURE__ */ radix2(6), /* @__PURE__ */ alphabet("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"), /* @__PURE__ */ join16(""));
743
- var BECH_ALPHABET = /* @__PURE__ */ chain(/* @__PURE__ */ alphabet("qpzry9x8gf2tvdw0s3jn54khce6mua7l"), /* @__PURE__ */ join16(""));
741
+ } : /* @__PURE__ */ chain(/* @__PURE__ */ radix2(6), /* @__PURE__ */ alphabet("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"), /* @__PURE__ */ padding(6), /* @__PURE__ */ join18(""));
742
+ var base64nopad = /* @__PURE__ */ chain(/* @__PURE__ */ radix2(6), /* @__PURE__ */ alphabet("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"), /* @__PURE__ */ join18(""));
743
+ var BECH_ALPHABET = /* @__PURE__ */ chain(/* @__PURE__ */ alphabet("qpzry9x8gf2tvdw0s3jn54khce6mua7l"), /* @__PURE__ */ join18(""));
744
744
  var POLYMOD_GENERATORS = [996825010, 642813549, 513874426, 1027748829, 705979059];
745
745
  function bech32Polymod(pre) {
746
746
  const b = pre >> 25;
@@ -5789,7 +5789,7 @@ var require_age_encryption = __commonJS({
5789
5789
  return new TextDecoder().decode(bytes);
5790
5790
  }
5791
5791
  function flatten(arr) {
5792
- const len = arr.reduce((sum, line) => sum + line.length, 0);
5792
+ const len = arr.reduce(((sum, line) => sum + line.length), 0);
5793
5793
  const out = new Uint8Array(len);
5794
5794
  let n = 0;
5795
5795
  for (const a of arr) {
@@ -6911,9 +6911,10 @@ var require_age_encryption = __commonJS({
6911
6911
  });
6912
6912
 
6913
6913
  // src/index.ts
6914
- var src_exports = {};
6915
- __export(src_exports, {
6914
+ var index_exports = {};
6915
+ __export(index_exports, {
6916
6916
  ArtifactPacker: () => ArtifactPacker,
6917
+ BackendMigrator: () => BackendMigrator,
6917
6918
  BulkOps: () => BulkOps,
6918
6919
  CLEF_MANIFEST_FILENAME: () => CLEF_MANIFEST_FILENAME,
6919
6920
  CLEF_REPORT_SCHEMA_VERSION: () => CLEF_REPORT_SCHEMA_VERSION,
@@ -6976,11 +6977,12 @@ __export(src_exports, {
6976
6977
  markResolved: () => markResolved,
6977
6978
  matchPatterns: () => matchPatterns,
6978
6979
  metadataPath: () => metadataPath,
6979
- parse: () => parse8,
6980
+ parse: () => parse9,
6980
6981
  parseDotenv: () => parseDotenv,
6981
6982
  parseIgnoreContent: () => parseIgnoreContent,
6982
6983
  parseJson: () => parseJson,
6983
6984
  parseYaml: () => parseYaml,
6985
+ readManifestYaml: () => readManifestYaml,
6984
6986
  redactValue: () => redactValue,
6985
6987
  removeAccessRequest: () => removeRequest,
6986
6988
  requestsFilePath: () => requestsFilePath,
@@ -6998,9 +7000,10 @@ __export(src_exports, {
6998
7000
  signKms: () => signKms,
6999
7001
  upsertRequest: () => upsertRequest,
7000
7002
  validateAgePublicKey: () => validateAgePublicKey,
7001
- verifySignature: () => verifySignature
7003
+ verifySignature: () => verifySignature,
7004
+ writeManifestYaml: () => writeManifestYaml
7002
7005
  });
7003
- module.exports = __toCommonJS(src_exports);
7006
+ module.exports = __toCommonJS(index_exports);
7004
7007
 
7005
7008
  // src/types/index.ts
7006
7009
  var CLEF_SUPPORTED_EXTENSIONS = [".enc.yaml", ".enc.json"];
@@ -7459,8 +7462,26 @@ var ManifestParser = class {
7459
7462
  "sops.default_backend"
7460
7463
  );
7461
7464
  }
7465
+ const ageObj = sopsObj.age;
7466
+ const ageRecipients = ageObj && Array.isArray(ageObj.recipients) ? ageObj.recipients : void 0;
7467
+ const parsedAge = ageRecipients ? {
7468
+ age: {
7469
+ recipients: ageRecipients.map((r) => {
7470
+ if (typeof r === "string") return r;
7471
+ if (typeof r === "object" && r !== null) {
7472
+ const obj2 = r;
7473
+ return {
7474
+ key: String(obj2.key ?? ""),
7475
+ ...typeof obj2.label === "string" ? { label: obj2.label } : {}
7476
+ };
7477
+ }
7478
+ return String(r);
7479
+ })
7480
+ }
7481
+ } : {};
7462
7482
  const sopsConfig = {
7463
7483
  default_backend: sopsObj.default_backend,
7484
+ ...parsedAge,
7464
7485
  ...typeof sopsObj.aws_kms_arn === "string" ? { aws_kms_arn: sopsObj.aws_kms_arn } : {},
7465
7486
  ...typeof sopsObj.gcp_kms_resource_id === "string" ? { gcp_kms_resource_id: sopsObj.gcp_kms_resource_id } : {},
7466
7487
  ...typeof sopsObj.azure_kv_url === "string" ? { azure_kv_url: sopsObj.azure_kv_url } : {},
@@ -7692,9 +7713,21 @@ var ManifestParser = class {
7692
7713
  }
7693
7714
  };
7694
7715
 
7716
+ // src/manifest/io.ts
7717
+ var fs2 = __toESM(require("fs"));
7718
+ var path = __toESM(require("path"));
7719
+ var YAML2 = __toESM(require("yaml"));
7720
+ function readManifestYaml(repoRoot) {
7721
+ const raw = fs2.readFileSync(path.join(repoRoot, CLEF_MANIFEST_FILENAME), "utf-8");
7722
+ return YAML2.parse(raw);
7723
+ }
7724
+ function writeManifestYaml(repoRoot, doc) {
7725
+ fs2.writeFileSync(path.join(repoRoot, CLEF_MANIFEST_FILENAME), YAML2.stringify(doc), "utf-8");
7726
+ }
7727
+
7695
7728
  // src/scanner/index.ts
7696
- var fs3 = __toESM(require("fs"));
7697
- var path2 = __toESM(require("path"));
7729
+ var fs4 = __toESM(require("fs"));
7730
+ var path3 = __toESM(require("path"));
7698
7731
 
7699
7732
  // src/scanner/patterns.ts
7700
7733
  var PATTERNS = [
@@ -7754,12 +7787,12 @@ function matchPatterns(line, lineNumber, filePath) {
7754
7787
  }
7755
7788
 
7756
7789
  // src/scanner/ignore.ts
7757
- var fs2 = __toESM(require("fs"));
7758
- var path = __toESM(require("path"));
7790
+ var fs3 = __toESM(require("fs"));
7791
+ var path2 = __toESM(require("path"));
7759
7792
  function loadIgnoreRules(repoRoot) {
7760
- const ignorePath = path.join(repoRoot, ".clefignore");
7793
+ const ignorePath = path2.join(repoRoot, ".clefignore");
7761
7794
  try {
7762
- const content = fs2.readFileSync(ignorePath, "utf-8");
7795
+ const content = fs3.readFileSync(ignorePath, "utf-8");
7763
7796
  return parseIgnoreContent(content);
7764
7797
  } catch {
7765
7798
  return { files: [], patterns: [], paths: [] };
@@ -7814,8 +7847,8 @@ function matchesGlob(filePath, pattern) {
7814
7847
  var ALWAYS_SKIP_EXTENSIONS = [".enc.yaml", ".enc.json"];
7815
7848
  var ALWAYS_SKIP_NAMES = [
7816
7849
  ".clef-meta.yaml",
7817
- ".sops.yaml"
7818
- // contains age public keys and KMS ARNs — configuration, not secrets
7850
+ "clef.yaml"
7851
+ // manifest — contains public keys and config, not secrets
7819
7852
  ];
7820
7853
  var ALWAYS_SKIP_DIRS = ["node_modules", ".git"];
7821
7854
  var MAX_FILE_SIZE = 1024 * 1024;
@@ -7842,9 +7875,9 @@ var ScanRunner = class {
7842
7875
  for (const ns of manifest.namespaces) {
7843
7876
  for (const env of manifest.environments) {
7844
7877
  const relPath = manifest.file_pattern.replace("{namespace}", ns.name).replace("{environment}", env.name);
7845
- const absPath = path2.join(repoRoot, relPath);
7846
- if (fs3.existsSync(absPath)) {
7847
- const content = fs3.readFileSync(absPath, "utf-8");
7878
+ const absPath = path3.join(repoRoot, relPath);
7879
+ if (fs4.existsSync(absPath)) {
7880
+ const content = fs4.readFileSync(absPath, "utf-8");
7848
7881
  if (!content.includes("sops:") && !content.includes('"sops"')) {
7849
7882
  unencryptedMatrixFiles.push(relPath);
7850
7883
  }
@@ -7860,8 +7893,8 @@ var ScanRunner = class {
7860
7893
  filesToScan = await this.getAllTrackedFiles(repoRoot);
7861
7894
  }
7862
7895
  for (const relFile of filesToScan) {
7863
- const absFile = path2.isAbsolute(relFile) ? relFile : path2.join(repoRoot, relFile);
7864
- const relPath = path2.relative(repoRoot, absFile).replace(/\\/g, "/");
7896
+ const absFile = path3.isAbsolute(relFile) ? relFile : path3.join(repoRoot, relFile);
7897
+ const relPath = path3.relative(repoRoot, absFile).replace(/\\/g, "/");
7865
7898
  if (this.shouldAlwaysSkip(relPath)) {
7866
7899
  filesSkipped++;
7867
7900
  continue;
@@ -7870,13 +7903,13 @@ var ScanRunner = class {
7870
7903
  filesSkipped++;
7871
7904
  continue;
7872
7905
  }
7873
- if (!fs3.existsSync(absFile)) {
7906
+ if (!fs4.existsSync(absFile)) {
7874
7907
  filesSkipped++;
7875
7908
  continue;
7876
7909
  }
7877
7910
  let stat;
7878
7911
  try {
7879
- stat = fs3.statSync(absFile);
7912
+ stat = fs4.statSync(absFile);
7880
7913
  } catch {
7881
7914
  filesSkipped++;
7882
7915
  continue;
@@ -7890,7 +7923,7 @@ var ScanRunner = class {
7890
7923
  continue;
7891
7924
  }
7892
7925
  filesScanned++;
7893
- const content = fs3.readFileSync(absFile, "utf-8");
7926
+ const content = fs4.readFileSync(absFile, "utf-8");
7894
7927
  const lines = content.split("\n");
7895
7928
  for (let i = 0; i < lines.length; i++) {
7896
7929
  const line = lines[i];
@@ -7932,10 +7965,10 @@ var ScanRunner = class {
7932
7965
  }
7933
7966
  isBinary(filePath) {
7934
7967
  try {
7935
- const fd = fs3.openSync(filePath, "r");
7968
+ const fd = fs4.openSync(filePath, "r");
7936
7969
  const buf = Buffer.alloc(512);
7937
- const bytesRead = fs3.readSync(fd, buf, 0, 512, 0);
7938
- fs3.closeSync(fd);
7970
+ const bytesRead = fs4.readSync(fd, buf, 0, 512, 0);
7971
+ fs4.closeSync(fd);
7939
7972
  for (let i = 0; i < bytesRead; i++) {
7940
7973
  if (buf[i] === 0) return true;
7941
7974
  }
@@ -7975,13 +8008,13 @@ var ScanRunner = class {
7975
8008
  async getFilesInPaths(repoRoot, paths) {
7976
8009
  const files = [];
7977
8010
  for (const p of paths) {
7978
- const absPath = path2.isAbsolute(p) ? p : path2.join(repoRoot, p);
7979
- if (!fs3.existsSync(absPath)) continue;
7980
- const stat = fs3.statSync(absPath);
8011
+ const absPath = path3.isAbsolute(p) ? p : path3.join(repoRoot, p);
8012
+ if (!fs4.existsSync(absPath)) continue;
8013
+ const stat = fs4.statSync(absPath);
7981
8014
  if (stat.isDirectory()) {
7982
8015
  files.push(...this.walkDir(absPath, repoRoot));
7983
8016
  } else {
7984
- files.push(path2.relative(repoRoot, absPath).replace(/\\/g, "/"));
8017
+ files.push(path3.relative(repoRoot, absPath).replace(/\\/g, "/"));
7985
8018
  }
7986
8019
  }
7987
8020
  return files;
@@ -7997,13 +8030,13 @@ var ScanRunner = class {
7997
8030
  const files = [];
7998
8031
  let entries;
7999
8032
  try {
8000
- entries = fs3.readdirSync(dir, { withFileTypes: true });
8033
+ entries = fs4.readdirSync(dir, { withFileTypes: true });
8001
8034
  } catch {
8002
8035
  return files;
8003
8036
  }
8004
8037
  for (const entry of entries) {
8005
- const fullPath = path2.join(dir, entry.name);
8006
- const relPath = path2.relative(repoRoot, fullPath).replace(/\\/g, "/");
8038
+ const fullPath = path3.join(dir, entry.name);
8039
+ const relPath = path3.relative(repoRoot, fullPath).replace(/\\/g, "/");
8007
8040
  if (entry.isDirectory()) {
8008
8041
  if (!ALWAYS_SKIP_DIRS.includes(entry.name)) {
8009
8042
  files.push(...this.walkDir(fullPath, repoRoot));
@@ -8017,29 +8050,29 @@ var ScanRunner = class {
8017
8050
  };
8018
8051
 
8019
8052
  // src/matrix/manager.ts
8020
- var fs6 = __toESM(require("fs"));
8021
- var path4 = __toESM(require("path"));
8022
- var YAML4 = __toESM(require("yaml"));
8053
+ var fs7 = __toESM(require("fs"));
8054
+ var path5 = __toESM(require("path"));
8055
+ var YAML5 = __toESM(require("yaml"));
8023
8056
 
8024
8057
  // src/pending/metadata.ts
8025
- var fs4 = __toESM(require("fs"));
8026
- var path3 = __toESM(require("path"));
8058
+ var fs5 = __toESM(require("fs"));
8059
+ var path4 = __toESM(require("path"));
8027
8060
  var crypto2 = __toESM(require("crypto"));
8028
- var YAML2 = __toESM(require("yaml"));
8061
+ var YAML3 = __toESM(require("yaml"));
8029
8062
  function metadataPath(encryptedFilePath) {
8030
- const dir = path3.dirname(encryptedFilePath);
8031
- const base = path3.basename(encryptedFilePath).replace(/\.enc\.(yaml|json)$/, "");
8032
- return path3.join(dir, `${base}.clef-meta.yaml`);
8063
+ const dir = path4.dirname(encryptedFilePath);
8064
+ const base = path4.basename(encryptedFilePath).replace(/\.enc\.(yaml|json)$/, "");
8065
+ return path4.join(dir, `${base}.clef-meta.yaml`);
8033
8066
  }
8034
8067
  var HEADER_COMMENT = "# Managed by Clef. Do not edit manually.\n";
8035
8068
  async function loadMetadata(filePath) {
8036
8069
  const metaPath = metadataPath(filePath);
8037
8070
  try {
8038
- if (!fs4.existsSync(metaPath)) {
8071
+ if (!fs5.existsSync(metaPath)) {
8039
8072
  return { version: 1, pending: [] };
8040
8073
  }
8041
- const content = fs4.readFileSync(metaPath, "utf-8");
8042
- const parsed = YAML2.parse(content);
8074
+ const content = fs5.readFileSync(metaPath, "utf-8");
8075
+ const parsed = YAML3.parse(content);
8043
8076
  if (!parsed || !Array.isArray(parsed.pending)) {
8044
8077
  return { version: 1, pending: [] };
8045
8078
  }
@@ -8057,9 +8090,9 @@ async function loadMetadata(filePath) {
8057
8090
  }
8058
8091
  async function saveMetadata(filePath, metadata) {
8059
8092
  const metaPath = metadataPath(filePath);
8060
- const dir = path3.dirname(metaPath);
8061
- if (!fs4.existsSync(dir)) {
8062
- fs4.mkdirSync(dir, { recursive: true });
8093
+ const dir = path4.dirname(metaPath);
8094
+ if (!fs5.existsSync(dir)) {
8095
+ fs5.mkdirSync(dir, { recursive: true });
8063
8096
  }
8064
8097
  const data = {
8065
8098
  version: metadata.version,
@@ -8069,7 +8102,7 @@ async function saveMetadata(filePath, metadata) {
8069
8102
  setBy: p.setBy
8070
8103
  }))
8071
8104
  };
8072
- fs4.writeFileSync(metaPath, HEADER_COMMENT + YAML2.stringify(data), "utf-8");
8105
+ fs5.writeFileSync(metaPath, HEADER_COMMENT + YAML3.stringify(data), "utf-8");
8073
8106
  }
8074
8107
  async function markPending(filePath, keys, setBy) {
8075
8108
  const metadata = await loadMetadata(filePath);
@@ -8110,12 +8143,12 @@ async function markPendingWithRetry(filePath, keys, setBy, retryDelayMs = 200) {
8110
8143
  }
8111
8144
 
8112
8145
  // src/sops/keys.ts
8113
- var fs5 = __toESM(require("fs"));
8114
- var YAML3 = __toESM(require("yaml"));
8146
+ var fs6 = __toESM(require("fs"));
8147
+ var YAML4 = __toESM(require("yaml"));
8115
8148
  function readSopsKeyNames(filePath) {
8116
8149
  try {
8117
- const raw = fs5.readFileSync(filePath, "utf-8");
8118
- const parsed = YAML3.parse(raw);
8150
+ const raw = fs6.readFileSync(filePath, "utf-8");
8151
+ const parsed = YAML4.parse(raw);
8119
8152
  if (parsed === null || parsed === void 0 || typeof parsed !== "object") return null;
8120
8153
  return Object.keys(parsed).filter((k) => k !== "sops");
8121
8154
  } catch {
@@ -8137,12 +8170,12 @@ var MatrixManager = class {
8137
8170
  for (const ns of manifest.namespaces) {
8138
8171
  for (const env of manifest.environments) {
8139
8172
  const relativePath = manifest.file_pattern.replace("{namespace}", ns.name).replace("{environment}", env.name);
8140
- const filePath = path4.join(repoRoot, relativePath);
8173
+ const filePath = path5.join(repoRoot, relativePath);
8141
8174
  cells.push({
8142
8175
  namespace: ns.name,
8143
8176
  environment: env.name,
8144
8177
  filePath,
8145
- exists: fs6.existsSync(filePath)
8178
+ exists: fs7.existsSync(filePath)
8146
8179
  });
8147
8180
  }
8148
8181
  }
@@ -8165,9 +8198,9 @@ var MatrixManager = class {
8165
8198
  * @param manifest - Parsed manifest used to determine the encryption backend.
8166
8199
  */
8167
8200
  async scaffoldCell(cell, sopsClient, manifest) {
8168
- const dir = path4.dirname(cell.filePath);
8169
- if (!fs6.existsSync(dir)) {
8170
- fs6.mkdirSync(dir, { recursive: true });
8201
+ const dir = path5.dirname(cell.filePath);
8202
+ if (!fs7.existsSync(dir)) {
8203
+ fs7.mkdirSync(dir, { recursive: true });
8171
8204
  }
8172
8205
  await sopsClient.encrypt(cell.filePath, {}, manifest, cell.environment);
8173
8206
  }
@@ -8243,8 +8276,8 @@ var MatrixManager = class {
8243
8276
  */
8244
8277
  readLastModified(filePath) {
8245
8278
  try {
8246
- const raw = fs6.readFileSync(filePath, "utf-8");
8247
- const parsed = YAML4.parse(raw);
8279
+ const raw = fs7.readFileSync(filePath, "utf-8");
8280
+ const parsed = YAML5.parse(raw);
8248
8281
  const sops = parsed?.sops;
8249
8282
  if (sops?.lastmodified) return new Date(String(sops.lastmodified));
8250
8283
  return null;
@@ -8265,8 +8298,8 @@ var MatrixManager = class {
8265
8298
  };
8266
8299
 
8267
8300
  // src/schema/validator.ts
8268
- var fs7 = __toESM(require("fs"));
8269
- var YAML5 = __toESM(require("yaml"));
8301
+ var fs8 = __toESM(require("fs"));
8302
+ var YAML6 = __toESM(require("yaml"));
8270
8303
  var SchemaValidator = class {
8271
8304
  /**
8272
8305
  * Read and parse a YAML schema file from disk.
@@ -8278,13 +8311,13 @@ var SchemaValidator = class {
8278
8311
  loadSchema(filePath) {
8279
8312
  let raw;
8280
8313
  try {
8281
- raw = fs7.readFileSync(filePath, "utf-8");
8314
+ raw = fs8.readFileSync(filePath, "utf-8");
8282
8315
  } catch {
8283
8316
  throw new SchemaLoadError(`Could not read schema file at '${filePath}'.`, filePath);
8284
8317
  }
8285
8318
  let parsed;
8286
8319
  try {
8287
- parsed = YAML5.parse(raw);
8320
+ parsed = YAML6.parse(raw);
8288
8321
  } catch {
8289
8322
  throw new SchemaLoadError(`Schema file '${filePath}' contains invalid YAML.`, filePath);
8290
8323
  }
@@ -8412,7 +8445,7 @@ var SchemaValidator = class {
8412
8445
  };
8413
8446
 
8414
8447
  // src/diff/engine.ts
8415
- var path5 = __toESM(require("path"));
8448
+ var path6 = __toESM(require("path"));
8416
8449
  var DiffEngine = class {
8417
8450
  /**
8418
8451
  * Compare two in-memory value maps and produce a sorted diff result.
@@ -8469,11 +8502,11 @@ var DiffEngine = class {
8469
8502
  * @throws {@link SopsDecryptionError} If either file cannot be decrypted.
8470
8503
  */
8471
8504
  async diffFiles(namespace, envA, envB, manifest, sopsClient, repoRoot) {
8472
- const fileA = path5.join(
8505
+ const fileA = path6.join(
8473
8506
  repoRoot,
8474
8507
  manifest.file_pattern.replace("{namespace}", namespace).replace("{environment}", envA)
8475
8508
  );
8476
- const fileB = path5.join(
8509
+ const fileB = path6.join(
8477
8510
  repoRoot,
8478
8511
  manifest.file_pattern.replace("{namespace}", namespace).replace("{environment}", envB)
8479
8512
  );
@@ -8486,7 +8519,7 @@ var DiffEngine = class {
8486
8519
  };
8487
8520
 
8488
8521
  // src/bulk/ops.ts
8489
- var path6 = __toESM(require("path"));
8522
+ var path7 = __toESM(require("path"));
8490
8523
  var BulkOps = class {
8491
8524
  /**
8492
8525
  * Set a key to different values in multiple environments at once.
@@ -8505,7 +8538,7 @@ var BulkOps = class {
8505
8538
  if (!(env.name in values)) {
8506
8539
  continue;
8507
8540
  }
8508
- const filePath = path6.join(
8541
+ const filePath = path7.join(
8509
8542
  repoRoot,
8510
8543
  manifest.file_pattern.replace("{namespace}", namespace).replace("{environment}", env.name)
8511
8544
  );
@@ -8539,7 +8572,7 @@ Successfully updated ${Object.keys(values).length - errors.length} environment(s
8539
8572
  async deleteAcrossEnvironments(namespace, key, manifest, sopsClient, repoRoot) {
8540
8573
  const errors = [];
8541
8574
  for (const env of manifest.environments) {
8542
- const filePath = path6.join(
8575
+ const filePath = path7.join(
8543
8576
  repoRoot,
8544
8577
  manifest.file_pattern.replace("{namespace}", namespace).replace("{environment}", env.name)
8545
8578
  );
@@ -8585,8 +8618,8 @@ ${details}`
8585
8618
  };
8586
8619
 
8587
8620
  // src/git/integration.ts
8588
- var fs8 = __toESM(require("fs"));
8589
- var path7 = __toESM(require("path"));
8621
+ var fs9 = __toESM(require("fs"));
8622
+ var path8 = __toESM(require("path"));
8590
8623
  var PRE_COMMIT_HOOK = `#!/bin/sh
8591
8624
  # Clef pre-commit hook \u2014 blocks commits of files missing SOPS encryption metadata
8592
8625
  # and scans staged files for plaintext secrets.
@@ -8793,15 +8826,15 @@ var GitIntegration = class {
8793
8826
  cwd: repoRoot
8794
8827
  });
8795
8828
  const gitConfig = configResult.exitCode === 0 && configResult.stdout.trim().length > 0;
8796
- const attrFilePath = path7.join(repoRoot, ".gitattributes");
8797
- const attrContent = fs8.existsSync(attrFilePath) ? fs8.readFileSync(attrFilePath, "utf-8") : "";
8829
+ const attrFilePath = path8.join(repoRoot, ".gitattributes");
8830
+ const attrContent = fs9.existsSync(attrFilePath) ? fs9.readFileSync(attrFilePath, "utf-8") : "";
8798
8831
  const gitattributes = attrContent.includes("merge=sops");
8799
8832
  return { gitConfig, gitattributes };
8800
8833
  }
8801
8834
  async ensureGitattributes(repoRoot) {
8802
- const attrPath = path7.join(repoRoot, ".gitattributes");
8835
+ const attrPath = path8.join(repoRoot, ".gitattributes");
8803
8836
  const mergeRule = "*.enc.yaml merge=sops\n*.enc.json merge=sops";
8804
- const existing = fs8.existsSync(attrPath) ? fs8.readFileSync(attrPath, "utf-8") : "";
8837
+ const existing = fs9.existsSync(attrPath) ? fs9.readFileSync(attrPath, "utf-8") : "";
8805
8838
  if (existing.includes("merge=sops")) {
8806
8839
  return;
8807
8840
  }
@@ -8828,7 +8861,7 @@ ${mergeRule}
8828
8861
  * @throws {@link GitOperationError} On failure.
8829
8862
  */
8830
8863
  async installPreCommitHook(repoRoot) {
8831
- const hookPath = path7.join(repoRoot, ".git", "hooks", "pre-commit");
8864
+ const hookPath = path8.join(repoRoot, ".git", "hooks", "pre-commit");
8832
8865
  const result = await this.runner.run("tee", [hookPath], {
8833
8866
  stdin: PRE_COMMIT_HOOK,
8834
8867
  cwd: repoRoot
@@ -8849,18 +8882,18 @@ ${mergeRule}
8849
8882
  };
8850
8883
 
8851
8884
  // src/sops/client.ts
8852
- var fs11 = __toESM(require("fs"));
8885
+ var fs12 = __toESM(require("fs"));
8853
8886
  var net = __toESM(require("net"));
8854
8887
  var import_crypto = require("crypto");
8855
- var YAML6 = __toESM(require("yaml"));
8888
+ var YAML7 = __toESM(require("yaml"));
8856
8889
 
8857
8890
  // src/sops/resolver.ts
8858
- var fs10 = __toESM(require("fs"));
8859
- var path9 = __toESM(require("path"));
8891
+ var fs11 = __toESM(require("fs"));
8892
+ var path10 = __toESM(require("path"));
8860
8893
 
8861
8894
  // src/sops/bundled.ts
8862
- var fs9 = __toESM(require("fs"));
8863
- var path8 = __toESM(require("path"));
8895
+ var fs10 = __toESM(require("fs"));
8896
+ var path9 = __toESM(require("path"));
8864
8897
  function tryBundled() {
8865
8898
  const platform = process.platform;
8866
8899
  const arch = process.arch;
@@ -8872,9 +8905,9 @@ function tryBundled() {
8872
8905
  const binName = platform === "win32" ? "sops.exe" : "sops";
8873
8906
  try {
8874
8907
  const packageMain = require.resolve(`${packageName}/package.json`);
8875
- const packageDir = path8.dirname(packageMain);
8876
- const binPath = path8.join(packageDir, "bin", binName);
8877
- return fs9.existsSync(binPath) ? binPath : null;
8908
+ const packageDir = path9.dirname(packageMain);
8909
+ const binPath = path9.join(packageDir, "bin", binName);
8910
+ return fs10.existsSync(binPath) ? binPath : null;
8878
8911
  } catch {
8879
8912
  return null;
8880
8913
  }
@@ -8882,7 +8915,7 @@ function tryBundled() {
8882
8915
 
8883
8916
  // src/sops/resolver.ts
8884
8917
  function validateSopsPath(candidate) {
8885
- if (!path9.isAbsolute(candidate)) {
8918
+ if (!path10.isAbsolute(candidate)) {
8886
8919
  throw new Error(`CLEF_SOPS_PATH must be an absolute path, got '${candidate}'.`);
8887
8920
  }
8888
8921
  const segments = candidate.split(/[/\\]/);
@@ -8898,7 +8931,7 @@ function resolveSopsPath() {
8898
8931
  const envPath = process.env.CLEF_SOPS_PATH?.trim();
8899
8932
  if (envPath) {
8900
8933
  validateSopsPath(envPath);
8901
- if (!fs10.existsSync(envPath)) {
8934
+ if (!fs11.existsSync(envPath)) {
8902
8935
  throw new Error(`CLEF_SOPS_PATH points to '${envPath}' but the file does not exist.`);
8903
8936
  }
8904
8937
  cached = { path: envPath, source: "env" };
@@ -9106,7 +9139,7 @@ var SopsClient = class {
9106
9139
  }
9107
9140
  let parsed;
9108
9141
  try {
9109
- parsed = YAML6.parse(result.stdout) ?? {};
9142
+ parsed = YAML7.parse(result.stdout) ?? {};
9110
9143
  } catch {
9111
9144
  throw new SopsDecryptionError(
9112
9145
  `Decrypted content of '${filePath}' is not valid YAML.`,
@@ -9133,7 +9166,7 @@ var SopsClient = class {
9133
9166
  async encrypt(filePath, values, manifest, environment) {
9134
9167
  await assertSops(this.runner, this.sopsCommand);
9135
9168
  const fmt = formatFromPath(filePath);
9136
- const content = fmt === "json" ? JSON.stringify(values, null, 2) : YAML6.stringify(values);
9169
+ const content = fmt === "json" ? JSON.stringify(values, null, 2) : YAML7.stringify(values);
9137
9170
  const args = this.buildEncryptArgs(filePath, manifest, environment);
9138
9171
  const env = this.buildSopsEnv();
9139
9172
  let inputArg;
@@ -9147,9 +9180,12 @@ var SopsClient = class {
9147
9180
  }
9148
9181
  let result;
9149
9182
  try {
9183
+ const configPath = process.platform === "win32" ? "NUL" : "/dev/null";
9150
9184
  result = await this.runner.run(
9151
9185
  this.sopsCommand,
9152
9186
  [
9187
+ "--config",
9188
+ configPath,
9153
9189
  "encrypt",
9154
9190
  ...args,
9155
9191
  "--input-type",
@@ -9177,7 +9213,7 @@ var SopsClient = class {
9177
9213
  );
9178
9214
  }
9179
9215
  try {
9180
- fs11.writeFileSync(filePath, result.stdout);
9216
+ fs12.writeFileSync(filePath, result.stdout);
9181
9217
  } catch {
9182
9218
  throw new SopsEncryptionError(`Failed to write encrypted data to '${filePath}'.`, filePath);
9183
9219
  }
@@ -9293,7 +9329,7 @@ var SopsClient = class {
9293
9329
  if (!this.ageKey && !this.ageKeyFile) return "key-not-found";
9294
9330
  let keyContent;
9295
9331
  try {
9296
- keyContent = this.ageKey ?? fs11.readFileSync(this.ageKeyFile, "utf-8");
9332
+ keyContent = this.ageKey ?? fs12.readFileSync(this.ageKeyFile, "utf-8");
9297
9333
  } catch {
9298
9334
  return "key-not-found";
9299
9335
  }
@@ -9310,7 +9346,7 @@ var SopsClient = class {
9310
9346
  parseMetadataFromFile(filePath) {
9311
9347
  let content;
9312
9348
  try {
9313
- content = fs11.readFileSync(filePath, "utf-8");
9349
+ content = fs12.readFileSync(filePath, "utf-8");
9314
9350
  } catch {
9315
9351
  throw new SopsDecryptionError(
9316
9352
  `Could not read file '${filePath}' to extract SOPS metadata.`,
@@ -9319,7 +9355,7 @@ var SopsClient = class {
9319
9355
  }
9320
9356
  let parsed;
9321
9357
  try {
9322
- parsed = YAML6.parse(content);
9358
+ parsed = YAML7.parse(content);
9323
9359
  } catch {
9324
9360
  throw new SopsDecryptionError(
9325
9361
  `File '${filePath}' is not valid YAML. Cannot extract SOPS metadata.`,
@@ -9386,8 +9422,15 @@ var SopsClient = class {
9386
9422
  pgp_fingerprint: manifest.sops.pgp_fingerprint
9387
9423
  };
9388
9424
  switch (config.backend) {
9389
- case "age":
9425
+ case "age": {
9426
+ const envRecipients = environment ? resolveRecipientsForEnvironment(manifest, environment) : void 0;
9427
+ const recipients = envRecipients ?? manifest.sops.age?.recipients ?? [];
9428
+ const keys = recipients.map((r) => typeof r === "string" ? r : r.key);
9429
+ if (keys.length > 0) {
9430
+ args.push("--age", keys.join(","));
9431
+ }
9390
9432
  break;
9433
+ }
9391
9434
  case "awskms":
9392
9435
  if (config.aws_kms_arn) {
9393
9436
  args.push("--kms", config.aws_kms_arn);
@@ -9414,7 +9457,7 @@ var SopsClient = class {
9414
9457
  };
9415
9458
 
9416
9459
  // src/lint/runner.ts
9417
- var path10 = __toESM(require("path"));
9460
+ var path11 = __toESM(require("path"));
9418
9461
  var LintRunner = class {
9419
9462
  constructor(matrixManager, schemaValidator, sopsClient) {
9420
9463
  this.matrixManager = matrixManager;
@@ -9514,7 +9557,7 @@ var LintRunner = class {
9514
9557
  }
9515
9558
  const ns = manifest.namespaces.find((n) => n.name === cell.namespace);
9516
9559
  if (ns?.schema) {
9517
- const schemaPath = path10.join(repoRoot, ns.schema);
9560
+ const schemaPath = path11.join(repoRoot, ns.schema);
9518
9561
  try {
9519
9562
  const schema = this.schemaValidator.loadSchema(schemaPath);
9520
9563
  const result = this.schemaValidator.validate(decrypted.values, schema);
@@ -9747,14 +9790,14 @@ Use 'clef exec' to inject secrets directly into a process, or 'clef export --for
9747
9790
  };
9748
9791
 
9749
9792
  // src/import/index.ts
9750
- var path12 = __toESM(require("path"));
9793
+ var path13 = __toESM(require("path"));
9751
9794
 
9752
9795
  // src/import/parsers.ts
9753
- var path11 = __toESM(require("path"));
9754
- var YAML7 = __toESM(require("yaml"));
9796
+ var path12 = __toESM(require("path"));
9797
+ var YAML8 = __toESM(require("yaml"));
9755
9798
  function detectFormat(filePath, content) {
9756
- const base = path11.basename(filePath);
9757
- const ext = path11.extname(filePath).toLowerCase();
9799
+ const base = path12.basename(filePath);
9800
+ const ext = path12.extname(filePath).toLowerCase();
9758
9801
  if (base === ".env" || base.startsWith(".env.")) {
9759
9802
  return "dotenv";
9760
9803
  }
@@ -9775,7 +9818,7 @@ function detectFormat(filePath, content) {
9775
9818
  } catch {
9776
9819
  }
9777
9820
  try {
9778
- const parsed = YAML7.parse(content);
9821
+ const parsed = YAML8.parse(content);
9779
9822
  if (parsed !== null && typeof parsed === "object" && !Array.isArray(parsed)) {
9780
9823
  return "yaml";
9781
9824
  }
@@ -9856,7 +9899,7 @@ function parseJson(content) {
9856
9899
  function parseYaml(content) {
9857
9900
  let parsed;
9858
9901
  try {
9859
- parsed = YAML7.parse(content);
9902
+ parsed = YAML8.parse(content);
9860
9903
  } catch (err) {
9861
9904
  throw new Error(`Invalid YAML: ${err.message}`);
9862
9905
  }
@@ -9890,7 +9933,7 @@ function parseYaml(content) {
9890
9933
  }
9891
9934
  return { pairs, format: "yaml", skipped, warnings };
9892
9935
  }
9893
- function parse8(content, format, filePath) {
9936
+ function parse9(content, format, filePath) {
9894
9937
  const resolved = format === "auto" ? detectFormat(filePath ?? "", content) : format;
9895
9938
  switch (resolved) {
9896
9939
  case "dotenv":
@@ -9919,11 +9962,11 @@ var ImportRunner = class {
9919
9962
  */
9920
9963
  async import(target, sourcePath, content, manifest, repoRoot, options) {
9921
9964
  const [ns, env] = target.split("/");
9922
- const filePath = path12.join(
9965
+ const filePath = path13.join(
9923
9966
  repoRoot,
9924
9967
  manifest.file_pattern.replace("{namespace}", ns).replace("{environment}", env)
9925
9968
  );
9926
- const parsed = parse8(content, options.format ?? "auto", sourcePath ?? "");
9969
+ const parsed = parse9(content, options.format ?? "auto", sourcePath ?? "");
9927
9970
  let candidates = Object.entries(parsed.pairs);
9928
9971
  if (options.prefix) {
9929
9972
  const prefix = options.prefix;
@@ -9977,9 +10020,8 @@ var ImportRunner = class {
9977
10020
  };
9978
10021
 
9979
10022
  // src/recipients/index.ts
9980
- var fs12 = __toESM(require("fs"));
9981
- var path13 = __toESM(require("path"));
9982
- var YAML8 = __toESM(require("yaml"));
10023
+ var fs13 = __toESM(require("fs"));
10024
+ var path14 = __toESM(require("path"));
9983
10025
  function parseRecipientEntry(entry) {
9984
10026
  if (typeof entry === "string") {
9985
10027
  return { key: entry };
@@ -10000,15 +10042,6 @@ function toRecipient(entry) {
10000
10042
  ...entry.label ? { label: entry.label } : {}
10001
10043
  };
10002
10044
  }
10003
- function readManifestYaml(repoRoot) {
10004
- const manifestPath = path13.join(repoRoot, CLEF_MANIFEST_FILENAME);
10005
- const raw = fs12.readFileSync(manifestPath, "utf-8");
10006
- return YAML8.parse(raw);
10007
- }
10008
- function writeManifestYaml(repoRoot, doc) {
10009
- const manifestPath = path13.join(repoRoot, CLEF_MANIFEST_FILENAME);
10010
- fs12.writeFileSync(manifestPath, YAML8.stringify(doc), "utf-8");
10011
- }
10012
10045
  function getRecipientsArray(doc) {
10013
10046
  const sops = doc.sops;
10014
10047
  if (!sops) return [];
@@ -10107,8 +10140,8 @@ var RecipientManager = class {
10107
10140
  if (currentKeys.includes(normalizedKey)) {
10108
10141
  throw new Error(`Recipient '${keyPreview(normalizedKey)}' is already present.`);
10109
10142
  }
10110
- const manifestPath = path13.join(repoRoot, CLEF_MANIFEST_FILENAME);
10111
- const manifestBackup = fs12.readFileSync(manifestPath, "utf-8");
10143
+ const manifestPath = path14.join(repoRoot, CLEF_MANIFEST_FILENAME);
10144
+ const manifestBackup = fs13.readFileSync(manifestPath, "utf-8");
10112
10145
  const recipients = environment ? ensureEnvironmentRecipientsArray(doc, environment) : ensureRecipientsArray(doc);
10113
10146
  if (label) {
10114
10147
  recipients.push({ key: normalizedKey, label });
@@ -10123,16 +10156,16 @@ var RecipientManager = class {
10123
10156
  const fileBackups = /* @__PURE__ */ new Map();
10124
10157
  for (const cell of cells) {
10125
10158
  try {
10126
- fileBackups.set(cell.filePath, fs12.readFileSync(cell.filePath, "utf-8"));
10159
+ fileBackups.set(cell.filePath, fs13.readFileSync(cell.filePath, "utf-8"));
10127
10160
  await this.encryption.addRecipient(cell.filePath, normalizedKey);
10128
10161
  reEncryptedFiles.push(cell.filePath);
10129
10162
  } catch {
10130
10163
  failedFiles.push(cell.filePath);
10131
- fs12.writeFileSync(manifestPath, manifestBackup, "utf-8");
10164
+ fs13.writeFileSync(manifestPath, manifestBackup, "utf-8");
10132
10165
  for (const reEncryptedFile of reEncryptedFiles) {
10133
10166
  const backup = fileBackups.get(reEncryptedFile);
10134
10167
  if (backup) {
10135
- fs12.writeFileSync(reEncryptedFile, backup, "utf-8");
10168
+ fs13.writeFileSync(reEncryptedFile, backup, "utf-8");
10136
10169
  }
10137
10170
  }
10138
10171
  const restoredDoc = readManifestYaml(repoRoot);
@@ -10185,8 +10218,8 @@ var RecipientManager = class {
10185
10218
  throw new Error(`Recipient '${keyPreview(trimmedKey)}' is not in the manifest.`);
10186
10219
  }
10187
10220
  const removedEntry = parsed[matchIndex];
10188
- const manifestPath = path13.join(repoRoot, CLEF_MANIFEST_FILENAME);
10189
- const manifestBackup = fs12.readFileSync(manifestPath, "utf-8");
10221
+ const manifestPath = path14.join(repoRoot, CLEF_MANIFEST_FILENAME);
10222
+ const manifestBackup = fs13.readFileSync(manifestPath, "utf-8");
10190
10223
  const recipients = environment ? ensureEnvironmentRecipientsArray(doc, environment) : ensureRecipientsArray(doc);
10191
10224
  recipients.splice(matchIndex, 1);
10192
10225
  writeManifestYaml(repoRoot, doc);
@@ -10197,16 +10230,16 @@ var RecipientManager = class {
10197
10230
  const fileBackups = /* @__PURE__ */ new Map();
10198
10231
  for (const cell of cells) {
10199
10232
  try {
10200
- fileBackups.set(cell.filePath, fs12.readFileSync(cell.filePath, "utf-8"));
10233
+ fileBackups.set(cell.filePath, fs13.readFileSync(cell.filePath, "utf-8"));
10201
10234
  await this.encryption.removeRecipient(cell.filePath, trimmedKey);
10202
10235
  reEncryptedFiles.push(cell.filePath);
10203
10236
  } catch {
10204
10237
  failedFiles.push(cell.filePath);
10205
- fs12.writeFileSync(manifestPath, manifestBackup, "utf-8");
10238
+ fs13.writeFileSync(manifestPath, manifestBackup, "utf-8");
10206
10239
  for (const reEncryptedFile of reEncryptedFiles) {
10207
10240
  const backup = fileBackups.get(reEncryptedFile);
10208
10241
  if (backup) {
10209
- fs12.writeFileSync(reEncryptedFile, backup, "utf-8");
10242
+ fs13.writeFileSync(reEncryptedFile, backup, "utf-8");
10210
10243
  }
10211
10244
  }
10212
10245
  const restoredDoc = readManifestYaml(repoRoot);
@@ -10240,19 +10273,19 @@ var RecipientManager = class {
10240
10273
  };
10241
10274
 
10242
10275
  // src/recipients/requests.ts
10243
- var fs13 = __toESM(require("fs"));
10244
- var path14 = __toESM(require("path"));
10276
+ var fs14 = __toESM(require("fs"));
10277
+ var path15 = __toESM(require("path"));
10245
10278
  var YAML9 = __toESM(require("yaml"));
10246
10279
  var REQUESTS_FILENAME = ".clef-requests.yaml";
10247
10280
  var HEADER_COMMENT2 = "# Pending recipient access requests. Approve with: clef recipients approve <label>\n";
10248
10281
  function requestsFilePath(repoRoot) {
10249
- return path14.join(repoRoot, REQUESTS_FILENAME);
10282
+ return path15.join(repoRoot, REQUESTS_FILENAME);
10250
10283
  }
10251
10284
  function loadRequests(repoRoot) {
10252
10285
  const filePath = requestsFilePath(repoRoot);
10253
10286
  try {
10254
- if (!fs13.existsSync(filePath)) return [];
10255
- const content = fs13.readFileSync(filePath, "utf-8");
10287
+ if (!fs14.existsSync(filePath)) return [];
10288
+ const content = fs14.readFileSync(filePath, "utf-8");
10256
10289
  const parsed = YAML9.parse(content);
10257
10290
  if (!parsed || !Array.isArray(parsed.requests)) return [];
10258
10291
  return parsed.requests.map((r) => ({
@@ -10269,7 +10302,7 @@ function saveRequests(repoRoot, requests) {
10269
10302
  const filePath = requestsFilePath(repoRoot);
10270
10303
  if (requests.length === 0) {
10271
10304
  try {
10272
- fs13.unlinkSync(filePath);
10305
+ fs14.unlinkSync(filePath);
10273
10306
  } catch {
10274
10307
  }
10275
10308
  return;
@@ -10285,7 +10318,7 @@ function saveRequests(repoRoot, requests) {
10285
10318
  return raw;
10286
10319
  })
10287
10320
  };
10288
- fs13.writeFileSync(filePath, HEADER_COMMENT2 + YAML9.stringify(data), "utf-8");
10321
+ fs14.writeFileSync(filePath, HEADER_COMMENT2 + YAML9.stringify(data), "utf-8");
10289
10322
  }
10290
10323
  function upsertRequest(repoRoot, key, label, environment) {
10291
10324
  const requests = loadRequests(repoRoot);
@@ -10321,7 +10354,7 @@ function findInList(requests, identifier) {
10321
10354
  }
10322
10355
 
10323
10356
  // src/drift/detector.ts
10324
- var path15 = __toESM(require("path"));
10357
+ var path16 = __toESM(require("path"));
10325
10358
  var DriftDetector = class {
10326
10359
  parser = new ManifestParser();
10327
10360
  matrix = new MatrixManager();
@@ -10334,8 +10367,8 @@ var DriftDetector = class {
10334
10367
  * @returns Drift result with any issues found.
10335
10368
  */
10336
10369
  detect(localRoot, remoteRoot, namespaceFilter) {
10337
- const localManifest = this.parser.parse(path15.join(localRoot, CLEF_MANIFEST_FILENAME));
10338
- const remoteManifest = this.parser.parse(path15.join(remoteRoot, CLEF_MANIFEST_FILENAME));
10370
+ const localManifest = this.parser.parse(path16.join(localRoot, CLEF_MANIFEST_FILENAME));
10371
+ const remoteManifest = this.parser.parse(path16.join(remoteRoot, CLEF_MANIFEST_FILENAME));
10339
10372
  const localCells = this.matrix.resolveMatrix(localManifest, localRoot);
10340
10373
  const remoteCells = this.matrix.resolveMatrix(remoteManifest, remoteRoot);
10341
10374
  const localEnvNames = localManifest.environments.map((e) => e.name);
@@ -10399,7 +10432,7 @@ var DriftDetector = class {
10399
10432
  };
10400
10433
 
10401
10434
  // src/report/generator.ts
10402
- var path16 = __toESM(require("path"));
10435
+ var path17 = __toESM(require("path"));
10403
10436
 
10404
10437
  // src/report/sanitizer.ts
10405
10438
  var ReportSanitizer = class {
@@ -10555,7 +10588,7 @@ var ReportGenerator = class {
10555
10588
  let manifest = null;
10556
10589
  try {
10557
10590
  const parser = new ManifestParser();
10558
- manifest = parser.parse(path16.join(repoRoot, "clef.yaml"));
10591
+ manifest = parser.parse(path17.join(repoRoot, "clef.yaml"));
10559
10592
  } catch {
10560
10593
  const emptyManifest = {
10561
10594
  manifestVersion: 0,
@@ -11033,9 +11066,9 @@ var SopsMergeDriver = class {
11033
11066
  };
11034
11067
 
11035
11068
  // src/service-identity/manager.ts
11036
- var fs14 = __toESM(require("fs"));
11069
+ var fs15 = __toESM(require("fs"));
11037
11070
  var os = __toESM(require("os"));
11038
- var path17 = __toESM(require("path"));
11071
+ var path18 = __toESM(require("path"));
11039
11072
  var YAML10 = __toESM(require("yaml"));
11040
11073
  var PartialRotationError = class extends Error {
11041
11074
  constructor(message, rotatedKeys) {
@@ -11087,8 +11120,8 @@ var ServiceIdentityManager = class {
11087
11120
  environments
11088
11121
  };
11089
11122
  await this.registerRecipients(definition, manifest, repoRoot);
11090
- const manifestPath = path17.join(repoRoot, CLEF_MANIFEST_FILENAME);
11091
- const raw = fs14.readFileSync(manifestPath, "utf-8");
11123
+ const manifestPath = path18.join(repoRoot, CLEF_MANIFEST_FILENAME);
11124
+ const raw = fs15.readFileSync(manifestPath, "utf-8");
11092
11125
  const doc = YAML10.parse(raw);
11093
11126
  if (!Array.isArray(doc.service_identities)) {
11094
11127
  doc.service_identities = [];
@@ -11099,13 +11132,13 @@ var ServiceIdentityManager = class {
11099
11132
  namespaces,
11100
11133
  environments
11101
11134
  });
11102
- const tmpCreate = path17.join(os.tmpdir(), `clef-manifest-${process.pid}-${Date.now()}.tmp`);
11135
+ const tmpCreate = path18.join(os.tmpdir(), `clef-manifest-${process.pid}-${Date.now()}.tmp`);
11103
11136
  try {
11104
- fs14.writeFileSync(tmpCreate, YAML10.stringify(doc), "utf-8");
11105
- fs14.renameSync(tmpCreate, manifestPath);
11137
+ fs15.writeFileSync(tmpCreate, YAML10.stringify(doc), "utf-8");
11138
+ fs15.renameSync(tmpCreate, manifestPath);
11106
11139
  } finally {
11107
11140
  try {
11108
- fs14.unlinkSync(tmpCreate);
11141
+ fs15.unlinkSync(tmpCreate);
11109
11142
  } catch {
11110
11143
  }
11111
11144
  }
@@ -11143,8 +11176,8 @@ var ServiceIdentityManager = class {
11143
11176
  } catch {
11144
11177
  }
11145
11178
  }
11146
- const manifestPath = path17.join(repoRoot, CLEF_MANIFEST_FILENAME);
11147
- const raw = fs14.readFileSync(manifestPath, "utf-8");
11179
+ const manifestPath = path18.join(repoRoot, CLEF_MANIFEST_FILENAME);
11180
+ const raw = fs15.readFileSync(manifestPath, "utf-8");
11148
11181
  const doc = YAML10.parse(raw);
11149
11182
  const identities = doc.service_identities;
11150
11183
  if (Array.isArray(identities)) {
@@ -11152,13 +11185,13 @@ var ServiceIdentityManager = class {
11152
11185
  (si) => si.name !== name
11153
11186
  );
11154
11187
  }
11155
- const tmp = path17.join(os.tmpdir(), `clef-manifest-${process.pid}-${Date.now()}.tmp`);
11188
+ const tmp = path18.join(os.tmpdir(), `clef-manifest-${process.pid}-${Date.now()}.tmp`);
11156
11189
  try {
11157
- fs14.writeFileSync(tmp, YAML10.stringify(doc), "utf-8");
11158
- fs14.renameSync(tmp, manifestPath);
11190
+ fs15.writeFileSync(tmp, YAML10.stringify(doc), "utf-8");
11191
+ fs15.renameSync(tmp, manifestPath);
11159
11192
  } finally {
11160
11193
  try {
11161
- fs14.unlinkSync(tmp);
11194
+ fs15.unlinkSync(tmp);
11162
11195
  } catch {
11163
11196
  }
11164
11197
  }
@@ -11173,8 +11206,8 @@ var ServiceIdentityManager = class {
11173
11206
  if (!identity) {
11174
11207
  throw new Error(`Service identity '${name}' not found.`);
11175
11208
  }
11176
- const manifestPath = path17.join(repoRoot, CLEF_MANIFEST_FILENAME);
11177
- const raw = fs14.readFileSync(manifestPath, "utf-8");
11209
+ const manifestPath = path18.join(repoRoot, CLEF_MANIFEST_FILENAME);
11210
+ const raw = fs15.readFileSync(manifestPath, "utf-8");
11178
11211
  const doc = YAML10.parse(raw);
11179
11212
  const identities = doc.service_identities;
11180
11213
  const siDoc = identities.find((si) => si.name === name);
@@ -11200,13 +11233,13 @@ var ServiceIdentityManager = class {
11200
11233
  envs[envName] = { kms: kmsConfig };
11201
11234
  identity.environments[envName] = { kms: kmsConfig };
11202
11235
  }
11203
- const tmp = path17.join(os.tmpdir(), `clef-manifest-${process.pid}-${Date.now()}.tmp`);
11236
+ const tmp = path18.join(os.tmpdir(), `clef-manifest-${process.pid}-${Date.now()}.tmp`);
11204
11237
  try {
11205
- fs14.writeFileSync(tmp, YAML10.stringify(doc), "utf-8");
11206
- fs14.renameSync(tmp, manifestPath);
11238
+ fs15.writeFileSync(tmp, YAML10.stringify(doc), "utf-8");
11239
+ fs15.renameSync(tmp, manifestPath);
11207
11240
  } finally {
11208
11241
  try {
11209
- fs14.unlinkSync(tmp);
11242
+ fs15.unlinkSync(tmp);
11210
11243
  } catch {
11211
11244
  }
11212
11245
  }
@@ -11242,8 +11275,8 @@ var ServiceIdentityManager = class {
11242
11275
  if (!identity) {
11243
11276
  throw new Error(`Service identity '${name}' not found.`);
11244
11277
  }
11245
- const manifestPath = path17.join(repoRoot, CLEF_MANIFEST_FILENAME);
11246
- const raw = fs14.readFileSync(manifestPath, "utf-8");
11278
+ const manifestPath = path18.join(repoRoot, CLEF_MANIFEST_FILENAME);
11279
+ const raw = fs15.readFileSync(manifestPath, "utf-8");
11247
11280
  const doc = YAML10.parse(raw);
11248
11281
  const identities = doc.service_identities;
11249
11282
  const siDoc = identities.find((si) => si.name === name);
@@ -11297,13 +11330,13 @@ var ServiceIdentityManager = class {
11297
11330
  }
11298
11331
  throw err;
11299
11332
  }
11300
- const tmpRotate = path17.join(os.tmpdir(), `clef-manifest-${process.pid}-${Date.now()}.tmp`);
11333
+ const tmpRotate = path18.join(os.tmpdir(), `clef-manifest-${process.pid}-${Date.now()}.tmp`);
11301
11334
  try {
11302
- fs14.writeFileSync(tmpRotate, YAML10.stringify(doc), "utf-8");
11303
- fs14.renameSync(tmpRotate, manifestPath);
11335
+ fs15.writeFileSync(tmpRotate, YAML10.stringify(doc), "utf-8");
11336
+ fs15.renameSync(tmpRotate, manifestPath);
11304
11337
  } finally {
11305
11338
  try {
11306
- fs14.unlinkSync(tmpRotate);
11339
+ fs15.unlinkSync(tmpRotate);
11307
11340
  } catch {
11308
11341
  }
11309
11342
  }
@@ -11424,22 +11457,21 @@ async function resolveIdentitySecrets(identityName, environment, manifest, repoR
11424
11457
  }
11425
11458
 
11426
11459
  // src/artifact/packer.ts
11427
- var fs15 = __toESM(require("fs"));
11428
- var path18 = __toESM(require("path"));
11460
+ var fs16 = __toESM(require("fs"));
11461
+ var path19 = __toESM(require("path"));
11429
11462
  var crypto4 = __toESM(require("crypto"));
11430
11463
 
11431
11464
  // src/artifact/signer.ts
11432
11465
  var crypto3 = __toESM(require("crypto"));
11433
11466
  function buildSigningPayload(artifact) {
11434
11467
  const fields = [
11435
- "clef-sig-v2",
11468
+ "clef-sig-v3",
11436
11469
  String(artifact.version),
11437
11470
  artifact.identity,
11438
11471
  artifact.environment,
11439
11472
  artifact.revision,
11440
11473
  artifact.packedAt,
11441
11474
  artifact.ciphertextHash,
11442
- [...artifact.keys].sort().join(","),
11443
11475
  artifact.expiresAt ?? "",
11444
11476
  artifact.envelope?.provider ?? "",
11445
11477
  artifact.envelope?.keyId ?? "",
@@ -11562,7 +11594,6 @@ var ArtifactPacker = class {
11562
11594
  revision,
11563
11595
  ciphertextHash,
11564
11596
  ciphertext,
11565
- keys: Object.keys(resolved.values),
11566
11597
  envelope: {
11567
11598
  provider: kmsConfig.provider,
11568
11599
  keyId: kmsConfig.keyId,
@@ -11596,13 +11627,12 @@ var ArtifactPacker = class {
11596
11627
  packedAt: (/* @__PURE__ */ new Date()).toISOString(),
11597
11628
  revision,
11598
11629
  ciphertextHash,
11599
- ciphertext,
11600
- keys: Object.keys(resolved.values)
11630
+ ciphertext
11601
11631
  };
11602
11632
  }
11603
- const outputDir = path18.dirname(config.outputPath);
11604
- if (!fs15.existsSync(outputDir)) {
11605
- fs15.mkdirSync(outputDir, { recursive: true });
11633
+ const outputDir = path19.dirname(config.outputPath);
11634
+ if (!fs16.existsSync(outputDir)) {
11635
+ fs16.mkdirSync(outputDir, { recursive: true });
11606
11636
  }
11607
11637
  if (config.ttl && config.ttl > 0) {
11608
11638
  artifact.expiresAt = new Date(Date.now() + config.ttl * 1e3).toISOString();
@@ -11621,8 +11651,8 @@ var ArtifactPacker = class {
11621
11651
  }
11622
11652
  const json = JSON.stringify(artifact, null, 2);
11623
11653
  const tmpOutput = `${config.outputPath}.tmp.${process.pid}`;
11624
- fs15.writeFileSync(tmpOutput, json, "utf-8");
11625
- fs15.renameSync(tmpOutput, config.outputPath);
11654
+ fs16.writeFileSync(tmpOutput, json, "utf-8");
11655
+ fs16.renameSync(tmpOutput, config.outputPath);
11626
11656
  return {
11627
11657
  outputPath: config.outputPath,
11628
11658
  namespaceCount: resolved.identity.namespaces.length,
@@ -11635,9 +11665,208 @@ var ArtifactPacker = class {
11635
11665
 
11636
11666
  // src/kms/types.ts
11637
11667
  var VALID_KMS_PROVIDERS = ["aws", "gcp", "azure"];
11668
+
11669
+ // src/migration/backend.ts
11670
+ var fs17 = __toESM(require("fs"));
11671
+ var path20 = __toESM(require("path"));
11672
+ var YAML11 = __toESM(require("yaml"));
11673
+ var BACKEND_KEY_FIELDS = {
11674
+ age: void 0,
11675
+ awskms: "aws_kms_arn",
11676
+ gcpkms: "gcp_kms_resource_id",
11677
+ azurekv: "azure_kv_url",
11678
+ pgp: "pgp_fingerprint"
11679
+ };
11680
+ var ALL_KEY_FIELDS = Object.values(BACKEND_KEY_FIELDS).filter(
11681
+ (v) => v !== void 0
11682
+ );
11683
+ function metadataMatchesTarget(meta, target) {
11684
+ if (meta.backend !== target.backend) return false;
11685
+ if (!target.key) return true;
11686
+ return meta.recipients.includes(target.key);
11687
+ }
11688
+ var BackendMigrator = class {
11689
+ constructor(encryption, matrixManager) {
11690
+ this.encryption = encryption;
11691
+ this.matrixManager = matrixManager;
11692
+ }
11693
+ async migrate(manifest, repoRoot, options, onProgress) {
11694
+ const { target, environment, dryRun, skipVerify } = options;
11695
+ if (environment) {
11696
+ const env = manifest.environments.find((e) => e.name === environment);
11697
+ if (!env) {
11698
+ throw new Error(`Environment '${environment}' not found in manifest.`);
11699
+ }
11700
+ }
11701
+ const allCells = this.matrixManager.resolveMatrix(manifest, repoRoot).filter((c) => c.exists);
11702
+ const targetCells = environment ? allCells.filter((c) => c.environment === environment) : allCells;
11703
+ if (targetCells.length === 0) {
11704
+ return {
11705
+ migratedFiles: [],
11706
+ skippedFiles: [],
11707
+ rolledBack: false,
11708
+ verifiedFiles: [],
11709
+ warnings: ["No encrypted files found to migrate."]
11710
+ };
11711
+ }
11712
+ const toMigrate = [];
11713
+ const skippedFiles = [];
11714
+ for (const cell of targetCells) {
11715
+ const meta = await this.encryption.getMetadata(cell.filePath);
11716
+ if (metadataMatchesTarget(meta, target)) {
11717
+ skippedFiles.push(cell.filePath);
11718
+ onProgress?.({
11719
+ type: "skip",
11720
+ file: cell.filePath,
11721
+ message: `${cell.namespace}/${cell.environment}: already on ${target.backend}, skipping`
11722
+ });
11723
+ } else {
11724
+ toMigrate.push(cell);
11725
+ }
11726
+ }
11727
+ if (toMigrate.length === 0) {
11728
+ return {
11729
+ migratedFiles: [],
11730
+ skippedFiles,
11731
+ rolledBack: false,
11732
+ verifiedFiles: [],
11733
+ warnings: ["All files already use the target backend and key. Nothing to migrate."]
11734
+ };
11735
+ }
11736
+ if (dryRun) {
11737
+ const warnings2 = [];
11738
+ for (const cell of toMigrate) {
11739
+ onProgress?.({
11740
+ type: "info",
11741
+ file: cell.filePath,
11742
+ message: `Would migrate ${cell.namespace}/${cell.environment} to ${target.backend}`
11743
+ });
11744
+ }
11745
+ if (environment) {
11746
+ warnings2.push(
11747
+ `Would add per-environment backend override for '${environment}' \u2192 ${target.backend}`
11748
+ );
11749
+ } else {
11750
+ warnings2.push(`Would update global default_backend \u2192 ${target.backend}`);
11751
+ }
11752
+ this.checkAgeRecipientsWarning(manifest, target, environment, warnings2);
11753
+ return {
11754
+ migratedFiles: [],
11755
+ skippedFiles,
11756
+ rolledBack: false,
11757
+ verifiedFiles: [],
11758
+ warnings: warnings2
11759
+ };
11760
+ }
11761
+ const manifestPath = path20.join(repoRoot, CLEF_MANIFEST_FILENAME);
11762
+ const manifestBackup = fs17.readFileSync(manifestPath, "utf-8");
11763
+ const fileBackups = /* @__PURE__ */ new Map();
11764
+ const doc = readManifestYaml(repoRoot);
11765
+ this.updateManifestDoc(doc, target, environment);
11766
+ writeManifestYaml(repoRoot, doc);
11767
+ const updatedManifest = YAML11.parse(YAML11.stringify(doc));
11768
+ const migratedFiles = [];
11769
+ for (const cell of toMigrate) {
11770
+ try {
11771
+ fileBackups.set(cell.filePath, fs17.readFileSync(cell.filePath, "utf-8"));
11772
+ onProgress?.({
11773
+ type: "migrate",
11774
+ file: cell.filePath,
11775
+ message: `Migrating ${cell.namespace}/${cell.environment}...`
11776
+ });
11777
+ const decrypted = await this.encryption.decrypt(cell.filePath);
11778
+ await this.encryption.encrypt(
11779
+ cell.filePath,
11780
+ decrypted.values,
11781
+ updatedManifest,
11782
+ cell.environment
11783
+ );
11784
+ migratedFiles.push(cell.filePath);
11785
+ } catch (err) {
11786
+ this.rollback(manifestPath, manifestBackup, fileBackups);
11787
+ const errorMsg = err instanceof Error ? err.message : String(err);
11788
+ onProgress?.({
11789
+ type: "warn",
11790
+ file: cell.filePath,
11791
+ message: `Migration failed: ${errorMsg}. All changes rolled back.`
11792
+ });
11793
+ return {
11794
+ migratedFiles: [],
11795
+ skippedFiles,
11796
+ rolledBack: true,
11797
+ error: `Failed on ${cell.namespace}/${cell.environment}: ${errorMsg}`,
11798
+ verifiedFiles: [],
11799
+ warnings: ["All changes have been rolled back."]
11800
+ };
11801
+ }
11802
+ }
11803
+ const verifiedFiles = [];
11804
+ const warnings = [];
11805
+ if (!skipVerify) {
11806
+ for (const cell of toMigrate) {
11807
+ try {
11808
+ onProgress?.({
11809
+ type: "verify",
11810
+ file: cell.filePath,
11811
+ message: `Verifying ${cell.namespace}/${cell.environment}...`
11812
+ });
11813
+ await this.encryption.decrypt(cell.filePath);
11814
+ verifiedFiles.push(cell.filePath);
11815
+ } catch (err) {
11816
+ const errorMsg = err instanceof Error ? err.message : String(err);
11817
+ warnings.push(
11818
+ `Verification failed for ${cell.namespace}/${cell.environment}: ${errorMsg}`
11819
+ );
11820
+ }
11821
+ }
11822
+ }
11823
+ this.checkAgeRecipientsWarning(manifest, target, environment, warnings);
11824
+ return { migratedFiles, skippedFiles, rolledBack: false, verifiedFiles, warnings };
11825
+ }
11826
+ // ── Private helpers ──────────────────────────────────────────────────
11827
+ updateManifestDoc(doc, target, environment) {
11828
+ const keyField = BACKEND_KEY_FIELDS[target.backend];
11829
+ if (environment) {
11830
+ const environments = doc.environments;
11831
+ const envDoc = environments.find(
11832
+ (e) => e.name === environment
11833
+ );
11834
+ const sopsOverride = { backend: target.backend };
11835
+ if (keyField && target.key) {
11836
+ sopsOverride[keyField] = target.key;
11837
+ }
11838
+ envDoc.sops = sopsOverride;
11839
+ } else {
11840
+ const sops = doc.sops;
11841
+ sops.default_backend = target.backend;
11842
+ for (const field of ALL_KEY_FIELDS) {
11843
+ delete sops[field];
11844
+ }
11845
+ if (keyField && target.key) {
11846
+ sops[keyField] = target.key;
11847
+ }
11848
+ }
11849
+ }
11850
+ rollback(manifestPath, manifestBackup, fileBackups) {
11851
+ for (const [filePath, backup] of fileBackups) {
11852
+ fs17.writeFileSync(filePath, backup, "utf-8");
11853
+ }
11854
+ fs17.writeFileSync(manifestPath, manifestBackup, "utf-8");
11855
+ }
11856
+ checkAgeRecipientsWarning(manifest, target, environment, warnings) {
11857
+ if (target.backend === "age") return;
11858
+ const hasRecipients = environment ? manifest.environments.find((e) => e.name === environment)?.recipients?.length : manifest.environments.some((e) => e.recipients?.length);
11859
+ if (hasRecipients) {
11860
+ warnings.push(
11861
+ "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."
11862
+ );
11863
+ }
11864
+ }
11865
+ };
11638
11866
  // Annotate the CommonJS export names for ESM import in node:
11639
11867
  0 && (module.exports = {
11640
11868
  ArtifactPacker,
11869
+ BackendMigrator,
11641
11870
  BulkOps,
11642
11871
  CLEF_MANIFEST_FILENAME,
11643
11872
  CLEF_REPORT_SCHEMA_VERSION,
@@ -11705,6 +11934,7 @@ var VALID_KMS_PROVIDERS = ["aws", "gcp", "azure"];
11705
11934
  parseIgnoreContent,
11706
11935
  parseJson,
11707
11936
  parseYaml,
11937
+ readManifestYaml,
11708
11938
  redactValue,
11709
11939
  removeAccessRequest,
11710
11940
  requestsFilePath,
@@ -11722,10 +11952,13 @@ var VALID_KMS_PROVIDERS = ["aws", "gcp", "azure"];
11722
11952
  signKms,
11723
11953
  upsertRequest,
11724
11954
  validateAgePublicKey,
11725
- verifySignature
11955
+ verifySignature,
11956
+ writeManifestYaml
11726
11957
  });
11727
11958
  /*! Bundled license information:
11728
11959
 
11960
+ @noble/hashes/utils.js:
11961
+ @noble/hashes/utils.js:
11729
11962
  @noble/hashes/utils.js:
11730
11963
  (*! noble-hashes - MIT License (c) 2022 Paul Miller (paulmillr.com) *)
11731
11964
 
@@ -11735,42 +11968,18 @@ var VALID_KMS_PROVIDERS = ["aws", "gcp", "azure"];
11735
11968
  @noble/ciphers/utils.js:
11736
11969
  (*! noble-ciphers - MIT License (c) 2023 Paul Miller (paulmillr.com) *)
11737
11970
 
11738
- @noble/hashes/utils.js:
11739
- (*! noble-hashes - MIT License (c) 2022 Paul Miller (paulmillr.com) *)
11740
-
11741
11971
  @noble/curves/utils.js:
11742
- (*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) *)
11743
-
11744
11972
  @noble/curves/abstract/modular.js:
11745
- (*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) *)
11746
-
11747
11973
  @noble/curves/abstract/curve.js:
11748
- (*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) *)
11749
-
11750
11974
  @noble/curves/abstract/montgomery.js:
11751
- (*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) *)
11752
-
11753
11975
  @noble/curves/abstract/weierstrass.js:
11754
- (*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) *)
11755
-
11756
11976
  @noble/curves/ed25519.js:
11757
- (*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) *)
11758
-
11759
11977
  @noble/curves/nist.js:
11760
11978
  (*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) *)
11761
11979
 
11762
- @noble/hashes/utils.js:
11763
- (*! noble-hashes - MIT License (c) 2022 Paul Miller (paulmillr.com) *)
11764
-
11765
11980
  @noble/post-quantum/utils.js:
11766
- (*! noble-post-quantum - MIT License (c) 2024 Paul Miller (paulmillr.com) *)
11767
-
11768
11981
  @noble/post-quantum/_crystals.js:
11769
- (*! noble-post-quantum - MIT License (c) 2024 Paul Miller (paulmillr.com) *)
11770
-
11771
11982
  @noble/post-quantum/ml-kem.js:
11772
- (*! noble-post-quantum - MIT License (c) 2024 Paul Miller (paulmillr.com) *)
11773
-
11774
11983
  @noble/post-quantum/hybrid.js:
11775
11984
  (*! noble-post-quantum - MIT License (c) 2024 Paul Miller (paulmillr.com) *)
11776
11985
  */