@apicircle/core 1.0.6 → 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/{chunk-PUXJFN2Z.js → chunk-L5DQT7V6.js} +3 -3
- package/dist/{chunk-PUXJFN2Z.js.map → chunk-L5DQT7V6.js.map} +1 -1
- package/dist/index.cjs +996 -17
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +123 -3
- package/dist/index.d.ts +123 -3
- package/dist/index.js +972 -6
- package/dist/index.js.map +1 -1
- package/dist/patches-ysO3y8pG.d.cts +312 -0
- package/dist/patches-ysO3y8pG.d.ts +312 -0
- package/dist/workspace/file-backed.cjs +2 -2
- package/dist/workspace/file-backed.cjs.map +1 -1
- package/dist/workspace/file-backed.d.cts +1 -1
- package/dist/workspace/file-backed.d.ts +1 -1
- package/dist/workspace/file-backed.js +1 -1
- package/dist/workspace/registry.cjs +2 -2
- package/dist/workspace/registry.cjs.map +1 -1
- package/dist/workspace/registry.d.cts +1 -1
- package/dist/workspace/registry.d.ts +1 -1
- package/dist/workspace/registry.js +1 -1
- package/package.json +28 -2
- package/dist/patches-N7mvDpXn.d.cts +0 -85
- package/dist/patches-N7mvDpXn.d.ts +0 -85
package/dist/index.js
CHANGED
|
@@ -3973,6 +3973,699 @@ function parseAuth2(auth, warnings, name) {
|
|
|
3973
3973
|
}
|
|
3974
3974
|
}
|
|
3975
3975
|
|
|
3976
|
+
// src/import/apicircleFolder.ts
|
|
3977
|
+
import { generateId } from "@apicircle/shared";
|
|
3978
|
+
|
|
3979
|
+
// src/export/folderExportCredentials.ts
|
|
3980
|
+
function collectFolderExportCredentials(envelope) {
|
|
3981
|
+
const out = [];
|
|
3982
|
+
if (envelope.folder.auth) {
|
|
3983
|
+
out.push(
|
|
3984
|
+
...authCredentialFields(envelope.folder.auth).map(
|
|
3985
|
+
(f) => buildCredential(
|
|
3986
|
+
"root-folder",
|
|
3987
|
+
envelope.source.folderId,
|
|
3988
|
+
envelope.folder.name,
|
|
3989
|
+
envelope.folder.auth,
|
|
3990
|
+
f
|
|
3991
|
+
)
|
|
3992
|
+
)
|
|
3993
|
+
);
|
|
3994
|
+
}
|
|
3995
|
+
for (const sub of envelope.folder.subfolders) {
|
|
3996
|
+
if (!sub.auth) continue;
|
|
3997
|
+
out.push(
|
|
3998
|
+
...authCredentialFields(sub.auth).map(
|
|
3999
|
+
(f) => buildCredential("subfolder", sub.id, sub.name, sub.auth, f)
|
|
4000
|
+
)
|
|
4001
|
+
);
|
|
4002
|
+
}
|
|
4003
|
+
for (const req of envelope.folder.requests) {
|
|
4004
|
+
out.push(
|
|
4005
|
+
...authCredentialFields(req.auth).map(
|
|
4006
|
+
(f) => buildCredential("request", req.id, req.name, req.auth, f)
|
|
4007
|
+
)
|
|
4008
|
+
);
|
|
4009
|
+
}
|
|
4010
|
+
return out.sort(credentialCompare);
|
|
4011
|
+
}
|
|
4012
|
+
function redactFolderExportCredentials(envelope, includeIds = /* @__PURE__ */ new Set()) {
|
|
4013
|
+
const next = {
|
|
4014
|
+
...envelope,
|
|
4015
|
+
folder: {
|
|
4016
|
+
...envelope.folder,
|
|
4017
|
+
auth: envelope.folder.auth ? redactAuthForScope(
|
|
4018
|
+
envelope.folder.auth,
|
|
4019
|
+
credentialIdsFor("root-folder", envelope.source.folderId, envelope.folder.auth),
|
|
4020
|
+
includeIds
|
|
4021
|
+
) : envelope.folder.auth,
|
|
4022
|
+
subfolders: envelope.folder.subfolders.map((sub) => {
|
|
4023
|
+
if (!sub.auth) return sub;
|
|
4024
|
+
const ids = credentialIdsFor("subfolder", sub.id, sub.auth);
|
|
4025
|
+
return {
|
|
4026
|
+
...sub,
|
|
4027
|
+
auth: redactAuthForScope(sub.auth, ids, includeIds)
|
|
4028
|
+
};
|
|
4029
|
+
}),
|
|
4030
|
+
requests: envelope.folder.requests.map((req) => ({
|
|
4031
|
+
...req,
|
|
4032
|
+
auth: redactAuthForScope(
|
|
4033
|
+
req.auth,
|
|
4034
|
+
credentialIdsFor("request", req.id, req.auth),
|
|
4035
|
+
includeIds
|
|
4036
|
+
)
|
|
4037
|
+
}))
|
|
4038
|
+
}
|
|
4039
|
+
};
|
|
4040
|
+
return next;
|
|
4041
|
+
}
|
|
4042
|
+
function authCredentialFields(auth) {
|
|
4043
|
+
switch (auth.type) {
|
|
4044
|
+
case "none":
|
|
4045
|
+
case "inherit":
|
|
4046
|
+
case "custom-header":
|
|
4047
|
+
return [];
|
|
4048
|
+
case "basic":
|
|
4049
|
+
return [{ field: "password", label: "Basic \xB7 password" }];
|
|
4050
|
+
case "bearer":
|
|
4051
|
+
return auth.token ? [{ field: "token", label: "Bearer \xB7 token" }] : [];
|
|
4052
|
+
case "api-key":
|
|
4053
|
+
return auth.value ? [{ field: "value", label: "API key \xB7 value" }] : [];
|
|
4054
|
+
case "digest":
|
|
4055
|
+
return [{ field: "password", label: "Digest \xB7 password" }];
|
|
4056
|
+
case "ntlm":
|
|
4057
|
+
return [{ field: "password", label: "NTLM \xB7 password" }];
|
|
4058
|
+
case "hawk":
|
|
4059
|
+
return auth.hawkKey ? [{ field: "hawkKey", label: "Hawk \xB7 hawkKey" }] : [];
|
|
4060
|
+
case "jwt-bearer":
|
|
4061
|
+
return [
|
|
4062
|
+
...auth.secretOrKey ? [{ field: "secretOrKey", label: "JWT \xB7 secretOrKey" }] : [],
|
|
4063
|
+
...auth.token ? [{ field: "token", label: "JWT \xB7 token" }] : []
|
|
4064
|
+
];
|
|
4065
|
+
case "aws-sigv4":
|
|
4066
|
+
return [
|
|
4067
|
+
...auth.secretAccessKey ? [{ field: "secretAccessKey", label: "AWS SigV4 \xB7 secretAccessKey" }] : [],
|
|
4068
|
+
...auth.sessionToken ? [{ field: "sessionToken", label: "AWS SigV4 \xB7 sessionToken" }] : []
|
|
4069
|
+
];
|
|
4070
|
+
case "oauth2-client-credentials":
|
|
4071
|
+
case "oauth2-auth-code":
|
|
4072
|
+
case "oauth2-pkce":
|
|
4073
|
+
return [
|
|
4074
|
+
...auth.clientSecret ? [{ field: "clientSecret", label: `${auth.type} \xB7 clientSecret` }] : [],
|
|
4075
|
+
...auth.accessToken ? [{ field: "accessToken", label: `${auth.type} \xB7 accessToken` }] : [],
|
|
4076
|
+
...auth.refreshToken ? [{ field: "refreshToken", label: `${auth.type} \xB7 refreshToken` }] : []
|
|
4077
|
+
];
|
|
4078
|
+
case "oauth2-password":
|
|
4079
|
+
return [
|
|
4080
|
+
...auth.clientSecret ? [{ field: "clientSecret", label: "oauth2-password \xB7 clientSecret" }] : [],
|
|
4081
|
+
...auth.password ? [{ field: "password", label: "oauth2-password \xB7 password" }] : [],
|
|
4082
|
+
...auth.accessToken ? [{ field: "accessToken", label: "oauth2-password \xB7 accessToken" }] : [],
|
|
4083
|
+
...auth.refreshToken ? [{ field: "refreshToken", label: "oauth2-password \xB7 refreshToken" }] : []
|
|
4084
|
+
];
|
|
4085
|
+
case "oauth2-implicit":
|
|
4086
|
+
return auth.accessToken ? [{ field: "accessToken", label: "oauth2-implicit \xB7 accessToken" }] : [];
|
|
4087
|
+
case "oauth2-device":
|
|
4088
|
+
return [
|
|
4089
|
+
...auth.accessToken ? [{ field: "accessToken", label: "oauth2-device \xB7 accessToken" }] : [],
|
|
4090
|
+
...auth.refreshToken ? [{ field: "refreshToken", label: "oauth2-device \xB7 refreshToken" }] : []
|
|
4091
|
+
];
|
|
4092
|
+
default:
|
|
4093
|
+
return [];
|
|
4094
|
+
}
|
|
4095
|
+
}
|
|
4096
|
+
function buildCredential(scope, ownerId, ownerName, auth, desc) {
|
|
4097
|
+
const prefix = scope === "request" ? "request" : "folder";
|
|
4098
|
+
return {
|
|
4099
|
+
id: `${prefix}:${ownerId}.${auth.type}.${desc.field}`,
|
|
4100
|
+
scope,
|
|
4101
|
+
authType: auth.type,
|
|
4102
|
+
field: desc.field,
|
|
4103
|
+
label: desc.label,
|
|
4104
|
+
ownerName,
|
|
4105
|
+
ownerId
|
|
4106
|
+
};
|
|
4107
|
+
}
|
|
4108
|
+
function credentialIdsFor(scope, ownerId, auth) {
|
|
4109
|
+
const ids = /* @__PURE__ */ new Map();
|
|
4110
|
+
const prefix = scope === "request" ? "request" : "folder";
|
|
4111
|
+
for (const desc of authCredentialFields(auth)) {
|
|
4112
|
+
ids.set(desc.field, `${prefix}:${ownerId}.${auth.type}.${desc.field}`);
|
|
4113
|
+
}
|
|
4114
|
+
return ids;
|
|
4115
|
+
}
|
|
4116
|
+
function redactAuthForScope(auth, ids, includeIds) {
|
|
4117
|
+
const shouldBlank = (field) => {
|
|
4118
|
+
const id = ids.get(field);
|
|
4119
|
+
return !!id && !includeIds.has(id);
|
|
4120
|
+
};
|
|
4121
|
+
switch (auth.type) {
|
|
4122
|
+
case "none":
|
|
4123
|
+
case "inherit":
|
|
4124
|
+
case "custom-header":
|
|
4125
|
+
return auth;
|
|
4126
|
+
case "basic":
|
|
4127
|
+
return shouldBlank("password") ? { ...auth, password: "" } : auth;
|
|
4128
|
+
case "bearer":
|
|
4129
|
+
return shouldBlank("token") ? { ...auth, token: "" } : auth;
|
|
4130
|
+
case "api-key":
|
|
4131
|
+
return shouldBlank("value") ? { ...auth, value: "" } : auth;
|
|
4132
|
+
case "digest":
|
|
4133
|
+
return shouldBlank("password") ? { ...auth, password: "" } : auth;
|
|
4134
|
+
case "ntlm":
|
|
4135
|
+
return shouldBlank("password") ? { ...auth, password: "" } : auth;
|
|
4136
|
+
case "hawk":
|
|
4137
|
+
return shouldBlank("hawkKey") ? { ...auth, hawkKey: "" } : auth;
|
|
4138
|
+
case "jwt-bearer":
|
|
4139
|
+
return {
|
|
4140
|
+
...auth,
|
|
4141
|
+
secretOrKey: shouldBlank("secretOrKey") ? "" : auth.secretOrKey,
|
|
4142
|
+
token: shouldBlank("token") ? "" : auth.token
|
|
4143
|
+
};
|
|
4144
|
+
case "aws-sigv4":
|
|
4145
|
+
return {
|
|
4146
|
+
...auth,
|
|
4147
|
+
secretAccessKey: shouldBlank("secretAccessKey") ? "" : auth.secretAccessKey,
|
|
4148
|
+
sessionToken: shouldBlank("sessionToken") ? "" : auth.sessionToken
|
|
4149
|
+
};
|
|
4150
|
+
case "oauth2-client-credentials":
|
|
4151
|
+
case "oauth2-auth-code":
|
|
4152
|
+
case "oauth2-pkce":
|
|
4153
|
+
return {
|
|
4154
|
+
...auth,
|
|
4155
|
+
clientSecret: shouldBlank("clientSecret") ? "" : auth.clientSecret,
|
|
4156
|
+
accessToken: shouldBlank("accessToken") ? "" : auth.accessToken,
|
|
4157
|
+
refreshToken: shouldBlank("refreshToken") ? "" : auth.refreshToken
|
|
4158
|
+
};
|
|
4159
|
+
case "oauth2-password":
|
|
4160
|
+
return {
|
|
4161
|
+
...auth,
|
|
4162
|
+
clientSecret: shouldBlank("clientSecret") ? "" : auth.clientSecret,
|
|
4163
|
+
password: shouldBlank("password") ? "" : auth.password,
|
|
4164
|
+
accessToken: shouldBlank("accessToken") ? "" : auth.accessToken,
|
|
4165
|
+
refreshToken: shouldBlank("refreshToken") ? "" : auth.refreshToken
|
|
4166
|
+
};
|
|
4167
|
+
case "oauth2-implicit":
|
|
4168
|
+
return {
|
|
4169
|
+
...auth,
|
|
4170
|
+
accessToken: shouldBlank("accessToken") ? "" : auth.accessToken
|
|
4171
|
+
};
|
|
4172
|
+
case "oauth2-device":
|
|
4173
|
+
return {
|
|
4174
|
+
...auth,
|
|
4175
|
+
accessToken: shouldBlank("accessToken") ? "" : auth.accessToken,
|
|
4176
|
+
refreshToken: shouldBlank("refreshToken") ? "" : auth.refreshToken
|
|
4177
|
+
};
|
|
4178
|
+
default:
|
|
4179
|
+
return auth;
|
|
4180
|
+
}
|
|
4181
|
+
}
|
|
4182
|
+
function scopeRank(scope) {
|
|
4183
|
+
if (scope === "root-folder") return 0;
|
|
4184
|
+
if (scope === "subfolder") return 1;
|
|
4185
|
+
return 2;
|
|
4186
|
+
}
|
|
4187
|
+
function credentialCompare(a, b) {
|
|
4188
|
+
const r = scopeRank(a.scope) - scopeRank(b.scope);
|
|
4189
|
+
if (r !== 0) return r;
|
|
4190
|
+
return a.ownerName.localeCompare(b.ownerName, void 0, { sensitivity: "base" });
|
|
4191
|
+
}
|
|
4192
|
+
|
|
4193
|
+
// src/export/folderExport.ts
|
|
4194
|
+
var APICIRCLE_FOLDER_EXPORT_FORMAT = "apicircle.folder/v1";
|
|
4195
|
+
function collectFolderExport(args) {
|
|
4196
|
+
const { synced, folderId } = args;
|
|
4197
|
+
const root = synced.collections.folders[folderId];
|
|
4198
|
+
if (!root) return null;
|
|
4199
|
+
const now = args.now ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
4200
|
+
const appVersion = args.appVersion ?? "apicircle-studio";
|
|
4201
|
+
const folderIds = /* @__PURE__ */ new Set([folderId]);
|
|
4202
|
+
let grew = true;
|
|
4203
|
+
while (grew) {
|
|
4204
|
+
grew = false;
|
|
4205
|
+
for (const f of Object.values(synced.collections.folders)) {
|
|
4206
|
+
if (folderIds.has(f.id)) continue;
|
|
4207
|
+
if (f.parentId && folderIds.has(f.parentId)) {
|
|
4208
|
+
folderIds.add(f.id);
|
|
4209
|
+
grew = true;
|
|
4210
|
+
}
|
|
4211
|
+
}
|
|
4212
|
+
}
|
|
4213
|
+
const subfolders = [];
|
|
4214
|
+
for (const f of Object.values(synced.collections.folders)) {
|
|
4215
|
+
if (f.id !== folderId && folderIds.has(f.id)) subfolders.push(cloneFolder(f));
|
|
4216
|
+
}
|
|
4217
|
+
const requests = [];
|
|
4218
|
+
for (const r of Object.values(synced.collections.requests)) {
|
|
4219
|
+
if (r.folderId && folderIds.has(r.folderId)) requests.push(cloneRequest(r));
|
|
4220
|
+
}
|
|
4221
|
+
const dependencies = collectDependencies(synced, requests);
|
|
4222
|
+
const envelope = {
|
|
4223
|
+
format: APICIRCLE_FOLDER_EXPORT_FORMAT,
|
|
4224
|
+
exportedAt: now,
|
|
4225
|
+
appVersion,
|
|
4226
|
+
source: {
|
|
4227
|
+
workspaceId: synced.workspaceId,
|
|
4228
|
+
folderId,
|
|
4229
|
+
folderName: root.name
|
|
4230
|
+
},
|
|
4231
|
+
folder: {
|
|
4232
|
+
name: root.name,
|
|
4233
|
+
auth: root.auth,
|
|
4234
|
+
subfolders,
|
|
4235
|
+
requests
|
|
4236
|
+
},
|
|
4237
|
+
dependencies
|
|
4238
|
+
};
|
|
4239
|
+
const report = buildReport(envelope);
|
|
4240
|
+
return { envelope, report };
|
|
4241
|
+
}
|
|
4242
|
+
function serializeFolderExport(envelope) {
|
|
4243
|
+
return JSON.stringify(envelope, null, 2);
|
|
4244
|
+
}
|
|
4245
|
+
function suggestFolderExportFilename(envelope) {
|
|
4246
|
+
const slug = envelope.folder.name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
4247
|
+
const base = slug || "folder";
|
|
4248
|
+
return `${base}.apicircle.json`;
|
|
4249
|
+
}
|
|
4250
|
+
function cloneFolder(f) {
|
|
4251
|
+
return {
|
|
4252
|
+
...f,
|
|
4253
|
+
auth: f.auth ? { ...f.auth } : void 0
|
|
4254
|
+
};
|
|
4255
|
+
}
|
|
4256
|
+
function cloneRequest(r) {
|
|
4257
|
+
return {
|
|
4258
|
+
...r,
|
|
4259
|
+
headers: r.headers.map((h) => ({ ...h })),
|
|
4260
|
+
query: r.query.map((q) => ({ ...q })),
|
|
4261
|
+
pathParams: r.pathParams ? { ...r.pathParams } : void 0,
|
|
4262
|
+
cookies: r.cookies ? r.cookies.map((c) => ({ ...c })) : void 0,
|
|
4263
|
+
body: cloneBody(r.body),
|
|
4264
|
+
auth: { ...r.auth },
|
|
4265
|
+
contextVars: r.contextVars.map((v) => ({ ...v })),
|
|
4266
|
+
extractions: r.extractions.map((e) => ({ ...e })),
|
|
4267
|
+
assertions: r.assertions.map((a) => ({ ...a }))
|
|
4268
|
+
};
|
|
4269
|
+
}
|
|
4270
|
+
function cloneBody(body) {
|
|
4271
|
+
if (body.type === "form-data") {
|
|
4272
|
+
return {
|
|
4273
|
+
...body,
|
|
4274
|
+
formRows: body.formRows?.map((row) => ({ ...row })) ?? body.formRows
|
|
4275
|
+
};
|
|
4276
|
+
}
|
|
4277
|
+
if (body.type === "binary") {
|
|
4278
|
+
return {
|
|
4279
|
+
...body,
|
|
4280
|
+
attachment: body.attachment ? { ...body.attachment } : void 0
|
|
4281
|
+
};
|
|
4282
|
+
}
|
|
4283
|
+
return { ...body };
|
|
4284
|
+
}
|
|
4285
|
+
function collectDependencies(synced, requests) {
|
|
4286
|
+
const schemaIds = /* @__PURE__ */ new Set();
|
|
4287
|
+
const graphqlIds = /* @__PURE__ */ new Set();
|
|
4288
|
+
const fileIds = /* @__PURE__ */ new Set();
|
|
4289
|
+
for (const r of requests) {
|
|
4290
|
+
if (r.bodySchemaId) schemaIds.add(r.bodySchemaId);
|
|
4291
|
+
if (r.graphqlSchemaId) graphqlIds.add(r.graphqlSchemaId);
|
|
4292
|
+
if (r.body.type === "binary" && r.body.attachment?.globalFileAssetId) {
|
|
4293
|
+
fileIds.add(r.body.attachment.globalFileAssetId);
|
|
4294
|
+
}
|
|
4295
|
+
if (r.body.type === "form-data" && r.body.formRows) {
|
|
4296
|
+
for (const row of r.body.formRows) {
|
|
4297
|
+
if (row.kind === "file" && row.globalFileAssetId) fileIds.add(row.globalFileAssetId);
|
|
4298
|
+
}
|
|
4299
|
+
}
|
|
4300
|
+
}
|
|
4301
|
+
const assets = synced.globalAssets;
|
|
4302
|
+
const schemas = [];
|
|
4303
|
+
for (const id of schemaIds) {
|
|
4304
|
+
const s = assets.schemas[id];
|
|
4305
|
+
if (s) schemas.push({ ...s });
|
|
4306
|
+
}
|
|
4307
|
+
const graphql = [];
|
|
4308
|
+
for (const id of graphqlIds) {
|
|
4309
|
+
const g = assets.graphql[id];
|
|
4310
|
+
if (g) graphql.push({ ...g });
|
|
4311
|
+
}
|
|
4312
|
+
const files = [];
|
|
4313
|
+
for (const id of fileIds) {
|
|
4314
|
+
const f = assets.files?.[id];
|
|
4315
|
+
if (f) files.push({ ...f });
|
|
4316
|
+
}
|
|
4317
|
+
schemas.sort(byNameThenId);
|
|
4318
|
+
graphql.sort(byNameThenId);
|
|
4319
|
+
files.sort(byNameThenId);
|
|
4320
|
+
return { schemas, graphql, files };
|
|
4321
|
+
}
|
|
4322
|
+
function byNameThenId(a, b) {
|
|
4323
|
+
const c = a.name.localeCompare(b.name, void 0, { sensitivity: "base" });
|
|
4324
|
+
return c !== 0 ? c : a.id.localeCompare(b.id);
|
|
4325
|
+
}
|
|
4326
|
+
function buildReport(envelope) {
|
|
4327
|
+
const subfolderCount = envelope.folder.subfolders.length;
|
|
4328
|
+
const requestCount = envelope.folder.requests.length;
|
|
4329
|
+
const totalFolderCount = subfolderCount + 1;
|
|
4330
|
+
const credentials = collectFolderExportCredentials(envelope);
|
|
4331
|
+
return {
|
|
4332
|
+
folderName: envelope.folder.name,
|
|
4333
|
+
requestCount,
|
|
4334
|
+
subfolderCount,
|
|
4335
|
+
totalFolderCount,
|
|
4336
|
+
dependencies: {
|
|
4337
|
+
schemas: envelope.dependencies.schemas.map((s) => ({ id: s.id, name: s.name })),
|
|
4338
|
+
graphql: envelope.dependencies.graphql.map((g) => ({
|
|
4339
|
+
id: g.id,
|
|
4340
|
+
name: g.name,
|
|
4341
|
+
kind: g.kind
|
|
4342
|
+
})),
|
|
4343
|
+
files: envelope.dependencies.files.map((f) => ({
|
|
4344
|
+
id: f.id,
|
|
4345
|
+
name: f.name,
|
|
4346
|
+
filename: f.filename,
|
|
4347
|
+
size: f.size,
|
|
4348
|
+
mimeType: f.mimeType
|
|
4349
|
+
}))
|
|
4350
|
+
},
|
|
4351
|
+
hasDependencies: envelope.dependencies.schemas.length > 0 || envelope.dependencies.graphql.length > 0 || envelope.dependencies.files.length > 0,
|
|
4352
|
+
credentials,
|
|
4353
|
+
hasCredentials: credentials.length > 0
|
|
4354
|
+
};
|
|
4355
|
+
}
|
|
4356
|
+
|
|
4357
|
+
// src/import/apicircleFolder.ts
|
|
4358
|
+
function isApicircleFolderExport(doc) {
|
|
4359
|
+
if (!doc || typeof doc !== "object") return false;
|
|
4360
|
+
const d = doc;
|
|
4361
|
+
return d.format === APICIRCLE_FOLDER_EXPORT_FORMAT;
|
|
4362
|
+
}
|
|
4363
|
+
function parseApicircleFolderExport(input, options = {}) {
|
|
4364
|
+
let doc;
|
|
4365
|
+
try {
|
|
4366
|
+
doc = JSON.parse(input);
|
|
4367
|
+
} catch (err) {
|
|
4368
|
+
throw new Error(`Couldn't parse JSON: ${err instanceof Error ? err.message : String(err)}`);
|
|
4369
|
+
}
|
|
4370
|
+
return parseApicircleFolderExportDoc(doc, options);
|
|
4371
|
+
}
|
|
4372
|
+
function parseApicircleFolderExportDoc(doc, options = {}) {
|
|
4373
|
+
const id = options.idGenerator ?? generateId;
|
|
4374
|
+
if (!isApicircleFolderExport(doc)) {
|
|
4375
|
+
throw new Error(
|
|
4376
|
+
`Unsupported format. Expected an API Circle folder export ("format": "${APICIRCLE_FOLDER_EXPORT_FORMAT}").`
|
|
4377
|
+
);
|
|
4378
|
+
}
|
|
4379
|
+
const envelope = doc;
|
|
4380
|
+
validateEnvelopeShape(envelope);
|
|
4381
|
+
const warnings = [];
|
|
4382
|
+
const folderIdMap = /* @__PURE__ */ new Map();
|
|
4383
|
+
folderIdMap.set(envelope.source.folderId, id());
|
|
4384
|
+
for (const f of envelope.folder.subfolders) folderIdMap.set(f.id, id());
|
|
4385
|
+
const requestIdMap = /* @__PURE__ */ new Map();
|
|
4386
|
+
for (const r of envelope.folder.requests) requestIdMap.set(r.id, id());
|
|
4387
|
+
const schemaIdMap = /* @__PURE__ */ new Map();
|
|
4388
|
+
for (const s of envelope.dependencies.schemas) schemaIdMap.set(s.id, id());
|
|
4389
|
+
const graphqlIdMap = /* @__PURE__ */ new Map();
|
|
4390
|
+
for (const g of envelope.dependencies.graphql) graphqlIdMap.set(g.id, id());
|
|
4391
|
+
const fileIdMap = /* @__PURE__ */ new Map();
|
|
4392
|
+
for (const f of envelope.dependencies.files) fileIdMap.set(f.id, id());
|
|
4393
|
+
const rootFolderId = folderIdMap.get(envelope.source.folderId);
|
|
4394
|
+
const subfolders = envelope.folder.subfolders.map((f) => {
|
|
4395
|
+
const newId = folderIdMap.get(f.id);
|
|
4396
|
+
let newParentId;
|
|
4397
|
+
if (f.parentId === null) {
|
|
4398
|
+
newParentId = rootFolderId;
|
|
4399
|
+
} else {
|
|
4400
|
+
const mapped = folderIdMap.get(f.parentId);
|
|
4401
|
+
if (!mapped) {
|
|
4402
|
+
warnings.push(
|
|
4403
|
+
`Subfolder "${f.name}" referenced parentId "${f.parentId}" that wasn't present in the export \u2014 reattached under "${envelope.folder.name}".`
|
|
4404
|
+
);
|
|
4405
|
+
newParentId = rootFolderId;
|
|
4406
|
+
} else {
|
|
4407
|
+
newParentId = mapped;
|
|
4408
|
+
}
|
|
4409
|
+
}
|
|
4410
|
+
return {
|
|
4411
|
+
...f,
|
|
4412
|
+
id: newId,
|
|
4413
|
+
parentId: newParentId,
|
|
4414
|
+
auth: f.auth ? { ...f.auth } : void 0
|
|
4415
|
+
};
|
|
4416
|
+
});
|
|
4417
|
+
const schemas = envelope.dependencies.schemas.map((s) => ({
|
|
4418
|
+
...s,
|
|
4419
|
+
id: schemaIdMap.get(s.id)
|
|
4420
|
+
}));
|
|
4421
|
+
const graphql = envelope.dependencies.graphql.map((g) => ({
|
|
4422
|
+
...g,
|
|
4423
|
+
id: graphqlIdMap.get(g.id)
|
|
4424
|
+
}));
|
|
4425
|
+
const files = envelope.dependencies.files.map((f) => ({
|
|
4426
|
+
...f,
|
|
4427
|
+
id: fileIdMap.get(f.id)
|
|
4428
|
+
}));
|
|
4429
|
+
const requests = envelope.folder.requests.map((r) => {
|
|
4430
|
+
const newId = requestIdMap.get(r.id);
|
|
4431
|
+
let newFolderId;
|
|
4432
|
+
if (r.folderId === null) {
|
|
4433
|
+
newFolderId = rootFolderId;
|
|
4434
|
+
} else {
|
|
4435
|
+
const mapped = folderIdMap.get(r.folderId);
|
|
4436
|
+
if (!mapped) {
|
|
4437
|
+
warnings.push(
|
|
4438
|
+
`Request "${r.name}" referenced folderId "${r.folderId}" that wasn't present in the export \u2014 reattached under "${envelope.folder.name}".`
|
|
4439
|
+
);
|
|
4440
|
+
newFolderId = rootFolderId;
|
|
4441
|
+
} else {
|
|
4442
|
+
newFolderId = mapped;
|
|
4443
|
+
}
|
|
4444
|
+
}
|
|
4445
|
+
const bodySchemaId = remapDependencyRef(
|
|
4446
|
+
r.bodySchemaId,
|
|
4447
|
+
schemaIdMap,
|
|
4448
|
+
`Request "${r.name}".bodySchemaId`,
|
|
4449
|
+
warnings
|
|
4450
|
+
);
|
|
4451
|
+
const graphqlSchemaId = remapDependencyRef(
|
|
4452
|
+
r.graphqlSchemaId,
|
|
4453
|
+
graphqlIdMap,
|
|
4454
|
+
`Request "${r.name}".graphqlSchemaId`,
|
|
4455
|
+
warnings
|
|
4456
|
+
);
|
|
4457
|
+
return {
|
|
4458
|
+
...r,
|
|
4459
|
+
id: newId,
|
|
4460
|
+
folderId: newFolderId,
|
|
4461
|
+
bodySchemaId,
|
|
4462
|
+
graphqlSchemaId,
|
|
4463
|
+
headers: r.headers.map((h) => ({ ...h })),
|
|
4464
|
+
query: r.query.map((q) => ({ ...q })),
|
|
4465
|
+
pathParams: r.pathParams ? { ...r.pathParams } : void 0,
|
|
4466
|
+
cookies: r.cookies ? r.cookies.map((c) => ({ ...c })) : void 0,
|
|
4467
|
+
body: remapBodyFileRefs(r.body, fileIdMap, r.name, warnings),
|
|
4468
|
+
auth: { ...r.auth },
|
|
4469
|
+
contextVars: r.contextVars.map((v) => ({ ...v })),
|
|
4470
|
+
extractions: r.extractions.map((e) => ({ ...e })),
|
|
4471
|
+
assertions: r.assertions.map((a) => ({ ...a }))
|
|
4472
|
+
};
|
|
4473
|
+
});
|
|
4474
|
+
return {
|
|
4475
|
+
rootFolder: {
|
|
4476
|
+
id: rootFolderId,
|
|
4477
|
+
name: envelope.folder.name,
|
|
4478
|
+
auth: envelope.folder.auth ? { ...envelope.folder.auth } : void 0
|
|
4479
|
+
},
|
|
4480
|
+
subfolders,
|
|
4481
|
+
requests,
|
|
4482
|
+
dependencies: { schemas, graphql, files },
|
|
4483
|
+
sourceFolderName: envelope.source.folderName,
|
|
4484
|
+
warnings
|
|
4485
|
+
};
|
|
4486
|
+
}
|
|
4487
|
+
function validateEnvelopeShape(envelope) {
|
|
4488
|
+
if (!envelope.folder || typeof envelope.folder !== "object") {
|
|
4489
|
+
throw new Error('API Circle folder export is missing the "folder" section.');
|
|
4490
|
+
}
|
|
4491
|
+
if (typeof envelope.folder.name !== "string" || envelope.folder.name.length === 0) {
|
|
4492
|
+
throw new Error('API Circle folder export must have a non-empty "folder.name".');
|
|
4493
|
+
}
|
|
4494
|
+
if (!Array.isArray(envelope.folder.subfolders)) {
|
|
4495
|
+
throw new Error('API Circle folder export must have a "folder.subfolders" array.');
|
|
4496
|
+
}
|
|
4497
|
+
if (!Array.isArray(envelope.folder.requests)) {
|
|
4498
|
+
throw new Error('API Circle folder export must have a "folder.requests" array.');
|
|
4499
|
+
}
|
|
4500
|
+
if (!envelope.dependencies || typeof envelope.dependencies !== "object") {
|
|
4501
|
+
throw new Error('API Circle folder export is missing the "dependencies" section.');
|
|
4502
|
+
}
|
|
4503
|
+
if (!Array.isArray(envelope.dependencies.schemas) || !Array.isArray(envelope.dependencies.graphql) || !Array.isArray(envelope.dependencies.files)) {
|
|
4504
|
+
throw new Error(
|
|
4505
|
+
'API Circle folder export "dependencies" must have schemas / graphql / files arrays.'
|
|
4506
|
+
);
|
|
4507
|
+
}
|
|
4508
|
+
if (!envelope.source || typeof envelope.source !== "object") {
|
|
4509
|
+
throw new Error('API Circle folder export is missing the "source" section.');
|
|
4510
|
+
}
|
|
4511
|
+
if (typeof envelope.source.folderId !== "string" || typeof envelope.source.folderName !== "string") {
|
|
4512
|
+
throw new Error('API Circle folder export "source" must include "folderId" and "folderName".');
|
|
4513
|
+
}
|
|
4514
|
+
}
|
|
4515
|
+
function remapDependencyRef(value, map, label, warnings) {
|
|
4516
|
+
if (value === null || value === void 0) return value;
|
|
4517
|
+
const mapped = map.get(value);
|
|
4518
|
+
if (mapped) return mapped;
|
|
4519
|
+
warnings.push(
|
|
4520
|
+
`${label} referenced a dependency ("${value}") that wasn't embedded in the export \u2014 reference dropped on import.`
|
|
4521
|
+
);
|
|
4522
|
+
return null;
|
|
4523
|
+
}
|
|
4524
|
+
function remapBodyFileRefs(body, fileIdMap, requestName, warnings) {
|
|
4525
|
+
if (body.type === "binary") {
|
|
4526
|
+
if (!body.attachment) return { ...body };
|
|
4527
|
+
const oldId = body.attachment.globalFileAssetId;
|
|
4528
|
+
let nextGlobalFileAssetId = oldId;
|
|
4529
|
+
if (oldId) {
|
|
4530
|
+
const mapped = fileIdMap.get(oldId);
|
|
4531
|
+
if (mapped) {
|
|
4532
|
+
nextGlobalFileAssetId = mapped;
|
|
4533
|
+
} else {
|
|
4534
|
+
warnings.push(
|
|
4535
|
+
`Request "${requestName}".body.attachment referenced file asset "${oldId}" that wasn't embedded in the export \u2014 re-attach the file after import.`
|
|
4536
|
+
);
|
|
4537
|
+
nextGlobalFileAssetId = null;
|
|
4538
|
+
}
|
|
4539
|
+
}
|
|
4540
|
+
return {
|
|
4541
|
+
...body,
|
|
4542
|
+
attachment: {
|
|
4543
|
+
...body.attachment,
|
|
4544
|
+
// Reset slotId — the destination workspace owns its own slots.
|
|
4545
|
+
slotId: null,
|
|
4546
|
+
globalFileAssetId: nextGlobalFileAssetId
|
|
4547
|
+
}
|
|
4548
|
+
};
|
|
4549
|
+
}
|
|
4550
|
+
if (body.type === "form-data") {
|
|
4551
|
+
const formRows = body.formRows?.map((row) => {
|
|
4552
|
+
if (row.kind !== "file") return { ...row };
|
|
4553
|
+
const oldId = row.globalFileAssetId;
|
|
4554
|
+
let nextGlobalFileAssetId = oldId;
|
|
4555
|
+
if (oldId) {
|
|
4556
|
+
const mapped = fileIdMap.get(oldId);
|
|
4557
|
+
if (mapped) {
|
|
4558
|
+
nextGlobalFileAssetId = mapped;
|
|
4559
|
+
} else {
|
|
4560
|
+
warnings.push(
|
|
4561
|
+
`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.`
|
|
4562
|
+
);
|
|
4563
|
+
nextGlobalFileAssetId = null;
|
|
4564
|
+
}
|
|
4565
|
+
}
|
|
4566
|
+
return {
|
|
4567
|
+
...row,
|
|
4568
|
+
slotId: null,
|
|
4569
|
+
globalFileAssetId: nextGlobalFileAssetId
|
|
4570
|
+
};
|
|
4571
|
+
});
|
|
4572
|
+
return { ...body, formRows };
|
|
4573
|
+
}
|
|
4574
|
+
return { ...body };
|
|
4575
|
+
}
|
|
4576
|
+
|
|
4577
|
+
// src/import/apicircleEnvironment.ts
|
|
4578
|
+
function isApicircleEnvironment(doc) {
|
|
4579
|
+
if (!doc || typeof doc !== "object") return false;
|
|
4580
|
+
const d = doc;
|
|
4581
|
+
return (d.apicircleEnvironment === 1 || d.apicircleEnvironment === 2) && typeof d.name === "string" && Array.isArray(d.variables);
|
|
4582
|
+
}
|
|
4583
|
+
function parseApicircleEnvironment(input) {
|
|
4584
|
+
let doc;
|
|
4585
|
+
try {
|
|
4586
|
+
doc = JSON.parse(input);
|
|
4587
|
+
} catch (err) {
|
|
4588
|
+
throw new Error(`Couldn't parse JSON: ${err instanceof Error ? err.message : String(err)}`);
|
|
4589
|
+
}
|
|
4590
|
+
return parseApicircleEnvironmentDoc(doc);
|
|
4591
|
+
}
|
|
4592
|
+
function parseApicircleEnvironmentDoc(doc) {
|
|
4593
|
+
if (!isApicircleEnvironment(doc)) {
|
|
4594
|
+
throw new Error(
|
|
4595
|
+
'Unsupported format. Expected an API Circle environment export ("apicircleEnvironment": 1 or 2).'
|
|
4596
|
+
);
|
|
4597
|
+
}
|
|
4598
|
+
const name = doc.name.trim();
|
|
4599
|
+
if (!name) {
|
|
4600
|
+
throw new Error('API Circle environment export must have a non-empty "name".');
|
|
4601
|
+
}
|
|
4602
|
+
const payloadVersion = doc.apicircleEnvironment;
|
|
4603
|
+
const warnings = [];
|
|
4604
|
+
const variables = [];
|
|
4605
|
+
const encryptedBindingHints = [];
|
|
4606
|
+
for (let i = 0; i < doc.variables.length; i += 1) {
|
|
4607
|
+
const raw = doc.variables[i];
|
|
4608
|
+
if (!raw || typeof raw !== "object") {
|
|
4609
|
+
warnings.push(`Row #${i + 1} was not an object \u2014 dropped.`);
|
|
4610
|
+
continue;
|
|
4611
|
+
}
|
|
4612
|
+
const key = typeof raw.key === "string" ? raw.key.trim() : "";
|
|
4613
|
+
if (!key) {
|
|
4614
|
+
warnings.push(`Row #${i + 1} had no key \u2014 dropped.`);
|
|
4615
|
+
continue;
|
|
4616
|
+
}
|
|
4617
|
+
if (raw.encrypted === true) {
|
|
4618
|
+
const secretKeyId = typeof raw.secretKeyId === "string" ? raw.secretKeyId : "";
|
|
4619
|
+
const labelFromSecret = readLabelFromSecretField(raw.secret);
|
|
4620
|
+
const ciphertext = payloadVersion === 2 && typeof raw.value === "string" && raw.value.startsWith("enc:") ? raw.value : null;
|
|
4621
|
+
const salt = payloadVersion === 2 ? readSaltFromSecretField(raw.secret) : null;
|
|
4622
|
+
if (!secretKeyId && !labelFromSecret) {
|
|
4623
|
+
warnings.push(
|
|
4624
|
+
`"${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.`
|
|
4625
|
+
);
|
|
4626
|
+
variables.push({ key, value: "", encrypted: false });
|
|
4627
|
+
continue;
|
|
4628
|
+
}
|
|
4629
|
+
variables.push({
|
|
4630
|
+
key,
|
|
4631
|
+
value: ciphertext ?? "",
|
|
4632
|
+
encrypted: true,
|
|
4633
|
+
secretKeyId: secretKeyId || void 0
|
|
4634
|
+
});
|
|
4635
|
+
const labelFromFallback = !labelFromSecret;
|
|
4636
|
+
encryptedBindingHints.push({
|
|
4637
|
+
varKey: key,
|
|
4638
|
+
label: labelFromSecret ?? key,
|
|
4639
|
+
originSecretKeyId: secretKeyId || void 0,
|
|
4640
|
+
labelFromFallback,
|
|
4641
|
+
ciphertext,
|
|
4642
|
+
salt
|
|
4643
|
+
});
|
|
4644
|
+
continue;
|
|
4645
|
+
}
|
|
4646
|
+
variables.push({
|
|
4647
|
+
key,
|
|
4648
|
+
value: typeof raw.value === "string" ? raw.value : "",
|
|
4649
|
+
encrypted: false
|
|
4650
|
+
});
|
|
4651
|
+
}
|
|
4652
|
+
return { name, variables, encryptedBindingHints, payloadVersion, warnings };
|
|
4653
|
+
}
|
|
4654
|
+
function readLabelFromSecretField(field) {
|
|
4655
|
+
if (!field || typeof field !== "object") return null;
|
|
4656
|
+
const f = field;
|
|
4657
|
+
if (typeof f.label !== "string") return null;
|
|
4658
|
+
const trimmed = f.label.trim();
|
|
4659
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
4660
|
+
}
|
|
4661
|
+
function readSaltFromSecretField(field) {
|
|
4662
|
+
if (!field || typeof field !== "object") return null;
|
|
4663
|
+
const f = field;
|
|
4664
|
+
if (typeof f.salt !== "string") return null;
|
|
4665
|
+
const trimmed = f.salt.trim();
|
|
4666
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
4667
|
+
}
|
|
4668
|
+
|
|
3976
4669
|
// src/assertions/runAssertions.ts
|
|
3977
4670
|
function runAssertions(assertions, exec) {
|
|
3978
4671
|
return assertions.map((a) => runOne(a, exec));
|
|
@@ -5720,7 +6413,232 @@ function structurallyEqual2(a, b) {
|
|
|
5720
6413
|
}
|
|
5721
6414
|
|
|
5722
6415
|
// src/workspace/applyMutation.ts
|
|
5723
|
-
import { envPriorityKey, generateId } from "@apicircle/shared";
|
|
6416
|
+
import { envPriorityKey, generateId as generateId2 } from "@apicircle/shared";
|
|
6417
|
+
|
|
6418
|
+
// src/workspace/apicircleFolderImport.ts
|
|
6419
|
+
function importApicircleFolderInto(synced, parsed, parentFolderId) {
|
|
6420
|
+
let cur = synced;
|
|
6421
|
+
const schemaRemap = /* @__PURE__ */ new Map();
|
|
6422
|
+
const graphqlRemap = /* @__PURE__ */ new Map();
|
|
6423
|
+
const fileRemap = /* @__PURE__ */ new Map();
|
|
6424
|
+
let schemasAdded = 0;
|
|
6425
|
+
let schemasReused = 0;
|
|
6426
|
+
let graphqlAdded = 0;
|
|
6427
|
+
let graphqlReused = 0;
|
|
6428
|
+
let filesAdded = 0;
|
|
6429
|
+
let filesReused = 0;
|
|
6430
|
+
const filesRequiringReattachment = [];
|
|
6431
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
6432
|
+
for (const incoming of parsed.dependencies.schemas) {
|
|
6433
|
+
const existing = findMatchingSchema(cur, incoming);
|
|
6434
|
+
if (existing) {
|
|
6435
|
+
schemaRemap.set(incoming.id, existing.id);
|
|
6436
|
+
schemasReused += 1;
|
|
6437
|
+
continue;
|
|
6438
|
+
}
|
|
6439
|
+
cur = mergeGlobalSchema(cur, incoming, now);
|
|
6440
|
+
schemaRemap.set(incoming.id, incoming.id);
|
|
6441
|
+
schemasAdded += 1;
|
|
6442
|
+
}
|
|
6443
|
+
for (const incoming of parsed.dependencies.graphql) {
|
|
6444
|
+
const existing = findMatchingGraphQL(cur, incoming);
|
|
6445
|
+
if (existing) {
|
|
6446
|
+
graphqlRemap.set(incoming.id, existing.id);
|
|
6447
|
+
graphqlReused += 1;
|
|
6448
|
+
continue;
|
|
6449
|
+
}
|
|
6450
|
+
cur = mergeGlobalGraphQL(cur, incoming, now);
|
|
6451
|
+
graphqlRemap.set(incoming.id, incoming.id);
|
|
6452
|
+
graphqlAdded += 1;
|
|
6453
|
+
}
|
|
6454
|
+
for (const incoming of parsed.dependencies.files) {
|
|
6455
|
+
const existing = findMatchingFile(cur, incoming);
|
|
6456
|
+
if (existing) {
|
|
6457
|
+
fileRemap.set(incoming.id, existing.id);
|
|
6458
|
+
filesReused += 1;
|
|
6459
|
+
continue;
|
|
6460
|
+
}
|
|
6461
|
+
cur = mergeGlobalFile(cur, incoming, now);
|
|
6462
|
+
fileRemap.set(incoming.id, incoming.id);
|
|
6463
|
+
filesAdded += 1;
|
|
6464
|
+
filesRequiringReattachment.push(incoming.id);
|
|
6465
|
+
}
|
|
6466
|
+
const rootName = uniquifyFolderName(cur, parentFolderId, parsed.rootFolder.name);
|
|
6467
|
+
const root = {
|
|
6468
|
+
id: parsed.rootFolder.id,
|
|
6469
|
+
name: rootName,
|
|
6470
|
+
parentId: parentFolderId,
|
|
6471
|
+
auth: parsed.rootFolder.auth ? { ...parsed.rootFolder.auth } : void 0
|
|
6472
|
+
};
|
|
6473
|
+
cur = insertFolder(
|
|
6474
|
+
cur,
|
|
6475
|
+
root,
|
|
6476
|
+
/* attachToTree */
|
|
6477
|
+
parentFolderId === null
|
|
6478
|
+
);
|
|
6479
|
+
for (const f of parsed.subfolders) {
|
|
6480
|
+
cur = insertFolder(
|
|
6481
|
+
cur,
|
|
6482
|
+
f,
|
|
6483
|
+
/* attachToTree */
|
|
6484
|
+
false
|
|
6485
|
+
);
|
|
6486
|
+
}
|
|
6487
|
+
for (const r of parsed.requests) {
|
|
6488
|
+
const rewritten = {
|
|
6489
|
+
...r,
|
|
6490
|
+
bodySchemaId: rewriteRef(r.bodySchemaId, schemaRemap),
|
|
6491
|
+
graphqlSchemaId: rewriteRef(r.graphqlSchemaId, graphqlRemap),
|
|
6492
|
+
body: rewriteBodyFileRefs(r.body, fileRemap)
|
|
6493
|
+
};
|
|
6494
|
+
cur = insertRequest(cur, rewritten);
|
|
6495
|
+
}
|
|
6496
|
+
return {
|
|
6497
|
+
synced: { ...cur, meta: { ...cur.meta, updatedAt: now } },
|
|
6498
|
+
rootFolderId: root.id,
|
|
6499
|
+
rootFolderName: rootName,
|
|
6500
|
+
counts: {
|
|
6501
|
+
folders: parsed.subfolders.length + 1,
|
|
6502
|
+
requests: parsed.requests.length,
|
|
6503
|
+
schemasAdded,
|
|
6504
|
+
schemasReused,
|
|
6505
|
+
graphqlAdded,
|
|
6506
|
+
graphqlReused,
|
|
6507
|
+
filesAdded,
|
|
6508
|
+
filesReused
|
|
6509
|
+
},
|
|
6510
|
+
filesRequiringReattachment
|
|
6511
|
+
};
|
|
6512
|
+
}
|
|
6513
|
+
function isFolderNameAvailable(synced, parentFolderId, name) {
|
|
6514
|
+
const trimmed = name.trim().toLowerCase();
|
|
6515
|
+
if (!trimmed) return false;
|
|
6516
|
+
for (const node of Object.values(synced.collections.folders)) {
|
|
6517
|
+
if (node.parentId !== parentFolderId) continue;
|
|
6518
|
+
if (node.name.trim().toLowerCase() === trimmed) return false;
|
|
6519
|
+
}
|
|
6520
|
+
return true;
|
|
6521
|
+
}
|
|
6522
|
+
function uniquifyFolderName(synced, parentFolderId, desired) {
|
|
6523
|
+
if (isFolderNameAvailable(synced, parentFolderId, desired)) return desired;
|
|
6524
|
+
let n = 2;
|
|
6525
|
+
while (!isFolderNameAvailable(synced, parentFolderId, `${desired} (${n})`)) {
|
|
6526
|
+
n += 1;
|
|
6527
|
+
if (n > 999) return `${desired} (${n})`;
|
|
6528
|
+
}
|
|
6529
|
+
return `${desired} (${n})`;
|
|
6530
|
+
}
|
|
6531
|
+
function insertFolder(synced, folder, attachToTree) {
|
|
6532
|
+
const folders = { ...synced.collections.folders, [folder.id]: folder };
|
|
6533
|
+
const tree = attachToTree ? {
|
|
6534
|
+
...synced.collections.tree,
|
|
6535
|
+
children: [...synced.collections.tree.children, { kind: "folder", id: folder.id }]
|
|
6536
|
+
} : synced.collections.tree;
|
|
6537
|
+
return {
|
|
6538
|
+
...synced,
|
|
6539
|
+
collections: { ...synced.collections, folders, tree }
|
|
6540
|
+
};
|
|
6541
|
+
}
|
|
6542
|
+
function insertRequest(synced, request) {
|
|
6543
|
+
return {
|
|
6544
|
+
...synced,
|
|
6545
|
+
collections: {
|
|
6546
|
+
...synced.collections,
|
|
6547
|
+
requests: { ...synced.collections.requests, [request.id]: request }
|
|
6548
|
+
}
|
|
6549
|
+
};
|
|
6550
|
+
}
|
|
6551
|
+
function withGlobalAssets(synced) {
|
|
6552
|
+
return synced.globalAssets ?? { schemas: {}, graphql: {}, files: {} };
|
|
6553
|
+
}
|
|
6554
|
+
function findMatchingSchema(synced, candidate) {
|
|
6555
|
+
const ga = withGlobalAssets(synced);
|
|
6556
|
+
for (const existing of Object.values(ga.schemas)) {
|
|
6557
|
+
if (existing.name === candidate.name && existing.schema === candidate.schema) {
|
|
6558
|
+
return existing;
|
|
6559
|
+
}
|
|
6560
|
+
}
|
|
6561
|
+
return null;
|
|
6562
|
+
}
|
|
6563
|
+
function findMatchingGraphQL(synced, candidate) {
|
|
6564
|
+
const ga = withGlobalAssets(synced);
|
|
6565
|
+
for (const existing of Object.values(ga.graphql)) {
|
|
6566
|
+
if (existing.name === candidate.name && existing.kind === candidate.kind && existing.source === candidate.source) {
|
|
6567
|
+
return existing;
|
|
6568
|
+
}
|
|
6569
|
+
}
|
|
6570
|
+
return null;
|
|
6571
|
+
}
|
|
6572
|
+
function findMatchingFile(synced, candidate) {
|
|
6573
|
+
const ga = withGlobalAssets(synced);
|
|
6574
|
+
const files = ga.files ?? {};
|
|
6575
|
+
for (const existing of Object.values(files)) {
|
|
6576
|
+
if (existing.name === candidate.name && existing.filename === candidate.filename && existing.size === candidate.size) {
|
|
6577
|
+
return existing;
|
|
6578
|
+
}
|
|
6579
|
+
}
|
|
6580
|
+
return null;
|
|
6581
|
+
}
|
|
6582
|
+
function mergeGlobalSchema(synced, schema, now) {
|
|
6583
|
+
const ga = withGlobalAssets(synced);
|
|
6584
|
+
return {
|
|
6585
|
+
...synced,
|
|
6586
|
+
globalAssets: {
|
|
6587
|
+
...ga,
|
|
6588
|
+
schemas: { ...ga.schemas, [schema.id]: { ...schema, updatedAt: now } }
|
|
6589
|
+
}
|
|
6590
|
+
};
|
|
6591
|
+
}
|
|
6592
|
+
function mergeGlobalGraphQL(synced, graphql, now) {
|
|
6593
|
+
const ga = withGlobalAssets(synced);
|
|
6594
|
+
return {
|
|
6595
|
+
...synced,
|
|
6596
|
+
globalAssets: {
|
|
6597
|
+
...ga,
|
|
6598
|
+
graphql: { ...ga.graphql, [graphql.id]: { ...graphql, updatedAt: now } }
|
|
6599
|
+
}
|
|
6600
|
+
};
|
|
6601
|
+
}
|
|
6602
|
+
function mergeGlobalFile(synced, file, now) {
|
|
6603
|
+
const ga = withGlobalAssets(synced);
|
|
6604
|
+
const files = ga.files ?? {};
|
|
6605
|
+
return {
|
|
6606
|
+
...synced,
|
|
6607
|
+
globalAssets: {
|
|
6608
|
+
...ga,
|
|
6609
|
+
files: { ...files, [file.id]: { ...file, updatedAt: now } }
|
|
6610
|
+
}
|
|
6611
|
+
};
|
|
6612
|
+
}
|
|
6613
|
+
function rewriteRef(value, remap) {
|
|
6614
|
+
if (value === null || value === void 0) return value;
|
|
6615
|
+
return remap.get(value) ?? value;
|
|
6616
|
+
}
|
|
6617
|
+
function rewriteBodyFileRefs(body, remap) {
|
|
6618
|
+
if (body.type === "binary") {
|
|
6619
|
+
if (!body.attachment) return body;
|
|
6620
|
+
const rewritten = rewriteRef(body.attachment.globalFileAssetId, remap);
|
|
6621
|
+
if (rewritten === body.attachment.globalFileAssetId) return body;
|
|
6622
|
+
return {
|
|
6623
|
+
...body,
|
|
6624
|
+
attachment: { ...body.attachment, globalFileAssetId: rewritten }
|
|
6625
|
+
};
|
|
6626
|
+
}
|
|
6627
|
+
if (body.type === "form-data" && body.formRows) {
|
|
6628
|
+
let mutated = false;
|
|
6629
|
+
const next = body.formRows.map((row) => {
|
|
6630
|
+
if (row.kind !== "file") return row;
|
|
6631
|
+
const rewritten = rewriteRef(row.globalFileAssetId, remap);
|
|
6632
|
+
if (rewritten === row.globalFileAssetId) return row;
|
|
6633
|
+
mutated = true;
|
|
6634
|
+
return { ...row, globalFileAssetId: rewritten };
|
|
6635
|
+
});
|
|
6636
|
+
return mutated ? { ...body, formRows: next } : body;
|
|
6637
|
+
}
|
|
6638
|
+
return body;
|
|
6639
|
+
}
|
|
6640
|
+
|
|
6641
|
+
// src/workspace/applyMutation.ts
|
|
5724
6642
|
function applyMutation(state, patch, options = {}) {
|
|
5725
6643
|
const now = options.now ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
5726
6644
|
switch (patch.kind) {
|
|
@@ -5736,6 +6654,8 @@ function applyMutation(state, patch, options = {}) {
|
|
|
5736
6654
|
return applyFolderDelete(state, patch.id, now);
|
|
5737
6655
|
case "folder.move":
|
|
5738
6656
|
return applyFolderMove(state, patch.id, patch.newParentId, now);
|
|
6657
|
+
case "folder.import_apicircle":
|
|
6658
|
+
return applyFolderImportApicircle(state, patch.parsed, patch.parentFolderId, now);
|
|
5739
6659
|
case "environment.upsert":
|
|
5740
6660
|
return applyEnvUpsert(state, patch.environment, now);
|
|
5741
6661
|
case "environment.delete":
|
|
@@ -5744,6 +6664,8 @@ function applyMutation(state, patch, options = {}) {
|
|
|
5744
6664
|
return applyEnvSetActive(state, patch.name, now);
|
|
5745
6665
|
case "environment.setPriority":
|
|
5746
6666
|
return applyEnvSetPriority(state, patch.order, now);
|
|
6667
|
+
case "secretKey.upsert":
|
|
6668
|
+
return applySecretKeyUpsert(state, patch.meta, now);
|
|
5747
6669
|
case "assertion.upsert":
|
|
5748
6670
|
return applyAssertionUpsert(state, patch.requestId, patch.assertion, now);
|
|
5749
6671
|
case "assertion.delete":
|
|
@@ -5904,6 +6826,23 @@ function applyFolderMove(state, id, newParentId, now) {
|
|
|
5904
6826
|
};
|
|
5905
6827
|
return { next: { ...state, synced }, changedIds: [id] };
|
|
5906
6828
|
}
|
|
6829
|
+
function applyFolderImportApicircle(state, parsed, parentFolderId, now) {
|
|
6830
|
+
const result = importApicircleFolderInto(
|
|
6831
|
+
state.synced,
|
|
6832
|
+
parsed,
|
|
6833
|
+
parentFolderId
|
|
6834
|
+
);
|
|
6835
|
+
const synced = {
|
|
6836
|
+
...result.synced,
|
|
6837
|
+
meta: { ...result.synced.meta, updatedAt: now }
|
|
6838
|
+
};
|
|
6839
|
+
const changedIds = [
|
|
6840
|
+
result.rootFolderId,
|
|
6841
|
+
...parsed.subfolders.map((f) => f.id),
|
|
6842
|
+
...parsed.requests.map((r) => r.id)
|
|
6843
|
+
];
|
|
6844
|
+
return { next: { ...state, synced }, changedIds };
|
|
6845
|
+
}
|
|
5907
6846
|
function applyEnvUpsert(state, environment, now) {
|
|
5908
6847
|
const trimmed = environment.name.trim();
|
|
5909
6848
|
if (!trimmed) {
|
|
@@ -5979,6 +6918,20 @@ function applyEnvSetPriority(state, order, now) {
|
|
|
5979
6918
|
};
|
|
5980
6919
|
return { next: { ...state, synced }, changedIds: filtered.map(envPriorityKey) };
|
|
5981
6920
|
}
|
|
6921
|
+
function applySecretKeyUpsert(state, meta, now) {
|
|
6922
|
+
if (!meta.id || !meta.label.trim() || !meta.salt) {
|
|
6923
|
+
return { next: state, changedIds: [] };
|
|
6924
|
+
}
|
|
6925
|
+
const synced = {
|
|
6926
|
+
...state.synced,
|
|
6927
|
+
secretKeys: {
|
|
6928
|
+
...state.synced.secretKeys ?? {},
|
|
6929
|
+
[meta.id]: { ...meta, label: meta.label.trim() }
|
|
6930
|
+
},
|
|
6931
|
+
meta: { ...state.synced.meta, updatedAt: now }
|
|
6932
|
+
};
|
|
6933
|
+
return { next: { ...state, synced }, changedIds: [meta.id] };
|
|
6934
|
+
}
|
|
5982
6935
|
function applyAssertionUpsert(state, requestId, assertion, now) {
|
|
5983
6936
|
const request = state.synced.collections.requests[requestId];
|
|
5984
6937
|
if (!request) {
|
|
@@ -6121,7 +7074,7 @@ function evictSnapshotsToCap(entries, maxBytes) {
|
|
|
6121
7074
|
};
|
|
6122
7075
|
}
|
|
6123
7076
|
function applySnapshotCapture(state, args, now) {
|
|
6124
|
-
const id = args.id ??
|
|
7077
|
+
const id = args.id ?? generateId2();
|
|
6125
7078
|
const snapshot2 = {
|
|
6126
7079
|
id,
|
|
6127
7080
|
createdAt: now,
|
|
@@ -6210,7 +7163,7 @@ function applyHistoryPurge(state, olderThanMs) {
|
|
|
6210
7163
|
}
|
|
6211
7164
|
|
|
6212
7165
|
// src/workspace/runPlan.ts
|
|
6213
|
-
import { envPriorityKey as envPriorityKey2, generateId as
|
|
7166
|
+
import { envPriorityKey as envPriorityKey2, generateId as generateId3, RUN_BODY_PREVIEW_LIMIT } from "@apicircle/shared";
|
|
6214
7167
|
var MAX_REQUEST_RUNS = 500;
|
|
6215
7168
|
var MAX_PLAN_RUNS = 200;
|
|
6216
7169
|
var ANONYMOUS_ACTOR = { kind: "unknown", name: "unknown" };
|
|
@@ -6357,7 +7310,7 @@ async function runPlan(state, planId, opts = {}) {
|
|
|
6357
7310
|
const baseRefs = plan.envPriorityOrder.length > 0 ? plan.envPriorityOrder : state.synced.environments.priorityOrder;
|
|
6358
7311
|
const envRefs = opts.env ? [{ kind: "local", name: opts.env }, ...baseRefs] : baseRefs;
|
|
6359
7312
|
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
6360
|
-
const planRunId =
|
|
7313
|
+
const planRunId = generateId3();
|
|
6361
7314
|
const t0 = Date.now();
|
|
6362
7315
|
const stepRecords = [];
|
|
6363
7316
|
const newRequestRuns = [];
|
|
@@ -6390,7 +7343,7 @@ async function runPlan(state, planId, opts = {}) {
|
|
|
6390
7343
|
const lookup2 = lookupPlanStepRequest(step, state.synced, state.local);
|
|
6391
7344
|
const baseRequest = lookup2.request;
|
|
6392
7345
|
if (!baseRequest) {
|
|
6393
|
-
const runId =
|
|
7346
|
+
const runId = generateId3();
|
|
6394
7347
|
const error = lookup2.error ?? "Request no longer exists in workspace.";
|
|
6395
7348
|
newRequestRuns.push(orphanRun(runId, step.requestId, error));
|
|
6396
7349
|
stepRecords.push({ requestRunId: runId, passed: false });
|
|
@@ -6622,7 +7575,7 @@ function orphanRun(id, requestId, error) {
|
|
|
6622
7575
|
function buildRequestRun(resolved, result, assertions) {
|
|
6623
7576
|
const { preview, truncated } = clampPreview(result.body ?? "");
|
|
6624
7577
|
return {
|
|
6625
|
-
id:
|
|
7578
|
+
id: generateId3(),
|
|
6626
7579
|
requestId: resolved.id,
|
|
6627
7580
|
startedAt: result.startedAt,
|
|
6628
7581
|
durationMs: result.durationMs,
|
|
@@ -6928,6 +7881,7 @@ var TRANSFORM_FORMAT_LABELS = {
|
|
|
6928
7881
|
};
|
|
6929
7882
|
export {
|
|
6930
7883
|
ANONYMOUS_ACTOR,
|
|
7884
|
+
APICIRCLE_FOLDER_EXPORT_FORMAT,
|
|
6931
7885
|
DESKTOP_APP_ORIGIN,
|
|
6932
7886
|
EMPTY_UNPUSHED_SUMMARY,
|
|
6933
7887
|
HTTP_HEADERS_MAP,
|
|
@@ -6952,6 +7906,8 @@ export {
|
|
|
6952
7906
|
buildRequest,
|
|
6953
7907
|
buildScope,
|
|
6954
7908
|
collectAttachmentSlots,
|
|
7909
|
+
collectFolderExport,
|
|
7910
|
+
collectFolderExportCredentials,
|
|
6955
7911
|
collectVariableSuggestions,
|
|
6956
7912
|
compareSemver,
|
|
6957
7913
|
composeBody,
|
|
@@ -6987,7 +7943,10 @@ export {
|
|
|
6987
7943
|
getLanguageFromContentType,
|
|
6988
7944
|
getVariableAutocomplete,
|
|
6989
7945
|
hasUnpushedChanges,
|
|
7946
|
+
importApicircleFolderInto,
|
|
6990
7947
|
importKey,
|
|
7948
|
+
isApicircleEnvironment,
|
|
7949
|
+
isApicircleFolderExport,
|
|
6991
7950
|
isDesktop,
|
|
6992
7951
|
isInsomniaExport,
|
|
6993
7952
|
isPostmanEnvironment,
|
|
@@ -6996,6 +7955,10 @@ export {
|
|
|
6996
7955
|
lookup,
|
|
6997
7956
|
mergeWithAutoHeaders,
|
|
6998
7957
|
normalizeContentType,
|
|
7958
|
+
parseApicircleEnvironment,
|
|
7959
|
+
parseApicircleEnvironmentDoc,
|
|
7960
|
+
parseApicircleFolderExport,
|
|
7961
|
+
parseApicircleFolderExportDoc,
|
|
6999
7962
|
parseCurl,
|
|
7000
7963
|
parseDigestChallenge,
|
|
7001
7964
|
parseGraphqlSchema,
|
|
@@ -7011,6 +7974,7 @@ export {
|
|
|
7011
7974
|
previewLinkedUpdate,
|
|
7012
7975
|
publishRelease,
|
|
7013
7976
|
readJsonPath,
|
|
7977
|
+
redactFolderExportCredentials,
|
|
7014
7978
|
redactForGit,
|
|
7015
7979
|
refreshToken,
|
|
7016
7980
|
requestDeviceAuthorization,
|
|
@@ -7023,11 +7987,13 @@ export {
|
|
|
7023
7987
|
runClientCredentials,
|
|
7024
7988
|
runPlan,
|
|
7025
7989
|
runRopc,
|
|
7990
|
+
serializeFolderExport,
|
|
7026
7991
|
serializePayload,
|
|
7027
7992
|
serializeWorkspaceForGit,
|
|
7028
7993
|
signJwt,
|
|
7029
7994
|
slugify,
|
|
7030
7995
|
sortVersionsDesc,
|
|
7996
|
+
suggestFolderExportFilename,
|
|
7031
7997
|
suggestHeaders,
|
|
7032
7998
|
summarizeUnpushedChanges,
|
|
7033
7999
|
supportedContentTypeLanguageMap,
|