@clef-sh/core 0.1.11-beta.74 → 0.1.12-beta.85

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
@@ -535,6 +535,12 @@ var ManifestParser = class {
535
535
  );
536
536
  }
537
537
  const siName = siObj.name;
538
+ if (!/^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/.test(siName)) {
539
+ throw new ManifestValidationError(
540
+ `Service identity '${siName}' has an invalid name. Names must be lowercase alphanumeric with hyphens, must not start or end with a hyphen, and max 63 characters (e.g. 'api-gateway', 'auth-service').`,
541
+ "service_identities"
542
+ );
543
+ }
538
544
  if (siObj.description != null && typeof siObj.description !== "string") {
539
545
  throw new ManifestValidationError(
540
546
  `Service identity '${siName}' has a non-string 'description'.`,
@@ -686,7 +692,7 @@ var ManifestParser = class {
686
692
  "cloud"
687
693
  );
688
694
  }
689
- if (!/^clef:[a-z0-9_]+\/[a-z0-9_-]+$/.test(cloudObj.keyId)) {
695
+ if (!/^clef:[a-zA-Z0-9_]+\/[a-zA-Z0-9_-]+$/.test(cloudObj.keyId)) {
690
696
  throw new ManifestValidationError(
691
697
  `Field 'cloud.keyId' has invalid format '${cloudObj.keyId}'. Must match: clef:<integrationId>/<keyAlias>`,
692
698
  "cloud"
@@ -2098,6 +2104,13 @@ function openWindowsInputPipe(content) {
2098
2104
  });
2099
2105
  });
2100
2106
  }
