snapshot of https://github.com/mountain-loop/yaak.git (main, 2026-09-23; blobs > 131072B omitted)
Some checks failed
Lint and Test / Lint/Test (push) Has been cancelled
Generate Sponsors README / deploy (push) Has been skipped

This commit is contained in:
upstream-mirror
2026-09-23 17:39:41 +05:00
commit 906fb231cc
1544 changed files with 164131 additions and 0 deletions

View File

@@ -0,0 +1,13 @@
{
"name": "@yaak/importer-postman",
"displayName": "Postman Importer",
"version": "0.1.0",
"private": true,
"description": "Import collections from Postman",
"main": "./build/index.js",
"scripts": {
"build": "yaakcli build",
"dev": "yaakcli dev",
"test": "vp test --run tests"
}
}

View File

@@ -0,0 +1,659 @@
/* oxlint-disable no-base-to-string */
import type {
Context,
Environment,
Folder,
HttpRequest,
HttpRequestHeader,
HttpUrlParameter,
PartialImportResources,
PluginDefinition,
Workspace,
} from "@yaakapp/api";
import type { ImportPluginResponse } from "@yaakapp/api/lib/plugins/ImporterPlugin";
const POSTMAN_2_1_0_SCHEMA = "https://schema.getpostman.com/json/collection/v2.1.0/collection.json";
const POSTMAN_2_0_0_SCHEMA = "https://schema.getpostman.com/json/collection/v2.0.0/collection.json";
const VALID_SCHEMAS = [POSTMAN_2_0_0_SCHEMA, POSTMAN_2_1_0_SCHEMA];
// Both products happen to spell these the same way, but the names are written
// out so anything Yaak's oauth1 plugin can't offer as a select option lands on
// the default instead of being passed straight through.
const OAUTH1_SIGNATURE_METHODS: Record<string, string> = {
"HMAC-SHA1": "HMAC-SHA1",
"HMAC-SHA256": "HMAC-SHA256",
"HMAC-SHA512": "HMAC-SHA512",
"RSA-SHA1": "RSA-SHA1",
"RSA-SHA256": "RSA-SHA256",
"RSA-SHA512": "RSA-SHA512",
PLAINTEXT: "PLAINTEXT",
};
const DEFAULT_OAUTH1_SIGNATURE_METHOD = "HMAC-SHA1";
// Postman's dynamic variables that a Yaak template function reproduces exactly.
// The faker-backed ones ({{$randomFirstName}} and friends) have no equivalent and
// are left alone. Arguments have to be single-quoted; the template parser reads a
// double-quoted call as raw text.
const POSTMAN_DYNAMIC_VARIABLES: Record<string, string> = {
$guid: "uuid.v4()",
$randomUUID: "uuid.v4()",
$timestamp: "timestamp.unix()",
$isoTimestamp: "timestamp.iso8601()",
// Postman's $randomInt is an integer from 0 to 1000
$randomInt: "random.range(min='0',max='1000',decimals='0')",
};
type AtLeast<T, K extends keyof T> = Partial<T> & Pick<T, K>;
interface ExportResources {
workspaces: AtLeast<Workspace, "name" | "id" | "model">[];
environments: AtLeast<Environment, "name" | "id" | "model" | "workspaceId">[];
httpRequests: AtLeast<HttpRequest, "name" | "id" | "model" | "workspaceId">[];
folders: AtLeast<Folder, "name" | "id" | "model" | "workspaceId">[];
}
export const plugin: PluginDefinition = {
importer: {
name: "Postman",
description: "Import postman collections",
onImport(_ctx: Context, args: { text: string }) {
return convertPostman(args.text);
},
},
};
export function convertPostman(contents: string): ImportPluginResponse | undefined {
const root = parseJSONToRecord(contents);
if (root == null) return;
const info = toRecord(root.info);
const isValidSchema = VALID_SCHEMAS.includes(
typeof info.schema === "string" ? info.schema : "n/a",
);
if (!isValidSchema || !Array.isArray(root.item)) {
return;
}
const globalAuth = importAuth(root.auth);
const sourceKeys: Record<string, string> = {};
const trackSourceKey = (modelId: string, v: Record<string, unknown>, prefix: string) => {
const id = v.id ?? v._postman_id;
if (typeof id === "string" && id !== "") sourceKeys[modelId] = `${prefix}:${id}`;
};
const exportResources: ExportResources = {
workspaces: [],
environments: [],
httpRequests: [],
folders: [],
};
const workspace: ExportResources["workspaces"][0] = {
model: "workspace",
id: generateId("workspace"),
name: info.name ? String(info.name) : "Postman Import",
description: importDescription(info.description),
...globalAuth,
};
trackSourceKey(workspace.id, info, "collection");
exportResources.workspaces.push(workspace);
// Create the base environment
const environment: ExportResources["environments"][0] = {
model: "environment",
id: generateId("environment"),
name: "Global Variables",
workspaceId: workspace.id,
parentModel: "workspace",
parentId: null,
variables:
toArray<{ key: string; value: string }>(root.variable).map((v) => ({
name: v.key,
value: v.value,
})) ?? [],
};
exportResources.environments.push(environment);
let sortPriorityIndex = 0;
const importItem = (v: Record<string, unknown>, folderId: string | null = null) => {
if (typeof v.name === "string" && Array.isArray(v.item)) {
const folder: ExportResources["folders"][0] = {
model: "folder",
sortPriority: sortPriorityIndex++,
workspaceId: workspace.id,
id: generateId("folder"),
name: v.name,
folderId,
...importAuth(v.auth),
};
trackSourceKey(folder.id, v, "item");
exportResources.folders.push(folder);
for (const child of v.item) {
importItem(child, folder.id);
}
} else if (typeof v.name === "string" && "request" in v) {
const r = toRecord(v.request);
const bodyPatch = importBody(r.body);
const requestAuth = importAuth(r.auth);
const headers: HttpRequestHeader[] = toArray<{
key: string;
value: string;
disabled?: boolean;
}>(r.header).map((h) => {
return {
name: h.key,
value: h.value,
enabled: !h.disabled,
};
});
// Add body headers only if they don't already exist
for (const bodyPatchHeader of bodyPatch.headers) {
const existingHeader = headers.find(
(h) => h.name.toLowerCase() === bodyPatchHeader.name.toLowerCase(),
);
if (existingHeader) {
continue;
}
headers.push(bodyPatchHeader);
}
const { url, urlParameters } = convertUrl(r.url);
const request: ExportResources["httpRequests"][0] = {
model: "http_request",
id: generateId("http_request"),
workspaceId: workspace.id,
folderId,
name: v.name,
description: importDescription(r.description),
method: typeof r.method === "string" ? r.method : "GET",
url,
urlParameters,
body: bodyPatch.body,
bodyType: bodyPatch.bodyType,
sortPriority: sortPriorityIndex++,
headers,
...requestAuth,
};
trackSourceKey(request.id, v, "item");
exportResources.httpRequests.push(request);
} else {
console.log("Unknown item", v, folderId);
}
};
for (const item of root.item) {
importItem(item);
}
const resources = deleteUndefinedAttrs(
convertTemplateSyntax(exportResources),
) as PartialImportResources;
return { resources, sourceKeys };
}
function convertUrl(rawUrl: unknown): Pick<HttpRequest, "url" | "urlParameters"> {
if (typeof rawUrl === "string") {
return { url: rawUrl, urlParameters: [] };
}
const url = toRecord(rawUrl);
let v = "";
if ("protocol" in url && typeof url.protocol === "string") {
v += `${url.protocol}://`;
}
if ("host" in url) {
v += `${Array.isArray(url.host) ? url.host.join(".") : String(url.host)}`;
}
if ("port" in url && typeof url.port === "string") {
v += `:${url.port}`;
}
if ("path" in url) {
if (Array.isArray(url.path) && url.path.length > 0) {
v += `/${url.path.join("/")}`;
} else if (typeof url.path === "string" && url.path.length > 0) {
v += `/${url.path.replace(/^\//, "")}`;
}
}
const params: HttpUrlParameter[] = [];
if ("query" in url && Array.isArray(url.query) && url.query.length > 0) {
for (const query of url.query) {
params.push({
name: query.key ?? "",
value: query.value ?? "",
enabled: !query.disabled,
});
}
}
if ("variable" in url && Array.isArray(url.variable) && url.variable.length > 0) {
for (const v of url.variable) {
params.push({
name: `:${v.key ?? ""}`,
value: v.value ?? "",
enabled: !v.disabled,
});
}
}
if ("hash" in url && typeof url.hash === "string") {
v += `#${url.hash}`;
}
return { url: v, urlParameters: params };
}
function importAuth(rawAuth: unknown): Pick<HttpRequest, "authentication" | "authenticationType"> {
const auth = toRecord<Record<string, string>>(rawAuth);
// Helper: Postman stores auth params as an array of { key, value, ... }
const pmArrayToObj = (v: unknown): Record<string, unknown> => {
if (!Array.isArray(v)) return toRecord(v);
const o: Record<string, unknown> = {};
for (const i of v) {
const ii = toRecord(i);
if (typeof ii.key === "string") {
o[ii.key] = ii.value;
}
}
return o;
};
const authType: string | undefined = auth.type ? String(auth.type) : undefined;
if (authType === "noauth") {
return {
authenticationType: "none",
authentication: {},
};
}
if ("basic" in auth && authType === "basic") {
const b = pmArrayToObj(auth.basic);
return {
authenticationType: "basic",
authentication: {
username: String(b.username ?? ""),
password: String(b.password ?? ""),
},
};
}
if ("bearer" in auth && authType === "bearer") {
const b = pmArrayToObj(auth.bearer);
// Postman uses key "token"
return {
authenticationType: "bearer",
authentication: {
token: String(b.token ?? ""),
},
};
}
if ("digest" in auth && authType === "digest") {
const d = pmArrayToObj(auth.digest);
return {
authenticationType: "digest",
authentication: {
username: d.username != null ? String(d.username) : undefined,
password: d.password != null ? String(d.password) : undefined,
realm: d.realm != null ? String(d.realm) : undefined,
},
};
}
if ("ntlm" in auth && authType === "ntlm") {
const n = pmArrayToObj(auth.ntlm);
return {
authenticationType: "windows",
authentication: {
username: n.username != null ? String(n.username) : undefined,
password: n.password != null ? String(n.password) : undefined,
domain: n.domain != null ? String(n.domain) : undefined,
workstation: n.workstation != null ? String(n.workstation) : undefined,
},
};
}
if ("awsv4" in auth && authType === "awsv4") {
const a = pmArrayToObj(auth.awsv4);
return {
authenticationType: "awsv4",
authentication: {
accessKeyId: a.accessKey != null ? String(a.accessKey) : undefined,
secretAccessKey: a.secretKey != null ? String(a.secretKey) : undefined,
sessionToken: a.sessionToken != null ? String(a.sessionToken) : undefined,
region: a.region != null ? String(a.region) : undefined,
service: a.service != null ? String(a.service) : undefined,
},
};
}
if ("apikey" in auth && authType === "apikey") {
const a = pmArrayToObj(auth.apikey);
return {
authenticationType: "apikey",
authentication: {
location: a.in === "query" ? "query" : "header",
// Postman's "key" is the header/parameter name and its "value" is the
// secret, matching Yaak's own apikey auth.
key: a.key != null ? String(a.key) : undefined,
value: a.value != null ? String(a.value) : undefined,
},
};
}
if ("jwt" in auth && authType === "jwt") {
const a = pmArrayToObj(auth.jwt);
return {
authenticationType: "jwt",
authentication: {
algorithm: a.algorithm != null ? String(a.algorithm).toUpperCase() : undefined,
secret: a.secret != null ? String(a.secret) : undefined,
secretBase64: !!a.isSecretBase64Encoded,
payload: a.payload != null ? String(a.payload) : undefined,
headerPrefix: a.headerPrefix != null ? String(a.headerPrefix) : undefined,
location: a.addTokenTo === "header" ? "header" : "query",
},
};
}
if ("oauth1" in auth && authType === "oauth1") {
const o = pmArrayToObj(auth.oauth1);
const signatureMethod =
o.signatureMethod != null
? String(o.signatureMethod).toUpperCase()
: DEFAULT_OAUTH1_SIGNATURE_METHOD;
return {
authenticationType: "oauth1",
authentication: {
signatureMethod:
OAUTH1_SIGNATURE_METHODS[signatureMethod] ?? DEFAULT_OAUTH1_SIGNATURE_METHOD,
consumerKey: o.consumerKey != null ? String(o.consumerKey) : undefined,
consumerSecret: o.consumerSecret != null ? String(o.consumerSecret) : undefined,
tokenKey: o.token != null ? String(o.token) : undefined,
tokenSecret: o.tokenSecret != null ? String(o.tokenSecret) : undefined,
privateKey: o.privateKey != null ? String(o.privateKey) : undefined,
callback: o.callback != null ? String(o.callback) : undefined,
verifier: o.verifier != null ? String(o.verifier) : undefined,
timestamp: o.timestamp != null ? String(o.timestamp) : undefined,
nonce: o.nonce != null ? String(o.nonce) : undefined,
version: o.version != null ? String(o.version) : undefined,
realm: o.realm != null ? String(o.realm) : undefined,
},
};
}
if ("oauth2" in auth && authType === "oauth2") {
const o = pmArrayToObj(auth.oauth2);
let grantType = o.grant_type ? String(o.grant_type) : "authorization_code";
let pkcePatch: Record<string, unknown> = {};
if (grantType === "authorization_code_with_pkce") {
grantType = "authorization_code";
pkcePatch =
o.grant_type === "authorization_code_with_pkce"
? {
usePkce: true,
pkceChallengeMethod: o.challengeAlgorithm ?? undefined,
pkceCodeVerifier: o.code_verifier != null ? String(o.code_verifier) : undefined,
}
: {};
} else if (grantType === "password_credentials") {
grantType = "password";
}
const accessTokenUrl = o.accessTokenUrl != null ? String(o.accessTokenUrl) : undefined;
const audience = o.audience != null ? String(o.audience) : undefined;
const authorizationUrl = o.authUrl != null ? String(o.authUrl) : undefined;
const clientId = o.clientId != null ? String(o.clientId) : undefined;
const clientSecret = o.clientSecret != null ? String(o.clientSecret) : undefined;
const credentials = o.client_authentication === "body" ? "body" : undefined;
const headerPrefix = o.headerPrefix ?? "Bearer";
const password = o.password != null ? String(o.password) : undefined;
const redirectUri = o.redirect_uri != null ? String(o.redirect_uri) : undefined;
const scope = o.scope != null ? String(o.scope) : undefined;
const state = o.state != null ? String(o.state) : undefined;
const username = o.username != null ? String(o.username) : undefined;
let grantPatch: Record<string, unknown> = {};
if (grantType === "authorization_code") {
grantPatch = {
clientSecret,
authorizationUrl,
accessTokenUrl,
redirectUri,
state,
...pkcePatch,
};
} else if (grantType === "implicit") {
grantPatch = { authorizationUrl, redirectUri, state };
} else if (grantType === "password") {
grantPatch = { clientSecret, accessTokenUrl, username, password };
} else if (grantType === "client_credentials") {
grantPatch = { clientSecret, accessTokenUrl };
}
const authentication = {
name: "oauth2",
grantType,
audience,
clientId,
credentials,
headerPrefix,
scope,
...grantPatch,
} as Record<string, unknown>;
return { authenticationType: "oauth2", authentication };
}
return { authenticationType: null, authentication: {} };
}
function importBody(rawBody: unknown): Pick<HttpRequest, "body" | "bodyType" | "headers"> {
const body = toRecord(rawBody) as {
mode: string;
graphql: { query?: string; variables?: string };
urlencoded?: { key?: string; value?: string; disabled?: boolean }[];
formdata?: {
key?: string;
value?: string;
disabled?: boolean;
contentType?: string;
src?: string;
}[];
raw?: string;
options?: { raw?: { language?: string } };
file?: { src?: string };
};
if (body.mode === "graphql") {
return {
headers: [
{
name: "Content-Type",
value: "application/json",
enabled: true,
},
],
bodyType: "graphql",
body: {
text: JSON.stringify(
{
query: body.graphql?.query || "",
variables: parseJSONToRecord(body.graphql?.variables || "{}"),
},
null,
2,
),
},
};
}
if (body.mode === "urlencoded") {
return {
headers: [
{
name: "Content-Type",
value: "application/x-www-form-urlencoded",
enabled: true,
},
],
bodyType: "application/x-www-form-urlencoded",
body: {
form: toArray<NonNullable<typeof body.urlencoded>[0]>(body.urlencoded).map((f) => ({
enabled: !f.disabled,
name: f.key ?? "",
value: f.value ?? "",
})),
},
};
}
if (body.mode === "formdata") {
return {
headers: [
{
name: "Content-Type",
value: "multipart/form-data",
enabled: true,
},
],
bodyType: "multipart/form-data",
body: {
form: toArray<NonNullable<typeof body.formdata>[0]>(body.formdata).map((f) =>
f.src != null
? {
enabled: !f.disabled,
contentType: f.contentType ?? null,
name: f.key ?? "",
file: f.src ?? "",
}
: {
enabled: !f.disabled,
name: f.key ?? "",
value: f.value ?? "",
},
),
},
};
}
if (body.mode === "raw") {
return {
headers: [
{
name: "Content-Type",
value: body.options?.raw?.language === "json" ? "application/json" : "",
enabled: true,
},
],
bodyType: body.options?.raw?.language === "json" ? "application/json" : "other",
body: {
text: body.raw ?? "",
},
};
}
if (body.mode === "file") {
return {
headers: [],
bodyType: "binary",
body: {
filePath: body.file?.src,
},
};
}
return { headers: [], bodyType: null, body: {} };
}
function parseJSONToRecord<T>(jsonStr: string): Record<string, T> | null {
try {
return toRecord(JSON.parse(jsonStr));
} catch {
return null;
}
}
function toRecord<T>(value: unknown): Record<string, T> {
if (value && typeof value === "object" && !Array.isArray(value)) {
return value as Record<string, T>;
}
return {};
}
function toArray<T>(value: unknown): T[] {
if (Object.prototype.toString.call(value) === "[object Array]") return value as T[];
return [];
}
function importDescription(rawDescription: unknown): string | undefined {
if (rawDescription == null) {
return undefined;
}
if (typeof rawDescription === "string") {
return rawDescription;
}
if (typeof rawDescription === "object" && !Array.isArray(rawDescription)) {
const description = toRecord(rawDescription);
if ("content" in description && description.content != null) {
return String(description.content);
}
return undefined;
}
return String(rawDescription);
}
/** Recursively render all nested object properties */
function convertTemplateSyntax<T>(obj: T): T {
if (typeof obj === "string") {
return obj.replace(/{{\s*(_\.)?([^}]*)\s*}}/g, (_m, _dot, expr) => {
const name = String(expr).trim();
// hasOwn, so a collection using {{constructor}} doesn't reach Object.prototype
if (Object.hasOwn(POSTMAN_DYNAMIC_VARIABLES, name)) {
return `\${[${POSTMAN_DYNAMIC_VARIABLES[name]}]}`;
}
return `\${[${name.replace(/^vault:/, "")}]}`;
}) 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).map(([k, v]) => [k, convertTemplateSyntax(v)]),
) as T;
}
return obj;
}
function deleteUndefinedAttrs<T>(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)
.filter(([, v]) => v !== undefined)
.map(([k, v]) => [k, deleteUndefinedAttrs(v)]),
) as T;
}
return obj;
}
const idCount: Partial<Record<string, number>> = {};
function generateId(model: string): string {
idCount[model] = (idCount[model] ?? -1) + 1;
return `GENERATE_ID::${model.toUpperCase()}_${idCount[model]}`;
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,421 @@
{
"resources": {
"workspaces": [
{
"model": "workspace",
"id": "GENERATE_ID::WORKSPACE_0",
"name": "Authentication",
"authenticationType": "basic",
"authentication": {
"username": "workspace",
"password": "workspace_secret"
}
}
],
"environments": [
{
"model": "environment",
"id": "GENERATE_ID::ENVIRONMENT_0",
"name": "Global Variables",
"workspaceId": "GENERATE_ID::WORKSPACE_0",
"parentModel": "workspace",
"parentId": null,
"variables": [
{
"name": "COLLECTION VARIABLE",
"value": "collection variable"
}
]
}
],
"httpRequests": [
{
"model": "http_request",
"id": "GENERATE_ID::HTTP_REQUEST_0",
"workspaceId": "GENERATE_ID::WORKSPACE_0",
"folderId": null,
"name": "No Auth",
"method": "GET",
"url": "https://yaak.app/x/echo",
"urlParameters": [],
"body": {},
"bodyType": null,
"sortPriority": 0,
"headers": [],
"authenticationType": "none",
"authentication": {}
},
{
"model": "http_request",
"id": "GENERATE_ID::HTTP_REQUEST_1",
"workspaceId": "GENERATE_ID::WORKSPACE_0",
"folderId": null,
"name": "Inherit",
"method": "GET",
"url": "https://yaak.app/x/echo",
"urlParameters": [],
"body": {},
"bodyType": null,
"sortPriority": 1,
"headers": [],
"authenticationType": null,
"authentication": {}
},
{
"model": "http_request",
"id": "GENERATE_ID::HTTP_REQUEST_2",
"workspaceId": "GENERATE_ID::WORKSPACE_0",
"folderId": null,
"name": "OAuth 2 Auth Code",
"method": "GET",
"url": "${[hello]}",
"urlParameters": [],
"body": {
"text": "{\n \"hello\": \"world\"\n}"
},
"bodyType": "application/json",
"sortPriority": 2,
"headers": [
{
"name": "Content-Type",
"value": "application/json",
"enabled": true
}
],
"authenticationType": "oauth2",
"authentication": {
"name": "oauth2",
"grantType": "authorization_code",
"clientId": "cliend id",
"headerPrefix": "Bearer",
"scope": "scope",
"clientSecret": "clientsecet",
"authorizationUrl": "https://github.com/login/oauth/authorize",
"accessTokenUrl": "https://github.com/login/oauth/access_token",
"redirectUri": "https://callback",
"state": "state"
}
},
{
"model": "http_request",
"id": "GENERATE_ID::HTTP_REQUEST_3",
"workspaceId": "GENERATE_ID::WORKSPACE_0",
"folderId": null,
"name": "OAuth 2 Auth Code (PKCE)",
"method": "GET",
"url": "${[hello]}",
"urlParameters": [],
"body": {
"text": "{\n \"hello\": \"world\"\n}"
},
"bodyType": "application/json",
"sortPriority": 3,
"headers": [
{
"name": "Content-Type",
"value": "application/json",
"enabled": true
}
],
"authenticationType": "oauth2",
"authentication": {
"name": "oauth2",
"grantType": "authorization_code",
"clientId": "cliend id",
"headerPrefix": "Bearer",
"scope": "scope",
"clientSecret": "clientsecet",
"authorizationUrl": "https://github.com/login/oauth/authorize",
"accessTokenUrl": "https://github.com/login/oauth/access_token",
"redirectUri": "https://callback",
"state": "state",
"usePkce": true,
"pkceChallengeMethod": "S256",
"pkceCodeVerifier": "verifier"
}
},
{
"model": "http_request",
"id": "GENERATE_ID::HTTP_REQUEST_4",
"workspaceId": "GENERATE_ID::WORKSPACE_0",
"folderId": null,
"name": "OAuth 2 Implicit",
"method": "GET",
"url": "${[hello]}",
"urlParameters": [],
"body": {
"text": "{\n \"hello\": \"world\"\n}"
},
"bodyType": "application/json",
"sortPriority": 4,
"headers": [
{
"name": "Content-Type",
"value": "application/json",
"enabled": true
}
],
"authenticationType": "oauth2",
"authentication": {
"name": "oauth2",
"grantType": "implicit",
"clientId": "cliend id",
"headerPrefix": "Bearer",
"scope": "scope",
"authorizationUrl": "https://github.com/login/oauth/authorize",
"redirectUri": "https://yaak.app/x/echo",
"state": "state"
}
},
{
"model": "http_request",
"id": "GENERATE_ID::HTTP_REQUEST_5",
"workspaceId": "GENERATE_ID::WORKSPACE_0",
"folderId": null,
"name": "OAuth 2 Password",
"method": "GET",
"url": "${[hello]}",
"urlParameters": [],
"body": {
"text": "{\n \"hello\": \"world\"\n}"
},
"bodyType": "application/json",
"sortPriority": 5,
"headers": [
{
"name": "Content-Type",
"value": "application/json",
"enabled": true
}
],
"authenticationType": "oauth2",
"authentication": {
"name": "oauth2",
"grantType": "password",
"clientId": "clientid",
"headerPrefix": "Bearer",
"scope": "scope",
"clientSecret": "clientsecret",
"accessTokenUrl": "https://github.com/login/oauth/access_token",
"username": "username",
"password": "password"
}
},
{
"model": "http_request",
"id": "GENERATE_ID::HTTP_REQUEST_6",
"workspaceId": "GENERATE_ID::WORKSPACE_0",
"folderId": null,
"name": "OAuth 2 Client Credentials",
"method": "GET",
"url": "${[hello]}",
"urlParameters": [],
"body": {
"text": "{\n \"hello\": \"world\"\n}"
},
"bodyType": "application/json",
"sortPriority": 6,
"headers": [
{
"name": "Content-Type",
"value": "application/json",
"enabled": true
}
],
"authenticationType": "oauth2",
"authentication": {
"name": "oauth2",
"grantType": "client_credentials",
"clientId": "clientid",
"headerPrefix": "Bearer",
"scope": "scope",
"clientSecret": "clientsecret",
"accessTokenUrl": "https://github.com/login/oauth/access_token"
}
},
{
"model": "http_request",
"id": "GENERATE_ID::HTTP_REQUEST_7",
"workspaceId": "GENERATE_ID::WORKSPACE_0",
"folderId": null,
"name": "AWS V4",
"method": "GET",
"url": "https://yaak.app/x/echo",
"urlParameters": [],
"body": {},
"bodyType": null,
"sortPriority": 7,
"headers": [],
"authenticationType": "awsv4",
"authentication": {
"accessKeyId": "access",
"secretAccessKey": "secret",
"sessionToken": "session",
"region": "us-west-1",
"service": "s3"
}
},
{
"model": "http_request",
"id": "GENERATE_ID::HTTP_REQUEST_8",
"workspaceId": "GENERATE_ID::WORKSPACE_0",
"folderId": null,
"name": "API Key",
"method": "GET",
"url": "https://yaak.app/x/echo",
"urlParameters": [],
"body": {},
"bodyType": null,
"sortPriority": 8,
"headers": [],
"authenticationType": "apikey",
"authentication": {
"location": "query",
"key": "X-API-Key",
"value": "secret-123"
}
},
{
"model": "http_request",
"id": "GENERATE_ID::HTTP_REQUEST_9",
"workspaceId": "GENERATE_ID::WORKSPACE_0",
"folderId": null,
"name": "JWT",
"method": "GET",
"url": "https://yaak.app/x/echo",
"urlParameters": [],
"body": {},
"bodyType": null,
"sortPriority": 9,
"headers": [],
"authenticationType": "jwt",
"authentication": {
"algorithm": "HS384",
"secret": "mysecret",
"secretBase64": false,
"payload": "{\n \"my\": \"payload\"\n}",
"headerPrefix": "Bearer",
"location": "header"
}
},
{
"model": "http_request",
"id": "GENERATE_ID::HTTP_REQUEST_10",
"workspaceId": "GENERATE_ID::WORKSPACE_0",
"folderId": null,
"name": "Digest",
"method": "GET",
"url": "https://yaak.app/x/echo",
"urlParameters": [],
"body": {},
"bodyType": null,
"sortPriority": 10,
"headers": [],
"authenticationType": "digest",
"authentication": {
"username": "digest-user",
"password": "digest-pass",
"realm": "testrealm@yaak.app"
}
},
{
"model": "http_request",
"id": "GENERATE_ID::HTTP_REQUEST_11",
"workspaceId": "GENERATE_ID::WORKSPACE_0",
"folderId": null,
"name": "NTLM",
"method": "GET",
"url": "https://yaak.app/x/echo",
"urlParameters": [],
"body": {},
"bodyType": null,
"sortPriority": 11,
"headers": [],
"authenticationType": "windows",
"authentication": {
"username": "ntlm-user",
"password": "ntlm-pass",
"domain": "CORP",
"workstation": "WORKSTATION-01"
}
},
{
"model": "http_request",
"id": "GENERATE_ID::HTTP_REQUEST_12",
"workspaceId": "GENERATE_ID::WORKSPACE_0",
"folderId": null,
"name": "OAuth 1",
"method": "GET",
"url": "https://yaak.app/x/echo",
"urlParameters": [],
"body": {},
"bodyType": null,
"sortPriority": 12,
"headers": [],
"authenticationType": "oauth1",
"authentication": {
"signatureMethod": "HMAC-SHA256",
"consumerKey": "consumer-key",
"consumerSecret": "consumer-secret",
"tokenKey": "access-token",
"tokenSecret": "token-secret",
"callback": "https://yaak.app/x/callback",
"verifier": "verifier-123",
"timestamp": "1700000000",
"nonce": "9e6dfada256c",
"version": "1.0",
"realm": "yaak.app"
}
},
{
"model": "http_request",
"id": "GENERATE_ID::HTTP_REQUEST_13",
"workspaceId": "GENERATE_ID::WORKSPACE_0",
"folderId": "GENERATE_ID::FOLDER_0",
"name": "Folder Child Inherit",
"method": "GET",
"url": "https://yaak.app/x/echo",
"urlParameters": [],
"body": {},
"bodyType": null,
"sortPriority": 14,
"headers": [],
"authenticationType": null,
"authentication": {}
},
{
"model": "http_request",
"id": "GENERATE_ID::HTTP_REQUEST_14",
"workspaceId": "GENERATE_ID::WORKSPACE_0",
"folderId": "GENERATE_ID::FOLDER_0",
"name": "Folder Child No Auth",
"method": "GET",
"url": "https://yaak.app/x/echo",
"urlParameters": [],
"body": {},
"bodyType": null,
"sortPriority": 15,
"headers": [],
"authenticationType": "none",
"authentication": {}
}
],
"folders": [
{
"model": "folder",
"sortPriority": 13,
"workspaceId": "GENERATE_ID::WORKSPACE_0",
"id": "GENERATE_ID::FOLDER_0",
"name": "Folder With Auth",
"folderId": null,
"authenticationType": "bearer",
"authentication": {
"token": "folder-token"
}
}
]
},
"sourceKeys": {
"GENERATE_ID::WORKSPACE_0": "collection:9e6dfada-256c-49ea-a38f-7d1b05b7ca2d"
}
}

View File

@@ -0,0 +1,67 @@
{
"info": {
"_postman_id": "5c0ffee0-dyn0-4var-9abc-000000000001",
"name": "Dynamic Variables",
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
},
"item": [
{
"name": "Dynamic Variables",
"request": {
"method": "POST",
"header": [
{
"key": "X-Request-Id",
"value": "{{$guid}}"
},
{
"key": "X-Correlation-Id",
"value": "{{$randomUUID}}"
},
{
"key": "X-Sent-At",
"value": "{{$timestamp}}"
},
{
"key": "X-Sent-At-Iso",
"value": "{{$isoTimestamp}}"
},
{
"key": "X-Spaced",
"value": "{{ $guid }}"
},
{
"key": "X-Faker",
"value": "{{$randomFirstName}}"
},
{
"key": "X-Plain",
"value": "{{base_url}}"
}
],
"url": {
"raw": "https://yaak.app/x/echo?n={{$randomInt}}",
"protocol": "https",
"host": ["yaak", "app"],
"path": ["x", "echo"],
"query": [
{
"key": "n",
"value": "{{$randomInt}}"
}
]
},
"body": {
"mode": "raw",
"raw": "{\n \"id\": \"{{$guid}}\",\n \"at\": {{$timestamp}},\n \"mixed\": \"{{$guid}}/{{base_url}}\"\n}",
"options": {
"raw": {
"language": "json"
}
}
}
},
"response": []
}
]
}

View File

@@ -0,0 +1,95 @@
{
"resources": {
"workspaces": [
{
"model": "workspace",
"id": "GENERATE_ID::WORKSPACE_1",
"name": "Dynamic Variables",
"authenticationType": null,
"authentication": {}
}
],
"environments": [
{
"model": "environment",
"id": "GENERATE_ID::ENVIRONMENT_1",
"name": "Global Variables",
"workspaceId": "GENERATE_ID::WORKSPACE_1",
"parentModel": "workspace",
"parentId": null,
"variables": []
}
],
"httpRequests": [
{
"model": "http_request",
"id": "GENERATE_ID::HTTP_REQUEST_15",
"workspaceId": "GENERATE_ID::WORKSPACE_1",
"folderId": null,
"name": "Dynamic Variables",
"method": "POST",
"url": "https://yaak.app/x/echo",
"urlParameters": [
{
"name": "n",
"value": "${[random.range(min='0',max='1000',decimals='0')]}",
"enabled": true
}
],
"body": {
"text": "{\n \"id\": \"${[uuid.v4()]}\",\n \"at\": ${[timestamp.unix()]},\n \"mixed\": \"${[uuid.v4()]}/${[base_url]}\"\n}"
},
"bodyType": "application/json",
"sortPriority": 0,
"headers": [
{
"name": "X-Request-Id",
"value": "${[uuid.v4()]}",
"enabled": true
},
{
"name": "X-Correlation-Id",
"value": "${[uuid.v4()]}",
"enabled": true
},
{
"name": "X-Sent-At",
"value": "${[timestamp.unix()]}",
"enabled": true
},
{
"name": "X-Sent-At-Iso",
"value": "${[timestamp.iso8601()]}",
"enabled": true
},
{
"name": "X-Spaced",
"value": "${[uuid.v4()]}",
"enabled": true
},
{
"name": "X-Faker",
"value": "${[$randomFirstName]}",
"enabled": true
},
{
"name": "X-Plain",
"value": "${[base_url]}",
"enabled": true
},
{
"name": "Content-Type",
"value": "application/json",
"enabled": true
}
],
"authenticationType": null,
"authentication": {}
}
],
"folders": []
},
"sourceKeys": {
"GENERATE_ID::WORKSPACE_1": "collection:5c0ffee0-dyn0-4var-9abc-000000000001"
}
}

View File

@@ -0,0 +1,38 @@
{
"info": {
"_postman_id": "9e6dfada-256c-49ea-a38f-7d1b05b7ca2d",
"name": "New Collection",
"schema": "https://schema.getpostman.com/json/collection/v2.0.0/collection.json",
"_exporter_id": "18798"
},
"item": [
{
"name": "Top Folder",
"item": [
{
"name": "Nested Folder",
"item": [
{
"name": "Request 1",
"request": {
"method": "GET"
}
}
]
},
{
"name": "Request 2",
"request": {
"method": "GET"
}
}
]
},
{
"name": "Request 3",
"request": {
"method": "GET"
}
}
]
}

View File

@@ -0,0 +1,99 @@
{
"resources": {
"workspaces": [
{
"model": "workspace",
"id": "GENERATE_ID::WORKSPACE_2",
"name": "New Collection",
"authenticationType": null,
"authentication": {}
}
],
"environments": [
{
"model": "environment",
"id": "GENERATE_ID::ENVIRONMENT_2",
"name": "Global Variables",
"workspaceId": "GENERATE_ID::WORKSPACE_2",
"parentModel": "workspace",
"parentId": null,
"variables": []
}
],
"httpRequests": [
{
"model": "http_request",
"id": "GENERATE_ID::HTTP_REQUEST_16",
"workspaceId": "GENERATE_ID::WORKSPACE_2",
"folderId": "GENERATE_ID::FOLDER_2",
"name": "Request 1",
"method": "GET",
"url": "",
"urlParameters": [],
"body": {},
"bodyType": null,
"sortPriority": 2,
"headers": [],
"authenticationType": null,
"authentication": {}
},
{
"model": "http_request",
"id": "GENERATE_ID::HTTP_REQUEST_17",
"workspaceId": "GENERATE_ID::WORKSPACE_2",
"folderId": "GENERATE_ID::FOLDER_1",
"name": "Request 2",
"method": "GET",
"url": "",
"urlParameters": [],
"body": {},
"bodyType": null,
"sortPriority": 3,
"headers": [],
"authenticationType": null,
"authentication": {}
},
{
"model": "http_request",
"id": "GENERATE_ID::HTTP_REQUEST_18",
"workspaceId": "GENERATE_ID::WORKSPACE_2",
"folderId": null,
"name": "Request 3",
"method": "GET",
"url": "",
"urlParameters": [],
"body": {},
"bodyType": null,
"sortPriority": 4,
"headers": [],
"authenticationType": null,
"authentication": {}
}
],
"folders": [
{
"model": "folder",
"sortPriority": 0,
"workspaceId": "GENERATE_ID::WORKSPACE_2",
"id": "GENERATE_ID::FOLDER_1",
"name": "Top Folder",
"folderId": null,
"authenticationType": null,
"authentication": {}
},
{
"model": "folder",
"sortPriority": 1,
"workspaceId": "GENERATE_ID::WORKSPACE_2",
"id": "GENERATE_ID::FOLDER_2",
"name": "Nested Folder",
"folderId": "GENERATE_ID::FOLDER_1",
"authenticationType": null,
"authentication": {}
}
]
},
"sourceKeys": {
"GENERATE_ID::WORKSPACE_2": "collection:9e6dfada-256c-49ea-a38f-7d1b05b7ca2d"
}
}

View File

@@ -0,0 +1,126 @@
{
"info": {
"_postman_id": "9e6dfada-256c-49ea-a38f-7d1b05b7ca2d",
"name": "New Collection",
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json",
"_exporter_id": "18798"
},
"item": [
{
"name": "Form URL",
"request": {
"auth": {
"type": "bearer",
"bearer": [
{
"key": "token",
"value": "my-token",
"type": "string"
}
]
},
"method": "POST",
"header": [
{
"key": "X-foo",
"value": "bar",
"description": "description"
},
{
"key": "Disabled",
"value": "tnroant",
"description": "ntisorantosra",
"disabled": true
}
],
"body": {
"mode": "formdata",
"formdata": [
{
"key": "Key",
"contentType": "Custom/COntent",
"description": "DEscription",
"type": "file",
"src": "/Users/gschier/Desktop/Screenshot 2024-05-31 at 12.05.11 PM.png"
}
]
},
"url": {
"raw": "example.com/:foo/:bar?q=qqq&",
"host": ["example", "com"],
"path": [":foo", ":bar"],
"query": [
{
"key": "disabled",
"value": "secondvalue",
"description": "this is disabled",
"disabled": true
},
{
"key": "q",
"value": "qqq",
"description": "hello"
},
{
"key": "",
"value": null
}
],
"variable": [
{
"key": "foo",
"value": "fff",
"description": "Description"
},
{
"key": "bar",
"value": "bbb",
"description": "bbb description"
}
]
}
},
"response": []
}
],
"auth": {
"type": "basic",
"basic": [
{
"key": "password",
"value": "globalpass",
"type": "string"
},
{
"key": "username",
"value": "globaluser",
"type": "string"
}
]
},
"event": [
{
"listen": "prerequest",
"script": {
"type": "text/javascript",
"packages": {},
"exec": [""]
}
},
{
"listen": "test",
"script": {
"type": "text/javascript",
"packages": {},
"exec": [""]
}
}
],
"variable": [
{
"key": "COLLECTION VARIABLE",
"value": "collection variable",
"type": "string"
}
]
}

View File

@@ -0,0 +1,107 @@
{
"resources": {
"workspaces": [
{
"model": "workspace",
"id": "GENERATE_ID::WORKSPACE_3",
"name": "New Collection",
"authenticationType": "basic",
"authentication": {
"username": "globaluser",
"password": "globalpass"
}
}
],
"environments": [
{
"model": "environment",
"id": "GENERATE_ID::ENVIRONMENT_3",
"name": "Global Variables",
"workspaceId": "GENERATE_ID::WORKSPACE_3",
"parentModel": "workspace",
"parentId": null,
"variables": [
{
"name": "COLLECTION VARIABLE",
"value": "collection variable"
}
]
}
],
"httpRequests": [
{
"model": "http_request",
"id": "GENERATE_ID::HTTP_REQUEST_19",
"workspaceId": "GENERATE_ID::WORKSPACE_3",
"folderId": null,
"name": "Form URL",
"method": "POST",
"url": "example.com/:foo/:bar",
"urlParameters": [
{
"name": "disabled",
"value": "secondvalue",
"enabled": false
},
{
"name": "q",
"value": "qqq",
"enabled": true
},
{
"name": "",
"value": "",
"enabled": true
},
{
"name": ":foo",
"value": "fff",
"enabled": true
},
{
"name": ":bar",
"value": "bbb",
"enabled": true
}
],
"body": {
"form": [
{
"enabled": true,
"contentType": "Custom/COntent",
"name": "Key",
"file": "/Users/gschier/Desktop/Screenshot 2024-05-31 at 12.05.11 PM.png"
}
]
},
"bodyType": "multipart/form-data",
"sortPriority": 0,
"headers": [
{
"name": "X-foo",
"value": "bar",
"enabled": true
},
{
"name": "Disabled",
"value": "tnroant",
"enabled": false
},
{
"name": "Content-Type",
"value": "multipart/form-data",
"enabled": true
}
],
"authenticationType": "bearer",
"authentication": {
"token": "my-token"
}
}
],
"folders": []
},
"sourceKeys": {
"GENERATE_ID::WORKSPACE_3": "collection:9e6dfada-256c-49ea-a38f-7d1b05b7ca2d"
}
}

View File

@@ -0,0 +1,201 @@
import * as fs from "node:fs";
import * as path from "node:path";
import { describe, expect, test } from "vite-plus/test";
import { convertPostman } from "../src";
describe("importer-postman", () => {
const p = path.join(__dirname, "fixtures");
const fixtures = fs.readdirSync(p);
for (const fixture of fixtures) {
if (fixture.includes(".output")) {
continue;
}
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 result = convertPostman(contents);
// console.log(JSON.stringify(result, null, 2))
expect(JSON.stringify(result, null, 2)).toEqual(
JSON.stringify(JSON.parse(expected), null, 2),
);
});
}
test("Imports object descriptions without [object Object]", () => {
const result = convertPostman(
JSON.stringify({
info: {
name: "Description Test",
schema: "https://schema.getpostman.com/json/collection/v2.1.0/collection.json",
},
item: [
{
name: "Request 1",
request: {
method: "GET",
description: {
content: "Lijst van klanten",
type: "text/plain",
},
},
},
],
}),
);
expect(result?.resources.workspaces).toEqual([
expect.objectContaining({
name: "Description Test",
}),
]);
expect(result?.resources.httpRequests).toEqual([
expect.objectContaining({
name: "Request 1",
description: "Lijst van klanten",
}),
]);
});
test("Imports url.path when it is a string instead of an array", () => {
const result = convertPostman(
JSON.stringify({
info: {
name: "String Path Test",
schema: "https://schema.getpostman.com/json/collection/v2.1.0/collection.json",
},
item: [
{
name: "String Path",
request: {
method: "GET",
url: {
host: ["example", "com"],
path: "foo/bar",
},
},
},
],
}),
);
expect(result?.resources.httpRequests).toEqual([
expect.objectContaining({
name: "String Path",
url: "example.com/foo/bar",
}),
]);
});
test("Keys items by their Postman ID, unchanged by a rename", () => {
const collection = (requestName: string) =>
JSON.stringify({
info: {
_postman_id: "collection-id",
name: "Keys",
schema: "https://schema.getpostman.com/json/collection/v2.1.0/collection.json",
},
item: [
{
id: "folder-id",
name: "Folder",
item: [
{
id: "request-id",
name: requestName,
request: { method: "GET", url: "https://yaak.app" },
},
],
},
],
});
const before = convertPostman(collection("Original"));
const after = convertPostman(collection("Renamed"));
const keyOf = (result: ReturnType<typeof convertPostman>, id: string | undefined) =>
id == null ? undefined : result?.sourceKeys?.[id];
expect(keyOf(before, before?.resources.httpRequests[0]?.id)).toBe("item:request-id");
expect(keyOf(after, after?.resources.httpRequests[0]?.id)).toBe("item:request-id");
expect(keyOf(before, before?.resources.folders[0]?.id)).toBe("item:folder-id");
expect(keyOf(before, before?.resources.workspaces[0]?.id)).toBe("collection:collection-id");
});
test("Falls back to the default OAuth 1 signature method for unrecognized ones", () => {
const result = convertPostman(
JSON.stringify({
info: {
name: "OAuth 1 Signature",
schema: "https://schema.getpostman.com/json/collection/v2.1.0/collection.json",
},
item: [
{
name: "Request",
request: {
method: "GET",
url: "https://yaak.app",
auth: {
type: "oauth1",
oauth1: [{ key: "signatureMethod", value: "HMAC-SHA384" }],
},
},
},
],
}),
);
expect(result?.resources.httpRequests).toEqual([
expect.objectContaining({
authenticationType: "oauth1",
authentication: { signatureMethod: "HMAC-SHA1" },
}),
]);
});
test("Leaves {{constructor}} alone instead of reaching Object.prototype", () => {
const result = convertPostman(
JSON.stringify({
info: {
name: "Prototype Key",
schema: "https://schema.getpostman.com/json/collection/v2.1.0/collection.json",
},
item: [
{
name: "Request",
request: {
method: "GET",
url: "https://yaak.app",
header: [
{ key: "X-A", value: "{{constructor}}" },
{ key: "X-B", value: "{{toString}}" },
],
},
},
],
}),
);
expect(result?.resources.httpRequests[0]?.headers).toEqual([
{ name: "X-A", value: "${[constructor]}", enabled: true },
{ name: "X-B", value: "${[toString]}", enabled: true },
]);
});
test("Omits keys for items the collection never identified", () => {
const result = convertPostman(
JSON.stringify({
info: {
name: "No IDs",
schema: "https://schema.getpostman.com/json/collection/v2.1.0/collection.json",
},
item: [{ name: "Request", request: { method: "GET", url: "https://yaak.app" } }],
}),
);
const requestId = result?.resources.httpRequests[0]?.id;
expect(requestId).toBeDefined();
expect(result?.sourceKeys).not.toHaveProperty(requestId as string);
});
});

View File

@@ -0,0 +1,3 @@
{
"extends": "../../tsconfig.json"
}