mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-09-09 11:21:50 +02:00
feat(import): add stable per-resource source keys to the importer contract (#614)
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
e63a87718c
commit
81a2a5d955
@@ -15,6 +15,21 @@ export function convertId(id: string): string {
|
||||
return `GENERATE_ID::${id}`;
|
||||
}
|
||||
|
||||
export function createSourceKeys() {
|
||||
const keys: Record<string, string> = {};
|
||||
return {
|
||||
/** Convert a resource's own document ID, keeping it as that resource's source key. */
|
||||
own(id: string): string {
|
||||
const converted = convertId(id);
|
||||
keys[converted] = id;
|
||||
return converted;
|
||||
},
|
||||
all: (): Record<string, string> => keys,
|
||||
};
|
||||
}
|
||||
|
||||
export type SourceKeys = ReturnType<typeof createSourceKeys>;
|
||||
|
||||
export function importHttpBodyAndHeaders(obj: any) {
|
||||
const { headers } = importHeaders(obj);
|
||||
const { body, bodyType } = importHttpBody(obj.body);
|
||||
|
||||
@@ -1,10 +1,18 @@
|
||||
/* oxlint-disable no-explicit-any */
|
||||
import type { PartialImportResources } from "@yaakapp/api";
|
||||
import { convertId, convertTemplateSyntax, importHttpBodyAndHeaders, isJSObject } from "./common";
|
||||
import {
|
||||
convertId,
|
||||
convertTemplateSyntax,
|
||||
createSourceKeys,
|
||||
importHttpBodyAndHeaders,
|
||||
isJSObject,
|
||||
type SourceKeys,
|
||||
} from "./common";
|
||||
|
||||
export function convertInsomniaV4(parsed: any) {
|
||||
if (!Array.isArray(parsed.resources)) return null;
|
||||
|
||||
const keys = createSourceKeys();
|
||||
const resources: PartialImportResources = {
|
||||
environments: [],
|
||||
folders: [],
|
||||
@@ -20,7 +28,7 @@ export function convertInsomniaV4(parsed: any) {
|
||||
);
|
||||
for (const w of workspacesToImport) {
|
||||
resources.workspaces.push({
|
||||
id: convertId(w._id),
|
||||
id: keys.own(w._id),
|
||||
createdAt: w.created ? new Date(w.created).toISOString().replace("Z", "") : undefined,
|
||||
updatedAt: w.updated ? new Date(w.updated).toISOString().replace("Z", "") : undefined,
|
||||
model: "workspace",
|
||||
@@ -31,7 +39,7 @@ export function convertInsomniaV4(parsed: any) {
|
||||
(r: any) => isJSObject(r) && r._type === "environment",
|
||||
);
|
||||
resources.environments.push(
|
||||
...environmentsToImport.map((r: any) => importEnvironment(r, w._id)),
|
||||
...environmentsToImport.map((r: any) => importEnvironment(r, w._id, keys)),
|
||||
);
|
||||
|
||||
const nextFolder = (parentId: string) => {
|
||||
@@ -40,12 +48,12 @@ export function convertInsomniaV4(parsed: any) {
|
||||
if (!isJSObject(child)) continue;
|
||||
|
||||
if (child._type === "request_group") {
|
||||
resources.folders.push(importFolder(child, w._id));
|
||||
resources.folders.push(importFolder(child, w._id, keys));
|
||||
nextFolder(child._id);
|
||||
} else if (child._type === "request") {
|
||||
resources.httpRequests.push(importHttpRequest(child, w._id));
|
||||
resources.httpRequests.push(importHttpRequest(child, w._id, keys));
|
||||
} else if (child._type === "grpc_request") {
|
||||
resources.grpcRequests.push(importGrpcRequest(child, w._id));
|
||||
resources.grpcRequests.push(importGrpcRequest(child, w._id, keys));
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -60,10 +68,14 @@ export function convertInsomniaV4(parsed: any) {
|
||||
resources.environments = resources.environments.filter(Boolean);
|
||||
resources.workspaces = resources.workspaces.filter(Boolean);
|
||||
|
||||
return { resources: convertTemplateSyntax(resources) };
|
||||
return { resources: convertTemplateSyntax(resources), sourceKeys: keys.all() };
|
||||
}
|
||||
|
||||
function importHttpRequest(r: any, workspaceId: string): PartialImportResources["httpRequests"][0] {
|
||||
function importHttpRequest(
|
||||
r: any,
|
||||
workspaceId: string,
|
||||
keys: SourceKeys,
|
||||
): PartialImportResources["httpRequests"][0] {
|
||||
let authenticationType: string | null = null;
|
||||
let authentication = {};
|
||||
if (r.authentication.type === "bearer") {
|
||||
@@ -80,7 +92,7 @@ function importHttpRequest(r: any, workspaceId: string): PartialImportResources[
|
||||
}
|
||||
|
||||
return {
|
||||
id: convertId(r.meta?.id ?? r._id),
|
||||
id: keys.own(r.meta?.id ?? r._id),
|
||||
createdAt: r.created ? new Date(r.created).toISOString().replace("Z", "") : undefined,
|
||||
updatedAt: r.modified ? new Date(r.modified).toISOString().replace("Z", "") : undefined,
|
||||
workspaceId: convertId(workspaceId),
|
||||
@@ -102,13 +114,17 @@ function importHttpRequest(r: any, workspaceId: string): PartialImportResources[
|
||||
};
|
||||
}
|
||||
|
||||
function importGrpcRequest(r: any, workspaceId: string): PartialImportResources["grpcRequests"][0] {
|
||||
function importGrpcRequest(
|
||||
r: any,
|
||||
workspaceId: string,
|
||||
keys: SourceKeys,
|
||||
): PartialImportResources["grpcRequests"][0] {
|
||||
const parts = r.protoMethodName.split("/").filter((p: any) => p !== "");
|
||||
const service = parts[0] ?? null;
|
||||
const method = parts[1] ?? null;
|
||||
|
||||
return {
|
||||
id: convertId(r.meta?.id ?? r._id),
|
||||
id: keys.own(r.meta?.id ?? r._id),
|
||||
createdAt: r.created ? new Date(r.created).toISOString().replace("Z", "") : undefined,
|
||||
updatedAt: r.modified ? new Date(r.modified).toISOString().replace("Z", "") : undefined,
|
||||
workspaceId: convertId(workspaceId),
|
||||
@@ -131,9 +147,13 @@ function importGrpcRequest(r: any, workspaceId: string): PartialImportResources[
|
||||
};
|
||||
}
|
||||
|
||||
function importFolder(f: any, workspaceId: string): PartialImportResources["folders"][0] {
|
||||
function importFolder(
|
||||
f: any,
|
||||
workspaceId: string,
|
||||
keys: SourceKeys,
|
||||
): PartialImportResources["folders"][0] {
|
||||
return {
|
||||
id: convertId(f._id),
|
||||
id: keys.own(f._id),
|
||||
createdAt: f.created ? new Date(f.created).toISOString().replace("Z", "") : undefined,
|
||||
updatedAt: f.modified ? new Date(f.modified).toISOString().replace("Z", "") : undefined,
|
||||
folderId: f.parentId === workspaceId ? null : convertId(f.parentId),
|
||||
@@ -147,11 +167,12 @@ function importFolder(f: any, workspaceId: string): PartialImportResources["fold
|
||||
function importEnvironment(
|
||||
e: any,
|
||||
workspaceId: string,
|
||||
keys: SourceKeys,
|
||||
isParentOg?: boolean,
|
||||
): PartialImportResources["environments"][0] {
|
||||
const isParent = isParentOg ?? e.parentId === workspaceId;
|
||||
return {
|
||||
id: convertId(e._id),
|
||||
id: keys.own(e._id),
|
||||
createdAt: e.created ? new Date(e.created).toISOString().replace("Z", "") : undefined,
|
||||
updatedAt: e.modified ? new Date(e.modified).toISOString().replace("Z", "") : undefined,
|
||||
workspaceId: convertId(workspaceId),
|
||||
|
||||
@@ -3,9 +3,11 @@ import type { PartialImportResources } from "@yaakapp/api";
|
||||
import {
|
||||
convertId,
|
||||
convertTemplateSyntax,
|
||||
createSourceKeys,
|
||||
importHeaders,
|
||||
importHttpBodyAndHeaders,
|
||||
isJSObject,
|
||||
type SourceKeys,
|
||||
} from "./common";
|
||||
|
||||
export function convertInsomniaV5(parsed: any) {
|
||||
@@ -18,6 +20,7 @@ export function convertInsomniaV5(parsed: any) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const keys = createSourceKeys();
|
||||
const resources: PartialImportResources = {
|
||||
environments: [],
|
||||
folders: [],
|
||||
@@ -30,7 +33,7 @@ export function convertInsomniaV5(parsed: any) {
|
||||
// Import workspaces
|
||||
const meta = ("meta" in parsed ? parsed.meta : {}) as Record<string, any>;
|
||||
resources.workspaces.push({
|
||||
id: convertId(meta.id ?? "collection"),
|
||||
id: keys.own(meta.id ?? "collection"),
|
||||
createdAt: meta.created ? new Date(meta.created).toISOString().replace("Z", "") : undefined,
|
||||
updatedAt: meta.modified ? new Date(meta.modified).toISOString().replace("Z", "") : undefined,
|
||||
model: "workspace",
|
||||
@@ -42,8 +45,10 @@ export function convertInsomniaV5(parsed: any) {
|
||||
|
||||
// Import environments
|
||||
resources.environments.push(
|
||||
importEnvironment(parsed.environments, meta.id, true),
|
||||
...(parsed.environments.subEnvironments ?? []).map((r: any) => importEnvironment(r, meta.id)),
|
||||
importEnvironment(parsed.environments, meta.id, keys, true),
|
||||
...(parsed.environments.subEnvironments ?? []).map((r: any) =>
|
||||
importEnvironment(r, meta.id, keys),
|
||||
),
|
||||
);
|
||||
|
||||
// Import folders
|
||||
@@ -52,16 +57,16 @@ export function convertInsomniaV5(parsed: any) {
|
||||
if (!isJSObject(child)) continue;
|
||||
|
||||
if (Array.isArray(child.children)) {
|
||||
const { folder, environment } = importFolder(child, meta.id, parentId);
|
||||
const { folder, environment } = importFolder(child, meta.id, parentId, keys);
|
||||
resources.folders.push(folder);
|
||||
if (environment) resources.environments.push(environment);
|
||||
nextFolder(child.children, child.meta.id);
|
||||
} else if (child.method) {
|
||||
resources.httpRequests.push(importHttpRequest(child, meta.id, parentId));
|
||||
resources.httpRequests.push(importHttpRequest(child, meta.id, parentId, keys));
|
||||
} else if (child.protoFileId) {
|
||||
resources.grpcRequests.push(importGrpcRequest(child, meta.id, parentId));
|
||||
resources.grpcRequests.push(importGrpcRequest(child, meta.id, parentId, keys));
|
||||
} else if (child.url) {
|
||||
resources.websocketRequests.push(importWebsocketRequest(child, meta.id, parentId));
|
||||
resources.websocketRequests.push(importWebsocketRequest(child, meta.id, parentId, keys));
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -75,13 +80,14 @@ export function convertInsomniaV5(parsed: any) {
|
||||
resources.environments = resources.environments.filter(Boolean);
|
||||
resources.workspaces = resources.workspaces.filter(Boolean);
|
||||
|
||||
return { resources: convertTemplateSyntax(resources) };
|
||||
return { resources: convertTemplateSyntax(resources), sourceKeys: keys.all() };
|
||||
}
|
||||
|
||||
function importHttpRequest(
|
||||
r: any,
|
||||
workspaceId: string,
|
||||
parentId: string,
|
||||
keys: SourceKeys,
|
||||
): PartialImportResources["httpRequests"][0] {
|
||||
const id = r.meta?.id ?? r._id;
|
||||
const created = r.meta?.created ?? r.created;
|
||||
@@ -89,7 +95,7 @@ function importHttpRequest(
|
||||
const sortKey = r.meta?.sortKey ?? r.sortKey;
|
||||
|
||||
return {
|
||||
id: convertId(id),
|
||||
id: keys.own(id),
|
||||
workspaceId: convertId(workspaceId),
|
||||
createdAt: created ? new Date(created).toISOString().replace("Z", "") : undefined,
|
||||
updatedAt: updated ? new Date(updated).toISOString().replace("Z", "") : undefined,
|
||||
@@ -114,6 +120,7 @@ function importGrpcRequest(
|
||||
r: any,
|
||||
workspaceId: string,
|
||||
parentId: string,
|
||||
keys: SourceKeys,
|
||||
): PartialImportResources["grpcRequests"][0] {
|
||||
const id = r.meta?.id ?? r._id;
|
||||
const created = r.meta?.created ?? r.created;
|
||||
@@ -126,7 +133,7 @@ function importGrpcRequest(
|
||||
|
||||
return {
|
||||
model: "grpc_request",
|
||||
id: convertId(id),
|
||||
id: keys.own(id),
|
||||
workspaceId: convertId(workspaceId),
|
||||
createdAt: created ? new Date(created).toISOString().replace("Z", "") : undefined,
|
||||
updatedAt: updated ? new Date(updated).toISOString().replace("Z", "") : undefined,
|
||||
@@ -152,6 +159,7 @@ function importWebsocketRequest(
|
||||
r: any,
|
||||
workspaceId: string,
|
||||
parentId: string,
|
||||
keys: SourceKeys,
|
||||
): PartialImportResources["websocketRequests"][0] {
|
||||
const id = r.meta?.id ?? r._id;
|
||||
const created = r.meta?.created ?? r.created;
|
||||
@@ -160,7 +168,7 @@ function importWebsocketRequest(
|
||||
|
||||
return {
|
||||
model: "websocket_request",
|
||||
id: convertId(id),
|
||||
id: keys.own(id),
|
||||
workspaceId: convertId(workspaceId),
|
||||
createdAt: created ? new Date(created).toISOString().replace("Z", "") : undefined,
|
||||
updatedAt: updated ? new Date(updated).toISOString().replace("Z", "") : undefined,
|
||||
@@ -198,6 +206,7 @@ function importFolder(
|
||||
f: any,
|
||||
workspaceId: string,
|
||||
parentId: string,
|
||||
keys: SourceKeys,
|
||||
): {
|
||||
folder: PartialImportResources["folders"][0];
|
||||
environment: PartialImportResources["environments"][0] | null;
|
||||
@@ -210,7 +219,7 @@ function importFolder(
|
||||
let environment: PartialImportResources["environments"][0] | null = null;
|
||||
if (Object.keys(f.environment ?? {}).length > 0) {
|
||||
environment = {
|
||||
id: convertId(`${id}folder`),
|
||||
id: keys.own(`${id}folder`),
|
||||
createdAt: created ? new Date(created).toISOString().replace("Z", "") : undefined,
|
||||
updatedAt: updated ? new Date(updated).toISOString().replace("Z", "") : undefined,
|
||||
workspaceId: convertId(workspaceId),
|
||||
@@ -230,7 +239,7 @@ function importFolder(
|
||||
return {
|
||||
folder: {
|
||||
model: "folder",
|
||||
id: convertId(id),
|
||||
id: keys.own(id),
|
||||
createdAt: created ? new Date(created).toISOString().replace("Z", "") : undefined,
|
||||
updatedAt: updated ? new Date(updated).toISOString().replace("Z", "") : undefined,
|
||||
folderId: parentId === workspaceId ? null : convertId(parentId),
|
||||
@@ -248,6 +257,7 @@ function importFolder(
|
||||
function importEnvironment(
|
||||
e: any,
|
||||
workspaceId: string,
|
||||
keys: SourceKeys,
|
||||
isParent?: boolean,
|
||||
): PartialImportResources["environments"][0] {
|
||||
const id = e.meta?.id ?? e._id;
|
||||
@@ -256,7 +266,7 @@ function importEnvironment(
|
||||
const sortKey = e.meta?.sortKey ?? e.sortKey;
|
||||
|
||||
return {
|
||||
id: convertId(id),
|
||||
id: keys.own(id),
|
||||
createdAt: created ? new Date(created).toISOString().replace("Z", "") : undefined,
|
||||
updatedAt: updated ? new Date(updated).toISOString().replace("Z", "") : undefined,
|
||||
workspaceId: convertId(workspaceId),
|
||||
|
||||
@@ -132,5 +132,13 @@
|
||||
"name": "Dummy"
|
||||
}
|
||||
]
|
||||
},
|
||||
"sourceKeys": {
|
||||
"GENERATE_ID::env_16c0dec5b77c414ae0e419b8f10c3701300c5900": "env_16c0dec5b77c414ae0e419b8f10c3701300c5900",
|
||||
"GENERATE_ID::env_799ae3d723ef44af91b4817e5d057e6d": "env_799ae3d723ef44af91b4817e5d057e6d",
|
||||
"GENERATE_ID::env_030fbfdbb274426ebd78e2e6518f8553": "env_030fbfdbb274426ebd78e2e6518f8553",
|
||||
"GENERATE_ID::fld_859d1df78261463480b6a3a1419517e3": "fld_859d1df78261463480b6a3a1419517e3",
|
||||
"GENERATE_ID::req_84cd9ae4bd034dd8bb730e856a665cbb": "req_84cd9ae4bd034dd8bb730e856a665cbb",
|
||||
"GENERATE_ID::wrk_d4d92f7c0ee947b89159243506687019": "wrk_d4d92f7c0ee947b89159243506687019"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,5 +116,13 @@
|
||||
"headers": []
|
||||
}
|
||||
]
|
||||
},
|
||||
"sourceKeys": {
|
||||
"GENERATE_ID::env_e46dc73e8ccda30ca132153e8f11183bd08119ce": "env_e46dc73e8ccda30ca132153e8f11183bd08119ce",
|
||||
"GENERATE_ID::fld_296933ea4ea84783a775d199997e9be7folder": "fld_296933ea4ea84783a775d199997e9be7folder",
|
||||
"GENERATE_ID::fld_296933ea4ea84783a775d199997e9be7": "fld_296933ea4ea84783a775d199997e9be7",
|
||||
"GENERATE_ID::req_9a80320365ac4509ade406359dbc6a71": "req_9a80320365ac4509ade406359dbc6a71",
|
||||
"GENERATE_ID::req_e3f8cdbd58784a539dd4c1e127d73451": "req_e3f8cdbd58784a539dd4c1e127d73451",
|
||||
"GENERATE_ID::wrk_9717dd1c9e0c4b2e9ed6d2abcf3bd45c": "wrk_9717dd1c9e0c4b2e9ed6d2abcf3bd45c"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -189,5 +189,15 @@
|
||||
"headers": []
|
||||
}
|
||||
]
|
||||
},
|
||||
"sourceKeys": {
|
||||
"GENERATE_ID::env_20945044d3c8497ca8b717bef750987e": "env_20945044d3c8497ca8b717bef750987e",
|
||||
"GENERATE_ID::env_6f7728bb7fc04d558d668e954d756ea2": "env_6f7728bb7fc04d558d668e954d756ea2",
|
||||
"GENERATE_ID::env_976a8b6eb5d44fb6a20150f65c32d243": "env_976a8b6eb5d44fb6a20150f65c32d243",
|
||||
"GENERATE_ID::fld_42eb2e2bb22b4cedacbd3d057634e80c": "fld_42eb2e2bb22b4cedacbd3d057634e80c",
|
||||
"GENERATE_ID::greq_06d659324df94504a4d64632be7106b3": "greq_06d659324df94504a4d64632be7106b3",
|
||||
"GENERATE_ID::req_d72fff2a6b104b91a2ebe9de9edd2785": "req_d72fff2a6b104b91a2ebe9de9edd2785",
|
||||
"GENERATE_ID::ws-req_5d1a4c7c79494743962e5176f6add270": "ws-req_5d1a4c7c79494743962e5176f6add270",
|
||||
"GENERATE_ID::wrk_c1eacfa750a04f3ea9985ef28043fa53": "wrk_c1eacfa750a04f3ea9985ef28043fa53"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +24,38 @@ describe("importer-yaak", () => {
|
||||
expect(result).toEqual(parseJsonOrYaml(expected));
|
||||
});
|
||||
}
|
||||
|
||||
test("Keys resources by their Insomnia _id, unchanged by a rename", () => {
|
||||
const collection = (requestName: string) =>
|
||||
YAML.stringify({
|
||||
type: "collection.insomnia.rest/5.0",
|
||||
name: "Keys",
|
||||
meta: { id: "wrk_1" },
|
||||
environments: { meta: { id: "env_1" }, name: "Base", data: {} },
|
||||
collection: [
|
||||
{
|
||||
meta: { id: "fld_1" },
|
||||
name: "Folder",
|
||||
children: [
|
||||
{
|
||||
meta: { id: "req_1" },
|
||||
name: requestName,
|
||||
method: "GET",
|
||||
url: "https://yaak.app",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const before = convertInsomnia(collection("Original"));
|
||||
const after = convertInsomnia(collection("Renamed"));
|
||||
|
||||
expect(before?.sourceKeys?.[before.resources.httpRequests[0]!.id]).toBe("req_1");
|
||||
expect(after?.sourceKeys?.[after.resources.httpRequests[0]!.id]).toBe("req_1");
|
||||
expect(before?.sourceKeys?.[before.resources.folders[0]!.id]).toBe("fld_1");
|
||||
expect(before?.sourceKeys?.[before.resources.workspaces[0]!.id]).toBe("wrk_1");
|
||||
});
|
||||
});
|
||||
|
||||
function parseJsonOrYaml(text: string): unknown {
|
||||
|
||||
Reference in New Issue
Block a user