@apicircle/core 1.0.8 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -27,22 +27,30 @@ __export(src_exports, {
27
27
  HTTP_HEADERS_MAP: () => HTTP_HEADERS_MAP,
28
28
  OAuth2TokenError: () => OAuth2TokenError,
29
29
  PlanRunDeniedError: () => PlanRunDeniedError,
30
+ REGISTRY_JSON_PATH: () => REGISTRY_JSON_PATH,
30
31
  RemoteWorkspaceParseError: () => RemoteWorkspaceParseError,
31
32
  TRANSFORM_FORMAT_LABELS: () => TRANSFORM_FORMAT_LABELS,
33
+ WORKSPACE_DIR: () => WORKSPACE_DIR,
34
+ appendReleaseEntry: () => appendReleaseEntry,
32
35
  applyAuth: () => applyAuth,
33
36
  applyAwsSigV4: () => applyAwsSigV4,
34
37
  applyContentTypeForBodyType: () => applyContentTypeForBodyType,
38
+ applyLinkedEnvironmentOverrides: () => applyLinkedEnvironmentOverrides,
35
39
  applyLinkedUpdate: () => applyLinkedUpdate,
36
40
  applyMerge: () => applyMerge,
37
41
  applyMutation: () => applyMutation,
38
42
  applyPathParams: () => applyPathParams,
39
43
  assertNoPlaintextCredentials: () => assertNoPlaintextCredentials,
44
+ attachmentPath: () => attachmentPath,
45
+ attachmentsDir: () => attachmentsDir,
40
46
  buildAuthorizeUrl: () => buildAuthorizeUrl,
41
47
  buildAutoHeaders: () => buildAutoHeaders,
42
48
  buildDigestAuthHeader: () => buildDigestAuthHeader,
43
49
  buildHawkAuthHeader: () => buildHawkAuthHeader,
50
+ buildLinkedSnapshot: () => buildLinkedSnapshot,
44
51
  buildNtlmType1Negotiate: () => buildNtlmType1Negotiate,
45
52
  buildNtlmType3Authenticate: () => buildNtlmType3Authenticate,
53
+ buildReleaseEntry: () => buildReleaseEntry,
46
54
  buildRequest: () => buildRequest,
47
55
  buildScope: () => buildScope,
48
56
  collectAttachmentSlots: () => collectAttachmentSlots,
@@ -56,6 +64,7 @@ __export(src_exports, {
56
64
  composeUrl: () => composeUrl,
57
65
  composeUrlWithQuery: () => composeUrlWithQuery,
58
66
  computeCodeChallenge: () => computeCodeChallenge,
67
+ computeRequestOverridePatch: () => computeRequestOverridePatch,
59
68
  computeThreeWayDiff: () => computeThreeWayDiff,
60
69
  computeTransformSavings: () => computeTransformSavings,
61
70
  decryptString: () => decryptString,
@@ -68,6 +77,7 @@ __export(src_exports, {
68
77
  exportKey: () => exportKey,
69
78
  extractContext: () => extractContext,
70
79
  fetchOAuth2Token: () => fetchOAuth2Token,
80
+ fetchRemoteWorkspaceJson: () => fetchRemoteWorkspaceJson,
71
81
  findPathPlaceholders: () => findPathPlaceholders,
72
82
  generateAesKey: () => generateAesKey,
73
83
  generateCodeVerifier: () => generateCodeVerifier,
@@ -85,14 +95,18 @@ __export(src_exports, {
85
95
  hasUnpushedChanges: () => hasUnpushedChanges,
86
96
  importApicircleFolderInto: () => importApicircleFolderInto,
87
97
  importKey: () => importKey,
98
+ initSecretCrypto: () => initSecretCrypto,
88
99
  isApicircleEnvironment: () => isApicircleEnvironment,
89
100
  isApicircleFolderExport: () => isApicircleFolderExport,
90
101
  isDesktop: () => isDesktop,
102
+ isEmptyOverridePatch: () => isEmptyOverridePatch,
91
103
  isInsomniaExport: () => isInsomniaExport,
92
104
  isPostmanEnvironment: () => isPostmanEnvironment,
93
105
  isPostmanV2Collection: () => isPostmanV2Collection,
94
106
  isValidSemver: () => isValidSemver,
107
+ ledgerFromProbe: () => ledgerFromProbe,
95
108
  lookup: () => lookup,
109
+ mergeRequestOverride: () => mergeRequestOverride,
96
110
  mergeWithAutoHeaders: () => mergeWithAutoHeaders,
97
111
  normalizeContentType: () => normalizeContentType,
98
112
  parseApicircleEnvironment: () => parseApicircleEnvironment,
@@ -103,12 +117,15 @@ __export(src_exports, {
103
117
  parseDigestChallenge: () => parseDigestChallenge,
104
118
  parseGraphqlSchema: () => parseGraphqlSchema,
105
119
  parseInsomniaCollection: () => parseInsomniaCollection,
120
+ parseLinkedWorkspaceJson: () => parseLinkedWorkspaceJson,
106
121
  parseNtlmType2Challenge: () => parseNtlmType2Challenge,
107
122
  parsePostmanCollection: () => parsePostmanCollection,
108
123
  parsePostmanEnvironment: () => parsePostmanEnvironment,
124
+ parseRegistryActiveId: () => parseRegistryActiveId,
109
125
  parseSemver: () => parseSemver,
110
126
  parseUrlQuery: () => parseUrlQuery,
111
127
  parseWorkspaceJson: () => parseWorkspaceJson,
128
+ plaintextEnvMap: () => plaintextEnvMap,
112
129
  pollDeviceFlow: () => pollDeviceFlow,
113
130
  preSendValidation: () => preSendValidation,
114
131
  previewLinkedUpdate: () => previewLinkedUpdate,
@@ -121,6 +138,7 @@ __export(src_exports, {
121
138
  requestRunToExecutionResult: () => requestRunToExecutionResult,
122
139
  resolveInheritedAuth: () => resolveInheritedAuth,
123
140
  resolvePlanRef: () => resolvePlanRef,
141
+ resolveRequestForExecution: () => resolveRequestForExecution,
124
142
  resolveString: () => resolveString,
125
143
  resolveStringMap: () => resolveStringMap,
126
144
  runAssertions: () => runAssertions,
@@ -142,7 +160,9 @@ __export(src_exports, {
142
160
  toYaml: () => toYaml,
143
161
  tokenizeCurl: () => tokenizeCurl,
144
162
  tryParsePayload: () => tryParsePayload,
163
+ unlockSecretCrypto: () => unlockSecretCrypto,
145
164
  validateBranchName: () => validateBranchName,
165
+ workspaceJsonPath: () => workspaceJsonPath,
146
166
  yankRelease: () => yankRelease
147
167
  });
148
168
  module.exports = __toCommonJS(src_exports);
@@ -1203,8 +1223,7 @@ async function importPkcs8(pem, algorithm) {
1203
1223
  "JWT: PKCS#1 RSA PEM (`BEGIN RSA PRIVATE KEY`) is not supported. Convert with `openssl pkcs8 -topk8 -in key.pem -out pkcs8.pem -nocrypt`."
1204
1224
  );
1205
1225
  }
1206
- const envelope = /-----BEGIN [A-Z ]+-----([\s\S]*?)-----END [A-Z ]+-----/.exec(pem);
1207
- const body = envelope ? envelope[1] : pem;
1226
+ const body = extractPemBody(pem);
1208
1227
  const stripped = body.replace(/\s+/g, "");
1209
1228
  if (!stripped) {
1210
1229
  throw new Error("JWT: PEM key is empty after stripping headers/whitespace");
@@ -1225,6 +1244,19 @@ async function importPkcs8(pem, algorithm) {
1225
1244
  ["sign"]
1226
1245
  );
1227
1246
  }
1247
+ function extractPemBody(pem) {
1248
+ const BEGIN = "-----BEGIN ";
1249
+ const END = "-----END ";
1250
+ const FENCE = "-----";
1251
+ const beginAt = pem.indexOf(BEGIN);
1252
+ if (beginAt === -1) return pem;
1253
+ const beginHeaderEnd = pem.indexOf(FENCE, beginAt + BEGIN.length);
1254
+ if (beginHeaderEnd === -1) return pem;
1255
+ const bodyStart = beginHeaderEnd + FENCE.length;
1256
+ const endAt = pem.indexOf(END, bodyStart);
1257
+ if (endAt === -1) return pem;
1258
+ return pem.slice(bodyStart, endAt);
1259
+ }
1228
1260
  function base64UrlEncode(bytes) {
1229
1261
  let s = "";
1230
1262
  for (let i = 0; i < bytes.length; i++) s += String.fromCharCode(bytes[i]);
@@ -1679,6 +1711,10 @@ async function fetchOAuth2Token(args) {
1679
1711
  if (args.extraParams) {
1680
1712
  for (const [k, v] of Object.entries(args.extraParams)) body.set(k, v);
1681
1713
  }
1714
+ const tokenUrlParsed = new URL(args.tokenUrl);
1715
+ if (tokenUrlParsed.protocol !== "https:" && tokenUrlParsed.protocol !== "http:") {
1716
+ throw new Error(`Token URL must use HTTP or HTTPS, got ${tokenUrlParsed.protocol}`);
1717
+ }
1682
1718
  const response = await fetchImpl(args.tokenUrl, {
1683
1719
  method: "POST",
1684
1720
  headers,
@@ -2625,6 +2661,30 @@ function getVariableAutocomplete(text, cursorPosition, scope) {
2625
2661
  );
2626
2662
  }
2627
2663
 
2664
+ // src/request/resolveInheritedAuth.ts
2665
+ var NONE = { type: "none" };
2666
+ function resolveInheritedAuth({
2667
+ requestAuth,
2668
+ folderId,
2669
+ folders
2670
+ }) {
2671
+ if (requestAuth.type !== "inherit") return requestAuth;
2672
+ let cursor = folderId;
2673
+ const visited = /* @__PURE__ */ new Set();
2674
+ while (cursor !== null) {
2675
+ if (visited.has(cursor)) {
2676
+ break;
2677
+ }
2678
+ visited.add(cursor);
2679
+ const folder = folders[cursor];
2680
+ if (!folder) break;
2681
+ const auth = folder.auth;
2682
+ if (auth && auth.type !== "inherit" && auth.type !== "none") return auth;
2683
+ cursor = folder.parentId;
2684
+ }
2685
+ return NONE;
2686
+ }
2687
+
2628
2688
  // src/request/preSendValidation.ts
2629
2689
  var TYPED_BODY_CT = {
2630
2690
  json: ["application/json", "application/ld+json", "application/vnd.api+json"],
@@ -2637,7 +2697,8 @@ function collectMissing(value, scope) {
2637
2697
  }
2638
2698
  function preSendValidation({
2639
2699
  request,
2640
- scope
2700
+ scope,
2701
+ folders
2641
2702
  }) {
2642
2703
  const warnings = [];
2643
2704
  const blockers = [];
@@ -2712,29 +2773,43 @@ function preSendValidation({
2712
2773
  });
2713
2774
  }
2714
2775
  }
2715
- const auth = request.auth;
2776
+ let effectiveAuth = request.auth;
2777
+ let resolvedFromInherit = false;
2778
+ if (folders && effectiveAuth?.type === "inherit") {
2779
+ effectiveAuth = resolveInheritedAuth({
2780
+ requestAuth: { type: "inherit" },
2781
+ folderId: request.folderId,
2782
+ folders
2783
+ });
2784
+ resolvedFromInherit = true;
2785
+ }
2786
+ const auth = effectiveAuth;
2787
+ const inheritedNote = resolvedFromInherit ? " (resolved from folder-level auth)" : "";
2716
2788
  if (auth) {
2717
2789
  if (auth.type === "bearer" && !auth.token?.trim()) {
2718
- blockers.push({ kind: "auth-fields-missing", message: "Bearer token is empty." });
2790
+ blockers.push({
2791
+ kind: "auth-fields-missing",
2792
+ message: `Bearer token is empty.${inheritedNote}`
2793
+ });
2719
2794
  } else if (auth.type === "basic") {
2720
2795
  if (!auth.username?.trim() || !auth.password?.trim()) {
2721
2796
  blockers.push({
2722
2797
  kind: "auth-fields-missing",
2723
- message: "Basic auth requires both username and password."
2798
+ message: `Basic auth requires both username and password.${inheritedNote}`
2724
2799
  });
2725
2800
  }
2726
2801
  } else if (auth.type === "api-key") {
2727
2802
  if (!auth.key?.trim() || !auth.value?.trim()) {
2728
2803
  blockers.push({
2729
2804
  kind: "auth-fields-missing",
2730
- message: "API key auth requires both name and value."
2805
+ message: `API key auth requires both name and value.${inheritedNote}`
2731
2806
  });
2732
2807
  }
2733
2808
  } else if (auth.type === "custom-header") {
2734
2809
  if (!auth.key?.trim()) {
2735
2810
  blockers.push({
2736
2811
  kind: "auth-fields-missing",
2737
- message: "Custom header auth requires a header name."
2812
+ message: `Custom header auth requires a header name.${inheritedNote}`
2738
2813
  });
2739
2814
  }
2740
2815
  }
@@ -3121,6 +3196,15 @@ async function executeRequest(req, opts = {}) {
3121
3196
  );
3122
3197
  const redirectMode = isBrowserRuntime() ? "follow" : "manual";
3123
3198
  let currentUrl = builtRequest.url;
3199
+ try {
3200
+ const parsedUrl = new URL(currentUrl);
3201
+ if (parsedUrl.protocol !== "http:" && parsedUrl.protocol !== "https:") {
3202
+ throw new Error(`Unsupported URL scheme: ${parsedUrl.protocol}`);
3203
+ }
3204
+ } catch (e) {
3205
+ if (e instanceof Error && e.message.startsWith("Unsupported URL scheme")) throw e;
3206
+ throw new Error(`Invalid request URL: ${currentUrl}`);
3207
+ }
3124
3208
  let currentHeaders = { ...builtRequest.headers };
3125
3209
  let currentMethod = builtRequest.method;
3126
3210
  let currentBody = builtRequest.body;
@@ -3419,30 +3503,6 @@ function base64UrlEncode2(bytes) {
3419
3503
  return btoa(s).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
3420
3504
  }
3421
3505
 
3422
- // src/request/resolveInheritedAuth.ts
3423
- var NONE = { type: "none" };
3424
- function resolveInheritedAuth({
3425
- requestAuth,
3426
- folderId,
3427
- folders
3428
- }) {
3429
- if (requestAuth.type !== "inherit") return requestAuth;
3430
- let cursor = folderId;
3431
- const visited = /* @__PURE__ */ new Set();
3432
- while (cursor !== null) {
3433
- if (visited.has(cursor)) {
3434
- break;
3435
- }
3436
- visited.add(cursor);
3437
- const folder = folders[cursor];
3438
- if (!folder) break;
3439
- const auth = folder.auth;
3440
- if (auth && auth.type !== "inherit" && auth.type !== "none") return auth;
3441
- cursor = folder.parentId;
3442
- }
3443
- return NONE;
3444
- }
3445
-
3446
3506
  // src/request/parseCurl.ts
3447
3507
  var HTTP_METHODS = /* @__PURE__ */ new Set(["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"]);
3448
3508
  function tokenizeCurl(input) {
@@ -3744,10 +3804,12 @@ function parsePostmanCollection(input) {
3744
3804
  items.forEach((item, idx) => {
3745
3805
  const pathIds = parentPathIds ? [...parentPathIds, idx] : [idx];
3746
3806
  if (Array.isArray(item.item)) {
3807
+ const folderAuth = item.auth ? parseAuth(item.auth, warnings, item.name) : void 0;
3747
3808
  folders.push({
3748
3809
  name: (item.name ?? "Untitled folder").trim() || "Untitled folder",
3749
3810
  pathIds,
3750
- parentPathIds
3811
+ parentPathIds,
3812
+ ...folderAuth && folderAuth.type !== "none" ? { auth: folderAuth } : {}
3751
3813
  });
3752
3814
  walk(item.item, pathIds);
3753
3815
  return;
@@ -3988,10 +4050,12 @@ function parseInsomniaCollection(input) {
3988
4050
  const parentPath = r.parentId ? folderIndexById.get(r.parentId) ?? null : null;
3989
4051
  const ourPath = parentPath ? [...parentPath, folderCounter++] : [folderCounter++];
3990
4052
  folderIndexById.set(r._id ?? "", ourPath);
4053
+ const folderAuth = r.authentication ? parseAuth2(r.authentication, warnings, r.name) : void 0;
3991
4054
  folders.push({
3992
4055
  name: (r.name ?? "Untitled folder").trim() || "Untitled folder",
3993
4056
  pathIds: ourPath,
3994
- parentPathIds: parentPath
4057
+ parentPathIds: parentPath,
4058
+ ...folderAuth && folderAuth.type !== "none" ? { auth: folderAuth } : {}
3995
4059
  });
3996
4060
  }
3997
4061
  const requests = [];
@@ -4392,7 +4456,7 @@ function serializeFolderExport(envelope) {
4392
4456
  return JSON.stringify(envelope, null, 2);
4393
4457
  }
4394
4458
  function suggestFolderExportFilename(envelope) {
4395
- const slug = envelope.folder.name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
4459
+ const slug = envelope.folder.name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-/, "").replace(/-$/, "");
4396
4460
  const base = slug || "folder";
4397
4461
  return `${base}.apicircle.json`;
4398
4462
  }
@@ -5018,6 +5082,140 @@ function extractContext(result, extractions) {
5018
5082
  return { extracted, warnings };
5019
5083
  }
5020
5084
 
5085
+ // src/environment/resolveRequest.ts
5086
+ var import_shared2 = require("@apicircle/shared");
5087
+ function resolveRequestForExecution(args) {
5088
+ const refs = args.envPriorityOverride && args.envPriorityOverride.length > 0 ? args.envPriorityOverride : args.synced.environments.priorityOrder;
5089
+ const flatEnvs = {};
5090
+ for (const [name, vars] of Object.entries(args.localEnvs)) {
5091
+ flatEnvs[(0, import_shared2.envPriorityKey)({ kind: "local", name })] = vars;
5092
+ }
5093
+ for (const [linkId, byEnv] of Object.entries(args.linkedEnvs ?? {})) {
5094
+ for (const [envName, vars] of Object.entries(byEnv)) {
5095
+ flatEnvs[(0, import_shared2.envPriorityKey)({ kind: "linked", linkedWorkspaceId: linkId, envName })] = vars;
5096
+ }
5097
+ }
5098
+ const ctxMap = { ...args.globalContext ?? {} };
5099
+ for (const v of args.planVariables ?? []) {
5100
+ if (v.key) ctxMap[v.key] = v.value;
5101
+ }
5102
+ for (const v of args.request.contextVars) {
5103
+ if (v.key) ctxMap[v.key] = v.value;
5104
+ }
5105
+ const contextVars = Object.entries(ctxMap).map(([key, value]) => ({ key, value }));
5106
+ const scope = buildScope({
5107
+ contextVars,
5108
+ environments: flatEnvs,
5109
+ activeEnvName: null,
5110
+ // priorityOrder is the sole list the resolver consults.
5111
+ priorityOrder: refs.map(import_shared2.envPriorityKey),
5112
+ secrets: args.secrets ?? {}
5113
+ });
5114
+ const missing = /* @__PURE__ */ new Set();
5115
+ const interp = (s) => {
5116
+ const r = resolveString(s, scope);
5117
+ for (const m of r.missing) missing.add(m);
5118
+ return r.value;
5119
+ };
5120
+ const url = interp(args.request.url);
5121
+ const headers = args.request.headers.map((h) => ({
5122
+ ...h,
5123
+ key: interp(h.key),
5124
+ value: interp(h.value)
5125
+ }));
5126
+ const query = args.request.query.map((q) => ({
5127
+ ...q,
5128
+ key: interp(q.key),
5129
+ value: interp(q.value)
5130
+ }));
5131
+ let body = args.request.body;
5132
+ if (body.type === "json" || body.type === "text" || body.type === "xml" || body.type === "graphql" || body.type === "urlencoded") {
5133
+ body = { ...body, content: interp(body.content) };
5134
+ } else if (body.type === "form-data" && body.formRows) {
5135
+ body = {
5136
+ ...body,
5137
+ formRows: body.formRows.map(
5138
+ (row) => row.kind === "text" ? { ...row, key: interp(row.key), value: interp(row.value) } : { ...row, key: interp(row.key) }
5139
+ )
5140
+ };
5141
+ }
5142
+ const inheritedAuth = resolveInheritedAuth({
5143
+ requestAuth: args.request.auth ?? { type: "none" },
5144
+ folderId: args.request.folderId,
5145
+ folders: args.synced.collections.folders
5146
+ });
5147
+ const auth = interpolateAuthVariables(inheritedAuth, interp);
5148
+ return {
5149
+ request: { ...args.request, url, headers, query, body, auth },
5150
+ scope,
5151
+ missing: [...missing]
5152
+ };
5153
+ }
5154
+ function interpolateAuthVariables(auth, interp) {
5155
+ const resolved = {};
5156
+ for (const [key, value] of Object.entries(auth)) {
5157
+ resolved[key] = key !== "type" && typeof value === "string" ? interp(value) : value;
5158
+ }
5159
+ return resolved;
5160
+ }
5161
+ function applyLinkedEnvironmentOverrides(source, linkedWorkspaceId, synced) {
5162
+ const overrides = Object.values(synced.linkedOverrides.environmentVars).filter(
5163
+ (o) => o.linkedWorkspaceId === linkedWorkspaceId
5164
+ );
5165
+ if (overrides.length === 0) return source;
5166
+ const items = {};
5167
+ for (const [envName, env] of Object.entries(source.items)) {
5168
+ const envOverrides = overrides.filter((o) => o.envName === envName);
5169
+ if (envOverrides.length === 0) {
5170
+ items[envName] = env;
5171
+ continue;
5172
+ }
5173
+ const removed = new Set(envOverrides.filter((o) => o.removed).map((o) => o.varKey));
5174
+ const replaceMap = new Map(envOverrides.filter((o) => !o.removed).map((o) => [o.varKey, o]));
5175
+ const variables = [];
5176
+ const seenKeys = /* @__PURE__ */ new Set();
5177
+ for (const v of env.variables) {
5178
+ if (removed.has(v.key)) continue;
5179
+ const ov = replaceMap.get(v.key);
5180
+ if (ov) {
5181
+ variables.push({
5182
+ key: v.key,
5183
+ value: ov.value ?? v.value,
5184
+ encrypted: ov.encrypted ?? v.encrypted,
5185
+ ...ov.secretKeyId !== void 0 ? { secretKeyId: ov.secretKeyId } : v.secretKeyId !== void 0 ? { secretKeyId: v.secretKeyId } : {}
5186
+ });
5187
+ } else {
5188
+ variables.push(v);
5189
+ }
5190
+ seenKeys.add(v.key);
5191
+ }
5192
+ for (const ov of envOverrides) {
5193
+ if (ov.removed) continue;
5194
+ if (seenKeys.has(ov.varKey)) continue;
5195
+ variables.push({
5196
+ key: ov.varKey,
5197
+ value: ov.value ?? "",
5198
+ encrypted: ov.encrypted ?? false,
5199
+ ...ov.secretKeyId !== void 0 ? { secretKeyId: ov.secretKeyId } : {}
5200
+ });
5201
+ }
5202
+ items[envName] = { ...env, variables };
5203
+ }
5204
+ return { ...source, items };
5205
+ }
5206
+ function plaintextEnvMap(source) {
5207
+ const out = {};
5208
+ for (const [name, env] of Object.entries(source.items)) {
5209
+ const vars = {};
5210
+ for (const v of env.variables) {
5211
+ if (v.encrypted) continue;
5212
+ vars[v.key] = v.value;
5213
+ }
5214
+ out[name] = vars;
5215
+ }
5216
+ return out;
5217
+ }
5218
+
5021
5219
  // src/secrets/crypto.ts
5022
5220
  var IV_BYTES = 12;
5023
5221
  var SALT_BYTES = 16;
@@ -5102,6 +5300,95 @@ function base64ToBytes(b64) {
5102
5300
  return out;
5103
5301
  }
5104
5302
 
5303
+ // src/secrets/passphraseKey.ts
5304
+ var PBKDF2_HASH = "SHA-256";
5305
+ var PBKDF2_ITERATIONS2 = 12e5;
5306
+ var SALT_BYTES2 = 16;
5307
+ var VERIFIER_SENTINEL = "apicircle/passphrase-verifier/v1";
5308
+ function base64Encode2(bytes) {
5309
+ let binary = "";
5310
+ for (let i = 0; i < bytes.length; i++) binary += String.fromCharCode(bytes[i]);
5311
+ return btoa(binary);
5312
+ }
5313
+ function base64Decode2(b64) {
5314
+ const binary = atob(b64);
5315
+ const bytes = new Uint8Array(binary.length);
5316
+ for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
5317
+ return bytes;
5318
+ }
5319
+ function utf8Bytes(s) {
5320
+ return new TextEncoder().encode(s);
5321
+ }
5322
+ async function deriveKey(passphrase, salt, iterations) {
5323
+ const baseKey = await crypto.subtle.importKey(
5324
+ "raw",
5325
+ utf8Bytes(passphrase),
5326
+ { name: "PBKDF2" },
5327
+ false,
5328
+ ["deriveKey"]
5329
+ );
5330
+ return crypto.subtle.deriveKey(
5331
+ {
5332
+ name: "PBKDF2",
5333
+ salt,
5334
+ iterations,
5335
+ hash: PBKDF2_HASH
5336
+ },
5337
+ baseKey,
5338
+ { name: "AES-GCM", length: 256 },
5339
+ /* extractable */
5340
+ false,
5341
+ ["encrypt", "decrypt"]
5342
+ );
5343
+ }
5344
+ async function computeVerifier(key) {
5345
+ const iv = new Uint8Array(12);
5346
+ const ct = await crypto.subtle.encrypt(
5347
+ { name: "AES-GCM", iv },
5348
+ key,
5349
+ utf8Bytes(VERIFIER_SENTINEL)
5350
+ );
5351
+ return base64Encode2(new Uint8Array(ct));
5352
+ }
5353
+ async function initSecretCrypto(passphrase, iterations = PBKDF2_ITERATIONS2) {
5354
+ if (passphrase.length === 0) throw new Error("Passphrase cannot be empty");
5355
+ if (iterations < 1) throw new Error("iterations must be >= 1");
5356
+ const salt = crypto.getRandomValues(new Uint8Array(SALT_BYTES2));
5357
+ const key = await deriveKey(passphrase, salt, iterations);
5358
+ const verifier = await computeVerifier(key);
5359
+ return {
5360
+ crypto: {
5361
+ kdf: "pbkdf2-sha256-v1",
5362
+ salt: base64Encode2(salt),
5363
+ iterations,
5364
+ verifier
5365
+ },
5366
+ key
5367
+ };
5368
+ }
5369
+ async function unlockSecretCrypto(passphrase, blob) {
5370
+ if (blob.kdf !== "pbkdf2-sha256-v1") {
5371
+ return { ok: false, reason: `Unsupported KDF: ${String(blob.kdf)}` };
5372
+ }
5373
+ let salt;
5374
+ try {
5375
+ salt = base64Decode2(blob.salt);
5376
+ } catch {
5377
+ return { ok: false, reason: "Workspace secret salt is corrupt." };
5378
+ }
5379
+ let key;
5380
+ try {
5381
+ key = await deriveKey(passphrase, salt, blob.iterations);
5382
+ } catch (err) {
5383
+ return { ok: false, reason: err instanceof Error ? err.message : "Key derivation failed" };
5384
+ }
5385
+ const verifier = await computeVerifier(key);
5386
+ if (verifier !== blob.verifier) {
5387
+ return { ok: false, reason: "Wrong passphrase." };
5388
+ }
5389
+ return { ok: true, key };
5390
+ }
5391
+
5105
5392
  // src/git/branchNames.ts
5106
5393
  var SLUG_FALLBACK = "workspace";
5107
5394
  var SUFFIX_LEN = 6;
@@ -5159,6 +5446,36 @@ function sortedReplacer(_key, value) {
5159
5446
  return out;
5160
5447
  }
5161
5448
 
5449
+ // src/git/repoPaths.ts
5450
+ var WORKSPACE_DIR = ".apicircle";
5451
+ var REGISTRY_JSON_PATH = `${WORKSPACE_DIR}/registry.json`;
5452
+ function workspaceJsonPath(workspaceId) {
5453
+ return `${WORKSPACE_DIR}/workspace-${workspaceId}/workspace.json`;
5454
+ }
5455
+ function attachmentsDir(workspaceId) {
5456
+ return `${WORKSPACE_DIR}/workspace-${workspaceId}/attachments`;
5457
+ }
5458
+ function attachmentPath(workspaceId, slotId) {
5459
+ return `${attachmentsDir(workspaceId)}/${slotId}`;
5460
+ }
5461
+ function parseRegistryActiveId(registryJsonContent) {
5462
+ try {
5463
+ const parsed = JSON.parse(registryJsonContent);
5464
+ return parsed.activeWorkspaceId ?? parsed.workspaces?.[0]?.id ?? null;
5465
+ } catch {
5466
+ return null;
5467
+ }
5468
+ }
5469
+ async function fetchRemoteWorkspaceJson(fetchFile) {
5470
+ const registryContent = await fetchFile(REGISTRY_JSON_PATH);
5471
+ if (registryContent === null) return { error: "No .apicircle/registry.json found in repo" };
5472
+ const wsId = parseRegistryActiveId(registryContent);
5473
+ if (!wsId) return { error: "Registry is empty \u2014 no workspaces found" };
5474
+ const wsContent = await fetchFile(workspaceJsonPath(wsId));
5475
+ if (wsContent === null) return { error: `No workspace.json at .apicircle/workspace-${wsId}/` };
5476
+ return { workspaceId: wsId, content: wsContent };
5477
+ }
5478
+
5162
5479
  // src/git/parseWorkspaceJson.ts
5163
5480
  var MAX_WORKSPACE_JSON_BYTES = 16 * 1024 * 1024;
5164
5481
  var FORBIDDEN_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
@@ -5415,18 +5732,14 @@ function sortVersionsDesc(versions) {
5415
5732
  }
5416
5733
 
5417
5734
  // src/release/publishRelease.ts
5418
- async function publishRelease(synced, args) {
5735
+ async function buildReleaseEntry(synced, args) {
5419
5736
  const version = args.version.trim();
5420
5737
  if (!isValidSemver(version)) {
5421
5738
  throw new Error(`Invalid semver: ${args.version}`);
5422
5739
  }
5423
- const ledger = synced.releases.self ?? emptyLedger();
5424
- if (ledger.versions.some((v) => v.version === version)) {
5425
- throw new Error(`Version ${version} already exists in this workspace's release ledger`);
5426
- }
5427
5740
  const snapshotSource = serializeWorkspaceForGit(synced);
5428
5741
  const workspaceSnapshot = await sha256Hex2(snapshotSource);
5429
- const entry = {
5742
+ return {
5430
5743
  version,
5431
5744
  publishedAt: args.publishedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
5432
5745
  notes: args.notes,
@@ -5436,23 +5749,36 @@ async function publishRelease(synced, args) {
5436
5749
  ...args.sha ? { sha: args.sha } : {},
5437
5750
  ...args.tagName ? { tagName: args.tagName } : {}
5438
5751
  };
5752
+ }
5753
+ function appendReleaseEntry(synced, entry, now = entry.publishedAt) {
5754
+ if (!isValidSemver(entry.version)) {
5755
+ throw new Error(`Invalid semver: ${entry.version}`);
5756
+ }
5757
+ const ledger = synced.releases.self ?? emptyLedger();
5758
+ if (ledger.versions.some((v) => v.version === entry.version)) {
5759
+ throw new Error(`Version ${entry.version} already exists in this workspace's release ledger`);
5760
+ }
5439
5761
  const next = {
5440
5762
  versions: [...ledger.versions, entry],
5441
- currentVersion: version
5763
+ currentVersion: entry.version
5442
5764
  };
5443
5765
  return {
5444
5766
  ...synced,
5445
5767
  releases: { ...synced.releases, self: next },
5446
- meta: { ...synced.meta, updatedAt: entry.publishedAt }
5768
+ meta: { ...synced.meta, updatedAt: now }
5447
5769
  };
5448
5770
  }
5449
- function deprecateRelease(synced, version) {
5450
- return mapReleaseVersion(synced, version, (v) => ({ ...v, deprecated: true }));
5771
+ async function publishRelease(synced, args) {
5772
+ const entry = await buildReleaseEntry(synced, args);
5773
+ return appendReleaseEntry(synced, entry);
5451
5774
  }
5452
- function yankRelease(synced, version) {
5453
- return mapReleaseVersion(synced, version, (v) => ({ ...v, yanked: true }));
5775
+ function deprecateRelease(synced, version, now = (/* @__PURE__ */ new Date()).toISOString()) {
5776
+ return mapReleaseVersion(synced, version, (v) => ({ ...v, deprecated: true }), now);
5454
5777
  }
5455
- function mapReleaseVersion(synced, version, fn) {
5778
+ function yankRelease(synced, version, now = (/* @__PURE__ */ new Date()).toISOString()) {
5779
+ return mapReleaseVersion(synced, version, (v) => ({ ...v, yanked: true }), now);
5780
+ }
5781
+ function mapReleaseVersion(synced, version, fn, now) {
5456
5782
  const ledger = synced.releases.self;
5457
5783
  if (!ledger) throw new Error("No releases to modify");
5458
5784
  const idx = ledger.versions.findIndex((v) => v.version === version);
@@ -5462,7 +5788,7 @@ function mapReleaseVersion(synced, version, fn) {
5462
5788
  return {
5463
5789
  ...synced,
5464
5790
  releases: { ...synced.releases, self: { ...ledger, versions } },
5465
- meta: { ...synced.meta, updatedAt: (/* @__PURE__ */ new Date()).toISOString() }
5791
+ meta: { ...synced.meta, updatedAt: now }
5466
5792
  };
5467
5793
  }
5468
5794
  function emptyLedger() {
@@ -5474,6 +5800,99 @@ async function sha256Hex2(text) {
5474
5800
  return [...new Uint8Array(digest)].map((b) => b.toString(16).padStart(2, "0")).join("");
5475
5801
  }
5476
5802
 
5803
+ // src/linked/linkedSnapshot.ts
5804
+ var LINKED_FORBIDDEN_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
5805
+ var MAX_LINKED_JSON_BYTES = 16 * 1024 * 1024;
5806
+ function parseLinkedWorkspaceJson(text) {
5807
+ if (text.length > MAX_LINKED_JSON_BYTES) {
5808
+ throw new Error("Remote workspace.json exceeds 16 MiB");
5809
+ }
5810
+ let raw;
5811
+ try {
5812
+ raw = JSON.parse(
5813
+ text,
5814
+ (key, value) => LINKED_FORBIDDEN_KEYS.has(key) ? void 0 : value
5815
+ );
5816
+ } catch {
5817
+ throw new Error("Remote workspace.json is not valid JSON");
5818
+ }
5819
+ if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
5820
+ throw new Error("Remote workspace.json is not an object");
5821
+ }
5822
+ const obj = raw;
5823
+ const asObject = (v) => typeof v === "object" && v !== null ? v : void 0;
5824
+ return {
5825
+ workspaceId: typeof obj.workspaceId === "string" ? obj.workspaceId : void 0,
5826
+ releases: asObject(obj.releases),
5827
+ collections: asObject(obj.collections),
5828
+ environments: asObject(obj.environments),
5829
+ secretKeys: asObject(obj.secretKeys),
5830
+ globalAssets: asObject(obj.globalAssets)
5831
+ };
5832
+ }
5833
+ function ledgerFromProbe(parsed) {
5834
+ return parsed.releases?.self ?? { versions: [], currentVersion: null };
5835
+ }
5836
+ function buildLinkedSnapshot(parsed, link) {
5837
+ if (!parsed.collections && !parsed.environments) return null;
5838
+ return {
5839
+ pulledAt: link.linkedAt,
5840
+ ref: link.pinnedVersion ? `v${link.pinnedVersion}` : `HEAD@${link.source.branch}`,
5841
+ collections: parsed.collections ?? {
5842
+ tree: { id: "remote-root", type: "root", children: [] },
5843
+ requests: {},
5844
+ folders: {}
5845
+ },
5846
+ environments: parsed.environments ?? {
5847
+ items: {},
5848
+ activeName: null,
5849
+ priorityOrder: []
5850
+ },
5851
+ ...parsed.secretKeys ? { secretKeys: parsed.secretKeys } : {},
5852
+ ...parsed.globalAssets ? { globalAssets: parsed.globalAssets } : {}
5853
+ };
5854
+ }
5855
+
5856
+ // src/linked/requestOverride.ts
5857
+ var OVERRIDABLE_FIELDS = [
5858
+ "name",
5859
+ "method",
5860
+ "url",
5861
+ "headers",
5862
+ "query",
5863
+ "pathParams",
5864
+ "cookies",
5865
+ "body",
5866
+ "auth",
5867
+ "contextVars",
5868
+ "extractions",
5869
+ "assertions"
5870
+ ];
5871
+ function mergeRequestOverride(base, patch) {
5872
+ const merged = { ...base };
5873
+ const p = patch;
5874
+ const target = merged;
5875
+ for (const field of OVERRIDABLE_FIELDS) {
5876
+ if (p[field] !== void 0) target[field] = p[field];
5877
+ }
5878
+ return merged;
5879
+ }
5880
+ function computeRequestOverridePatch(base, effective) {
5881
+ const baseRec = { ...base };
5882
+ const effRec = { ...effective };
5883
+ const patch = {};
5884
+ const patchRec = patch;
5885
+ for (const field of OVERRIDABLE_FIELDS) {
5886
+ if (JSON.stringify(baseRec[field]) !== JSON.stringify(effRec[field])) {
5887
+ patchRec[field] = effRec[field];
5888
+ }
5889
+ }
5890
+ return patch;
5891
+ }
5892
+ function isEmptyOverridePatch(patch) {
5893
+ return Object.keys(patch).filter((k) => OVERRIDABLE_FIELDS.includes(k)).length === 0;
5894
+ }
5895
+
5477
5896
  // src/editors/contentTypeLanguageMap.ts
5478
5897
  var CONTENT_TYPE_LANGUAGE_MAP = {
5479
5898
  "application/json": "json",
@@ -6406,7 +6825,8 @@ function previewLinkedUpdate(args) {
6406
6825
  status,
6407
6826
  base,
6408
6827
  target,
6409
- override
6828
+ override,
6829
+ ...status === "both-changed" && base && target && override ? { autoMergeable: requestOverrideIsDisjoint(base, target, override) } : {}
6410
6830
  });
6411
6831
  }
6412
6832
  const baseFolders = args.base?.collections.folders ?? {};
@@ -6486,6 +6906,34 @@ function classifyRequest(base, target, override) {
6486
6906
  if (sourceChanged && !hasOverride) return "source-only";
6487
6907
  return "both-changed";
6488
6908
  }
6909
+ var OVERRIDABLE_REQUEST_FIELDS = [
6910
+ "name",
6911
+ "method",
6912
+ "url",
6913
+ "headers",
6914
+ "query",
6915
+ "pathParams",
6916
+ "cookies",
6917
+ "body",
6918
+ "auth",
6919
+ "contextVars",
6920
+ "extractions",
6921
+ "assertions"
6922
+ ];
6923
+ function requestOverrideIsDisjoint(base, target, override) {
6924
+ const baseRec = { ...base };
6925
+ const targetRec = { ...target };
6926
+ const overriddenFields = Object.keys(override.patch);
6927
+ for (const f of overriddenFields) {
6928
+ if (!OVERRIDABLE_REQUEST_FIELDS.includes(f)) {
6929
+ continue;
6930
+ }
6931
+ if (!structurallyEqual2(baseRec[f], targetRec[f])) {
6932
+ return false;
6933
+ }
6934
+ }
6935
+ return true;
6936
+ }
6489
6937
  function classifyFolder(base, target) {
6490
6938
  if (!base && target) return "new-in-source";
6491
6939
  if (base && !target) return "removed-in-source";
@@ -6535,7 +6983,7 @@ function applyLinkedUpdate(args) {
6535
6983
  continue;
6536
6984
  }
6537
6985
  if (entry.status === "both-changed") {
6538
- const choice = args.resolutions[id];
6986
+ const choice = args.resolutions[id] ?? (entry.autoMergeable ? "mine" : void 0);
6539
6987
  if (!choice) {
6540
6988
  throw new Error(
6541
6989
  `applyLinkedUpdate: unresolved both-changed entry "${entry.label}" (${id})`
@@ -6546,7 +6994,8 @@ function applyLinkedUpdate(args) {
6546
6994
  else if (entry.bucket === "environment-var") envVarOverridesByKey.delete(entry.key);
6547
6995
  log.push({ entryKey: id, bucket: entry.bucket, action: "accept-source" });
6548
6996
  } else {
6549
- log.push({ entryKey: id, bucket: entry.bucket, action: "keep-mine" });
6997
+ const auto = entry.autoMergeable === true && !args.resolutions[id];
6998
+ log.push({ entryKey: id, bucket: entry.bucket, action: auto ? "auto-merge" : "keep-mine" });
6550
6999
  }
6551
7000
  }
6552
7001
  }
@@ -6562,7 +7011,7 @@ function structurallyEqual2(a, b) {
6562
7011
  }
6563
7012
 
6564
7013
  // src/workspace/applyMutation.ts
6565
- var import_shared2 = require("@apicircle/shared");
7014
+ var import_shared3 = require("@apicircle/shared");
6566
7015
 
6567
7016
  // src/workspace/apicircleFolderImport.ts
6568
7017
  function importApicircleFolderInto(synced, parsed, parentFolderId) {
@@ -6803,6 +7252,8 @@ function applyMutation(state, patch, options = {}) {
6803
7252
  return applyFolderDelete(state, patch.id, now);
6804
7253
  case "folder.move":
6805
7254
  return applyFolderMove(state, patch.id, patch.newParentId, now);
7255
+ case "folder.update":
7256
+ return applyFolderUpdate(state, patch.id, patch.patch, now);
6806
7257
  case "folder.import_apicircle":
6807
7258
  return applyFolderImportApicircle(state, patch.parsed, patch.parentFolderId, now);
6808
7259
  case "environment.upsert":
@@ -6815,6 +7266,10 @@ function applyMutation(state, patch, options = {}) {
6815
7266
  return applyEnvSetPriority(state, patch.order, now);
6816
7267
  case "secretKey.upsert":
6817
7268
  return applySecretKeyUpsert(state, patch.meta, now);
7269
+ case "secret.crypto.set":
7270
+ return applySecretCryptoSet(state, patch.crypto, now);
7271
+ case "secret.crypto.clear":
7272
+ return applySecretCryptoClear(state, now);
6818
7273
  case "assertion.upsert":
6819
7274
  return applyAssertionUpsert(state, patch.requestId, patch.assertion, now);
6820
7275
  case "assertion.delete":
@@ -6823,6 +7278,46 @@ function applyMutation(state, patch, options = {}) {
6823
7278
  return applyMockUpsert(state, patch.mock, now);
6824
7279
  case "mock.delete":
6825
7280
  return applyMockDelete(state, patch.id, now);
7281
+ case "release.publish":
7282
+ return applyReleasePublish(state, patch.entry, now);
7283
+ case "release.deprecate":
7284
+ return applyReleaseDeprecate(state, patch.version, now);
7285
+ case "release.yank":
7286
+ return applyReleaseYank(state, patch.version, now);
7287
+ case "linkedWorkspace.upsert":
7288
+ return applyLinkedWorkspaceUpsert(state, patch.link, patch.ledger, patch.snapshot, now);
7289
+ case "linkedWorkspace.remove":
7290
+ return applyLinkedWorkspaceRemove(state, patch.id, now);
7291
+ case "linkedWorkspace.applyUpdate":
7292
+ return applyLinkedWorkspaceApplyUpdate(state, patch, now);
7293
+ case "linkedOverride.setRequest":
7294
+ return applyLinkedOverrideSetRequest(state, patch.override, now);
7295
+ case "linkedOverride.removeRequest":
7296
+ return applyLinkedOverrideRemoveRequest(state, patch.linkedWorkspaceId, patch.itemId, now);
7297
+ case "linkedOverride.setEnvVar":
7298
+ return applyLinkedOverrideSetEnvVar(state, patch.override, now);
7299
+ case "linkedOverride.removeEnvVar":
7300
+ return applyLinkedOverrideRemoveEnvVar(
7301
+ state,
7302
+ patch.linkedWorkspaceId,
7303
+ patch.envName,
7304
+ patch.varKey,
7305
+ now
7306
+ );
7307
+ case "linkedOverride.clearForLink":
7308
+ return applyLinkedOverrideClearForLink(state, patch.linkedWorkspaceId, now);
7309
+ case "globalAsset.upsertFile":
7310
+ return applyGlobalAssetUpsertFile(state, patch.file, now);
7311
+ case "globalAsset.removeFile":
7312
+ return applyGlobalAssetRemoveFile(state, patch.id, now);
7313
+ case "globalAsset.markPushed":
7314
+ return applyGlobalAssetMarkPushed(state, patch.id, patch.ref, now);
7315
+ case "globalAsset.markMerged":
7316
+ return applyGlobalAssetMarkMerged(state, patch.id, patch.ref, now);
7317
+ case "globalAsset.cleanupWorkingRef":
7318
+ return applyGlobalAssetCleanupWorkingRef(state, patch.id, now);
7319
+ case "globalAsset.invalidateRef":
7320
+ return applyGlobalAssetInvalidateRef(state, patch.id, patch.which, now);
6826
7321
  case "plan.upsert":
6827
7322
  return applyPlanUpsert(state, patch.plan, now);
6828
7323
  case "plan.delete":
@@ -6847,12 +7342,13 @@ function applyRequestCreate(state, request, now) {
6847
7342
  if (state.synced.collections.requests[request.id]) {
6848
7343
  return { next: state, changedIds: [] };
6849
7344
  }
7345
+ const tree = request.folderId ? state.synced.collections.tree : pushTreeChild(state.synced.collections.tree, { kind: "request", id: request.id });
6850
7346
  const synced = {
6851
7347
  ...state.synced,
6852
7348
  collections: {
6853
7349
  ...state.synced.collections,
6854
7350
  requests: { ...state.synced.collections.requests, [request.id]: request },
6855
- tree: pushTreeChild(state.synced.collections.tree, { kind: "request", id: request.id })
7351
+ tree
6856
7352
  },
6857
7353
  meta: { ...state.synced.meta, updatedAt: now }
6858
7354
  };
@@ -6901,12 +7397,13 @@ function applyFolderCreate(state, folder, now) {
6901
7397
  if (state.synced.collections.folders[folder.id]) {
6902
7398
  return { next: state, changedIds: [] };
6903
7399
  }
7400
+ const tree = folder.parentId ? state.synced.collections.tree : pushTreeChild(state.synced.collections.tree, { kind: "folder", id: folder.id });
6904
7401
  const synced = {
6905
7402
  ...state.synced,
6906
7403
  collections: {
6907
7404
  ...state.synced.collections,
6908
7405
  folders: { ...state.synced.collections.folders, [folder.id]: folder },
6909
- tree: pushTreeChild(state.synced.collections.tree, { kind: "folder", id: folder.id })
7406
+ tree
6910
7407
  },
6911
7408
  meta: { ...state.synced.meta, updatedAt: now }
6912
7409
  };
@@ -6975,6 +7472,57 @@ function applyFolderMove(state, id, newParentId, now) {
6975
7472
  };
6976
7473
  return { next: { ...state, synced }, changedIds: [id] };
6977
7474
  }
7475
+ function applyFolderUpdate(state, id, patch, now) {
7476
+ const folder = state.synced.collections.folders[id];
7477
+ if (!folder) {
7478
+ return { next: state, changedIds: [] };
7479
+ }
7480
+ const nameChanging = "name" in patch && patch.name !== void 0;
7481
+ const authChanging = "auth" in patch;
7482
+ if (!nameChanging && !authChanging) {
7483
+ return { next: state, changedIds: [] };
7484
+ }
7485
+ let nextName = folder.name;
7486
+ if (nameChanging) {
7487
+ const trimmed = patch.name.trim();
7488
+ if (!trimmed) {
7489
+ } else if (trimmed === folder.name) {
7490
+ } else if (!isFolderNameUnique(state, folder.parentId, trimmed, id)) {
7491
+ return { next: state, changedIds: [] };
7492
+ } else {
7493
+ nextName = trimmed;
7494
+ }
7495
+ }
7496
+ const nextFolder = { ...folder, name: nextName };
7497
+ if (authChanging) {
7498
+ if (patch.auth === void 0) {
7499
+ delete nextFolder.auth;
7500
+ } else {
7501
+ nextFolder.auth = patch.auth;
7502
+ }
7503
+ }
7504
+ if (nextFolder.name === folder.name && nextFolder.auth === folder.auth) {
7505
+ return { next: state, changedIds: [] };
7506
+ }
7507
+ const synced = {
7508
+ ...state.synced,
7509
+ collections: {
7510
+ ...state.synced.collections,
7511
+ folders: { ...state.synced.collections.folders, [id]: nextFolder }
7512
+ },
7513
+ meta: { ...state.synced.meta, updatedAt: now }
7514
+ };
7515
+ return { next: { ...state, synced }, changedIds: [id] };
7516
+ }
7517
+ function isFolderNameUnique(state, parentId, trimmedCandidate, ignoreId) {
7518
+ const target = trimmedCandidate.toLowerCase();
7519
+ for (const f of Object.values(state.synced.collections.folders)) {
7520
+ if (f.id === ignoreId) continue;
7521
+ if (f.parentId !== parentId) continue;
7522
+ if (f.name.trim().toLowerCase() === target) return false;
7523
+ }
7524
+ return true;
7525
+ }
6978
7526
  function applyFolderImportApicircle(state, parsed, parentFolderId, now) {
6979
7527
  const result = importApicircleFolderInto(
6980
7528
  state.synced,
@@ -7054,7 +7602,7 @@ function applyEnvSetPriority(state, order, now) {
7054
7602
  const knownLocal = new Set(Object.keys(state.synced.environments.items));
7055
7603
  const seen = /* @__PURE__ */ new Set();
7056
7604
  const filtered = order.filter((ref) => {
7057
- const key = (0, import_shared2.envPriorityKey)(ref);
7605
+ const key = (0, import_shared3.envPriorityKey)(ref);
7058
7606
  if (seen.has(key)) return false;
7059
7607
  if (ref.kind === "local" && !knownLocal.has(ref.name)) return false;
7060
7608
  seen.add(key);
@@ -7065,7 +7613,7 @@ function applyEnvSetPriority(state, order, now) {
7065
7613
  environments: { ...state.synced.environments, priorityOrder: filtered },
7066
7614
  meta: { ...state.synced.meta, updatedAt: now }
7067
7615
  };
7068
- return { next: { ...state, synced }, changedIds: filtered.map(import_shared2.envPriorityKey) };
7616
+ return { next: { ...state, synced }, changedIds: filtered.map(import_shared3.envPriorityKey) };
7069
7617
  }
7070
7618
  function applySecretKeyUpsert(state, meta, now) {
7071
7619
  if (!meta.id || !meta.label.trim() || !meta.salt) {
@@ -7081,6 +7629,28 @@ function applySecretKeyUpsert(state, meta, now) {
7081
7629
  };
7082
7630
  return { next: { ...state, synced }, changedIds: [meta.id] };
7083
7631
  }
7632
+ function applySecretCryptoSet(state, crypto2, now) {
7633
+ if (!crypto2 || crypto2.kdf !== "pbkdf2-sha256-v1" || !crypto2.salt || !crypto2.verifier || !(crypto2.iterations >= 1)) {
7634
+ return { next: state, changedIds: [] };
7635
+ }
7636
+ const synced = {
7637
+ ...state.synced,
7638
+ secretCrypto: { ...crypto2 },
7639
+ meta: { ...state.synced.meta, updatedAt: now }
7640
+ };
7641
+ return { next: { ...state, synced }, changedIds: ["secret.crypto"] };
7642
+ }
7643
+ function applySecretCryptoClear(state, now) {
7644
+ if (!state.synced.secretCrypto) {
7645
+ return { next: state, changedIds: [] };
7646
+ }
7647
+ const synced = {
7648
+ ...state.synced,
7649
+ secretCrypto: null,
7650
+ meta: { ...state.synced.meta, updatedAt: now }
7651
+ };
7652
+ return { next: { ...state, synced }, changedIds: ["secret.crypto"] };
7653
+ }
7084
7654
  function applyAssertionUpsert(state, requestId, assertion, now) {
7085
7655
  const request = state.synced.collections.requests[requestId];
7086
7656
  if (!request) {
@@ -7138,6 +7708,340 @@ function applyMockDelete(state, id, now) {
7138
7708
  } : state.local;
7139
7709
  return { next: { synced, local }, changedIds: [id] };
7140
7710
  }
7711
+ function applyReleasePublish(state, entry, now) {
7712
+ const synced = appendReleaseEntry(state.synced, entry, now);
7713
+ return { next: { ...state, synced }, changedIds: [entry.version] };
7714
+ }
7715
+ function applyReleaseDeprecate(state, version, now) {
7716
+ const synced = deprecateRelease(state.synced, version, now);
7717
+ return { next: { ...state, synced }, changedIds: [version] };
7718
+ }
7719
+ function applyReleaseYank(state, version, now) {
7720
+ const synced = yankRelease(state.synced, version, now);
7721
+ return { next: { ...state, synced }, changedIds: [version] };
7722
+ }
7723
+ function applyLinkedWorkspaceUpsert(state, link, ledger, snapshot2, now) {
7724
+ const synced = {
7725
+ ...state.synced,
7726
+ linkedWorkspaces: { ...state.synced.linkedWorkspaces, [link.id]: link },
7727
+ releases: ledger ? {
7728
+ ...state.synced.releases,
7729
+ perLink: { ...state.synced.releases.perLink, [link.id]: ledger }
7730
+ } : state.synced.releases,
7731
+ meta: { ...state.synced.meta, updatedAt: now }
7732
+ };
7733
+ const local = snapshot2 ? {
7734
+ ...state.local,
7735
+ linkedCollections: { ...state.local.linkedCollections, [link.id]: snapshot2 }
7736
+ } : state.local;
7737
+ return { next: { synced, local }, changedIds: [link.id] };
7738
+ }
7739
+ function applyLinkedWorkspaceRemove(state, id, now) {
7740
+ if (!state.synced.linkedWorkspaces[id]) {
7741
+ return { next: state, changedIds: [] };
7742
+ }
7743
+ const linkedWorkspaces = { ...state.synced.linkedWorkspaces };
7744
+ delete linkedWorkspaces[id];
7745
+ const perLink = { ...state.synced.releases.perLink };
7746
+ delete perLink[id];
7747
+ const prefix = `${id}:`;
7748
+ const dropPrefixed = (map) => Object.fromEntries(Object.entries(map).filter(([k]) => !k.startsWith(prefix)));
7749
+ const synced = {
7750
+ ...state.synced,
7751
+ linkedWorkspaces,
7752
+ releases: { ...state.synced.releases, perLink },
7753
+ linkedOverrides: {
7754
+ requests: dropPrefixed(state.synced.linkedOverrides.requests),
7755
+ environmentVars: dropPrefixed(state.synced.linkedOverrides.environmentVars)
7756
+ },
7757
+ meta: { ...state.synced.meta, updatedAt: now }
7758
+ };
7759
+ const linkedCollections = { ...state.local.linkedCollections };
7760
+ delete linkedCollections[id];
7761
+ const githubLinks = { ...state.local.sessions.github.links };
7762
+ delete githubLinks[id];
7763
+ const local = {
7764
+ ...state.local,
7765
+ linkedCollections,
7766
+ sessions: {
7767
+ ...state.local.sessions,
7768
+ github: { ...state.local.sessions.github, links: githubLinks }
7769
+ }
7770
+ };
7771
+ return { next: { synced, local }, changedIds: [id] };
7772
+ }
7773
+ function applyLinkedWorkspaceApplyUpdate(state, patch, now) {
7774
+ const link = state.synced.linkedWorkspaces[patch.id];
7775
+ if (!link) {
7776
+ return { next: state, changedIds: [] };
7777
+ }
7778
+ const prefix = `${patch.id}:`;
7779
+ const otherRequests = Object.fromEntries(
7780
+ Object.entries(state.synced.linkedOverrides.requests).filter(([k]) => !k.startsWith(prefix))
7781
+ );
7782
+ for (const o of patch.requestOverrides) {
7783
+ otherRequests[`${o.linkedWorkspaceId}:${o.itemId}`] = o;
7784
+ }
7785
+ const otherEnvVars = Object.fromEntries(
7786
+ Object.entries(state.synced.linkedOverrides.environmentVars).filter(
7787
+ ([k]) => !k.startsWith(prefix)
7788
+ )
7789
+ );
7790
+ for (const o of patch.envVarOverrides) {
7791
+ otherEnvVars[`${o.linkedWorkspaceId}:${o.envName}:${o.varKey}`] = o;
7792
+ }
7793
+ const synced = {
7794
+ ...state.synced,
7795
+ linkedWorkspaces: {
7796
+ ...state.synced.linkedWorkspaces,
7797
+ [patch.id]: { ...link, pinnedVersion: patch.pinnedVersion }
7798
+ },
7799
+ releases: {
7800
+ ...state.synced.releases,
7801
+ perLink: { ...state.synced.releases.perLink, [patch.id]: patch.ledger }
7802
+ },
7803
+ linkedOverrides: { requests: otherRequests, environmentVars: otherEnvVars },
7804
+ meta: { ...state.synced.meta, updatedAt: now }
7805
+ };
7806
+ const local = {
7807
+ ...state.local,
7808
+ linkedCollections: { ...state.local.linkedCollections, [patch.id]: patch.snapshot }
7809
+ };
7810
+ return { next: { synced, local }, changedIds: [patch.id] };
7811
+ }
7812
+ function applyLinkedOverrideSetRequest(state, override, now) {
7813
+ const key = `${override.linkedWorkspaceId}:${override.itemId}`;
7814
+ const synced = {
7815
+ ...state.synced,
7816
+ linkedOverrides: {
7817
+ ...state.synced.linkedOverrides,
7818
+ requests: {
7819
+ ...state.synced.linkedOverrides.requests,
7820
+ [key]: { ...override, updatedAt: now }
7821
+ }
7822
+ },
7823
+ meta: { ...state.synced.meta, updatedAt: now }
7824
+ };
7825
+ return { next: { ...state, synced }, changedIds: [key] };
7826
+ }
7827
+ function applyLinkedOverrideRemoveRequest(state, linkedWorkspaceId, itemId, now) {
7828
+ const key = `${linkedWorkspaceId}:${itemId}`;
7829
+ if (!state.synced.linkedOverrides.requests[key]) {
7830
+ return { next: state, changedIds: [] };
7831
+ }
7832
+ const requests = { ...state.synced.linkedOverrides.requests };
7833
+ delete requests[key];
7834
+ const synced = {
7835
+ ...state.synced,
7836
+ linkedOverrides: { ...state.synced.linkedOverrides, requests },
7837
+ meta: { ...state.synced.meta, updatedAt: now }
7838
+ };
7839
+ return { next: { ...state, synced }, changedIds: [key] };
7840
+ }
7841
+ function applyLinkedOverrideSetEnvVar(state, override, now) {
7842
+ const key = `${override.linkedWorkspaceId}:${override.envName}:${override.varKey}`;
7843
+ const synced = {
7844
+ ...state.synced,
7845
+ linkedOverrides: {
7846
+ ...state.synced.linkedOverrides,
7847
+ environmentVars: {
7848
+ ...state.synced.linkedOverrides.environmentVars,
7849
+ [key]: { ...override, updatedAt: now }
7850
+ }
7851
+ },
7852
+ meta: { ...state.synced.meta, updatedAt: now }
7853
+ };
7854
+ return { next: { ...state, synced }, changedIds: [key] };
7855
+ }
7856
+ function applyLinkedOverrideRemoveEnvVar(state, linkedWorkspaceId, envName, varKey, now) {
7857
+ const key = `${linkedWorkspaceId}:${envName}:${varKey}`;
7858
+ if (!state.synced.linkedOverrides.environmentVars[key]) {
7859
+ return { next: state, changedIds: [] };
7860
+ }
7861
+ const environmentVars = { ...state.synced.linkedOverrides.environmentVars };
7862
+ delete environmentVars[key];
7863
+ const synced = {
7864
+ ...state.synced,
7865
+ linkedOverrides: { ...state.synced.linkedOverrides, environmentVars },
7866
+ meta: { ...state.synced.meta, updatedAt: now }
7867
+ };
7868
+ return { next: { ...state, synced }, changedIds: [key] };
7869
+ }
7870
+ function applyLinkedOverrideClearForLink(state, linkedWorkspaceId, now) {
7871
+ const prefix = `${linkedWorkspaceId}:`;
7872
+ const requestKeys = Object.keys(state.synced.linkedOverrides.requests).filter(
7873
+ (k) => k.startsWith(prefix)
7874
+ );
7875
+ const envKeys = Object.keys(state.synced.linkedOverrides.environmentVars).filter(
7876
+ (k) => k.startsWith(prefix)
7877
+ );
7878
+ if (requestKeys.length === 0 && envKeys.length === 0) {
7879
+ return { next: state, changedIds: [] };
7880
+ }
7881
+ const requests = Object.fromEntries(
7882
+ Object.entries(state.synced.linkedOverrides.requests).filter(([k]) => !k.startsWith(prefix))
7883
+ );
7884
+ const environmentVars = Object.fromEntries(
7885
+ Object.entries(state.synced.linkedOverrides.environmentVars).filter(
7886
+ ([k]) => !k.startsWith(prefix)
7887
+ )
7888
+ );
7889
+ const synced = {
7890
+ ...state.synced,
7891
+ linkedOverrides: { requests, environmentVars },
7892
+ meta: { ...state.synced.meta, updatedAt: now }
7893
+ };
7894
+ return { next: { ...state, synced }, changedIds: [...requestKeys, ...envKeys] };
7895
+ }
7896
+ function applyGlobalAssetUpsertFile(state, file, now) {
7897
+ const files = state.synced.globalAssets.files ?? {};
7898
+ const existing = files[file.id];
7899
+ const next = {
7900
+ ...file,
7901
+ workingBranchRef: file.workingBranchRef !== void 0 ? file.workingBranchRef : existing?.workingBranchRef,
7902
+ baseBranchRef: file.baseBranchRef !== void 0 ? file.baseBranchRef : existing?.baseBranchRef,
7903
+ updatedAt: now
7904
+ };
7905
+ const synced = {
7906
+ ...state.synced,
7907
+ globalAssets: {
7908
+ ...state.synced.globalAssets,
7909
+ files: { ...files, [file.id]: next }
7910
+ },
7911
+ meta: { ...state.synced.meta, updatedAt: now }
7912
+ };
7913
+ return { next: { ...state, synced }, changedIds: [file.id] };
7914
+ }
7915
+ function applyGlobalAssetRemoveFile(state, id, now) {
7916
+ const files = state.synced.globalAssets.files ?? {};
7917
+ if (!files[id]) {
7918
+ return { next: state, changedIds: [] };
7919
+ }
7920
+ const { [id]: _drop, ...rest } = files;
7921
+ void _drop;
7922
+ const requests = { ...state.synced.collections.requests };
7923
+ for (const [reqId, req] of Object.entries(requests)) {
7924
+ const body = clearAssetFromRequestBody(req.body, id);
7925
+ if (body !== req.body) requests[reqId] = { ...req, body, updatedAt: now };
7926
+ }
7927
+ const mockServers = { ...state.synced.mockServers };
7928
+ for (const [serverId, server] of Object.entries(mockServers)) {
7929
+ let touchedServer = false;
7930
+ const endpoints = server.endpoints.map((endpoint) => {
7931
+ let touched = false;
7932
+ const defaultResponse = clearAssetFromMockResponse(endpoint.defaultResponse, id);
7933
+ if (defaultResponse !== endpoint.defaultResponse) touched = true;
7934
+ const requestValidation = endpoint.requestValidation.map((rule) => {
7935
+ const failResponse = clearAssetFromMockResponse(rule.failResponse, id);
7936
+ if (failResponse === rule.failResponse) return rule;
7937
+ touched = true;
7938
+ return { ...rule, failResponse };
7939
+ });
7940
+ const responseRules = endpoint.responseRules.map((rule) => {
7941
+ const response = clearAssetFromMockResponse(rule.response, id);
7942
+ if (response === rule.response) return rule;
7943
+ touched = true;
7944
+ return { ...rule, response };
7945
+ });
7946
+ if (!touched) return endpoint;
7947
+ touchedServer = true;
7948
+ return { ...endpoint, defaultResponse, requestValidation, responseRules };
7949
+ });
7950
+ if (touchedServer) {
7951
+ const source = server.source.kind === "manual" ? { kind: "manual", endpoints } : server.source;
7952
+ mockServers[serverId] = { ...server, source, endpoints, updatedAt: now };
7953
+ }
7954
+ }
7955
+ let local = state.local;
7956
+ if (local.pendingFileUploads && local.pendingFileUploads[id]) {
7957
+ const nextPending = { ...local.pendingFileUploads };
7958
+ delete nextPending[id];
7959
+ local = { ...local, pendingFileUploads: nextPending };
7960
+ }
7961
+ if (local.assetUsageIndex && local.assetUsageIndex[id]) {
7962
+ const nextUsage = { ...local.assetUsageIndex };
7963
+ delete nextUsage[id];
7964
+ local = { ...local, assetUsageIndex: nextUsage };
7965
+ }
7966
+ const existing = files[id];
7967
+ const hadRemoteRef = Boolean(existing.workingBranchRef || existing.baseBranchRef);
7968
+ if (hadRemoteRef && !(local.pendingAttachmentDeletes ?? []).includes(existing.slotId)) {
7969
+ local = {
7970
+ ...local,
7971
+ pendingAttachmentDeletes: [...local.pendingAttachmentDeletes ?? [], existing.slotId]
7972
+ };
7973
+ }
7974
+ const synced = {
7975
+ ...state.synced,
7976
+ collections: { ...state.synced.collections, requests },
7977
+ mockServers,
7978
+ globalAssets: { ...state.synced.globalAssets, files: rest },
7979
+ meta: { ...state.synced.meta, updatedAt: now }
7980
+ };
7981
+ return { next: { synced, local }, changedIds: [id] };
7982
+ }
7983
+ function applyGlobalAssetMarkPushed(state, id, ref, now) {
7984
+ return mutateAssetRef(state, id, now, (asset) => ({ ...asset, workingBranchRef: ref }));
7985
+ }
7986
+ function applyGlobalAssetMarkMerged(state, id, ref, now) {
7987
+ return mutateAssetRef(state, id, now, (asset) => ({ ...asset, baseBranchRef: ref }));
7988
+ }
7989
+ function applyGlobalAssetCleanupWorkingRef(state, id, now) {
7990
+ return mutateAssetRef(state, id, now, (asset) => {
7991
+ if (!asset.workingBranchRef) return asset;
7992
+ return { ...asset, workingBranchRef: null };
7993
+ });
7994
+ }
7995
+ function applyGlobalAssetInvalidateRef(state, id, which, now) {
7996
+ return mutateAssetRef(state, id, now, (asset) => {
7997
+ if (which === "working") {
7998
+ if (!asset.workingBranchRef) return asset;
7999
+ return { ...asset, workingBranchRef: null };
8000
+ }
8001
+ if (!asset.baseBranchRef) return asset;
8002
+ return { ...asset, baseBranchRef: null };
8003
+ });
8004
+ }
8005
+ function mutateAssetRef(state, id, now, transform) {
8006
+ const files = state.synced.globalAssets.files ?? {};
8007
+ const existing = files[id];
8008
+ if (!existing) return { next: state, changedIds: [] };
8009
+ const updated = transform(existing);
8010
+ if (updated === existing) return { next: state, changedIds: [] };
8011
+ const next = { ...updated, updatedAt: now };
8012
+ const synced = {
8013
+ ...state.synced,
8014
+ globalAssets: {
8015
+ ...state.synced.globalAssets,
8016
+ files: { ...files, [id]: next }
8017
+ },
8018
+ meta: { ...state.synced.meta, updatedAt: now }
8019
+ };
8020
+ return { next: { ...state, synced }, changedIds: [id] };
8021
+ }
8022
+ function clearAssetFromRequestBody(body, id) {
8023
+ if (body.type === "binary" && body.attachment?.globalFileAssetId === id) {
8024
+ return { type: "binary", content: "" };
8025
+ }
8026
+ if (body.type !== "form-data" || !body.formRows) return body;
8027
+ let touched = false;
8028
+ const formRows = body.formRows.map((row) => {
8029
+ if (row.kind !== "file" || row.globalFileAssetId !== id) return row;
8030
+ touched = true;
8031
+ return { kind: "file", key: row.key, enabled: row.enabled, slotId: null };
8032
+ });
8033
+ return touched ? { ...body, formRows } : body;
8034
+ }
8035
+ function clearAssetFromMockResponse(response, id) {
8036
+ const body = clearAssetFromMockBody(response.body, id);
8037
+ return body === response.body ? response : { ...response, body };
8038
+ }
8039
+ function clearAssetFromMockBody(body, id) {
8040
+ if (body.type === "binary" && body.attachment?.globalFileAssetId === id) {
8041
+ return { type: "binary", content: "" };
8042
+ }
8043
+ return body;
8044
+ }
7141
8045
  function applyPlanUpsert(state, plan, now) {
7142
8046
  const existing = state.local.executionPlans[plan.id];
7143
8047
  const merged = existing ? { ...existing, ...plan, id: existing.id, createdAt: existing.createdAt, updatedAt: now } : { ...plan, updatedAt: now };
@@ -7223,7 +8127,7 @@ function evictSnapshotsToCap(entries, maxBytes) {
7223
8127
  };
7224
8128
  }
7225
8129
  function applySnapshotCapture(state, args, now) {
7226
- const id = args.id ?? (0, import_shared2.generateId)();
8130
+ const id = args.id ?? (0, import_shared3.generateId)();
7227
8131
  const snapshot2 = {
7228
8132
  id,
7229
8133
  createdAt: now,
@@ -7312,7 +8216,7 @@ function applyHistoryPurge(state, olderThanMs) {
7312
8216
  }
7313
8217
 
7314
8218
  // src/workspace/runPlan.ts
7315
- var import_shared3 = require("@apicircle/shared");
8219
+ var import_shared4 = require("@apicircle/shared");
7316
8220
  var MAX_REQUEST_RUNS = 500;
7317
8221
  var MAX_PLAN_RUNS = 200;
7318
8222
  var ANONYMOUS_ACTOR = { kind: "unknown", name: "unknown" };
@@ -7378,22 +8282,6 @@ function lookupPlanStepRequest(step, synced, local) {
7378
8282
  linkedGlobalAssets: snapshot2.globalAssets
7379
8283
  };
7380
8284
  }
7381
- function mergeRequestOverride(base, patch) {
7382
- const merged = { ...base };
7383
- if (patch.name !== void 0) merged.name = patch.name;
7384
- if (patch.method !== void 0) merged.method = patch.method;
7385
- if (patch.url !== void 0) merged.url = patch.url;
7386
- if (patch.headers !== void 0) merged.headers = patch.headers;
7387
- if (patch.query !== void 0) merged.query = patch.query;
7388
- if (patch.pathParams !== void 0) merged.pathParams = patch.pathParams;
7389
- if (patch.cookies !== void 0) merged.cookies = patch.cookies;
7390
- if (patch.body !== void 0) merged.body = patch.body;
7391
- if (patch.auth !== void 0) merged.auth = patch.auth;
7392
- if (patch.contextVars !== void 0) merged.contextVars = patch.contextVars;
7393
- if (patch.extractions !== void 0) merged.extractions = patch.extractions;
7394
- if (patch.assertions !== void 0) merged.assertions = patch.assertions;
7395
- return merged;
7396
- }
7397
8285
  function applyEnvironmentOverrides(source, linkedWorkspaceId, synced) {
7398
8286
  const overrides = Object.values(synced.linkedOverrides.environmentVars).filter(
7399
8287
  (override) => override.linkedWorkspaceId === linkedWorkspaceId
@@ -7459,7 +8347,7 @@ async function runPlan(state, planId, opts = {}) {
7459
8347
  const baseRefs = plan.envPriorityOrder.length > 0 ? plan.envPriorityOrder : state.synced.environments.priorityOrder;
7460
8348
  const envRefs = opts.env ? [{ kind: "local", name: opts.env }, ...baseRefs] : baseRefs;
7461
8349
  const startedAt = (/* @__PURE__ */ new Date()).toISOString();
7462
- const planRunId = (0, import_shared3.generateId)();
8350
+ const planRunId = (0, import_shared4.generateId)();
7463
8351
  const t0 = Date.now();
7464
8352
  const stepRecords = [];
7465
8353
  const newRequestRuns = [];
@@ -7492,7 +8380,7 @@ async function runPlan(state, planId, opts = {}) {
7492
8380
  const lookup2 = lookupPlanStepRequest(step, state.synced, state.local);
7493
8381
  const baseRequest = lookup2.request;
7494
8382
  if (!baseRequest) {
7495
- const runId = (0, import_shared3.generateId)();
8383
+ const runId = (0, import_shared4.generateId)();
7496
8384
  const error = lookup2.error ?? "Request no longer exists in workspace.";
7497
8385
  newRequestRuns.push(orphanRun(runId, step.requestId, error));
7498
8386
  stepRecords.push({ requestRunId: runId, passed: false });
@@ -7610,7 +8498,7 @@ function buildEnvMaps(synced, secretsById, local) {
7610
8498
  vars[v.key] = v.value;
7611
8499
  }
7612
8500
  }
7613
- flat[(0, import_shared3.envPriorityKey)({ kind: "local", name })] = vars;
8501
+ flat[(0, import_shared4.envPriorityKey)({ kind: "local", name })] = vars;
7614
8502
  }
7615
8503
  if (local) {
7616
8504
  for (const [linkId, snapshot2] of Object.entries(local.linkedCollections)) {
@@ -7627,7 +8515,7 @@ function buildEnvMaps(synced, secretsById, local) {
7627
8515
  vars[variable.key] = variable.value;
7628
8516
  }
7629
8517
  }
7630
- flat[(0, import_shared3.envPriorityKey)({ kind: "linked", linkedWorkspaceId: linkId, envName })] = vars;
8518
+ flat[(0, import_shared4.envPriorityKey)({ kind: "linked", linkedWorkspaceId: linkId, envName })] = vars;
7631
8519
  }
7632
8520
  }
7633
8521
  }
@@ -7654,7 +8542,7 @@ function resolveRequest(request, synced, plan, envRefs, globalContext, flatEnvs,
7654
8542
  contextVars: Object.entries(ctxMap).map(([key, value]) => ({ key, value })),
7655
8543
  environments: flatEnvs,
7656
8544
  activeEnvName: null,
7657
- priorityOrder: envRefs.map(import_shared3.envPriorityKey),
8545
+ priorityOrder: envRefs.map(import_shared4.envPriorityKey),
7658
8546
  secrets: secretsByLabel
7659
8547
  });
7660
8548
  const missing = /* @__PURE__ */ new Set();
@@ -7724,7 +8612,7 @@ function orphanRun(id, requestId, error) {
7724
8612
  function buildRequestRun(resolved, result, assertions) {
7725
8613
  const { preview, truncated } = clampPreview(result.body ?? "");
7726
8614
  return {
7727
- id: (0, import_shared3.generateId)(),
8615
+ id: (0, import_shared4.generateId)(),
7728
8616
  requestId: resolved.id,
7729
8617
  startedAt: result.startedAt,
7730
8618
  durationMs: result.durationMs,
@@ -7744,8 +8632,8 @@ function buildRequestRun(resolved, result, assertions) {
7744
8632
  };
7745
8633
  }
7746
8634
  function clampPreview(value) {
7747
- if (value.length <= import_shared3.RUN_BODY_PREVIEW_LIMIT) return { preview: value, truncated: false };
7748
- return { preview: value.slice(0, import_shared3.RUN_BODY_PREVIEW_LIMIT), truncated: true };
8635
+ if (value.length <= import_shared4.RUN_BODY_PREVIEW_LIMIT) return { preview: value, truncated: false };
8636
+ return { preview: value.slice(0, import_shared4.RUN_BODY_PREVIEW_LIMIT), truncated: true };
7749
8637
  }
7750
8638
  function redactUrlCredentials(url) {
7751
8639
  try {
@@ -7968,9 +8856,9 @@ function toCsv(value) {
7968
8856
  }
7969
8857
 
7970
8858
  // src/transform/computeSavings.ts
7971
- var import_shared4 = require("@apicircle/shared");
8859
+ var import_shared5 = require("@apicircle/shared");
7972
8860
  function computeTransformSavings(body, contentType) {
7973
- const originalBytes = (0, import_shared4.utf8ByteLength)(body);
8861
+ const originalBytes = (0, import_shared5.utf8ByteLength)(body);
7974
8862
  if (!isJsonLike(body, contentType)) {
7975
8863
  return { originalBytes, minifiedBytes: originalBytes, candidates: [] };
7976
8864
  }
@@ -7981,7 +8869,7 @@ function computeTransformSavings(body, contentType) {
7981
8869
  return { originalBytes, minifiedBytes: originalBytes, candidates: [] };
7982
8870
  }
7983
8871
  const minified = JSON.stringify(parsed);
7984
- const minifiedBytes = (0, import_shared4.utf8ByteLength)(minified);
8872
+ const minifiedBytes = (0, import_shared5.utf8ByteLength)(minified);
7985
8873
  const candidates = [];
7986
8874
  try {
7987
8875
  const toon = toToon(parsed);
@@ -8008,7 +8896,7 @@ function computeTransformSavings(body, contentType) {
8008
8896
  };
8009
8897
  }
8010
8898
  function makeCandidate(format, preview, baselineBytes) {
8011
- const bytes = (0, import_shared4.utf8ByteLength)(preview);
8899
+ const bytes = (0, import_shared5.utf8ByteLength)(preview);
8012
8900
  const ratio = baselineBytes === 0 ? 0 : 1 - bytes / baselineBytes;
8013
8901
  return {
8014
8902
  format,
@@ -8037,22 +8925,30 @@ var TRANSFORM_FORMAT_LABELS = {
8037
8925
  HTTP_HEADERS_MAP,
8038
8926
  OAuth2TokenError,
8039
8927
  PlanRunDeniedError,
8928
+ REGISTRY_JSON_PATH,
8040
8929
  RemoteWorkspaceParseError,
8041
8930
  TRANSFORM_FORMAT_LABELS,
8931
+ WORKSPACE_DIR,
8932
+ appendReleaseEntry,
8042
8933
  applyAuth,
8043
8934
  applyAwsSigV4,
8044
8935
  applyContentTypeForBodyType,
8936
+ applyLinkedEnvironmentOverrides,
8045
8937
  applyLinkedUpdate,
8046
8938
  applyMerge,
8047
8939
  applyMutation,
8048
8940
  applyPathParams,
8049
8941
  assertNoPlaintextCredentials,
8942
+ attachmentPath,
8943
+ attachmentsDir,
8050
8944
  buildAuthorizeUrl,
8051
8945
  buildAutoHeaders,
8052
8946
  buildDigestAuthHeader,
8053
8947
  buildHawkAuthHeader,
8948
+ buildLinkedSnapshot,
8054
8949
  buildNtlmType1Negotiate,
8055
8950
  buildNtlmType3Authenticate,
8951
+ buildReleaseEntry,
8056
8952
  buildRequest,
8057
8953
  buildScope,
8058
8954
  collectAttachmentSlots,
@@ -8066,6 +8962,7 @@ var TRANSFORM_FORMAT_LABELS = {
8066
8962
  composeUrl,
8067
8963
  composeUrlWithQuery,
8068
8964
  computeCodeChallenge,
8965
+ computeRequestOverridePatch,
8069
8966
  computeThreeWayDiff,
8070
8967
  computeTransformSavings,
8071
8968
  decryptString,
@@ -8078,6 +8975,7 @@ var TRANSFORM_FORMAT_LABELS = {
8078
8975
  exportKey,
8079
8976
  extractContext,
8080
8977
  fetchOAuth2Token,
8978
+ fetchRemoteWorkspaceJson,
8081
8979
  findPathPlaceholders,
8082
8980
  generateAesKey,
8083
8981
  generateCodeVerifier,
@@ -8095,14 +8993,18 @@ var TRANSFORM_FORMAT_LABELS = {
8095
8993
  hasUnpushedChanges,
8096
8994
  importApicircleFolderInto,
8097
8995
  importKey,
8996
+ initSecretCrypto,
8098
8997
  isApicircleEnvironment,
8099
8998
  isApicircleFolderExport,
8100
8999
  isDesktop,
9000
+ isEmptyOverridePatch,
8101
9001
  isInsomniaExport,
8102
9002
  isPostmanEnvironment,
8103
9003
  isPostmanV2Collection,
8104
9004
  isValidSemver,
9005
+ ledgerFromProbe,
8105
9006
  lookup,
9007
+ mergeRequestOverride,
8106
9008
  mergeWithAutoHeaders,
8107
9009
  normalizeContentType,
8108
9010
  parseApicircleEnvironment,
@@ -8113,12 +9015,15 @@ var TRANSFORM_FORMAT_LABELS = {
8113
9015
  parseDigestChallenge,
8114
9016
  parseGraphqlSchema,
8115
9017
  parseInsomniaCollection,
9018
+ parseLinkedWorkspaceJson,
8116
9019
  parseNtlmType2Challenge,
8117
9020
  parsePostmanCollection,
8118
9021
  parsePostmanEnvironment,
9022
+ parseRegistryActiveId,
8119
9023
  parseSemver,
8120
9024
  parseUrlQuery,
8121
9025
  parseWorkspaceJson,
9026
+ plaintextEnvMap,
8122
9027
  pollDeviceFlow,
8123
9028
  preSendValidation,
8124
9029
  previewLinkedUpdate,
@@ -8131,6 +9036,7 @@ var TRANSFORM_FORMAT_LABELS = {
8131
9036
  requestRunToExecutionResult,
8132
9037
  resolveInheritedAuth,
8133
9038
  resolvePlanRef,
9039
+ resolveRequestForExecution,
8134
9040
  resolveString,
8135
9041
  resolveStringMap,
8136
9042
  runAssertions,
@@ -8152,7 +9058,9 @@ var TRANSFORM_FORMAT_LABELS = {
8152
9058
  toYaml,
8153
9059
  tokenizeCurl,
8154
9060
  tryParsePayload,
9061
+ unlockSecretCrypto,
8155
9062
  validateBranchName,
9063
+ workspaceJsonPath,
8156
9064
  yankRelease
8157
9065
  });
8158
9066
  //# sourceMappingURL=index.cjs.map