diff --git a/package-lock.json b/package-lock.json index 7dfa923f..7e558688 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "plugins/*" ], "dependencies": { - "@yaakapp/api": "^0.5.1" + "@yaakapp/api": "^0.5.3" }, "devDependencies": { "@types/node": "^22.7.4", @@ -999,9 +999,9 @@ } }, "node_modules/@yaakapp/api": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/@yaakapp/api/-/api-0.5.1.tgz", - "integrity": "sha512-0YFrrTJVjrnsSm9BTxSnz1pd1+Q52/CBV1QpTVtXPPqlSIwcvj7jMdwuDpSKy5G8xbaoVzTgBnW25RgKog/q7g==", + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/@yaakapp/api/-/api-0.5.3.tgz", + "integrity": "sha512-JKO0t5H+wM2KWpNgiNW5UVmk66c7p2WFCHa8TnLwnkFpub/3ktZfMY1Y+c21N2gsurqUe3wmcNRM0J1nQrR9rA==", "dependencies": { "@types/node": "^22.5.4" } diff --git a/package.json b/package.json index 1e60d96c..882533ab 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,6 @@ "workspaces-run": "^1.0.2" }, "dependencies": { - "@yaakapp/api": "^0.5.1" + "@yaakapp/api": "^0.5.3" } } diff --git a/plugins/importer-insomnia/src/common.ts b/plugins/importer-insomnia/src/common.ts new file mode 100644 index 00000000..253924f0 --- /dev/null +++ b/plugins/importer-insomnia/src/common.ts @@ -0,0 +1,34 @@ + +export function convertSyntax(variable: string): string { + if (!isJSString(variable)) return variable; + return variable.replaceAll(/{{\s*(_\.)?([^}]+)\s*}}/g, '${[$2]}'); +} + +export function isJSObject(obj: any) { + return Object.prototype.toString.call(obj) === '[object Object]'; +} + +export function isJSString(obj: any) { + return Object.prototype.toString.call(obj) === '[object String]'; +} + +export function convertId(id: string): string { + if (id.startsWith('GENERATE_ID::')) { + return id; + } + return `GENERATE_ID::${id}`; +} + +export function deleteUndefinedAttrs(obj: T): T { + if (Array.isArray(obj) && obj != null) { + return obj.map(deleteUndefinedAttrs) as T; + } else if (typeof obj === 'object' && obj != null) { + return Object.fromEntries( + Object.entries(obj) + .filter(([, v]) => v !== undefined) + .map(([k, v]) => [k, deleteUndefinedAttrs(v)]), + ) as T; + } else { + return obj; + } +} diff --git a/plugins/importer-insomnia/src/index.ts b/plugins/importer-insomnia/src/index.ts index e589365a..4d95ac2b 100644 --- a/plugins/importer-insomnia/src/index.ts +++ b/plugins/importer-insomnia/src/index.ts @@ -1,22 +1,15 @@ -import { Context, Environment, Folder, GrpcRequest, HttpRequest, PluginDefinition, Workspace } from '@yaakapp/api'; +import { Context, PluginDefinition } from '@yaakapp/api'; import YAML from 'yaml'; - -type AtLeast = Partial & Pick; - -export interface ExportResources { - workspaces: AtLeast[]; - environments: AtLeast[]; - httpRequests: AtLeast[]; - grpcRequests: AtLeast[]; - folders: AtLeast[]; -} +import { deleteUndefinedAttrs, isJSObject } from './common'; +import { convertInsomniaV4 } from './v4'; +import { convertInsomniaV5 } from './v5'; export const plugin: PluginDefinition = { importer: { name: 'Insomnia', description: 'Import Insomnia workspaces', - onImport(_ctx: Context, args: { text: string }) { - return convertInsomnia(args.text) as any; + async onImport(_ctx: Context, args: { text: string }) { + return convertInsomnia(args.text); }, }, }; @@ -34,258 +27,9 @@ export function convertInsomnia(contents: string) { } catch (e) { } - if (!isJSObject(parsed)) return; - if (!Array.isArray(parsed.resources)) return; + if (!isJSObject(parsed)) return null; - const resources: ExportResources = { - workspaces: [], - httpRequests: [], - grpcRequests: [], - environments: [], - folders: [], - }; + const result = convertInsomniaV5(parsed) ?? convertInsomniaV4(parsed); - // Import workspaces - const workspacesToImport = parsed.resources.filter(isWorkspace); - for (const w of workspacesToImport) { - resources.workspaces.push({ - id: convertId(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', - name: w.name, - description: w.description || undefined, - }); - const environmentsToImport = parsed.resources.filter( - (r: any) => isEnvironment(r), - ); - resources.environments.push( - ...environmentsToImport.map((r: any) => importEnvironment(r, w._id)), - ); - - const nextFolder = (parentId: string) => { - const children = parsed.resources.filter((r: any) => r.parentId === parentId); - let sortPriority = 0; - for (const child of children) { - if (isRequestGroup(child)) { - resources.folders.push(importFolder(child, w._id)); - nextFolder(child._id); - } else if (isHttpRequest(child)) { - resources.httpRequests.push( - importHttpRequest(child, w._id, sortPriority++), - ); - } else if (isGrpcRequest(child)) { - resources.grpcRequests.push( - importGrpcRequest(child, w._id, sortPriority++), - ); - } - } - }; - - // Import folders - nextFolder(w._id); - } - - // Filter out any `null` values - resources.httpRequests = resources.httpRequests.filter(Boolean); - resources.grpcRequests = resources.grpcRequests.filter(Boolean); - resources.environments = resources.environments.filter(Boolean); - resources.workspaces = resources.workspaces.filter(Boolean); - - return { resources: deleteUndefinedAttrs(resources) }; -} - -function importEnvironment(e: any, workspaceId: string): ExportResources['environments'][0] { - return { - id: convertId(e._id), - createdAt: e.created ? new Date(e.created).toISOString().replace('Z', '') : undefined, - updatedAt: e.updated ? new Date(e.updated).toISOString().replace('Z', '') : undefined, - workspaceId: convertId(workspaceId), - base: e.parentId === workspaceId, - model: 'environment', - name: e.name, - variables: Object.entries(e.data).map(([name, value]) => ({ - enabled: true, - name, - value: `${value}`, - })), - }; -} - -function importFolder(f: any, workspaceId: string): ExportResources['folders'][0] { - return { - id: convertId(f._id), - createdAt: f.created ? new Date(f.created).toISOString().replace('Z', '') : undefined, - updatedAt: f.updated ? new Date(f.updated).toISOString().replace('Z', '') : undefined, - folderId: f.parentId === workspaceId ? null : convertId(f.parentId), - workspaceId: convertId(workspaceId), - description: f.description || undefined, - model: 'folder', - name: f.name, - }; -} - -function importGrpcRequest( - r: any, - workspaceId: string, - sortPriority = 0, -): ExportResources['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._id), - createdAt: r.created ? new Date(r.created).toISOString().replace('Z', '') : undefined, - updatedAt: r.updated ? new Date(r.updated).toISOString().replace('Z', '') : undefined, - workspaceId: convertId(workspaceId), - folderId: r.parentId === workspaceId ? null : convertId(r.parentId), - model: 'grpc_request', - sortPriority, - name: r.name, - description: r.description || undefined, - url: convertSyntax(r.url), - service, - method, - message: r.body?.text ?? '', - metadata: (r.metadata ?? []) - .map((h: any) => ({ - enabled: !h.disabled, - name: h.name ?? '', - value: h.value ?? '', - })) - .filter(({ name, value }: any) => name !== '' || value !== ''), - }; -} - -function importHttpRequest( - r: any, - workspaceId: string, - sortPriority = 0, -): ExportResources['httpRequests'][0] { - let bodyType: string | null = null; - let body = {}; - if (r.body.mimeType === 'application/octet-stream') { - bodyType = 'binary'; - body = { filePath: r.body.fileName ?? '' }; - } else if (r.body?.mimeType === 'application/x-www-form-urlencoded') { - bodyType = 'application/x-www-form-urlencoded'; - body = { - form: (r.body.params ?? []).map((p: any) => ({ - enabled: !p.disabled, - name: p.name ?? '', - value: p.value ?? '', - })), - }; - } else if (r.body?.mimeType === 'multipart/form-data') { - bodyType = 'multipart/form-data'; - body = { - form: (r.body.params ?? []).map((p: any) => ({ - enabled: !p.disabled, - name: p.name ?? '', - value: p.value ?? '', - file: p.fileName ?? null, - })), - }; - } else if (r.body?.mimeType === 'application/graphql') { - bodyType = 'graphql'; - body = { text: convertSyntax(r.body.text ?? '') }; - } else if (r.body?.mimeType === 'application/json') { - bodyType = 'application/json'; - body = { text: convertSyntax(r.body.text ?? '') }; - } - - let authenticationType: string | null = null; - let authentication = {}; - if (r.authentication.type === 'bearer') { - authenticationType = 'bearer'; - authentication = { - token: convertSyntax(r.authentication.token), - }; - } else if (r.authentication.type === 'basic') { - authenticationType = 'basic'; - authentication = { - username: convertSyntax(r.authentication.username), - password: convertSyntax(r.authentication.password), - }; - } - - return { - id: convertId(r._id), - createdAt: r.created ? new Date(r.created).toISOString().replace('Z', '') : undefined, - updatedAt: r.updated ? new Date(r.updated).toISOString().replace('Z', '') : undefined, - workspaceId: convertId(workspaceId), - folderId: r.parentId === workspaceId ? null : convertId(r.parentId), - model: 'http_request', - sortPriority, - name: r.name, - description: r.description || undefined, - url: convertSyntax(r.url), - body, - bodyType, - authentication, - authenticationType, - method: r.method, - headers: (r.headers ?? []) - .map((h: any) => ({ - enabled: !h.disabled, - name: h.name ?? '', - value: h.value ?? '', - })) - .filter(({ name, value }: any) => name !== '' || value !== ''), - }; -} - -function convertSyntax(variable: string): string { - if (!isJSString(variable)) return variable; - return variable.replaceAll(/{{\s*(_\.)?([^}]+)\s*}}/g, '${[$2]}'); -} - -function isWorkspace(obj: any) { - return isJSObject(obj) && obj._type === 'workspace'; -} - -function isRequestGroup(obj: any) { - return isJSObject(obj) && obj._type === 'request_group'; -} - -function isHttpRequest(obj: any) { - return isJSObject(obj) && obj._type === 'request'; -} - -function isGrpcRequest(obj: any) { - return isJSObject(obj) && obj._type === 'grpc_request'; -} - -function isEnvironment(obj: any) { - return isJSObject(obj) && obj._type === 'environment'; -} - -function isJSObject(obj: any) { - return Object.prototype.toString.call(obj) === '[object Object]'; -} - -function isJSString(obj: any) { - return Object.prototype.toString.call(obj) === '[object String]'; -} - -function convertId(id: string): string { - if (id.startsWith('GENERATE_ID::')) { - return id; - } - return `GENERATE_ID::${id}`; -} - -function deleteUndefinedAttrs(obj: T): T { - if (Array.isArray(obj) && obj != null) { - return obj.map(deleteUndefinedAttrs) as T; - } else if (typeof obj === 'object' && obj != null) { - return Object.fromEntries( - Object.entries(obj) - .filter(([, v]) => v !== undefined) - .map(([k, v]) => [k, deleteUndefinedAttrs(v)]), - ) as T; - } else { - return obj; - } + return deleteUndefinedAttrs(result); } diff --git a/plugins/importer-insomnia/src/v4.ts b/plugins/importer-insomnia/src/v4.ts new file mode 100644 index 00000000..8fcb6173 --- /dev/null +++ b/plugins/importer-insomnia/src/v4.ts @@ -0,0 +1,206 @@ +import { PartialImportResources } from '@yaakapp/api'; +import { convertId, convertSyntax, isJSObject } from './common'; + +export function convertInsomniaV4(parsed: Record) { + if (!Array.isArray(parsed.resources)) return null; + + const resources: PartialImportResources = { + environments: [], + folders: [], + grpcRequests: [], + httpRequests: [], + websocketRequests: [], + workspaces: [], + }; + + // Import workspaces + const workspacesToImport = parsed.resources.filter(r => isJSObject(r) && r._type === 'workspace'); + for (const w of workspacesToImport) { + resources.workspaces.push({ + id: convertId(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', + name: w.name, + description: w.description || undefined, + }); + const environmentsToImport = parsed.resources.filter( + (r: any) => isJSObject(r) && r._type === 'environment', + ); + resources.environments.push( + ...environmentsToImport.map((r: any) => importEnvironment(r, w._id)), + ); + + const nextFolder = (parentId: string) => { + const children = parsed.resources.filter((r: any) => r.parentId === parentId); + for (const child of children) { + if (!isJSObject(child)) continue; + + if (child._type === 'request_group') { + resources.folders.push(importFolder(child, w._id)); + nextFolder(child._id); + } else if (child._type === 'request') { + resources.httpRequests.push( + importHttpRequest(child, w._id), + ); + } else if (child._type === 'grpc_request') { + resources.grpcRequests.push( + importGrpcRequest(child, w._id), + ); + } + } + }; + + // Import folders + nextFolder(w._id); + } + + // Filter out any `null` values + resources.httpRequests = resources.httpRequests.filter(Boolean); + resources.grpcRequests = resources.grpcRequests.filter(Boolean); + resources.environments = resources.environments.filter(Boolean); + resources.workspaces = resources.workspaces.filter(Boolean); + + return { resources }; +} + +function importHttpRequest( + r: any, + workspaceId: string, +): PartialImportResources['httpRequests'][0] { + let bodyType: string | null = null; + let body = {}; + if (r.body.mimeType === 'application/octet-stream') { + bodyType = 'binary'; + body = { filePath: r.body.fileName ?? '' }; + } else if (r.body?.mimeType === 'application/x-www-form-urlencoded') { + bodyType = 'application/x-www-form-urlencoded'; + body = { + form: (r.body.params ?? []).map((p: any) => ({ + enabled: !p.disabled, + name: p.name ?? '', + value: p.value ?? '', + })), + }; + } else if (r.body?.mimeType === 'multipart/form-data') { + bodyType = 'multipart/form-data'; + body = { + form: (r.body.params ?? []).map((p: any) => ({ + enabled: !p.disabled, + name: p.name ?? '', + value: p.value ?? '', + file: p.fileName ?? null, + })), + }; + } else if (r.body?.mimeType === 'application/graphql') { + bodyType = 'graphql'; + body = { text: convertSyntax(r.body.text ?? '') }; + } else if (r.body?.mimeType === 'application/json') { + bodyType = 'application/json'; + body = { text: convertSyntax(r.body.text ?? '') }; + } + + let authenticationType: string | null = null; + let authentication = {}; + if (r.authentication.type === 'bearer') { + authenticationType = 'bearer'; + authentication = { + token: convertSyntax(r.authentication.token), + }; + } else if (r.authentication.type === 'basic') { + authenticationType = 'basic'; + authentication = { + username: convertSyntax(r.authentication.username), + password: convertSyntax(r.authentication.password), + }; + } + + return { + id: convertId(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), + folderId: r.parentId === workspaceId ? null : convertId(r.parentId), + model: 'http_request', + sortPriority: r.metaSortKey, + name: r.name, + description: r.description || undefined, + url: convertSyntax(r.url), + body, + bodyType, + authentication, + authenticationType, + method: r.method, + headers: (r.headers ?? []) + .map((h: any) => ({ + enabled: !h.disabled, + name: h.name ?? '', + value: h.value ?? '', + })) + .filter(({ name, value }: any) => name !== '' || value !== ''), + }; +} + +function importGrpcRequest( + r: any, + workspaceId: string, +): 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), + 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), + folderId: r.parentId === workspaceId ? null : convertId(r.parentId), + model: 'grpc_request', + sortPriority: r.metaSortKey, + name: r.name, + description: r.description || undefined, + url: convertSyntax(r.url), + service, + method, + message: r.body?.text ?? '', + metadata: (r.metadata ?? []) + .map((h: any) => ({ + enabled: !h.disabled, + name: h.name ?? '', + value: h.value ?? '', + })) + .filter(({ name, value }: any) => name !== '' || value !== ''), + }; +} + +function importFolder(f: any, workspaceId: string): PartialImportResources['folders'][0] { + return { + id: convertId(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), + workspaceId: convertId(workspaceId), + description: f.description || undefined, + model: 'folder', + name: f.name, + }; +} + +function importEnvironment(e: any, workspaceId: string, isParent?: boolean): PartialImportResources['environments'][0] { + return { + id: convertId(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), + // @ts-ignore + sortPriority: e.metaSortKey, // Will be added to Yaak later + base: isParent ?? e.parentId === workspaceId, + model: 'environment', + name: e.name, + variables: Object.entries(e.data).map(([name, value]) => ({ + enabled: true, + name, + value: `${value}`, + })), + }; +} diff --git a/plugins/importer-insomnia/src/v5.ts b/plugins/importer-insomnia/src/v5.ts new file mode 100644 index 00000000..f85b9010 --- /dev/null +++ b/plugins/importer-insomnia/src/v5.ts @@ -0,0 +1,265 @@ +import { PartialImportResources } from '@yaakapp/api'; +import { convertId, convertSyntax, isJSObject } from './common'; + +export function convertInsomniaV5(parsed: Record) { + if (!Array.isArray(parsed.collection)) return null; + + const resources: PartialImportResources = { + environments: [], + folders: [], + grpcRequests: [], + httpRequests: [], + websocketRequests: [], + workspaces: [], + }; + + // Import workspaces + const meta: Record = parsed.meta ?? {}; + resources.workspaces.push({ + id: convertId(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', + name: parsed.name, + description: meta.description || undefined, + }); + resources.environments.push( + importEnvironment(parsed.environments, meta.id, true), + ...(parsed.environments.subEnvironments ?? []).map((r: any) => importEnvironment(r, meta.id)), + ); + + const nextFolder = (children: any[], parentId: string) => { + for (const child of children ?? []) { + if (!isJSObject(child)) continue; + + if (Array.isArray(child.children)) { + resources.folders.push(importFolder(child, meta.id, parentId)); + nextFolder(child.children, child.meta.id); + } else if (child.method) { + resources.httpRequests.push( + importHttpRequest(child, meta.id, parentId), + ); + } else if (child.protoFileId) { + resources.grpcRequests.push( + importGrpcRequest(child, meta.id, parentId), + ); + } else if (child.url) { + resources.websocketRequests.push( + importWebsocketRequest(child, meta.id, parentId), + ); + } + } + }; + + // Import folders + nextFolder(parsed.collection ?? [], meta.id); + + // Filter out any `null` values + resources.httpRequests = resources.httpRequests.filter(Boolean); + resources.grpcRequests = resources.grpcRequests.filter(Boolean); + resources.environments = resources.environments.filter(Boolean); + resources.workspaces = resources.workspaces.filter(Boolean); + + return { resources }; +} + +function importHttpRequest( + r: any, + workspaceId: string, + parentId: string, +): PartialImportResources['httpRequests'][0] { + const id = r.meta?.id ?? r._id; + const created = r.meta?.created ?? r.created; + const updated = r.meta?.modified ?? r.updated; + const sortKey = r.meta?.sortKey ?? r.sortKey; + + let bodyType: string | null = null; + let body = {}; + if (r.body.mimeType === 'application/octet-stream') { + bodyType = 'binary'; + body = { filePath: r.body.fileName ?? '' }; + } else if (r.body?.mimeType === 'application/x-www-form-urlencoded') { + bodyType = 'application/x-www-form-urlencoded'; + body = { + form: (r.body.params ?? []).map((p: any) => ({ + enabled: !p.disabled, + name: p.name ?? '', + value: p.value ?? '', + })), + }; + } else if (r.body?.mimeType === 'multipart/form-data') { + bodyType = 'multipart/form-data'; + body = { + form: (r.body.params ?? []).map((p: any) => ({ + enabled: !p.disabled, + name: p.name ?? '', + value: p.value ?? '', + file: p.fileName ?? null, + })), + }; + } else if (r.body?.mimeType === 'application/graphql') { + bodyType = 'graphql'; + body = { text: convertSyntax(r.body.text ?? '') }; + } else if (r.body?.mimeType === 'application/json') { + bodyType = 'application/json'; + body = { text: convertSyntax(r.body.text ?? '') }; + } + + return { + id: convertId(id), + workspaceId: convertId(workspaceId), + createdAt: created ? new Date(created).toISOString().replace('Z', '') : undefined, + updatedAt: updated ? new Date(updated).toISOString().replace('Z', '') : undefined, + folderId: parentId === workspaceId ? null : convertId(parentId), + sortPriority: sortKey, + model: 'http_request', + name: r.name, + description: r.meta?.description || undefined, + url: convertSyntax(r.url), + body, + bodyType, + method: r.method, + ...importHeaders(r), + ...importAuthentication(r), + }; +} + +function importGrpcRequest( + r: any, + workspaceId: string, + parentId: string, +): PartialImportResources['grpcRequests'][0] { + const id = r.meta?.id ?? r._id; + const created = r.meta?.created ?? r.created; + const updated = r.meta?.modified ?? r.updated; + const sortKey = r.meta?.sortKey ?? r.sortKey; + + const parts = r.protoMethodName.split('/').filter((p: any) => p !== ''); + const service = parts[0] ?? null; + const method = parts[1] ?? null; + + return { + model: 'grpc_request', + id: convertId(id), + workspaceId: convertId(workspaceId), + createdAt: created ? new Date(created).toISOString().replace('Z', '') : undefined, + updatedAt: updated ? new Date(updated).toISOString().replace('Z', '') : undefined, + folderId: parentId === workspaceId ? null : convertId(parentId), + sortPriority: sortKey, + name: r.name, + description: r.description || undefined, + url: convertSyntax(r.url), + service, + method, + message: r.body?.text ?? '', + metadata: (r.metadata ?? []) + .map((h: any) => ({ + enabled: !h.disabled, + name: h.name ?? '', + value: h.value ?? '', + })) + .filter(({ name, value }: any) => name !== '' || value !== ''), + }; +} + +function importWebsocketRequest( + r: any, + workspaceId: string, + parentId: string, +): PartialImportResources['websocketRequests'][0] { + const id = r.meta?.id ?? r._id; + const created = r.meta?.created ?? r.created; + const updated = r.meta?.modified ?? r.updated; + const sortKey = r.meta?.sortKey ?? r.sortKey; + + return { + model: 'websocket_request', + id: convertId(id), + workspaceId: convertId(workspaceId), + createdAt: created ? new Date(created).toISOString().replace('Z', '') : undefined, + updatedAt: updated ? new Date(updated).toISOString().replace('Z', '') : undefined, + folderId: parentId === workspaceId ? null : convertId(parentId), + sortPriority: sortKey, + name: r.name, + description: r.description || undefined, + url: convertSyntax(r.url), + message: r.body?.text ?? '', + ...importHeaders(r), + ...importAuthentication(r), + }; +} + +function importHeaders(r: any) { + const headers = (r.headers ?? []) + .map((h: any) => ({ + enabled: !h.disabled, + name: h.name ?? '', + value: h.value ?? '', + })) + .filter(({ name, value }: any) => name !== '' || value !== ''); + return { headers } as const; +} + +function importAuthentication(r: any) { + let authenticationType: string | null = null; + let authentication = {}; + if (r.authentication?.type === 'bearer') { + authenticationType = 'bearer'; + authentication = { + token: convertSyntax(r.authentication.token), + }; + } else if (r.authentication?.type === 'basic') { + authenticationType = 'basic'; + authentication = { + username: convertSyntax(r.authentication.username), + password: convertSyntax(r.authentication.password), + }; + } + + return { authenticationType, authentication } as const; +} + +function importFolder(f: any, workspaceId: string, parentId: string): PartialImportResources['folders'][0] { + const id = f.meta?.id ?? f._id; + const created = f.meta?.created ?? f.created; + const updated = f.meta?.modified ?? f.updated; + const sortKey = f.meta?.sortKey ?? f.sortKey; + + return { + model: 'folder', + id: convertId(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), + sortPriority: sortKey, + workspaceId: convertId(workspaceId), + description: f.description || undefined, + name: f.name, + }; +} + + +function importEnvironment(e: any, workspaceId: string, isParent?: boolean): PartialImportResources['environments'][0] { + const id = e.meta?.id ?? e._id; + const created = e.meta?.created ?? e.created; + const updated = e.meta?.modified ?? e.updated; + const sortKey = e.meta?.sortKey ?? e.sortKey; + + return { + id: convertId(id), + createdAt: created ? new Date(created).toISOString().replace('Z', '') : undefined, + updatedAt: updated ? new Date(updated).toISOString().replace('Z', '') : undefined, + workspaceId: convertId(workspaceId), + public: !e.isPrivate, + // @ts-ignore + sortPriority: sortKey, // Will be added to Yaak later + base: isParent ?? e.parentId === workspaceId, + model: 'environment', + name: e.name, + variables: Object.entries(e.data).map(([name, value]) => ({ + enabled: true, + name, + value: `${value}`, + })), + }; +} diff --git a/plugins/importer-insomnia/tests/fixtures/basic.output.json b/plugins/importer-insomnia/tests/fixtures/basic.output.json index c1381750..f640dc7c 100644 --- a/plugins/importer-insomnia/tests/fixtures/basic.output.json +++ b/plugins/importer-insomnia/tests/fixtures/basic.output.json @@ -3,6 +3,8 @@ "environments": [ { "createdAt": "2025-01-13T15:15:43.767", + "updatedAt": "2025-01-13T15:15:55.209", + "sortPriority": 1736781343767, "base": true, "id": "GENERATE_ID::env_16c0dec5b77c414ae0e419b8f10c3701300c5900", "model": "environment", @@ -18,6 +20,8 @@ }, { "createdAt": "2025-01-13T15:15:58.515", + "updatedAt": "2025-01-13T15:16:34.705", + "sortPriority": 1736781358515, "base": false, "id": "GENERATE_ID::env_799ae3d723ef44af91b4817e5d057e6d", "model": "environment", @@ -33,6 +37,8 @@ }, { "createdAt": "2025-01-13T15:16:14.707", + "updatedAt": "2025-01-13T15:16:31.078", + "sortPriority": 1736781358565, "base": false, "id": "GENERATE_ID::env_030fbfdbb274426ebd78e2e6518f8553", "model": "environment", @@ -50,6 +56,7 @@ "folders": [ { "createdAt": "2025-01-13T15:16:44.718", + "updatedAt": "2025-01-13T15:16:44.718", "folderId": null, "id": "GENERATE_ID::fld_859d1df78261463480b6a3a1419517e3", "model": "folder", @@ -77,6 +84,8 @@ }, "bodyType": "multipart/form-data", "createdAt": "2025-01-13T15:16:46.672", + "sortPriority": -1736781406672, + "updatedAt": "2025-01-13T15:17:53.176", "description": "My description of the request", "folderId": "GENERATE_ID::fld_859d1df78261463480b6a3a1419517e3", "headers": [ @@ -100,7 +109,6 @@ "method": "GET", "model": "http_request", "name": "New Request", - "sortPriority": 0, "url": "${[BASE_URL ]}/foo/:id", "workspaceId": "GENERATE_ID::wrk_d4d92f7c0ee947b89159243506687019" } diff --git a/plugins/importer-insomnia/tests/fixtures/version-5.input.yaml b/plugins/importer-insomnia/tests/fixtures/version-5.input.yaml new file mode 100644 index 00000000..6bbd81f4 --- /dev/null +++ b/plugins/importer-insomnia/tests/fixtures/version-5.input.yaml @@ -0,0 +1,142 @@ +type: collection.insomnia.rest/5.0 +name: Dummy +meta: + id: wrk_c1eacfa750a04f3ea9985ef28043fa53 + created: 1746799305927 + modified: 1746843054272 + description: This is the description +collection: + - name: Top Level + meta: + id: fld_42eb2e2bb22b4cedacbd3d057634e80c + created: 1736781404718 + modified: 1736781404718 + sortKey: -1736781404718 + children: + - url: "{{ _.BASE_URL }}/foo/:id" + name: New Request + meta: + id: req_d72fff2a6b104b91a2ebe9de9edd2785 + created: 1736781406672 + modified: 1736781473176 + isPrivate: false + description: My description of the request + sortKey: -1736781406672 + method: GET + body: + mimeType: multipart/form-data + params: + - id: pair_7c86036ae8ef499dbbc0b43d0800c5a3 + name: form + value: data + disabled: false + parameters: + - id: pair_b22f6ff611cd4250a6e405ca7b713d09 + name: query + value: qqq + disabled: false + headers: + - name: Content-Type + value: multipart/form-data + id: pair_4af845963bd14256b98716617971eecd + - name: User-Agent + value: insomnia/10.3.0 + id: pair_535ffd00ce48462cb1b7258832ade65a + - id: pair_ab4b870278e943cba6babf5a73e213e3 + name: X-Header + value: xxxx + disabled: false + authentication: + type: basic + useISO88591: false + disabled: false + username: user + password: pass + settings: + renderRequestBody: true + encodeUrl: true + followRedirects: global + cookies: + send: true + store: true + rebuildPath: true + pathParameters: + - name: id + value: iii + - url: grpcb.in:9000 + name: New Request + meta: + id: greq_06d659324df94504a4d64632be7106b3 + created: 1746799344864 + modified: 1746799544082 + isPrivate: false + sortKey: -1746799344864 + body: + text: |- + { + "greeting": "Greg" + } + protoFileId: pf_9d45b0dfaccc4bcc9d930746716786c5 + protoMethodName: /hello.HelloService/SayHello + reflectionApi: + enabled: false + url: https://buf.build + module: buf.build/connectrpc/eliza + - url: wss://echo.websocket.org + name: New WebSocket Request + meta: + id: ws-req_5d1a4c7c79494743962e5176f6add270 + created: 1746799553909 + modified: 1746887120958 + sortKey: -1746799553909 + settings: + encodeUrl: true + followRedirects: global + cookies: + send: true + store: true + authentication: + type: basic + useISO88591: false + disabled: false + username: user + password: password + headers: + - name: User-Agent + value: insomnia/11.1.0 +cookieJar: + name: Default Jar + meta: + id: jar_663d5741b072441aa2709a6113371510 + created: 1736781343768 + modified: 1736781343768 +environments: + name: Base Environment + meta: + id: env_20945044d3c8497ca8b717bef750987e + created: 1736781343767 + modified: 1736781355209 + isPrivate: false + data: + BASE_VAR: hello + subEnvironments: + - name: Production + meta: + id: env_6f7728bb7fc04d558d668e954d756ea2 + created: 1736781358515 + modified: 1736781394705 + isPrivate: false + sortKey: 1736781358515 + data: + BASE_URL: https://api.yaak.app + color: "#f22c2c" + - name: Staging + meta: + id: env_976a8b6eb5d44fb6a20150f65c32d243 + created: 1736781374707 + modified: 1736781391078 + isPrivate: false + sortKey: 1736781358565 + data: + BASE_URL: https://api.staging.yaak.app + color: "#206fac" diff --git a/plugins/importer-insomnia/tests/fixtures/version-5.output.json b/plugins/importer-insomnia/tests/fixtures/version-5.output.json new file mode 100644 index 00000000..6b00c40a --- /dev/null +++ b/plugins/importer-insomnia/tests/fixtures/version-5.output.json @@ -0,0 +1,172 @@ +{ + "resources": { + "environments": [ + { + "createdAt": "2025-01-13T15:15:43.767", + "updatedAt": "2025-01-13T15:15:55.209", + "base": true, + "public": true, + "id": "GENERATE_ID::env_20945044d3c8497ca8b717bef750987e", + "model": "environment", + "name": "Base Environment", + "variables": [ + { + "enabled": true, + "name": "BASE_VAR", + "value": "hello" + } + ], + "workspaceId": "GENERATE_ID::wrk_c1eacfa750a04f3ea9985ef28043fa53" + }, + { + "createdAt": "2025-01-13T15:15:58.515", + "updatedAt": "2025-01-13T15:16:34.705", + "base": false, + "public": true, + "id": "GENERATE_ID::env_6f7728bb7fc04d558d668e954d756ea2", + "model": "environment", + "name": "Production", + "sortPriority": 1736781358515, + "variables": [ + { + "enabled": true, + "name": "BASE_URL", + "value": "https://api.yaak.app" + } + ], + "workspaceId": "GENERATE_ID::wrk_c1eacfa750a04f3ea9985ef28043fa53" + }, + { + "createdAt": "2025-01-13T15:16:14.707", + "updatedAt": "2025-01-13T15:16:31.078", + "base": false, + "public": true, + "id": "GENERATE_ID::env_976a8b6eb5d44fb6a20150f65c32d243", + "model": "environment", + "name": "Staging", + "sortPriority": 1736781358565, + "variables": [ + { + "enabled": true, + "name": "BASE_URL", + "value": "https://api.staging.yaak.app" + } + ], + "workspaceId": "GENERATE_ID::wrk_c1eacfa750a04f3ea9985ef28043fa53" + } + ], + "folders": [ + { + "createdAt": "2025-01-13T15:16:44.718", + "updatedAt": "2025-01-13T15:16:44.718", + "folderId": null, + "id": "GENERATE_ID::fld_42eb2e2bb22b4cedacbd3d057634e80c", + "model": "folder", + "name": "Top Level", + "sortPriority": -1736781404718, + "workspaceId": "GENERATE_ID::wrk_c1eacfa750a04f3ea9985ef28043fa53" + } + ], + "grpcRequests": [ + { + "model": "grpc_request", + "createdAt": "2025-05-09T14:02:24.864", + "folderId": null, + "id": "GENERATE_ID::greq_06d659324df94504a4d64632be7106b3", + "message": "{\n\t\"greeting\": \"Greg\"\n}", + "metadata": [], + "method": "SayHello", + "name": "New Request", + "service": "hello.HelloService", + "sortPriority": -1746799344864, + "updatedAt": "2025-05-09T14:05:44.082", + "url": "grpcb.in:9000", + "workspaceId": "GENERATE_ID::wrk_c1eacfa750a04f3ea9985ef28043fa53" + } + ], + "httpRequests": [ + { + "authentication": { + "password": "pass", + "username": "user" + }, + "authenticationType": "basic", + "body": { + "form": [ + { + "enabled": true, + "file": null, + "name": "form", + "value": "data" + } + ] + }, + "bodyType": "multipart/form-data", + "createdAt": "2025-01-13T15:16:46.672", + "updatedAt": "2025-01-13T15:17:53.176", + "description": "My description of the request", + "folderId": "GENERATE_ID::fld_42eb2e2bb22b4cedacbd3d057634e80c", + "headers": [ + { + "enabled": true, + "name": "Content-Type", + "value": "multipart/form-data" + }, + { + "enabled": true, + "name": "User-Agent", + "value": "insomnia/10.3.0" + }, + { + "enabled": true, + "name": "X-Header", + "value": "xxxx" + } + ], + "id": "GENERATE_ID::req_d72fff2a6b104b91a2ebe9de9edd2785", + "method": "GET", + "model": "http_request", + "name": "New Request", + "sortPriority": -1736781406672, + "url": "${[BASE_URL ]}/foo/:id", + "workspaceId": "GENERATE_ID::wrk_c1eacfa750a04f3ea9985ef28043fa53" + } + ], + "websocketRequests": [ + { + "id": "GENERATE_ID::ws-req_5d1a4c7c79494743962e5176f6add270", + "createdAt": "2025-05-09T14:05:53.909", + "updatedAt": "2025-05-10T14:25:20.958", + "message": "", + "model": "websocket_request", + "name": "New WebSocket Request", + "sortPriority": -1746799553909, + "authenticationType": "basic", + "authentication": { + "password": "password", + "username": "user" + }, + "folderId": null, + "headers": [ + { + "enabled": true, + "name": "User-Agent", + "value": "insomnia/11.1.0" + } + ], + "url": "wss://echo.websocket.org", + "workspaceId": "GENERATE_ID::wrk_c1eacfa750a04f3ea9985ef28043fa53" + } + ], + "workspaces": [ + { + "createdAt": "2025-05-09T14:01:45.927", + "updatedAt": "2025-05-10T02:10:54.272", + "description": "This is the description", + "id": "GENERATE_ID::wrk_c1eacfa750a04f3ea9985ef28043fa53", + "model": "workspace", + "name": "Dummy" + } + ] + } +} diff --git a/plugins/importer-insomnia/tests/index.test.ts b/plugins/importer-insomnia/tests/index.test.ts index 4fb3ecc4..da561926 100644 --- a/plugins/importer-insomnia/tests/index.test.ts +++ b/plugins/importer-insomnia/tests/index.test.ts @@ -1,6 +1,7 @@ import * as fs from 'node:fs'; import * as path from 'node:path'; import { describe, expect, test } from 'vitest'; +import YAML from 'yaml'; import { convertInsomnia } from '../src'; describe('importer-yaak', () => { @@ -14,10 +15,18 @@ describe('importer-yaak', () => { test('Imports ' + fixture, () => { const contents = fs.readFileSync(path.join(p, fixture), 'utf-8'); - const expected = fs.readFileSync(path.join(p, fixture.replace('.input', '.output')), 'utf-8'); + const expected = fs.readFileSync(path.join(p, fixture.replace(/.input\..*/, '.output.json')), 'utf-8'); const result = convertInsomnia(contents); // console.log(JSON.stringify(result, null, 2)) - expect(result).toEqual(JSON.parse(expected)); + expect(result).toEqual(parseJsonOrYaml(expected)); }); } }); + +function parseJsonOrYaml(text: string): unknown { + try { + return JSON.parse(text); + } catch { + return YAML.parse(text); + } +}