import type { Context, Environment, PartialImportResources, PluginDefinition, Workspace, } from '@yaakapp/api'; import type { ImportPluginResponse } from '@yaakapp/api/lib/plugins/ImporterPlugin'; type AtLeast = Partial & Pick; interface ExportResources { workspaces: AtLeast[]; environments: AtLeast[]; } export const plugin: PluginDefinition = { importer: { name: 'Postman Environment', description: 'Import postman environment exports', onImport(_ctx: Context, args: { text: string }) { return convertPostmanEnvironment(args.text); }, }, }; export function convertPostmanEnvironment(contents: string): ImportPluginResponse | undefined { const root = parseJSONToRecord(contents); if (root == null) return; // Validate that it looks like a Postman Environment export const values = toArray<{ key?: string; value?: unknown; enabled?: boolean; description?: string; type?: string; }>(root.values); const scope = root._postman_variable_scope; const hasEnvMarkers = typeof scope === 'string'; if (values.length === 0 || (!hasEnvMarkers && typeof root.name !== 'string')) { // Not a Postman environment file, skip return; } const exportResources: ExportResources = { workspaces: [], environments: [], }; const envVariables = values .map((v) => ({ enabled: v.enabled ?? true, name: String(v.key ?? ''), value: String(v.value), description: v.description ? String(v.description) : null, })) .filter((v) => v.name.length > 0); const environment: ExportResources['environments'][0] = { model: 'environment', id: generateId('environment'), name: root.name ? String(root.name) : 'Environment', workspaceId: 'CURRENT_WORKSPACE', parentModel: 'environment', parentId: null, variables: envVariables, }; exportResources.environments.push(environment); const resources = deleteUndefinedAttrs( convertTemplateSyntax(exportResources), ) as PartialImportResources; return { resources }; } function parseJSONToRecord(jsonStr: string): Record | null { try { return toRecord(JSON.parse(jsonStr)); } catch { return null; } } function toRecord(value: Record | unknown): Record { if (value && typeof value === 'object' && !Array.isArray(value)) { return value as Record; } return {} as Record; } function toArray(value: unknown): T[] { if (Object.prototype.toString.call(value) === '[object Array]') return value as T[]; return [] as T[]; } /** Recursively render all nested object properties */ function convertTemplateSyntax(obj: T): T { if (typeof obj === 'string') { return obj.replace(/{{\s*(_\.)?([^}]*)\s*}}/g, (_m, _dot, expr) => `\${[${expr.trim()}]}`) as T; } if (Array.isArray(obj) && obj != null) { return obj.map(convertTemplateSyntax) as T; } if (typeof obj === 'object' && obj != null) { return Object.fromEntries( Object.entries(obj as Record).map(([k, v]) => [k, convertTemplateSyntax(v)]), ) as T; } return obj; } function deleteUndefinedAttrs(obj: T): T { if (Array.isArray(obj) && obj != null) { return obj.map(deleteUndefinedAttrs) as T; } if (typeof obj === 'object' && obj != null) { return Object.fromEntries( Object.entries(obj as Record) .filter(([, v]) => v !== undefined) .map(([k, v]) => [k, deleteUndefinedAttrs(v)]), ) as T; } return obj; } const idCount: Partial> = {}; function generateId(model: string): string { idCount[model] = (idCount[model] ?? -1) + 1; return `GENERATE_ID::${model.toUpperCase()}_${idCount[model]}`; } export default plugin;