2107
+ function cloudKeyToArn(keyId) {
2108
+ const body = keyId.replace(/^clef:/, "");
2109
+ const sep = body.indexOf("/");
2110
+ const integration = sep >= 0 ? body.slice(0, sep) : body;
2111
+ const env = sep >= 0 ? body.slice(sep + 1) : "default";
2112
+ return `arn:aws:kms:us-east-1:000000000000:alias/clef/${integration}/${env}`;
2113
+ }
2101
2114
  var SopsClient = class {
2102
2115
  /**
2103
2116
  * @param runner - Subprocess runner used to invoke the `sops` binary.
@@ -2143,7 +2156,7 @@ var SopsClient = class {
2143
2156
  const env = this.buildSopsEnv();
2144
2157
  const result = await this.runner.run(
2145
2158
  this.sopsCommand,
2146
- [...this.keyserviceArgs, "decrypt", "--output-type", fmt, filePath],
2159
+ ["decrypt", ...this.keyserviceArgs, "--output-type", fmt, filePath],
2147
2160
  {
2148
2161
  ...env ? { env } : {}
2149
2162
  }
@@ -2209,8 +2222,8 @@ var SopsClient = class {
2209
2222
  [
2210
2223
  "--config",
2211
2224
  configPath,
2212
- ...this.keyserviceArgs,
2213
2225
  "encrypt",
2226
+ ...this.keyserviceArgs,
2214
2227
  ...args,
2215
2228
  "--input-type",
2216
2229
  fmt,
@@ -2264,7 +2277,7 @@ var SopsClient = class {
2264
2277
  const env = this.buildSopsEnv();
2265
2278
  const result = await this.runner.run(
2266
2279
  this.sopsCommand,
2267
- [...this.keyserviceArgs, "rotate", "-i", "--add-age", key, filePath],
2280
+ ["rotate", ...this.keyserviceArgs, "-i", "--add-age", key, filePath],
2268
2281
  {
2269
2282
  ...env ? { env } : {}
2270
2283
  }
@@ -2288,7 +2301,7 @@ var SopsClient = class {
2288
2301
  const env = this.buildSopsEnv();
2289
2302
  const result = await this.runner.run(
2290
2303
  this.sopsCommand,
2291
- [...this.keyserviceArgs, "rotate", "-i", "--rm-age", key, filePath],
2304
+ ["rotate", ...this.keyserviceArgs, "-i", "--rm-age", key, filePath],
2292
2305
  {
2293
2306
  ...env ? { env } : {}
2294
2307
  }
@@ -2402,7 +2415,7 @@ var SopsClient = class {
2402
2415
  if (sops.age && Array.isArray(sops.age) && sops.age.length > 0) return "age";
2403
2416
  if (sops.kms && Array.isArray(sops.kms) && sops.kms.length > 0) {
2404
2417
  const firstArn = sops.kms[0]?.arn;
2405
- if (typeof firstArn === "string" && firstArn.startsWith("clef:")) {
2418
+ if (typeof firstArn === "string" && (firstArn.startsWith("clef:") || firstArn.includes("alias/clef/"))) {
2406
2419
  return "cloud";
2407
2420
  }
2408
2421
  return "awskms";
@@ -2485,7 +2498,7 @@ var SopsClient = class {
2485
2498
  case "cloud": {
2486
2499
  const cloudKeyId = manifest.cloud?.keyId;
2487
2500
  if (cloudKeyId) {
2488
- args.push("--kms", cloudKeyId);
2501
+ args.push("--kms", cloudKeyToArn(cloudKeyId));
2489
2502
  }
2490
2503
  break;
2491
2504
  }
@@ -3952,7 +3965,7 @@ var CloudClient = class {
3952
3965
  response = await fetch(url, init);
3953
3966
  } catch (retryErr) {
3954
3967
  throw new CloudApiError(
3955
- `Network error contacting Clef Pro: ${retryErr.message}`,
3968
+ `Network error contacting Clef Cloud: ${retryErr.message}`,
3956
3969
  0,
3957
3970
  "Check your network connection and CLEF_API_URL."
3958
3971
  );
@@ -3974,7 +3987,7 @@ var CloudClient = class {
3974
3987
  buildError(response) {
3975
3988
  const hint = response.status === 401 || response.status === 403 ? "Check your API token (--api-token or CLEF_API_TOKEN)." : response.status === 404 ? "Check your cloud.integrationId in clef.yaml." : void 0;
3976
3989
  return new CloudApiError(
3977
- `Clef Pro API returned ${response.status} ${response.statusText}`,
3990
+ `Clef Cloud API returned ${response.status} ${response.statusText}`,
3978
3991
  response.status,
3979
3992
  hint
3980
3993
  );
@@ -4495,9 +4508,41 @@ async function resolveIdentitySecrets(identityName, environment, manifest, repoR
4495
4508
  }
4496
4509
 
4497
4510
  // src/artifact/packer.ts
4511
+ import * as crypto3 from "crypto";
4512
+
4513
+ // src/artifact/output.ts
4498
4514
  import * as fs16 from "fs";
4499
4515
  import * as path19 from "path";
4500
- import * as crypto3 from "crypto";
4516
+ var FilePackOutput = class {
4517
+ constructor(outputPath) {
4518
+ this.outputPath = outputPath;
4519
+ }
4520
+ async write(_artifact, json) {
4521
+ const outputDir = path19.dirname(this.outputPath);
4522
+ if (!fs16.existsSync(outputDir)) {
4523
+ fs16.mkdirSync(outputDir, { recursive: true });
4524
+ }
4525
+ const tmpOutput = `${this.outputPath}.tmp.${process.pid}`;
4526
+ fs16.writeFileSync(tmpOutput, json, "utf-8");
4527
+ fs16.renameSync(tmpOutput, this.outputPath);
4528
+ }
4529
+ };
4530
+ var MemoryPackOutput = class {
4531
+ _artifact = null;
4532
+ _json = null;
4533
+ async write(artifact, json) {
4534
+ this._artifact = artifact;
4535
+ this._json = json;
4536
+ }
4537
+ /** The packed artifact, or null if `write` hasn't been called. */
4538
+ get artifact() {
4539
+ return this._artifact;
4540
+ }
4541
+ /** The serialized JSON, or null if `write` hasn't been called. */
4542
+ get json() {
4543
+ return this._json;
4544
+ }
4545
+ };
4501
4546
 
4502
4547
  // src/artifact/signer.ts
4503
4548
  import * as crypto2 from "crypto";
@@ -4668,10 +4713,6 @@ var ArtifactPacker = class {
4668
4713
  ciphertext
4669
4714
  };
4670
4715
  }
4671
- const outputDir = path19.dirname(config.outputPath);
4672
- if (!fs16.existsSync(outputDir)) {
4673
- fs16.mkdirSync(outputDir, { recursive: true });
4674
- }
4675
4716
  if (config.ttl && config.ttl > 0) {
4676
4717
  artifact.expiresAt = new Date(Date.now() + config.ttl * 1e3).toISOString();
4677
4718
  }
@@ -4688,11 +4729,10 @@ var ArtifactPacker = class {
4688
4729
  artifact.signatureAlgorithm = "ECDSA_SHA256";
4689
4730
  }
4690
4731
  const json = JSON.stringify(artifact, null, 2);
