@apicircle/core 1.0.5 → 1.0.7

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
@@ -21,6 +21,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
21
21
  var src_exports = {};
22
22
  __export(src_exports, {
23
23
  ANONYMOUS_ACTOR: () => ANONYMOUS_ACTOR,
24
+ APICIRCLE_FOLDER_EXPORT_FORMAT: () => APICIRCLE_FOLDER_EXPORT_FORMAT,
24
25
  DESKTOP_APP_ORIGIN: () => DESKTOP_APP_ORIGIN,
25
26
  EMPTY_UNPUSHED_SUMMARY: () => EMPTY_UNPUSHED_SUMMARY,
26
27
  HTTP_HEADERS_MAP: () => HTTP_HEADERS_MAP,
@@ -45,6 +46,8 @@ __export(src_exports, {
45
46
  buildRequest: () => buildRequest,
46
47
  buildScope: () => buildScope,
47
48
  collectAttachmentSlots: () => collectAttachmentSlots,
49
+ collectFolderExport: () => collectFolderExport,
50
+ collectFolderExportCredentials: () => collectFolderExportCredentials,
48
51
  collectVariableSuggestions: () => collectVariableSuggestions,
49
52
  compareSemver: () => compareSemver,
50
53
  composeBody: () => composeBody,
@@ -80,7 +83,10 @@ __export(src_exports, {
80
83
  getLanguageFromContentType: () => getLanguageFromContentType,
81
84
  getVariableAutocomplete: () => getVariableAutocomplete,
82
85
  hasUnpushedChanges: () => hasUnpushedChanges,
86
+ importApicircleFolderInto: () => importApicircleFolderInto,
83
87
  importKey: () => importKey,
88
+ isApicircleEnvironment: () => isApicircleEnvironment,
89
+ isApicircleFolderExport: () => isApicircleFolderExport,
84
90
  isDesktop: () => isDesktop,
85
91
  isInsomniaExport: () => isInsomniaExport,
86
92
  isPostmanEnvironment: () => isPostmanEnvironment,
@@ -89,6 +95,10 @@ __export(src_exports, {
89
95
  lookup: () => lookup,
90
96
  mergeWithAutoHeaders: () => mergeWithAutoHeaders,
91
97
  normalizeContentType: () => normalizeContentType,
98
+ parseApicircleEnvironment: () => parseApicircleEnvironment,
99
+ parseApicircleEnvironmentDoc: () => parseApicircleEnvironmentDoc,
100
+ parseApicircleFolderExport: () => parseApicircleFolderExport,
101
+ parseApicircleFolderExportDoc: () => parseApicircleFolderExportDoc,
92
102
  parseCurl: () => parseCurl,
93
103
  parseDigestChallenge: () => parseDigestChallenge,
94
104
  parseGraphqlSchema: () => parseGraphqlSchema,
@@ -104,6 +114,7 @@ __export(src_exports, {
104
114
  previewLinkedUpdate: () => previewLinkedUpdate,
105
115
  publishRelease: () => publishRelease,
106
116
  readJsonPath: () => readJsonPath,
117
+ redactFolderExportCredentials: () => redactFolderExportCredentials,
107
118
  redactForGit: () => redactForGit,
108
119
  refreshToken: () => refreshToken,
109
120
  requestDeviceAuthorization: () => requestDeviceAuthorization,
@@ -116,11 +127,13 @@ __export(src_exports, {
116
127
  runClientCredentials: () => runClientCredentials,
117
128
  runPlan: () => runPlan,
118
129
  runRopc: () => runRopc,
130
+ serializeFolderExport: () => serializeFolderExport,
119
131
  serializePayload: () => serializePayload,
120
132
  serializeWorkspaceForGit: () => serializeWorkspaceForGit,
121
133
  signJwt: () => signJwt,
122
134
  slugify: () => slugify,
123
135
  sortVersionsDesc: () => sortVersionsDesc,
136
+ suggestFolderExportFilename: () => suggestFolderExportFilename,
124
137
  suggestHeaders: () => suggestHeaders,
125
138
  summarizeUnpushedChanges: () => summarizeUnpushedChanges,
126
139
  supportedContentTypeLanguageMap: () => supportedContentTypeLanguageMap,
@@ -3079,6 +3092,9 @@ function resolveLocation(from, location) {
3079
3092
  return null;
3080
3093
  }
3081
3094
  }
3095
+ function isBrowserRuntime() {
3096
+ return typeof window !== "undefined" && typeof window.document !== "undefined";
3097
+ }
3082
3098
  async function executeRequest(req, opts = {}) {
3083
3099
  const fetchImpl = opts.fetchImpl ?? globalThis.fetch;
3084
3100
  const timeoutMs = opts.timeoutMs === void 0 ? DEFAULT_TIMEOUT_MS : opts.timeoutMs;
@@ -3103,6 +3119,7 @@ async function executeRequest(req, opts = {}) {
3103
3119
  () => controller.abort(new Error(`Request timed out after ${timeoutMs}ms`)),
3104
3120
  timeoutMs
3105
3121
  );
3122
+ const redirectMode = isBrowserRuntime() ? "follow" : "manual";
3106
3123
  let currentUrl = builtRequest.url;
3107
3124
  let currentHeaders = { ...builtRequest.headers };
3108
3125
  let currentMethod = builtRequest.method;
@@ -3112,7 +3129,7 @@ async function executeRequest(req, opts = {}) {
3112
3129
  headers: currentHeaders,
3113
3130
  body: currentBody,
3114
3131
  signal: controller.signal,
3115
- redirect: "manual"
3132
+ redirect: redirectMode
3116
3133
  });
3117
3134
  let redirectCount = 0;
3118
3135
  while (REDIRECT_STATUSES.has(response.status) && redirectCount < MAX_REDIRECTS) {
@@ -3138,7 +3155,7 @@ async function executeRequest(req, opts = {}) {
3138
3155
  headers: currentHeaders,
3139
3156
  body: currentBody,
3140
3157
  signal: controller.signal,
3141
- redirect: "manual"
3158
+ redirect: redirectMode
3142
3159
  });
3143
3160
  redirectCount++;
3144
3161
  }
@@ -4105,6 +4122,699 @@ function parseAuth2(auth, warnings, name) {
4105
4122
  }
4106
4123
  }
4107
4124
 
4125
+ // src/import/apicircleFolder.ts
4126
+ var import_shared = require("@apicircle/shared");
4127
+
4128
+ // src/export/folderExportCredentials.ts
4129
+ function collectFolderExportCredentials(envelope) {
4130
+ const out = [];
4131
+ if (envelope.folder.auth) {
4132
+ out.push(
4133
+ ...authCredentialFields(envelope.folder.auth).map(
4134
+ (f) => buildCredential(
4135
+ "root-folder",
4136
+ envelope.source.folderId,
4137
+ envelope.folder.name,
4138
+ envelope.folder.auth,
4139
+ f
4140
+ )
4141
+ )
4142
+ );
4143
+ }
4144
+ for (const sub of envelope.folder.subfolders) {
4145
+ if (!sub.auth) continue;
4146
+ out.push(
4147
+ ...authCredentialFields(sub.auth).map(
4148
+ (f) => buildCredential("subfolder", sub.id, sub.name, sub.auth, f)
4149
+ )
4150
+ );
4151
+ }
4152
+ for (const req of envelope.folder.requests) {
4153
+ out.push(
4154
+ ...authCredentialFields(req.auth).map(
4155
+ (f) => buildCredential("request", req.id, req.name, req.auth, f)
4156
+ )
4157
+ );
4158
+ }
4159
+ return out.sort(credentialCompare);
4160
+ }
4161
+ function redactFolderExportCredentials(envelope, includeIds = /* @__PURE__ */ new Set()) {
4162
+ const next = {
4163
+ ...envelope,
4164
+ folder: {
4165
+ ...envelope.folder,
4166
+ auth: envelope.folder.auth ? redactAuthForScope(
4167
+ envelope.folder.auth,
4168
+ credentialIdsFor("root-folder", envelope.source.folderId, envelope.folder.auth),
4169
+ includeIds
4170
+ ) : envelope.folder.auth,
4171
+ subfolders: envelope.folder.subfolders.map((sub) => {
4172
+ if (!sub.auth) return sub;
4173
+ const ids = credentialIdsFor("subfolder", sub.id, sub.auth);
4174
+ return {
4175
+ ...sub,
4176
+ auth: redactAuthForScope(sub.auth, ids, includeIds)
4177
+ };
4178
+ }),
4179
+ requests: envelope.folder.requests.map((req) => ({
4180
+ ...req,
4181
+ auth: redactAuthForScope(
4182
+ req.auth,
4183
+ credentialIdsFor("request", req.id, req.auth),
4184
+ includeIds
4185
+ )
4186
+ }))
4187
+ }
4188
+ };
4189
+ return next;
4190
+ }
4191
+ function authCredentialFields(auth) {
4192
+ switch (auth.type) {
4193
+ case "none":
4194
+ case "inherit":
4195
+ case "custom-header":
4196
+ return [];
4197
+ case "basic":
4198
+ return [{ field: "password", label: "Basic \xB7 password" }];
4199
+ case "bearer":
4200
+ return auth.token ? [{ field: "token", label: "Bearer \xB7 token" }] : [];
4201
+ case "api-key":
4202
+ return auth.value ? [{ field: "value", label: "API key \xB7 value" }] : [];
4203
+ case "digest":
4204
+ return [{ field: "password", label: "Digest \xB7 password" }];
4205
+ case "ntlm":
4206
+ return [{ field: "password", label: "NTLM \xB7 password" }];
4207
+ case "hawk":
4208
+ return auth.hawkKey ? [{ field: "hawkKey", label: "Hawk \xB7 hawkKey" }] : [];
4209
+ case "jwt-bearer":
4210
+ return [
4211
+ ...auth.secretOrKey ? [{ field: "secretOrKey", label: "JWT \xB7 secretOrKey" }] : [],
4212
+ ...auth.token ? [{ field: "token", label: "JWT \xB7 token" }] : []
4213
+ ];
4214
+ case "aws-sigv4":
4215
+ return [
4216
+ ...auth.secretAccessKey ? [{ field: "secretAccessKey", label: "AWS SigV4 \xB7 secretAccessKey" }] : [],
4217
+ ...auth.sessionToken ? [{ field: "sessionToken", label: "AWS SigV4 \xB7 sessionToken" }] : []
4218
+ ];
4219
+ case "oauth2-client-credentials":
4220
+ case "oauth2-auth-code":
4221
+ case "oauth2-pkce":
4222
+ return [
4223
+ ...auth.clientSecret ? [{ field: "clientSecret", label: `${auth.type} \xB7 clientSecret` }] : [],
4224
+ ...auth.accessToken ? [{ field: "accessToken", label: `${auth.type} \xB7 accessToken` }] : [],
4225
+ ...auth.refreshToken ? [{ field: "refreshToken", label: `${auth.type} \xB7 refreshToken` }] : []
4226
+ ];
4227
+ case "oauth2-password":
4228
+ return [
4229
+ ...auth.clientSecret ? [{ field: "clientSecret", label: "oauth2-password \xB7 clientSecret" }] : [],
4230
+ ...auth.password ? [{ field: "password", label: "oauth2-password \xB7 password" }] : [],
4231
+ ...auth.accessToken ? [{ field: "accessToken", label: "oauth2-password \xB7 accessToken" }] : [],
4232
+ ...auth.refreshToken ? [{ field: "refreshToken", label: "oauth2-password \xB7 refreshToken" }] : []
4233
+ ];
4234
+ case "oauth2-implicit":
4235
+ return auth.accessToken ? [{ field: "accessToken", label: "oauth2-implicit \xB7 accessToken" }] : [];
4236
+ case "oauth2-device":
4237
+ return [
4238
+ ...auth.accessToken ? [{ field: "accessToken", label: "oauth2-device \xB7 accessToken" }] : [],
4239
+ ...auth.refreshToken ? [{ field: "refreshToken", label: "oauth2-device \xB7 refreshToken" }] : []
4240
+ ];
4241
+ default:
4242
+ return [];
4243
+ }
4244
+ }
4245
+ function buildCredential(scope, ownerId, ownerName, auth, desc) {
4246
+ const prefix = scope === "request" ? "request" : "folder";
4247
+ return {
4248
+ id: `${prefix}:${ownerId}.${auth.type}.${desc.field}`,
4249
+ scope,
4250
+ authType: auth.type,
4251
+ field: desc.field,
4252
+ label: desc.label,
4253
+ ownerName,
4254
+ ownerId
4255
+ };
4256
+ }
4257
+ function credentialIdsFor(scope, ownerId, auth) {
4258
+ const ids = /* @__PURE__ */ new Map();
4259
+ const prefix = scope === "request" ? "request" : "folder";
4260
+ for (const desc of authCredentialFields(auth)) {
4261
+ ids.set(desc.field, `${prefix}:${ownerId}.${auth.type}.${desc.field}`);
4262
+ }
4263
+ return ids;
4264
+ }
4265
+ function redactAuthForScope(auth, ids, includeIds) {
4266
+ const shouldBlank = (field) => {
4267
+ const id = ids.get(field);
4268
+ return !!id && !includeIds.has(id);
4269
+ };
4270
+ switch (auth.type) {
4271
+ case "none":
4272
+ case "inherit":
4273
+ case "custom-header":
4274
+ return auth;
4275
+ case "basic":
4276
+ return shouldBlank("password") ? { ...auth, password: "" } : auth;
4277
+ case "bearer":
4278
+ return shouldBlank("token") ? { ...auth, token: "" } : auth;
4279
+ case "api-key":
4280
+ return shouldBlank("value") ? { ...auth, value: "" } : auth;
4281
+ case "digest":
4282
+ return shouldBlank("password") ? { ...auth, password: "" } : auth;
4283
+ case "ntlm":
4284
+ return shouldBlank("password") ? { ...auth, password: "" } : auth;
4285
+ case "hawk":
4286
+ return shouldBlank("hawkKey") ? { ...auth, hawkKey: "" } : auth;
4287
+ case "jwt-bearer":
4288
+ return {
4289
+ ...auth,
4290
+ secretOrKey: shouldBlank("secretOrKey") ? "" : auth.secretOrKey,
4291
+ token: shouldBlank("token") ? "" : auth.token
4292
+ };
4293
+ case "aws-sigv4":
4294
+ return {
4295
+ ...auth,
4296
+ secretAccessKey: shouldBlank("secretAccessKey") ? "" : auth.secretAccessKey,
4297
+ sessionToken: shouldBlank("sessionToken") ? "" : auth.sessionToken
4298
+ };
4299
+ case "oauth2-client-credentials":
4300
+ case "oauth2-auth-code":
4301
+ case "oauth2-pkce":
4302
+ return {
4303
+ ...auth,
4304
+ clientSecret: shouldBlank("clientSecret") ? "" : auth.clientSecret,
4305
+ accessToken: shouldBlank("accessToken") ? "" : auth.accessToken,
4306
+ refreshToken: shouldBlank("refreshToken") ? "" : auth.refreshToken
4307
+ };
4308
+ case "oauth2-password":
4309
+ return {
4310
+ ...auth,
4311
+ clientSecret: shouldBlank("clientSecret") ? "" : auth.clientSecret,
4312
+ password: shouldBlank("password") ? "" : auth.password,
4313
+ accessToken: shouldBlank("accessToken") ? "" : auth.accessToken,
4314
+ refreshToken: shouldBlank("refreshToken") ? "" : auth.refreshToken
4315
+ };
4316
+ case "oauth2-implicit":
4317
+ return {
4318
+ ...auth,
4319
+ accessToken: shouldBlank("accessToken") ? "" : auth.accessToken
4320
+ };
4321
+ case "oauth2-device":
4322
+ return {
4323
+ ...auth,
4324
+ accessToken: shouldBlank("accessToken") ? "" : auth.accessToken,
4325
+ refreshToken: shouldBlank("refreshToken") ? "" : auth.refreshToken
4326
+ };
4327
+ default:
4328
+ return auth;
4329
+ }
4330
+ }
4331
+ function scopeRank(scope) {
4332
+ if (scope === "root-folder") return 0;
4333
+ if (scope === "subfolder") return 1;
4334
+ return 2;
4335
+ }
4336
+ function credentialCompare(a, b) {
4337
+ const r = scopeRank(a.scope) - scopeRank(b.scope);
4338
+ if (r !== 0) return r;
4339
+ return a.ownerName.localeCompare(b.ownerName, void 0, { sensitivity: "base" });
4340
+ }
4341
+
4342
+ // src/export/folderExport.ts
4343
+ var APICIRCLE_FOLDER_EXPORT_FORMAT = "apicircle.folder/v1";
4344
+ function collectFolderExport(args) {
4345
+ const { synced, folderId } = args;
4346
+ const root = synced.collections.folders[folderId];
4347
+ if (!root) return null;
4348
+ const now = args.now ?? (/* @__PURE__ */ new Date()).toISOString();
4349
+ const appVersion = args.appVersion ?? "apicircle-studio";
4350
+ const folderIds = /* @__PURE__ */ new Set([folderId]);
4351
+ let grew = true;
4352
+ while (grew) {
4353
+ grew = false;
4354
+ for (const f of Object.values(synced.collections.folders)) {
4355
+ if (folderIds.has(f.id)) continue;
4356
+ if (f.parentId && folderIds.has(f.parentId)) {
4357
+ folderIds.add(f.id);
4358
+ grew = true;
4359
+ }
4360
+ }
4361
+ }
4362
+ const subfolders = [];
4363
+ for (const f of Object.values(synced.collections.folders)) {
4364
+ if (f.id !== folderId && folderIds.has(f.id)) subfolders.push(cloneFolder(f));
4365
+ }
4366
+ const requests = [];
4367
+ for (const r of Object.values(synced.collections.requests)) {
4368
+ if (r.folderId && folderIds.has(r.folderId)) requests.push(cloneRequest(r));
4369
+ }
4370
+ const dependencies = collectDependencies(synced, requests);
4371
+ const envelope = {
4372
+ format: APICIRCLE_FOLDER_EXPORT_FORMAT,
4373
+ exportedAt: now,
4374
+ appVersion,
4375
+ source: {
4376
+ workspaceId: synced.workspaceId,
4377
+ folderId,
4378
+ folderName: root.name
4379
+ },
4380
+ folder: {
4381
+ name: root.name,
4382
+ auth: root.auth,
4383
+ subfolders,
4384
+ requests
4385
+ },
4386
+ dependencies
4387
+ };
4388
+ const report = buildReport(envelope);
4389
+ return { envelope, report };
4390
+ }
4391
+ function serializeFolderExport(envelope) {
4392
+ return JSON.stringify(envelope, null, 2);
4393
+ }
4394
+ function suggestFolderExportFilename(envelope) {
4395
+ const slug = envelope.folder.name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
4396
+ const base = slug || "folder";
4397
+ return `${base}.apicircle.json`;
4398
+ }
4399
+ function cloneFolder(f) {
4400
+ return {
4401
+ ...f,
4402
+ auth: f.auth ? { ...f.auth } : void 0
4403
+ };
4404
+ }
4405
+ function cloneRequest(r) {
4406
+ return {
4407
+ ...r,
4408
+ headers: r.headers.map((h) => ({ ...h })),
4409
+ query: r.query.map((q) => ({ ...q })),
4410
+ pathParams: r.pathParams ? { ...r.pathParams } : void 0,
4411
+ cookies: r.cookies ? r.cookies.map((c) => ({ ...c })) : void 0,
4412
+ body: cloneBody(r.body),
4413
+ auth: { ...r.auth },
4414
+ contextVars: r.contextVars.map((v) => ({ ...v })),
4415
+ extractions: r.extractions.map((e) => ({ ...e })),
4416
+ assertions: r.assertions.map((a) => ({ ...a }))
4417
+ };
4418
+ }
4419
+ function cloneBody(body) {
4420
+ if (body.type === "form-data") {
4421
+ return {
4422
+ ...body,
4423
+ formRows: body.formRows?.map((row) => ({ ...row })) ?? body.formRows
4424
+ };
4425
+ }
4426
+ if (body.type === "binary") {
4427
+ return {
4428
+ ...body,
4429
+ attachment: body.attachment ? { ...body.attachment } : void 0
4430
+ };
4431
+ }
4432
+ return { ...body };
4433
+ }
4434
+ function collectDependencies(synced, requests) {
4435
+ const schemaIds = /* @__PURE__ */ new Set();
4436
+ const graphqlIds = /* @__PURE__ */ new Set();
4437
+ const fileIds = /* @__PURE__ */ new Set();
4438
+ for (const r of requests) {
4439
+ if (r.bodySchemaId) schemaIds.add(r.bodySchemaId);
4440
+ if (r.graphqlSchemaId) graphqlIds.add(r.graphqlSchemaId);
4441
+ if (r.body.type === "binary" && r.body.attachment?.globalFileAssetId) {
4442
+ fileIds.add(r.body.attachment.globalFileAssetId);
4443
+ }
4444
+ if (r.body.type === "form-data" && r.body.formRows) {
4445
+ for (const row of r.body.formRows) {
4446
+ if (row.kind === "file" && row.globalFileAssetId) fileIds.add(row.globalFileAssetId);
4447
+ }
4448
+ }
4449
+ }
4450
+ const assets = synced.globalAssets;
4451
+ const schemas = [];
4452
+ for (const id of schemaIds) {
4453
+ const s = assets.schemas[id];
4454
+ if (s) schemas.push({ ...s });
4455
+ }
4456
+ const graphql = [];
4457
+ for (const id of graphqlIds) {
4458
+ const g = assets.graphql[id];
4459
+ if (g) graphql.push({ ...g });
4460
+ }
4461
+ const files = [];
4462
+ for (const id of fileIds) {
4463
+ const f = assets.files?.[id];
4464
+ if (f) files.push({ ...f });
4465
+ }
4466
+ schemas.sort(byNameThenId);
4467
+ graphql.sort(byNameThenId);
4468
+ files.sort(byNameThenId);
4469
+ return { schemas, graphql, files };
4470
+ }
4471
+ function byNameThenId(a, b) {
4472
+ const c = a.name.localeCompare(b.name, void 0, { sensitivity: "base" });
4473
+ return c !== 0 ? c : a.id.localeCompare(b.id);
4474
+ }
4475
+ function buildReport(envelope) {
4476
+ const subfolderCount = envelope.folder.subfolders.length;
4477
+ const requestCount = envelope.folder.requests.length;
4478
+ const totalFolderCount = subfolderCount + 1;
4479
+ const credentials = collectFolderExportCredentials(envelope);
4480
+ return {
4481
+ folderName: envelope.folder.name,
4482
+ requestCount,
4483
+ subfolderCount,
4484
+ totalFolderCount,
4485
+ dependencies: {
4486
+ schemas: envelope.dependencies.schemas.map((s) => ({ id: s.id, name: s.name })),
4487
+ graphql: envelope.dependencies.graphql.map((g) => ({
4488
+ id: g.id,
4489
+ name: g.name,
4490
+ kind: g.kind
4491
+ })),
4492
+ files: envelope.dependencies.files.map((f) => ({
4493
+ id: f.id,
4494
+ name: f.name,
4495
+ filename: f.filename,
4496
+ size: f.size,
4497
+ mimeType: f.mimeType
4498
+ }))
4499
+ },
4500
+ hasDependencies: envelope.dependencies.schemas.length > 0 || envelope.dependencies.graphql.length > 0 || envelope.dependencies.files.length > 0,
4501
+ credentials,
4502
+ hasCredentials: credentials.length > 0
4503
+ };
4504
+ }
4505
+
4506
+ // src/import/apicircleFolder.ts
4507
+ function isApicircleFolderExport(doc) {
4508
+ if (!doc || typeof doc !== "object") return false;
4509
+ const d = doc;
4510
+ return d.format === APICIRCLE_FOLDER_EXPORT_FORMAT;
4511
+ }
4512
+ function parseApicircleFolderExport(input, options = {}) {
4513
+ let doc;
4514
+ try {
4515
+ doc = JSON.parse(input);
4516
+ } catch (err) {
4517
+ throw new Error(`Couldn't parse JSON: ${err instanceof Error ? err.message : String(err)}`);
4518
+ }
4519
+ return parseApicircleFolderExportDoc(doc, options);
4520
+ }
4521
+ function parseApicircleFolderExportDoc(doc, options = {}) {
4522
+ const id = options.idGenerator ?? import_shared.generateId;
4523
+ if (!isApicircleFolderExport(doc)) {
4524
+ throw new Error(
4525
+ `Unsupported format. Expected an API Circle folder export ("format": "${APICIRCLE_FOLDER_EXPORT_FORMAT}").`
4526
+ );
4527
+ }
4528
+ const envelope = doc;
4529
+ validateEnvelopeShape(envelope);
4530
+ const warnings = [];
4531
+ const folderIdMap = /* @__PURE__ */ new Map();
4532
+ folderIdMap.set(envelope.source.folderId, id());
4533
+ for (const f of envelope.folder.subfolders) folderIdMap.set(f.id, id());
4534
+ const requestIdMap = /* @__PURE__ */ new Map();
4535
+ for (const r of envelope.folder.requests) requestIdMap.set(r.id, id());
4536
+ const schemaIdMap = /* @__PURE__ */ new Map();
4537
+ for (const s of envelope.dependencies.schemas) schemaIdMap.set(s.id, id());
4538
+ const graphqlIdMap = /* @__PURE__ */ new Map();
4539
+ for (const g of envelope.dependencies.graphql) graphqlIdMap.set(g.id, id());
4540
+ const fileIdMap = /* @__PURE__ */ new Map();
4541
+ for (const f of envelope.dependencies.files) fileIdMap.set(f.id, id());
4542
+ const rootFolderId = folderIdMap.get(envelope.source.folderId);
4543
+ const subfolders = envelope.folder.subfolders.map((f) => {
4544
+ const newId = folderIdMap.get(f.id);
4545
+ let newParentId;
4546
+ if (f.parentId === null) {
4547
+ newParentId = rootFolderId;
4548
+ } else {
4549
+ const mapped = folderIdMap.get(f.parentId);
4550
+ if (!mapped) {
4551
+ warnings.push(
4552
+ `Subfolder "${f.name}" referenced parentId "${f.parentId}" that wasn't present in the export \u2014 reattached under "${envelope.folder.name}".`
4553
+ );
4554
+ newParentId = rootFolderId;
4555
+ } else {
4556
+ newParentId = mapped;
4557
+ }
4558
+ }
4559
+ return {
4560
+ ...f,
4561
+ id: newId,
4562
+ parentId: newParentId,
4563
+ auth: f.auth ? { ...f.auth } : void 0
4564
+ };
4565
+ });
4566
+ const schemas = envelope.dependencies.schemas.map((s) => ({
4567
+ ...s,
4568
+ id: schemaIdMap.get(s.id)
4569
+ }));
4570
+ const graphql = envelope.dependencies.graphql.map((g) => ({
4571
+ ...g,
4572
+ id: graphqlIdMap.get(g.id)
4573
+ }));
4574
+ const files = envelope.dependencies.files.map((f) => ({
4575
+ ...f,
4576
+ id: fileIdMap.get(f.id)
4577
+ }));
4578
+ const requests = envelope.folder.requests.map((r) => {
4579
+ const newId = requestIdMap.get(r.id);
4580
+ let newFolderId;
4581
+ if (r.folderId === null) {
4582
+ newFolderId = rootFolderId;
4583
+ } else {
4584
+ const mapped = folderIdMap.get(r.folderId);
4585
+ if (!mapped) {
4586
+ warnings.push(
4587
+ `Request "${r.name}" referenced folderId "${r.folderId}" that wasn't present in the export \u2014 reattached under "${envelope.folder.name}".`
4588
+ );
4589
+ newFolderId = rootFolderId;
4590
+ } else {
4591
+ newFolderId = mapped;
4592
+ }
4593
+ }
4594
+ const bodySchemaId = remapDependencyRef(
4595
+ r.bodySchemaId,
4596
+ schemaIdMap,
4597
+ `Request "${r.name}".bodySchemaId`,
4598
+ warnings
4599
+ );
4600
+ const graphqlSchemaId = remapDependencyRef(
4601
+ r.graphqlSchemaId,
4602
+ graphqlIdMap,
4603
+ `Request "${r.name}".graphqlSchemaId`,
4604
+ warnings
4605
+ );
4606
+ return {
4607
+ ...r,
4608
+ id: newId,
4609
+ folderId: newFolderId,
4610
+ bodySchemaId,
4611
+ graphqlSchemaId,
4612
+ headers: r.headers.map((h) => ({ ...h })),
4613
+ query: r.query.map((q) => ({ ...q })),
4614
+ pathParams: r.pathParams ? { ...r.pathParams } : void 0,
4615
+ cookies: r.cookies ? r.cookies.map((c) => ({ ...c })) : void 0,
4616
+ body: remapBodyFileRefs(r.body, fileIdMap, r.name, warnings),
4617
+ auth: { ...r.auth },
4618
+ contextVars: r.contextVars.map((v) => ({ ...v })),
4619
+ extractions: r.extractions.map((e) => ({ ...e })),
4620
+ assertions: r.assertions.map((a) => ({ ...a }))
4621
+ };
4622
+ });
4623
+ return {
4624
+ rootFolder: {
4625
+ id: rootFolderId,
4626
+ name: envelope.folder.name,
4627
+ auth: envelope.folder.auth ? { ...envelope.folder.auth } : void 0
4628
+ },
4629
+ subfolders,
4630
+ requests,
4631
+ dependencies: { schemas, graphql, files },
4632
+ sourceFolderName: envelope.source.folderName,
4633
+ warnings
4634
+ };
4635
+ }
4636
+ function validateEnvelopeShape(envelope) {
4637
+ if (!envelope.folder || typeof envelope.folder !== "object") {
4638
+ throw new Error('API Circle folder export is missing the "folder" section.');
4639
+ }
4640
+ if (typeof envelope.folder.name !== "string" || envelope.folder.name.length === 0) {
4641
+ throw new Error('API Circle folder export must have a non-empty "folder.name".');
4642
+ }
4643
+ if (!Array.isArray(envelope.folder.subfolders)) {
4644
+ throw new Error('API Circle folder export must have a "folder.subfolders" array.');
4645
+ }
4646
+ if (!Array.isArray(envelope.folder.requests)) {
4647
+ throw new Error('API Circle folder export must have a "folder.requests" array.');
4648
+ }
4649
+ if (!envelope.dependencies || typeof envelope.dependencies !== "object") {
4650
+ throw new Error('API Circle folder export is missing the "dependencies" section.');
4651
+ }
4652
+ if (!Array.isArray(envelope.dependencies.schemas) || !Array.isArray(envelope.dependencies.graphql) || !Array.isArray(envelope.dependencies.files)) {
4653
+ throw new Error(
4654
+ 'API Circle folder export "dependencies" must have schemas / graphql / files arrays.'
4655
+ );
4656
+ }
4657
+ if (!envelope.source || typeof envelope.source !== "object") {
4658
+ throw new Error('API Circle folder export is missing the "source" section.');
4659
+ }
4660
+ if (typeof envelope.source.folderId !== "string" || typeof envelope.source.folderName !== "string") {
4661
+ throw new Error('API Circle folder export "source" must include "folderId" and "folderName".');
4662
+ }
4663
+ }
4664
+ function remapDependencyRef(value, map, label, warnings) {
4665
+ if (value === null || value === void 0) return value;
4666
+ const mapped = map.get(value);
4667
+ if (mapped) return mapped;
4668
+ warnings.push(
4669
+ `${label} referenced a dependency ("${value}") that wasn't embedded in the export \u2014 reference dropped on import.`
4670
+ );
4671
+ return null;
4672
+ }
4673
+ function remapBodyFileRefs(body, fileIdMap, requestName, warnings) {
4674
+ if (body.type === "binary") {
4675
+ if (!body.attachment) return { ...body };
4676
+ const oldId = body.attachment.globalFileAssetId;
4677
+ let nextGlobalFileAssetId = oldId;
4678
+ if (oldId) {
4679
+ const mapped = fileIdMap.get(oldId);
4680
+ if (mapped) {
4681
+ nextGlobalFileAssetId = mapped;
4682
+ } else {
4683
+ warnings.push(
4684
+ `Request "${requestName}".body.attachment referenced file asset "${oldId}" that wasn't embedded in the export \u2014 re-attach the file after import.`
4685
+ );
4686
+ nextGlobalFileAssetId = null;
4687
+ }
4688
+ }
4689
+ return {
4690
+ ...body,
4691
+ attachment: {
4692
+ ...body.attachment,
4693
+ // Reset slotId — the destination workspace owns its own slots.
4694
+ slotId: null,
4695
+ globalFileAssetId: nextGlobalFileAssetId
4696
+ }
4697
+ };
4698
+ }
4699
+ if (body.type === "form-data") {
4700
+ const formRows = body.formRows?.map((row) => {
4701
+ if (row.kind !== "file") return { ...row };
4702
+ const oldId = row.globalFileAssetId;
4703
+ let nextGlobalFileAssetId = oldId;
4704
+ if (oldId) {
4705
+ const mapped = fileIdMap.get(oldId);
4706
+ if (mapped) {
4707
+ nextGlobalFileAssetId = mapped;
4708
+ } else {
4709
+ warnings.push(
4710
+ `Request "${requestName}" form-data row "${row.key}" referenced file asset "${oldId}" that wasn't embedded in the export \u2014 re-attach the file after import.`
4711
+ );
4712
+ nextGlobalFileAssetId = null;
4713
+ }
4714
+ }
4715
+ return {
4716
+ ...row,
4717
+ slotId: null,
4718
+ globalFileAssetId: nextGlobalFileAssetId
4719
+ };
4720
+ });
4721
+ return { ...body, formRows };
4722
+ }
4723
+ return { ...body };
4724
+ }
4725
+
4726
+ // src/import/apicircleEnvironment.ts
4727
+ function isApicircleEnvironment(doc) {
4728
+ if (!doc || typeof doc !== "object") return false;
4729
+ const d = doc;
4730
+ return (d.apicircleEnvironment === 1 || d.apicircleEnvironment === 2) && typeof d.name === "string" && Array.isArray(d.variables);
4731
+ }
4732
+ function parseApicircleEnvironment(input) {
4733
+ let doc;
4734
+ try {
4735
+ doc = JSON.parse(input);
4736
+ } catch (err) {
4737
+ throw new Error(`Couldn't parse JSON: ${err instanceof Error ? err.message : String(err)}`);
4738
+ }
4739
+ return parseApicircleEnvironmentDoc(doc);
4740
+ }
4741
+ function parseApicircleEnvironmentDoc(doc) {
4742
+ if (!isApicircleEnvironment(doc)) {
4743
+ throw new Error(
4744
+ 'Unsupported format. Expected an API Circle environment export ("apicircleEnvironment": 1 or 2).'
4745
+ );
4746
+ }
4747
+ const name = doc.name.trim();
4748
+ if (!name) {
4749
+ throw new Error('API Circle environment export must have a non-empty "name".');
4750
+ }
4751
+ const payloadVersion = doc.apicircleEnvironment;
4752
+ const warnings = [];
4753
+ const variables = [];
4754
+ const encryptedBindingHints = [];
4755
+ for (let i = 0; i < doc.variables.length; i += 1) {
4756
+ const raw = doc.variables[i];
4757
+ if (!raw || typeof raw !== "object") {
4758
+ warnings.push(`Row #${i + 1} was not an object \u2014 dropped.`);
4759
+ continue;
4760
+ }
4761
+ const key = typeof raw.key === "string" ? raw.key.trim() : "";
4762
+ if (!key) {
4763
+ warnings.push(`Row #${i + 1} had no key \u2014 dropped.`);
4764
+ continue;
4765
+ }
4766
+ if (raw.encrypted === true) {
4767
+ const secretKeyId = typeof raw.secretKeyId === "string" ? raw.secretKeyId : "";
4768
+ const labelFromSecret = readLabelFromSecretField(raw.secret);
4769
+ const ciphertext = payloadVersion === 2 && typeof raw.value === "string" && raw.value.startsWith("enc:") ? raw.value : null;
4770
+ const salt = payloadVersion === 2 ? readSaltFromSecretField(raw.secret) : null;
4771
+ if (!secretKeyId && !labelFromSecret) {
4772
+ warnings.push(
4773
+ `"${key}" was marked encrypted but carried no secretKeyId and no secret label \u2014 imported as an empty plain variable. Re-bind it under Environments after import.`
4774
+ );
4775
+ variables.push({ key, value: "", encrypted: false });
4776
+ continue;
4777
+ }
4778
+ variables.push({
4779
+ key,
4780
+ value: ciphertext ?? "",
4781
+ encrypted: true,
4782
+ secretKeyId: secretKeyId || void 0
4783
+ });
4784
+ const labelFromFallback = !labelFromSecret;
4785
+ encryptedBindingHints.push({
4786
+ varKey: key,
4787
+ label: labelFromSecret ?? key,
4788
+ originSecretKeyId: secretKeyId || void 0,
4789
+ labelFromFallback,
4790
+ ciphertext,
4791
+ salt
4792
+ });
4793
+ continue;
4794
+ }
4795
+ variables.push({
4796
+ key,
4797
+ value: typeof raw.value === "string" ? raw.value : "",
4798
+ encrypted: false
4799
+ });
4800
+ }
4801
+ return { name, variables, encryptedBindingHints, payloadVersion, warnings };
4802
+ }
4803
+ function readLabelFromSecretField(field) {
4804
+ if (!field || typeof field !== "object") return null;
4805
+ const f = field;
4806
+ if (typeof f.label !== "string") return null;
4807
+ const trimmed = f.label.trim();
4808
+ return trimmed.length > 0 ? trimmed : null;
4809
+ }
4810
+ function readSaltFromSecretField(field) {
4811
+ if (!field || typeof field !== "object") return null;
4812
+ const f = field;
4813
+ if (typeof f.salt !== "string") return null;
4814
+ const trimmed = f.salt.trim();
4815
+ return trimmed.length > 0 ? trimmed : null;
4816
+ }
4817
+
4108
4818
  // src/assertions/runAssertions.ts
4109
4819
  function runAssertions(assertions, exec) {
4110
4820
  return assertions.map((a) => runOne(a, exec));
@@ -5852,7 +6562,232 @@ function structurallyEqual2(a, b) {
5852
6562
  }
5853
6563
 
5854
6564
  // src/workspace/applyMutation.ts
5855
- var import_shared = require("@apicircle/shared");
6565
+ var import_shared2 = require("@apicircle/shared");
6566
+
6567
+ // src/workspace/apicircleFolderImport.ts
6568
+ function importApicircleFolderInto(synced, parsed, parentFolderId) {
6569
+ let cur = synced;
6570
+ const schemaRemap = /* @__PURE__ */ new Map();
6571
+ const graphqlRemap = /* @__PURE__ */ new Map();
6572
+ const fileRemap = /* @__PURE__ */ new Map();
6573
+ let schemasAdded = 0;
6574
+ let schemasReused = 0;
6575
+ let graphqlAdded = 0;
6576
+ let graphqlReused = 0;
6577
+ let filesAdded = 0;
6578
+ let filesReused = 0;
6579
+ const filesRequiringReattachment = [];
6580
+ const now = (/* @__PURE__ */ new Date()).toISOString();
6581
+ for (const incoming of parsed.dependencies.schemas) {
6582
+ const existing = findMatchingSchema(cur, incoming);
6583
+ if (existing) {
6584
+ schemaRemap.set(incoming.id, existing.id);
6585
+ schemasReused += 1;
6586
+ continue;
6587
+ }
6588
+ cur = mergeGlobalSchema(cur, incoming, now);
6589
+ schemaRemap.set(incoming.id, incoming.id);
6590
+ schemasAdded += 1;
6591
+ }
6592
+ for (const incoming of parsed.dependencies.graphql) {
6593
+ const existing = findMatchingGraphQL(cur, incoming);
6594
+ if (existing) {
6595
+ graphqlRemap.set(incoming.id, existing.id);
6596
+ graphqlReused += 1;
6597
+ continue;
6598
+ }
6599
+ cur = mergeGlobalGraphQL(cur, incoming, now);
6600
+ graphqlRemap.set(incoming.id, incoming.id);
6601
+ graphqlAdded += 1;
6602
+ }
6603
+ for (const incoming of parsed.dependencies.files) {
6604
+ const existing = findMatchingFile(cur, incoming);
6605
+ if (existing) {
6606
+ fileRemap.set(incoming.id, existing.id);
6607
+ filesReused += 1;
6608
+ continue;
6609
+ }
6610
+ cur = mergeGlobalFile(cur, incoming, now);
6611
+ fileRemap.set(incoming.id, incoming.id);
6612
+ filesAdded += 1;
6613
+ filesRequiringReattachment.push(incoming.id);
6614
+ }
6615
+ const rootName = uniquifyFolderName(cur, parentFolderId, parsed.rootFolder.name);
6616
+ const root = {
6617
+ id: parsed.rootFolder.id,
6618
+ name: rootName,
6619
+ parentId: parentFolderId,
6620
+ auth: parsed.rootFolder.auth ? { ...parsed.rootFolder.auth } : void 0
6621
+ };
6622
+ cur = insertFolder(
6623
+ cur,
6624
+ root,
6625
+ /* attachToTree */
6626
+ parentFolderId === null
6627
+ );
6628
+ for (const f of parsed.subfolders) {
6629
+ cur = insertFolder(
6630
+ cur,
6631
+ f,
6632
+ /* attachToTree */
6633
+ false
6634
+ );
6635
+ }
6636
+ for (const r of parsed.requests) {
6637
+ const rewritten = {
6638
+ ...r,
6639
+ bodySchemaId: rewriteRef(r.bodySchemaId, schemaRemap),
6640
+ graphqlSchemaId: rewriteRef(r.graphqlSchemaId, graphqlRemap),
6641
+ body: rewriteBodyFileRefs(r.body, fileRemap)
6642
+ };
6643
+ cur = insertRequest(cur, rewritten);
6644
+ }
6645
+ return {
6646
+ synced: { ...cur, meta: { ...cur.meta, updatedAt: now } },
6647
+ rootFolderId: root.id,
6648
+ rootFolderName: rootName,
6649
+ counts: {
6650
+ folders: parsed.subfolders.length + 1,
6651
+ requests: parsed.requests.length,
6652
+ schemasAdded,
6653
+ schemasReused,
6654
+ graphqlAdded,
6655
+ graphqlReused,
6656
+ filesAdded,
6657
+ filesReused
6658
+ },
6659
+ filesRequiringReattachment
6660
+ };
6661
+ }
6662
+ function isFolderNameAvailable(synced, parentFolderId, name) {
6663
+ const trimmed = name.trim().toLowerCase();
6664
+ if (!trimmed) return false;
6665
+ for (const node of Object.values(synced.collections.folders)) {
6666
+ if (node.parentId !== parentFolderId) continue;
6667
+ if (node.name.trim().toLowerCase() === trimmed) return false;
6668
+ }
6669
+ return true;
6670
+ }
6671
+ function uniquifyFolderName(synced, parentFolderId, desired) {
6672
+ if (isFolderNameAvailable(synced, parentFolderId, desired)) return desired;
6673
+ let n = 2;
6674
+ while (!isFolderNameAvailable(synced, parentFolderId, `${desired} (${n})`)) {
6675
+ n += 1;
6676
+ if (n > 999) return `${desired} (${n})`;
6677
+ }
6678
+ return `${desired} (${n})`;
6679
+ }
6680
+ function insertFolder(synced, folder, attachToTree) {
6681
+ const folders = { ...synced.collections.folders, [folder.id]: folder };
6682
+ const tree = attachToTree ? {
6683
+ ...synced.collections.tree,
6684
+ children: [...synced.collections.tree.children, { kind: "folder", id: folder.id }]
6685
+ } : synced.collections.tree;
6686
+ return {
6687
+ ...synced,
6688
+ collections: { ...synced.collections, folders, tree }
6689
+ };
6690
+ }
6691
+ function insertRequest(synced, request) {
6692
+ return {
6693
+ ...synced,
6694
+ collections: {
6695
+ ...synced.collections,
6696
+ requests: { ...synced.collections.requests, [request.id]: request }
6697
+ }
6698
+ };
6699
+ }
6700
+ function withGlobalAssets(synced) {
6701
+ return synced.globalAssets ?? { schemas: {}, graphql: {}, files: {} };
6702
+ }
6703
+ function findMatchingSchema(synced, candidate) {
6704
+ const ga = withGlobalAssets(synced);
6705
+ for (const existing of Object.values(ga.schemas)) {
6706
+ if (existing.name === candidate.name && existing.schema === candidate.schema) {
6707
+ return existing;
6708
+ }
6709
+ }
6710
+ return null;
6711
+ }
6712
+ function findMatchingGraphQL(synced, candidate) {
6713
+ const ga = withGlobalAssets(synced);
6714
+ for (const existing of Object.values(ga.graphql)) {
6715
+ if (existing.name === candidate.name && existing.kind === candidate.kind && existing.source === candidate.source) {
6716
+ return existing;
6717
+ }
6718
+ }
6719
+ return null;
6720
+ }
6721
+ function findMatchingFile(synced, candidate) {
6722
+ const ga = withGlobalAssets(synced);
6723
+ const files = ga.files ?? {};
6724
+ for (const existing of Object.values(files)) {
6725
+ if (existing.name === candidate.name && existing.filename === candidate.filename && existing.size === candidate.size) {
6726
+ return existing;
6727
+ }
6728
+ }
6729
+ return null;
6730
+ }
6731
+ function mergeGlobalSchema(synced, schema, now) {
6732
+ const ga = withGlobalAssets(synced);
6733
+ return {
6734
+ ...synced,
6735
+ globalAssets: {
6736
+ ...ga,
6737
+ schemas: { ...ga.schemas, [schema.id]: { ...schema, updatedAt: now } }
6738
+ }
6739
+ };
6740
+ }
6741
+ function mergeGlobalGraphQL(synced, graphql, now) {
6742
+ const ga = withGlobalAssets(synced);
6743
+ return {
6744
+ ...synced,
6745
+ globalAssets: {
6746
+ ...ga,
6747
+ graphql: { ...ga.graphql, [graphql.id]: { ...graphql, updatedAt: now } }
6748
+ }
6749
+ };
6750
+ }
6751
+ function mergeGlobalFile(synced, file, now) {
6752
+ const ga = withGlobalAssets(synced);
6753
+ const files = ga.files ?? {};
6754
+ return {
6755
+ ...synced,
6756
+ globalAssets: {
6757
+ ...ga,
6758
+ files: { ...files, [file.id]: { ...file, updatedAt: now } }
6759
+ }
6760
+ };
6761
+ }
6762
+ function rewriteRef(value, remap) {
6763
+ if (value === null || value === void 0) return value;
6764
+ return remap.get(value) ?? value;
6765
+ }
6766
+ function rewriteBodyFileRefs(body, remap) {
6767
+ if (body.type === "binary") {
6768
+ if (!body.attachment) return body;
6769
+ const rewritten = rewriteRef(body.attachment.globalFileAssetId, remap);
6770
+ if (rewritten === body.attachment.globalFileAssetId) return body;
6771
+ return {
6772
+ ...body,
6773
+ attachment: { ...body.attachment, globalFileAssetId: rewritten }
6774
+ };
6775
+ }
6776
+ if (body.type === "form-data" && body.formRows) {
6777
+ let mutated = false;
6778
+ const next = body.formRows.map((row) => {
6779
+ if (row.kind !== "file") return row;
6780
+ const rewritten = rewriteRef(row.globalFileAssetId, remap);
6781
+ if (rewritten === row.globalFileAssetId) return row;
6782
+ mutated = true;
6783
+ return { ...row, globalFileAssetId: rewritten };
6784
+ });
6785
+ return mutated ? { ...body, formRows: next } : body;
6786
+ }
6787
+ return body;
6788
+ }
6789
+
6790
+ // src/workspace/applyMutation.ts
5856
6791
  function applyMutation(state, patch, options = {}) {
5857
6792
  const now = options.now ?? (/* @__PURE__ */ new Date()).toISOString();
5858
6793
  switch (patch.kind) {
@@ -5868,6 +6803,8 @@ function applyMutation(state, patch, options = {}) {
5868
6803
  return applyFolderDelete(state, patch.id, now);
5869
6804
  case "folder.move":
5870
6805
  return applyFolderMove(state, patch.id, patch.newParentId, now);
6806
+ case "folder.import_apicircle":
6807
+ return applyFolderImportApicircle(state, patch.parsed, patch.parentFolderId, now);
5871
6808
  case "environment.upsert":
5872
6809
  return applyEnvUpsert(state, patch.environment, now);
5873
6810
  case "environment.delete":
@@ -5876,6 +6813,8 @@ function applyMutation(state, patch, options = {}) {
5876
6813
  return applyEnvSetActive(state, patch.name, now);
5877
6814
  case "environment.setPriority":
5878
6815
  return applyEnvSetPriority(state, patch.order, now);
6816
+ case "secretKey.upsert":
6817
+ return applySecretKeyUpsert(state, patch.meta, now);
5879
6818
  case "assertion.upsert":
5880
6819
  return applyAssertionUpsert(state, patch.requestId, patch.assertion, now);
5881
6820
  case "assertion.delete":
@@ -6036,6 +6975,23 @@ function applyFolderMove(state, id, newParentId, now) {
6036
6975
  };
6037
6976
  return { next: { ...state, synced }, changedIds: [id] };
6038
6977
  }
6978
+ function applyFolderImportApicircle(state, parsed, parentFolderId, now) {
6979
+ const result = importApicircleFolderInto(
6980
+ state.synced,
6981
+ parsed,
6982
+ parentFolderId
6983
+ );
6984
+ const synced = {
6985
+ ...result.synced,
6986
+ meta: { ...result.synced.meta, updatedAt: now }
6987
+ };
6988
+ const changedIds = [
6989
+ result.rootFolderId,
6990
+ ...parsed.subfolders.map((f) => f.id),
6991
+ ...parsed.requests.map((r) => r.id)
6992
+ ];
6993
+ return { next: { ...state, synced }, changedIds };
6994
+ }
6039
6995
  function applyEnvUpsert(state, environment, now) {
6040
6996
  const trimmed = environment.name.trim();
6041
6997
  if (!trimmed) {
@@ -6098,7 +7054,7 @@ function applyEnvSetPriority(state, order, now) {
6098
7054
  const knownLocal = new Set(Object.keys(state.synced.environments.items));
6099
7055
  const seen = /* @__PURE__ */ new Set();
6100
7056
  const filtered = order.filter((ref) => {
6101
- const key = (0, import_shared.envPriorityKey)(ref);
7057
+ const key = (0, import_shared2.envPriorityKey)(ref);
6102
7058
  if (seen.has(key)) return false;
6103
7059
  if (ref.kind === "local" && !knownLocal.has(ref.name)) return false;
6104
7060
  seen.add(key);
@@ -6109,7 +7065,21 @@ function applyEnvSetPriority(state, order, now) {
6109
7065
  environments: { ...state.synced.environments, priorityOrder: filtered },
6110
7066
  meta: { ...state.synced.meta, updatedAt: now }
6111
7067
  };
6112
- return { next: { ...state, synced }, changedIds: filtered.map(import_shared.envPriorityKey) };
7068
+ return { next: { ...state, synced }, changedIds: filtered.map(import_shared2.envPriorityKey) };
7069
+ }
7070
+ function applySecretKeyUpsert(state, meta, now) {
7071
+ if (!meta.id || !meta.label.trim() || !meta.salt) {
7072
+ return { next: state, changedIds: [] };
7073
+ }
7074
+ const synced = {
7075
+ ...state.synced,
7076
+ secretKeys: {
7077
+ ...state.synced.secretKeys ?? {},
7078
+ [meta.id]: { ...meta, label: meta.label.trim() }
7079
+ },
7080
+ meta: { ...state.synced.meta, updatedAt: now }
7081
+ };
7082
+ return { next: { ...state, synced }, changedIds: [meta.id] };
6113
7083
  }
6114
7084
  function applyAssertionUpsert(state, requestId, assertion, now) {
6115
7085
  const request = state.synced.collections.requests[requestId];
@@ -6253,7 +7223,7 @@ function evictSnapshotsToCap(entries, maxBytes) {
6253
7223
  };
6254
7224
  }
6255
7225
  function applySnapshotCapture(state, args, now) {
6256
- const id = args.id ?? (0, import_shared.generateId)();
7226
+ const id = args.id ?? (0, import_shared2.generateId)();
6257
7227
  const snapshot2 = {
6258
7228
  id,
6259
7229
  createdAt: now,
@@ -6342,7 +7312,7 @@ function applyHistoryPurge(state, olderThanMs) {
6342
7312
  }
6343
7313
 
6344
7314
  // src/workspace/runPlan.ts
6345
- var import_shared2 = require("@apicircle/shared");
7315
+ var import_shared3 = require("@apicircle/shared");
6346
7316
  var MAX_REQUEST_RUNS = 500;
6347
7317
  var MAX_PLAN_RUNS = 200;
6348
7318
  var ANONYMOUS_ACTOR = { kind: "unknown", name: "unknown" };
@@ -6489,7 +7459,7 @@ async function runPlan(state, planId, opts = {}) {
6489
7459
  const baseRefs = plan.envPriorityOrder.length > 0 ? plan.envPriorityOrder : state.synced.environments.priorityOrder;
6490
7460
  const envRefs = opts.env ? [{ kind: "local", name: opts.env }, ...baseRefs] : baseRefs;
6491
7461
  const startedAt = (/* @__PURE__ */ new Date()).toISOString();
6492
- const planRunId = (0, import_shared2.generateId)();
7462
+ const planRunId = (0, import_shared3.generateId)();
6493
7463
  const t0 = Date.now();
6494
7464
  const stepRecords = [];
6495
7465
  const newRequestRuns = [];
@@ -6522,7 +7492,7 @@ async function runPlan(state, planId, opts = {}) {
6522
7492
  const lookup2 = lookupPlanStepRequest(step, state.synced, state.local);
6523
7493
  const baseRequest = lookup2.request;
6524
7494
  if (!baseRequest) {
6525
- const runId = (0, import_shared2.generateId)();
7495
+ const runId = (0, import_shared3.generateId)();
6526
7496
  const error = lookup2.error ?? "Request no longer exists in workspace.";
6527
7497
  newRequestRuns.push(orphanRun(runId, step.requestId, error));
6528
7498
  stepRecords.push({ requestRunId: runId, passed: false });
@@ -6640,7 +7610,7 @@ function buildEnvMaps(synced, secretsById, local) {
6640
7610
  vars[v.key] = v.value;
6641
7611
  }
6642
7612
  }
6643
- flat[(0, import_shared2.envPriorityKey)({ kind: "local", name })] = vars;
7613
+ flat[(0, import_shared3.envPriorityKey)({ kind: "local", name })] = vars;
6644
7614
  }
6645
7615
  if (local) {
6646
7616
  for (const [linkId, snapshot2] of Object.entries(local.linkedCollections)) {
@@ -6657,7 +7627,7 @@ function buildEnvMaps(synced, secretsById, local) {
6657
7627
  vars[variable.key] = variable.value;
6658
7628
  }
6659
7629
  }
6660
- flat[(0, import_shared2.envPriorityKey)({ kind: "linked", linkedWorkspaceId: linkId, envName })] = vars;
7630
+ flat[(0, import_shared3.envPriorityKey)({ kind: "linked", linkedWorkspaceId: linkId, envName })] = vars;
6661
7631
  }
6662
7632
  }
6663
7633
  }
@@ -6684,7 +7654,7 @@ function resolveRequest(request, synced, plan, envRefs, globalContext, flatEnvs,
6684
7654
  contextVars: Object.entries(ctxMap).map(([key, value]) => ({ key, value })),
6685
7655
  environments: flatEnvs,
6686
7656
  activeEnvName: null,
6687
- priorityOrder: envRefs.map(import_shared2.envPriorityKey),
7657
+ priorityOrder: envRefs.map(import_shared3.envPriorityKey),
6688
7658
  secrets: secretsByLabel
6689
7659
  });
6690
7660
  const missing = /* @__PURE__ */ new Set();
@@ -6754,7 +7724,7 @@ function orphanRun(id, requestId, error) {
6754
7724
  function buildRequestRun(resolved, result, assertions) {
6755
7725
  const { preview, truncated } = clampPreview(result.body ?? "");
6756
7726
  return {
6757
- id: (0, import_shared2.generateId)(),
7727
+ id: (0, import_shared3.generateId)(),
6758
7728
  requestId: resolved.id,
6759
7729
  startedAt: result.startedAt,
6760
7730
  durationMs: result.durationMs,
@@ -6774,8 +7744,8 @@ function buildRequestRun(resolved, result, assertions) {
6774
7744
  };
6775
7745
  }
6776
7746
  function clampPreview(value) {
6777
- if (value.length <= import_shared2.RUN_BODY_PREVIEW_LIMIT) return { preview: value, truncated: false };
6778
- return { preview: value.slice(0, import_shared2.RUN_BODY_PREVIEW_LIMIT), truncated: true };
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 };
6779
7749
  }
6780
7750
  function redactUrlCredentials(url) {
6781
7751
  try {
@@ -6998,9 +7968,9 @@ function toCsv(value) {
6998
7968
  }
6999
7969
 
7000
7970
  // src/transform/computeSavings.ts
7001
- var import_shared3 = require("@apicircle/shared");
7971
+ var import_shared4 = require("@apicircle/shared");
7002
7972
  function computeTransformSavings(body, contentType) {
7003
- const originalBytes = (0, import_shared3.utf8ByteLength)(body);
7973
+ const originalBytes = (0, import_shared4.utf8ByteLength)(body);
7004
7974
  if (!isJsonLike(body, contentType)) {
7005
7975
  return { originalBytes, minifiedBytes: originalBytes, candidates: [] };
7006
7976
  }
@@ -7011,7 +7981,7 @@ function computeTransformSavings(body, contentType) {
7011
7981
  return { originalBytes, minifiedBytes: originalBytes, candidates: [] };
7012
7982
  }
7013
7983
  const minified = JSON.stringify(parsed);
7014
- const minifiedBytes = (0, import_shared3.utf8ByteLength)(minified);
7984
+ const minifiedBytes = (0, import_shared4.utf8ByteLength)(minified);
7015
7985
  const candidates = [];
7016
7986
  try {
7017
7987
  const toon = toToon(parsed);
@@ -7038,7 +8008,7 @@ function computeTransformSavings(body, contentType) {
7038
8008
  };
7039
8009
  }
7040
8010
  function makeCandidate(format, preview, baselineBytes) {
7041
- const bytes = (0, import_shared3.utf8ByteLength)(preview);
8011
+ const bytes = (0, import_shared4.utf8ByteLength)(preview);
7042
8012
  const ratio = baselineBytes === 0 ? 0 : 1 - bytes / baselineBytes;
7043
8013
  return {
7044
8014
  format,
@@ -7061,6 +8031,7 @@ var TRANSFORM_FORMAT_LABELS = {
7061
8031
  // Annotate the CommonJS export names for ESM import in node:
7062
8032
  0 && (module.exports = {
7063
8033
  ANONYMOUS_ACTOR,
8034
+ APICIRCLE_FOLDER_EXPORT_FORMAT,
7064
8035
  DESKTOP_APP_ORIGIN,
7065
8036
  EMPTY_UNPUSHED_SUMMARY,
7066
8037
  HTTP_HEADERS_MAP,
@@ -7085,6 +8056,8 @@ var TRANSFORM_FORMAT_LABELS = {
7085
8056
  buildRequest,
7086
8057
  buildScope,
7087
8058
  collectAttachmentSlots,
8059
+ collectFolderExport,
8060
+ collectFolderExportCredentials,
7088
8061
  collectVariableSuggestions,
7089
8062
  compareSemver,
7090
8063
  composeBody,
@@ -7120,7 +8093,10 @@ var TRANSFORM_FORMAT_LABELS = {
7120
8093
  getLanguageFromContentType,
7121
8094
  getVariableAutocomplete,
7122
8095
  hasUnpushedChanges,
8096
+ importApicircleFolderInto,
7123
8097
  importKey,
8098
+ isApicircleEnvironment,
8099
+ isApicircleFolderExport,
7124
8100
  isDesktop,
7125
8101
  isInsomniaExport,
7126
8102
  isPostmanEnvironment,
@@ -7129,6 +8105,10 @@ var TRANSFORM_FORMAT_LABELS = {
7129
8105
  lookup,
7130
8106
  mergeWithAutoHeaders,
7131
8107
  normalizeContentType,
8108
+ parseApicircleEnvironment,
8109
+ parseApicircleEnvironmentDoc,
8110
+ parseApicircleFolderExport,
8111
+ parseApicircleFolderExportDoc,
7132
8112
  parseCurl,
7133
8113
  parseDigestChallenge,
7134
8114
  parseGraphqlSchema,
@@ -7144,6 +8124,7 @@ var TRANSFORM_FORMAT_LABELS = {
7144
8124
  previewLinkedUpdate,
7145
8125
  publishRelease,
7146
8126
  readJsonPath,
8127
+ redactFolderExportCredentials,
7147
8128
  redactForGit,
7148
8129
  refreshToken,
7149
8130
  requestDeviceAuthorization,
@@ -7156,11 +8137,13 @@ var TRANSFORM_FORMAT_LABELS = {
7156
8137
  runClientCredentials,
7157
8138
  runPlan,
7158
8139
  runRopc,
8140
+ serializeFolderExport,
7159
8141
  serializePayload,
7160
8142
  serializeWorkspaceForGit,
7161
8143
  signJwt,
7162
8144
  slugify,
7163
8145
  sortVersionsDesc,
8146
+ suggestFolderExportFilename,
7164
8147
  suggestHeaders,
7165
8148
  summarizeUnpushedChanges,
7166
8149
  supportedContentTypeLanguageMap,