@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/{chunk-PUXJFN2Z.js → chunk-L5DQT7V6.js} +3 -3
- package/dist/{chunk-PUXJFN2Z.js.map → chunk-L5DQT7V6.js.map} +1 -1
- package/dist/index.cjs +1002 -19
- 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 +978 -8
- 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
|
@@ -2943,6 +2943,9 @@ function resolveLocation(from, location) {
|
|
|
2943
2943
|
return null;
|
|
2944
2944
|
}
|
|
2945
2945
|
}
|
|
2946
|
+
function isBrowserRuntime() {
|
|
2947
|
+
return typeof window !== "undefined" && typeof window.document !== "undefined";
|
|
2948
|
+
}
|
|
2946
2949
|
async function executeRequest(req, opts = {}) {
|
|
2947
2950
|
const fetchImpl = opts.fetchImpl ?? globalThis.fetch;
|
|
2948
2951
|
const timeoutMs = opts.timeoutMs === void 0 ? DEFAULT_TIMEOUT_MS : opts.timeoutMs;
|
|
@@ -2967,6 +2970,7 @@ async function executeRequest(req, opts = {}) {
|
|
|
2967
2970
|
() => controller.abort(new Error(`Request timed out after ${timeoutMs}ms`)),
|
|
2968
2971
|
timeoutMs
|
|
2969
2972
|
);
|
|
2973
|
+
const redirectMode = isBrowserRuntime() ? "follow" : "manual";
|
|
2970
2974
|
let currentUrl = builtRequest.url;
|
|
2971
2975
|
let currentHeaders = { ...builtRequest.headers };
|
|
2972
2976
|
let currentMethod = builtRequest.method;
|
|
@@ -2976,7 +2980,7 @@ async function executeRequest(req, opts = {}) {
|
|
|
2976
2980
|
headers: currentHeaders,
|
|
2977
2981
|
body: currentBody,
|
|
2978
2982
|
signal: controller.signal,
|
|
2979
|
-
redirect:
|
|
2983
|
+
redirect: redirectMode
|
|
2980
2984
|
});
|
|
2981
2985
|
let redirectCount = 0;
|
|
2982
2986
|
while (REDIRECT_STATUSES.has(response.status) && redirectCount < MAX_REDIRECTS) {
|
|
@@ -3002,7 +3006,7 @@ async function executeRequest(req, opts = {}) {
|
|
|
3002
3006
|
headers: currentHeaders,
|
|
3003
3007
|
body: currentBody,
|
|
3004
3008
|
signal: controller.signal,
|
|
3005
|
-
redirect:
|
|
3009
|
+
redirect: redirectMode
|
|
3006
3010
|
});
|
|
3007
3011
|
redirectCount++;
|
|
3008
3012
|
}
|
|
@@ -3969,6 +3973,699 @@ function parseAuth2(auth, warnings, name) {
|
|
|
3969
3973
|
}
|
|
3970
3974
|
}
|
|
3971
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
|
+
|
|
3972
4669
|
// src/assertions/runAssertions.ts
|
|
3973
4670
|
function runAssertions(assertions, exec) {
|
|
3974
4671
|
return assertions.map((a) => runOne(a, exec));
|
|
@@ -5716,7 +6413,232 @@ function structurallyEqual2(a, b) {
|
|
|
5716
6413
|
}
|
|
5717
6414
|
|
|
5718
6415
|
// src/workspace/applyMutation.ts
|
|
5719
|
-
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
|
|
5720
6642
|
function applyMutation(state, patch, options = {}) {
|
|
5721
6643
|
const now = options.now ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
5722
6644
|
switch (patch.kind) {
|
|
@@ -5732,6 +6654,8 @@ function applyMutation(state, patch, options = {}) {
|
|
|
5732
6654
|
return applyFolderDelete(state, patch.id, now);
|
|
5733
6655
|
case "folder.move":
|
|
5734
6656
|
return applyFolderMove(state, patch.id, patch.newParentId, now);
|
|
6657
|
+
case "folder.import_apicircle":
|
|
6658
|
+
return applyFolderImportApicircle(state, patch.parsed, patch.parentFolderId, now);
|
|
5735
6659
|
case "environment.upsert":
|
|
5736
6660
|
return applyEnvUpsert(state, patch.environment, now);
|
|
5737
6661
|
case "environment.delete":
|
|
@@ -5740,6 +6664,8 @@ function applyMutation(state, patch, options = {}) {
|
|
|
5740
6664
|
return applyEnvSetActive(state, patch.name, now);
|
|
5741
6665
|
case "environment.setPriority":
|
|
5742
6666
|
return applyEnvSetPriority(state, patch.order, now);
|
|
6667
|
+
case "secretKey.upsert":
|
|
6668
|
+
return applySecretKeyUpsert(state, patch.meta, now);
|
|
5743
6669
|
case "assertion.upsert":
|
|
5744
6670
|
return applyAssertionUpsert(state, patch.requestId, patch.assertion, now);
|
|
5745
6671
|
case "assertion.delete":
|
|
@@ -5900,6 +6826,23 @@ function applyFolderMove(state, id, newParentId, now) {
|
|
|
5900
6826
|
};
|
|
5901
6827
|
return { next: { ...state, synced }, changedIds: [id] };
|
|
5902
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
|
+
}
|
|
5903
6846
|
function applyEnvUpsert(state, environment, now) {
|
|
5904
6847
|
const trimmed = environment.name.trim();
|
|
5905
6848
|
if (!trimmed) {
|
|
@@ -5975,6 +6918,20 @@ function applyEnvSetPriority(state, order, now) {
|
|
|
5975
6918
|
};
|
|
5976
6919
|
return { next: { ...state, synced }, changedIds: filtered.map(envPriorityKey) };
|
|
5977
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
|
+
}
|
|
5978
6935
|
function applyAssertionUpsert(state, requestId, assertion, now) {
|
|
5979
6936
|
const request = state.synced.collections.requests[requestId];
|
|
5980
6937
|
if (!request) {
|
|
@@ -6117,7 +7074,7 @@ function evictSnapshotsToCap(entries, maxBytes) {
|
|
|
6117
7074
|
};
|
|
6118
7075
|
}
|
|
6119
7076
|
function applySnapshotCapture(state, args, now) {
|
|
6120
|
-
const id = args.id ??
|
|
7077
|
+
const id = args.id ?? generateId2();
|
|
6121
7078
|
const snapshot2 = {
|
|
6122
7079
|
id,
|
|
6123
7080
|
createdAt: now,
|
|
@@ -6206,7 +7163,7 @@ function applyHistoryPurge(state, olderThanMs) {
|
|
|
6206
7163
|
}
|
|
6207
7164
|
|
|
6208
7165
|
// src/workspace/runPlan.ts
|
|
6209
|
-
import { envPriorityKey as envPriorityKey2, generateId as
|
|
7166
|
+
import { envPriorityKey as envPriorityKey2, generateId as generateId3, RUN_BODY_PREVIEW_LIMIT } from "@apicircle/shared";
|
|
6210
7167
|
var MAX_REQUEST_RUNS = 500;
|
|
6211
7168
|
var MAX_PLAN_RUNS = 200;
|
|
6212
7169
|
var ANONYMOUS_ACTOR = { kind: "unknown", name: "unknown" };
|
|
@@ -6353,7 +7310,7 @@ async function runPlan(state, planId, opts = {}) {
|
|
|
6353
7310
|
const baseRefs = plan.envPriorityOrder.length > 0 ? plan.envPriorityOrder : state.synced.environments.priorityOrder;
|
|
6354
7311
|
const envRefs = opts.env ? [{ kind: "local", name: opts.env }, ...baseRefs] : baseRefs;
|
|
6355
7312
|
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
6356
|
-
const planRunId =
|
|
7313
|
+
const planRunId = generateId3();
|
|
6357
7314
|
const t0 = Date.now();
|
|
6358
7315
|
const stepRecords = [];
|
|
6359
7316
|
const newRequestRuns = [];
|
|
@@ -6386,7 +7343,7 @@ async function runPlan(state, planId, opts = {}) {
|
|
|
6386
7343
|
const lookup2 = lookupPlanStepRequest(step, state.synced, state.local);
|
|
6387
7344
|
const baseRequest = lookup2.request;
|
|
6388
7345
|
if (!baseRequest) {
|
|
6389
|
-
const runId =
|
|
7346
|
+
const runId = generateId3();
|
|
6390
7347
|
const error = lookup2.error ?? "Request no longer exists in workspace.";
|
|
6391
7348
|
newRequestRuns.push(orphanRun(runId, step.requestId, error));
|
|
6392
7349
|
stepRecords.push({ requestRunId: runId, passed: false });
|
|
@@ -6618,7 +7575,7 @@ function orphanRun(id, requestId, error) {
|
|
|
6618
7575
|
function buildRequestRun(resolved, result, assertions) {
|
|
6619
7576
|
const { preview, truncated } = clampPreview(result.body ?? "");
|
|
6620
7577
|
return {
|
|
6621
|
-
id:
|
|
7578
|
+
id: generateId3(),
|
|
6622
7579
|
requestId: resolved.id,
|
|
6623
7580
|
startedAt: result.startedAt,
|
|
6624
7581
|
durationMs: result.durationMs,
|
|
@@ -6924,6 +7881,7 @@ var TRANSFORM_FORMAT_LABELS = {
|
|
|
6924
7881
|
};
|
|
6925
7882
|
export {
|
|
6926
7883
|
ANONYMOUS_ACTOR,
|
|
7884
|
+
APICIRCLE_FOLDER_EXPORT_FORMAT,
|
|
6927
7885
|
DESKTOP_APP_ORIGIN,
|
|
6928
7886
|
EMPTY_UNPUSHED_SUMMARY,
|
|
6929
7887
|
HTTP_HEADERS_MAP,
|
|
@@ -6948,6 +7906,8 @@ export {
|
|
|
6948
7906
|
buildRequest,
|
|
6949
7907
|
buildScope,
|
|
6950
7908
|
collectAttachmentSlots,
|
|
7909
|
+
collectFolderExport,
|
|
7910
|
+
collectFolderExportCredentials,
|
|
6951
7911
|
collectVariableSuggestions,
|
|
6952
7912
|
compareSemver,
|
|
6953
7913
|
composeBody,
|
|
@@ -6983,7 +7943,10 @@ export {
|
|
|
6983
7943
|
getLanguageFromContentType,
|
|
6984
7944
|
getVariableAutocomplete,
|
|
6985
7945
|
hasUnpushedChanges,
|
|
7946
|
+
importApicircleFolderInto,
|
|
6986
7947
|
importKey,
|
|
7948
|
+
isApicircleEnvironment,
|
|
7949
|
+
isApicircleFolderExport,
|
|
6987
7950
|
isDesktop,
|
|
6988
7951
|
isInsomniaExport,
|
|
6989
7952
|
isPostmanEnvironment,
|
|
@@ -6992,6 +7955,10 @@ export {
|
|
|
6992
7955
|
lookup,
|
|
6993
7956
|
mergeWithAutoHeaders,
|
|
6994
7957
|
normalizeContentType,
|
|
7958
|
+
parseApicircleEnvironment,
|
|
7959
|
+
parseApicircleEnvironmentDoc,
|
|
7960
|
+
parseApicircleFolderExport,
|
|
7961
|
+
parseApicircleFolderExportDoc,
|
|
6995
7962
|
parseCurl,
|
|
6996
7963
|
parseDigestChallenge,
|
|
6997
7964
|
parseGraphqlSchema,
|
|
@@ -7007,6 +7974,7 @@ export {
|
|
|
7007
7974
|
previewLinkedUpdate,
|
|
7008
7975
|
publishRelease,
|
|
7009
7976
|
readJsonPath,
|
|
7977
|
+
redactFolderExportCredentials,
|
|
7010
7978
|
redactForGit,
|
|
7011
7979
|
refreshToken,
|
|
7012
7980
|
requestDeviceAuthorization,
|
|
@@ -7019,11 +7987,13 @@ export {
|
|
|
7019
7987
|
runClientCredentials,
|
|
7020
7988
|
runPlan,
|
|
7021
7989
|
runRopc,
|
|
7990
|
+
serializeFolderExport,
|
|
7022
7991
|
serializePayload,
|
|
7023
7992
|
serializeWorkspaceForGit,
|
|
7024
7993
|
signJwt,
|
|
7025
7994
|
slugify,
|
|
7026
7995
|
sortVersionsDesc,
|
|
7996
|
+
suggestFolderExportFilename,
|
|
7027
7997
|
suggestHeaders,
|
|
7028
7998
|
summarizeUnpushedChanges,
|
|
7029
7999
|
supportedContentTypeLanguageMap,
|