4691
- const tmpOutput = `${config.outputPath}.tmp.${process.pid}`;
4692
- fs16.writeFileSync(tmpOutput, json, "utf-8");
4693
- fs16.renameSync(tmpOutput, config.outputPath);
4732
+ const output = config.output ?? new FilePackOutput(config.outputPath ?? "artifact.json");
4733
+ await output.write(artifact, json);
4694
4734
  return {
4695
- outputPath: config.outputPath,
4735
+ outputPath: config.outputPath ?? "",
4696
4736
  namespaceCount: resolved.identity.namespaces.length,
4697
4737
  keyCount: Object.keys(resolved.values).length,
4698
4738
  artifactSize: Buffer.byteLength(json, "utf-8"),
@@ -4725,10 +4765,19 @@ function metadataMatchesTarget(meta, target) {
4725
4765
  return meta.recipients.includes(target.key);
4726
4766
  }
4727
4767
  var BackendMigrator = class {
4728
- constructor(encryption, matrixManager) {
4729
- this.encryption = encryption;
4768
+ /**
4769
+ * @param encryption - Backend used for both decrypt and encrypt (standard case).
4770
+ * @param matrixManager - Matrix resolver.
4771
+ * @param targetEncryption - Optional separate backend for encrypt. Use when migrating
4772
+ * from cloud (decrypt via keyservice) to another backend (encrypt via local credentials).
4773
+ */
4774
+ constructor(encryption, matrixManager, targetEncryption) {
4730
4775
  this.matrixManager = matrixManager;
4776
+ this.decryptBackend = encryption;
4777
+ this.encryptBackend = targetEncryption ?? encryption;
4731
4778
  }
4779
+ decryptBackend;
4780
+ encryptBackend;
4732
4781
  async migrate(manifest, repoRoot, options, onProgress) {
4733
4782
  const { target, environment, dryRun, skipVerify } = options;
4734
4783
  if (environment) {
@@ -4751,7 +4800,7 @@ var BackendMigrator = class {
4751
4800
  const toMigrate = [];
4752
4801
  const skippedFiles = [];
4753
4802
  for (const cell of targetCells) {
4754
- const meta = await this.encryption.getMetadata(cell.filePath);
4803
+ const meta = await this.decryptBackend.getMetadata(cell.filePath);
4755
4804
  if (metadataMatchesTarget(meta, target)) {
4756
4805
  skippedFiles.push(cell.filePath);
4757
4806
  onProgress?.({
@@ -4813,8 +4862,8 @@ var BackendMigrator = class {
4813
4862
  file: cell.filePath,
4814
4863
  message: `Migrating ${cell.namespace}/${cell.environment}...`
4815
4864
  });
4816
- const decrypted = await this.encryption.decrypt(cell.filePath);
4817
- await this.encryption.encrypt(
4865
+ const decrypted = await this.decryptBackend.decrypt(cell.filePath);
4866
+ await this.encryptBackend.encrypt(
4818
4867
  cell.filePath,
4819
4868
  decrypted.values,
4820
4869
  updatedManifest,
@@ -4849,7 +4898,7 @@ var BackendMigrator = class {
4849
4898
  file: cell.filePath,
4850
4899
  message: `Verifying ${cell.namespace}/${cell.environment}...`
4851
4900
  });
4852
- await this.encryption.decrypt(cell.filePath);
4901
+ await this.encryptBackend.decrypt(cell.filePath);
4853
4902
  verifiedFiles.push(cell.filePath);
4854
4903
  } catch (err) {
4855
4904
  const errorMsg = err instanceof Error ? err.message : String(err);
@@ -4885,6 +4934,18 @@ var BackendMigrator = class {
4885
4934
  sops[keyField] = target.key;
4886
4935
  }
4887
4936
  }
4937
+ if (doc.cloud && target.backend !== "cloud") {
4938
+ const sops = doc.sops;
4939
+ const environments = doc.environments;
4940
+ const defaultIsCloud = sops.default_backend === "cloud";
4941
+ const anyEnvIsCloud = environments.some((e) => {
4942
+ const envSops = e.sops;
4943
+ return envSops?.backend === "cloud";
4944
+ });
4945
+ if (!defaultIsCloud && !anyEnvIsCloud) {
4946
+ delete doc.cloud;
4947
+ }
4948
+ }
4888
4949
  }
4889
4950
  rollback(manifestPath, manifestBackup, fileBackups) {
4890
4951
  for (const [filePath, backup] of fileBackups) {
@@ -5065,11 +5126,10 @@ function readCloudCredentials() {
5065
5126
  }
5066
5127
  if (!raw || typeof raw !== "object") return null;
5067
5128
  const obj = raw;
5068
- if (typeof obj.token !== "string" || obj.token.length === 0) return null;
5069
- return {
5070
- token: obj.token,
5071
- endpoint: typeof obj.endpoint === "string" ? obj.endpoint : CLOUD_DEFAULT_ENDPOINT
5072
- };
5129
+ const token = typeof obj.token === "string" && obj.token.length > 0 ? obj.token : "";
5130
+ const endpoint = typeof obj.endpoint === "string" ? obj.endpoint : CLOUD_DEFAULT_ENDPOINT;
5131
+ if (!token && endpoint === CLOUD_DEFAULT_ENDPOINT) return null;
5132
+ return { token, endpoint };
5073
5133
  }
5074
5134
  function writeCloudCredentials(credentials) {
5075
5135
  const clefDir = path23.join(os2.homedir(), ".clef");
@@ -5085,21 +5145,30 @@ function writeCloudCredentials(credentials) {
5085
5145
  // src/cloud/device-flow.ts
5086
5146
  async function initiateDeviceFlow(endpoint, options) {
5087
5147
  const base = endpoint ?? CLOUD_DEFAULT_ENDPOINT;
5148
+ const payload = {
5149
+ clientType: "cli",
5150
+ clientVersion: options.clientVersion,
5151
+ repoName: options.repoName,
5152
+ flow: options.flow
5153
+ };
5154
+ if (options.environment) {
5155
+ payload.environment = options.environment;
5156
+ }
5088
5157
  const res = await fetch(`${base}/api/v1/device/init`, {
5089
5158
  method: "POST",
5090
5159
  headers: { "Content-Type": "application/json" },
5091
- body: JSON.stringify({
5092
- clientType: "cli",
5093
- clientVersion: options.clientVersion,
5094
- repoName: options.repoName,
5095
- environment: options.environment
5096
- })
5160
+ body: JSON.stringify(payload)
5097
5161
  });
5098
5162
  if (!res.ok) {
5099
5163
  const body = await res.text().catch(() => "");
5100
5164
  throw new Error(`Device flow init failed (${res.status}): ${body}`);
5101
5165
  }
5102
- return await res.json();
5166
+ const json = await res.json();
5167
+ const session = json.data ?? json;
5168
+ if (session.pollUrl && !session.pollUrl.startsWith("http")) {
5169
+ session.pollUrl = `${base}${session.pollUrl}`;
5170
+ }
5171
+ return session;
5103
5172
  }
5104
5173
  async function pollDeviceFlow(pollUrl) {
5105
5174
  const res = await fetch(pollUrl);
@@ -5107,7 +5176,8 @@ async function pollDeviceFlow(pollUrl) {
5107
5176
  const body = await res.text().catch(() => "");
5108
5177
  throw new Error(`Device flow poll failed (${res.status}): ${body}`);
5109
5178
  }
5110
- return await res.json();
5179
+ const json = await res.json();
5180
+ return json.data ?? json;
5111
5181
  }
5112
5182
 
5113
5183
  // src/cloud/pack-client.ts
@@ -5154,7 +5224,6 @@ var CloudArtifactClient = class {
5154
5224
  this.endpoint = endpoint ?? CLOUD_DEFAULT_ENDPOINT;
5155
5225
  }
5156
5226
  async upload(token, config) {
5157
- const content = fs21.readFileSync(config.artifactPath, "utf-8");
5158
5227
  const res = await fetch(
5159
5228
  `${this.endpoint}/api/v1/cloud/artifacts/${config.identity}/${config.environment}`,
5160
5229
  {
@@ -5163,7 +5232,7 @@ var CloudArtifactClient = class {
5163
5232
  Authorization: `Bearer ${token}`,
5164
5233
  "Content-Type": "application/json"
5165
5234
  },
5166
- body: content
5235
+ body: config.artifactJson
5167
5236
  }
5168
5237
  );
5169
5238
  if (!res.ok) {
@@ -5187,6 +5256,7 @@ export {
5187
5256
  ConsumptionClient,
5188
5257
  DiffEngine,
5189
5258
  DriftDetector,
5259
+ FilePackOutput,
5190
5260
  GitIntegration,
5191
5261
  GitOperationError,
5192
5262
  ImportRunner,
@@ -5194,6 +5264,7 @@ export {
5194
5264
  ManifestParser,
5195
5265
  ManifestValidationError,
5196
5266
  MatrixManager,
5267
+ MemoryPackOutput,
5197
5268
  PartialRotationError,
5198
5269
  REQUESTS_FILENAME,
5199
5270
  REQUIREMENTS,