mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-04-25 02:08:28 +02:00
Move plugins to this repo
This commit is contained in:
133
.gitignore
vendored
Normal file
133
.gitignore
vendored
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
.pnpm-debug.log*
|
||||||
|
|
||||||
|
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||||
|
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||||
|
|
||||||
|
# Runtime data
|
||||||
|
pids
|
||||||
|
*.pid
|
||||||
|
*.seed
|
||||||
|
*.pid.lock
|
||||||
|
|
||||||
|
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||||
|
lib-cov
|
||||||
|
|
||||||
|
# Coverage directory used by tools like istanbul
|
||||||
|
coverage
|
||||||
|
*.lcov
|
||||||
|
|
||||||
|
# nyc test coverage
|
||||||
|
.nyc_output
|
||||||
|
|
||||||
|
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||||
|
.grunt
|
||||||
|
|
||||||
|
# Bower dependency directory (https://bower.io/)
|
||||||
|
bower_components
|
||||||
|
|
||||||
|
# node-waf configuration
|
||||||
|
.lock-wscript
|
||||||
|
|
||||||
|
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||||
|
build/Release
|
||||||
|
|
||||||
|
# Dependency directories
|
||||||
|
node_modules/
|
||||||
|
jspm_packages/
|
||||||
|
|
||||||
|
# Snowpack dependency directory (https://snowpack.dev/)
|
||||||
|
web_modules/
|
||||||
|
|
||||||
|
# TypeScript cache
|
||||||
|
*.tsbuildinfo
|
||||||
|
|
||||||
|
# Optional npm cache directory
|
||||||
|
.npm
|
||||||
|
|
||||||
|
# Optional eslint cache
|
||||||
|
.eslintcache
|
||||||
|
|
||||||
|
# Optional stylelint cache
|
||||||
|
.stylelintcache
|
||||||
|
|
||||||
|
# Microbundle cache
|
||||||
|
.rpt2_cache/
|
||||||
|
.rts2_cache_cjs/
|
||||||
|
.rts2_cache_es/
|
||||||
|
.rts2_cache_umd/
|
||||||
|
|
||||||
|
# Optional REPL history
|
||||||
|
.node_repl_history
|
||||||
|
|
||||||
|
# Output of 'npm pack'
|
||||||
|
*.tgz
|
||||||
|
|
||||||
|
# Yarn Integrity file
|
||||||
|
.yarn-integrity
|
||||||
|
|
||||||
|
# dotenv environment variable files
|
||||||
|
.env
|
||||||
|
.env.development.local
|
||||||
|
.env.test.local
|
||||||
|
.env.production.local
|
||||||
|
.env.local
|
||||||
|
|
||||||
|
# parcel-bundler cache (https://parceljs.org/)
|
||||||
|
.cache
|
||||||
|
.parcel-cache
|
||||||
|
|
||||||
|
# Next.js build output
|
||||||
|
.next
|
||||||
|
out
|
||||||
|
|
||||||
|
# Nuxt.js build / generate output
|
||||||
|
.nuxt
|
||||||
|
dist
|
||||||
|
|
||||||
|
# Gatsby files
|
||||||
|
.cache/
|
||||||
|
# Comment in the public line in if your project uses Gatsby and not Next.js
|
||||||
|
# https://nextjs.org/blog/next-9-1#public-directory-support
|
||||||
|
# public
|
||||||
|
|
||||||
|
# vuepress build output
|
||||||
|
.vuepress/dist
|
||||||
|
|
||||||
|
# vuepress v2.x temp and cache directory
|
||||||
|
.temp
|
||||||
|
|
||||||
|
# Docusaurus cache and generated files
|
||||||
|
.docusaurus
|
||||||
|
|
||||||
|
# Serverless directories
|
||||||
|
.serverless/
|
||||||
|
|
||||||
|
# FuseBox cache
|
||||||
|
.fusebox/
|
||||||
|
|
||||||
|
# DynamoDB Local files
|
||||||
|
.dynamodb/
|
||||||
|
|
||||||
|
# TernJS port file
|
||||||
|
.tern-port
|
||||||
|
|
||||||
|
# Stores VSCode versions used for testing VSCode extensions
|
||||||
|
.vscode-test
|
||||||
|
|
||||||
|
# yarn v2
|
||||||
|
.yarn/cache
|
||||||
|
.yarn/unplugged
|
||||||
|
.yarn/build-state.yml
|
||||||
|
.yarn/install-state.gz
|
||||||
|
.pnp.*
|
||||||
|
|
||||||
|
# Other
|
||||||
|
build
|
||||||
|
.idea
|
||||||
1544
plugins/exporter-curl/package-lock.json
generated
Normal file
1544
plugins/exporter-curl/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
7
plugins/exporter-curl/package.json
Normal file
7
plugins/exporter-curl/package.json
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"name": "exporter-curl",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"devDependencies": {
|
||||||
|
"vitest": "^1.4.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
76
plugins/exporter-curl/src/index.ts
Normal file
76
plugins/exporter-curl/src/index.ts
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
import { HttpRequest } from '../../../src-web/lib/models';
|
||||||
|
|
||||||
|
const NEWLINE = '\\\n ';
|
||||||
|
|
||||||
|
export function pluginHookExport(_: any, request: Partial<HttpRequest>) {
|
||||||
|
const xs = ['curl'];
|
||||||
|
|
||||||
|
// Add method and URL all on first line
|
||||||
|
if (request.method) xs.push('-X', request.method);
|
||||||
|
if (request.url) xs.push(quote(request.url));
|
||||||
|
|
||||||
|
xs.push(NEWLINE);
|
||||||
|
|
||||||
|
// Add URL params
|
||||||
|
for (const p of (request.urlParameters ?? []).filter(onlyEnabled)) {
|
||||||
|
xs.push('--url-query', quote(`${p.name}=${p.value}`));
|
||||||
|
xs.push(NEWLINE);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add headers
|
||||||
|
for (const h of (request.headers ?? []).filter(onlyEnabled)) {
|
||||||
|
xs.push('--header', quote(`${h.name}: ${h.value}`));
|
||||||
|
xs.push(NEWLINE);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add form params
|
||||||
|
if (Array.isArray(request.body?.form)) {
|
||||||
|
const flag = request.bodyType === 'multipart/form-data' ? '--form' : '--data';
|
||||||
|
for (const p of (request.body?.form ?? []).filter(onlyEnabled)) {
|
||||||
|
if (p.file) {
|
||||||
|
let v = `${p.name}=@${p.file}`;
|
||||||
|
v += p.contentType ? `;type=${p.contentType}` : '';
|
||||||
|
xs.push(flag, v);
|
||||||
|
} else {
|
||||||
|
xs.push(flag, quote(`${p.name}=${p.value}`));
|
||||||
|
}
|
||||||
|
xs.push(NEWLINE);
|
||||||
|
}
|
||||||
|
} else if (typeof request.body?.text === 'string') {
|
||||||
|
// --data-raw $'...' to do special ANSI C quoting
|
||||||
|
xs.push('--data-raw', `$${quote(request.body.text)}`);
|
||||||
|
xs.push(NEWLINE);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add basic/digest authentication
|
||||||
|
if (request.authenticationType === 'basic' || request.authenticationType === 'digest') {
|
||||||
|
if (request.authenticationType === 'digest') xs.push('--digest');
|
||||||
|
xs.push(
|
||||||
|
'--user',
|
||||||
|
quote(`${request.authentication?.username ?? ''}:${request.authentication?.password ?? ''}`),
|
||||||
|
);
|
||||||
|
xs.push(NEWLINE);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add bearer authentication
|
||||||
|
if (request.authenticationType === 'bearer') {
|
||||||
|
xs.push('--header', quote(`Authorization: Bearer ${request.authentication?.token ?? ''}`));
|
||||||
|
xs.push(NEWLINE);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove trailing newline
|
||||||
|
if (xs[xs.length - 1] === NEWLINE) {
|
||||||
|
xs.splice(xs.length - 1, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return xs.join(' ');
|
||||||
|
}
|
||||||
|
|
||||||
|
function quote(arg: string): string {
|
||||||
|
const escaped = arg.replace(/'/g, "\\'");
|
||||||
|
return `'${escaped}'`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function onlyEnabled(v: { name?: string; enabled?: boolean }): boolean {
|
||||||
|
return v.enabled !== false && !!v.name;
|
||||||
|
}
|
||||||
177
plugins/exporter-curl/tests/index.test.ts
Normal file
177
plugins/exporter-curl/tests/index.test.ts
Normal file
@@ -0,0 +1,177 @@
|
|||||||
|
import { describe, expect, test } from 'vitest';
|
||||||
|
import { pluginHookExport } from '../src';
|
||||||
|
|
||||||
|
const ctx = {};
|
||||||
|
|
||||||
|
describe('exporter-curl', () => {
|
||||||
|
test('Exports GET with params', () => {
|
||||||
|
expect(
|
||||||
|
pluginHookExport(ctx, {
|
||||||
|
url: 'https://yaak.app',
|
||||||
|
urlParameters: [
|
||||||
|
{ name: 'a', value: 'aaa' },
|
||||||
|
{ name: 'b', value: 'bbb', enabled: true },
|
||||||
|
{ name: 'c', value: 'ccc', enabled: false },
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
).toEqual(
|
||||||
|
[`curl 'https://yaak.app'`, `--url-query 'a=aaa'`, `--url-query 'b=bbb'`].join(` \\\n `),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
test('Exports POST with url form data', () => {
|
||||||
|
expect(
|
||||||
|
pluginHookExport(ctx, {
|
||||||
|
url: 'https://yaak.app',
|
||||||
|
method: 'POST',
|
||||||
|
bodyType: 'application/x-www-form-urlencoded',
|
||||||
|
body: {
|
||||||
|
form: [
|
||||||
|
{ name: 'a', value: 'aaa' },
|
||||||
|
{ name: 'b', value: 'bbb', enabled: true },
|
||||||
|
{ name: 'c', value: 'ccc', enabled: false },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).toEqual(
|
||||||
|
[`curl -X POST 'https://yaak.app'`, `--data 'a=aaa'`, `--data 'b=bbb'`].join(` \\\n `),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Exports PUT with multipart form', () => {
|
||||||
|
expect(
|
||||||
|
pluginHookExport(ctx, {
|
||||||
|
url: 'https://yaak.app',
|
||||||
|
method: 'PUT',
|
||||||
|
bodyType: 'multipart/form-data',
|
||||||
|
body: {
|
||||||
|
form: [
|
||||||
|
{ name: 'a', value: 'aaa' },
|
||||||
|
{ name: 'b', value: 'bbb', enabled: true },
|
||||||
|
{ name: 'c', value: 'ccc', enabled: false },
|
||||||
|
{ name: 'f', file: '/foo/bar.png', contentType: 'image/png' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).toEqual(
|
||||||
|
[
|
||||||
|
`curl -X PUT 'https://yaak.app'`,
|
||||||
|
`--form 'a=aaa'`,
|
||||||
|
`--form 'b=bbb'`,
|
||||||
|
`--form f=@/foo/bar.png;type=image/png`,
|
||||||
|
].join(` \\\n `),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Exports JSON body', () => {
|
||||||
|
expect(
|
||||||
|
pluginHookExport(ctx, {
|
||||||
|
url: 'https://yaak.app',
|
||||||
|
method: 'POST',
|
||||||
|
bodyType: 'application/json',
|
||||||
|
body: {
|
||||||
|
text: `{"foo":"bar's"}`,
|
||||||
|
},
|
||||||
|
headers: [{ name: 'Content-Type', value: 'application/json' }],
|
||||||
|
}),
|
||||||
|
).toEqual(
|
||||||
|
[
|
||||||
|
`curl -X POST 'https://yaak.app'`,
|
||||||
|
`--header 'Content-Type: application/json'`,
|
||||||
|
`--data-raw $'{"foo":"bar\\'s"}'`,
|
||||||
|
].join(` \\\n `),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Exports multi-line JSON body', () => {
|
||||||
|
expect(
|
||||||
|
pluginHookExport(ctx, {
|
||||||
|
url: 'https://yaak.app',
|
||||||
|
method: 'POST',
|
||||||
|
bodyType: 'application/json',
|
||||||
|
body: {
|
||||||
|
text: `{"foo":"bar",\n"baz":"qux"}`,
|
||||||
|
},
|
||||||
|
headers: [{ name: 'Content-Type', value: 'application/json' }],
|
||||||
|
}),
|
||||||
|
).toEqual(
|
||||||
|
[
|
||||||
|
`curl -X POST 'https://yaak.app'`,
|
||||||
|
`--header 'Content-Type: application/json'`,
|
||||||
|
`--data-raw $'{"foo":"bar",\n"baz":"qux"}'`,
|
||||||
|
].join(` \\\n `),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Exports headers', () => {
|
||||||
|
expect(
|
||||||
|
pluginHookExport(ctx, {
|
||||||
|
headers: [
|
||||||
|
{ name: 'a', value: 'aaa' },
|
||||||
|
{ name: 'b', value: 'bbb', enabled: true },
|
||||||
|
{ name: 'c', value: 'ccc', enabled: false },
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
).toEqual([`curl`, `--header 'a: aaa'`, `--header 'b: bbb'`].join(` \\\n `));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Basic auth', () => {
|
||||||
|
expect(
|
||||||
|
pluginHookExport(ctx, {
|
||||||
|
url: 'https://yaak.app',
|
||||||
|
authenticationType: 'basic',
|
||||||
|
authentication: {
|
||||||
|
username: 'user',
|
||||||
|
password: 'pass',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).toEqual([`curl 'https://yaak.app'`, `--user 'user:pass'`].join(` \\\n `));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Broken basic auth', () => {
|
||||||
|
expect(
|
||||||
|
pluginHookExport(ctx, {
|
||||||
|
url: 'https://yaak.app',
|
||||||
|
authenticationType: 'basic',
|
||||||
|
authentication: {},
|
||||||
|
}),
|
||||||
|
).toEqual([`curl 'https://yaak.app'`, `--user ':'`].join(` \\\n `));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Digest auth', () => {
|
||||||
|
expect(
|
||||||
|
pluginHookExport(ctx, {
|
||||||
|
url: 'https://yaak.app',
|
||||||
|
authenticationType: 'digest',
|
||||||
|
authentication: {
|
||||||
|
username: 'user',
|
||||||
|
password: 'pass',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).toEqual([`curl 'https://yaak.app'`, `--digest --user 'user:pass'`].join(` \\\n `));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Bearer auth', () => {
|
||||||
|
expect(
|
||||||
|
pluginHookExport(ctx, {
|
||||||
|
url: 'https://yaak.app',
|
||||||
|
authenticationType: 'bearer',
|
||||||
|
authentication: {
|
||||||
|
token: 'tok',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).toEqual([`curl 'https://yaak.app'`, `--header 'Authorization: Bearer tok'`].join(` \\\n `));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Broken bearer auth', () => {
|
||||||
|
expect(
|
||||||
|
pluginHookExport(ctx, {
|
||||||
|
url: 'https://yaak.app',
|
||||||
|
authenticationType: 'bearer',
|
||||||
|
authentication: {
|
||||||
|
username: 'user',
|
||||||
|
password: 'pass',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).toEqual([`curl 'https://yaak.app'`, `--header 'Authorization: Bearer '`].join(` \\\n `));
|
||||||
|
});
|
||||||
|
});
|
||||||
15
plugins/exporter-curl/vite.config.js
Normal file
15
plugins/exporter-curl/vite.config.js
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import { resolve } from 'path';
|
||||||
|
import { defineConfig } from 'vite';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
build: {
|
||||||
|
lib: {
|
||||||
|
entry: resolve(__dirname, 'src/index.ts'),
|
||||||
|
fileName: 'index',
|
||||||
|
formats: ['es'],
|
||||||
|
},
|
||||||
|
emptyOutDir: true,
|
||||||
|
sourcemap: true,
|
||||||
|
outDir: resolve(__dirname, 'build'),
|
||||||
|
},
|
||||||
|
});
|
||||||
182
plugins/filter-jsonpath/package-lock.json
generated
Normal file
182
plugins/filter-jsonpath/package-lock.json
generated
Normal file
@@ -0,0 +1,182 @@
|
|||||||
|
{
|
||||||
|
"name": "filter-jsonpath",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"lockfileVersion": 3,
|
||||||
|
"requires": true,
|
||||||
|
"packages": {
|
||||||
|
"": {
|
||||||
|
"name": "filter-jsonpath",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"dependencies": {
|
||||||
|
"jsonpath": "^1.1.1"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/jsonpath": "^0.2.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@types/jsonpath": {
|
||||||
|
"version": "0.2.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/jsonpath/-/jsonpath-0.2.4.tgz",
|
||||||
|
"integrity": "sha512-K3hxB8Blw0qgW6ExKgMbXQv2UPZBoE2GqLpVY+yr7nMD2Pq86lsuIzyAaiQ7eMqFL5B6di6pxSkogLJEyEHoGA==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"node_modules/deep-is": {
|
||||||
|
"version": "0.1.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
|
||||||
|
"integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="
|
||||||
|
},
|
||||||
|
"node_modules/escodegen": {
|
||||||
|
"version": "1.14.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.14.3.tgz",
|
||||||
|
"integrity": "sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw==",
|
||||||
|
"dependencies": {
|
||||||
|
"esprima": "^4.0.1",
|
||||||
|
"estraverse": "^4.2.0",
|
||||||
|
"esutils": "^2.0.2",
|
||||||
|
"optionator": "^0.8.1"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"escodegen": "bin/escodegen.js",
|
||||||
|
"esgenerate": "bin/esgenerate.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4.0"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"source-map": "~0.6.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/escodegen/node_modules/esprima": {
|
||||||
|
"version": "4.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
|
||||||
|
"integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
|
||||||
|
"bin": {
|
||||||
|
"esparse": "bin/esparse.js",
|
||||||
|
"esvalidate": "bin/esvalidate.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/esprima": {
|
||||||
|
"version": "1.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/esprima/-/esprima-1.2.2.tgz",
|
||||||
|
"integrity": "sha512-+JpPZam9w5DuJ3Q67SqsMGtiHKENSMRVoxvArfJZK01/BfLEObtZ6orJa/MtoGNR/rfMgp5837T41PAmTwAv/A==",
|
||||||
|
"bin": {
|
||||||
|
"esparse": "bin/esparse.js",
|
||||||
|
"esvalidate": "bin/esvalidate.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/estraverse": {
|
||||||
|
"version": "4.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz",
|
||||||
|
"integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/esutils": {
|
||||||
|
"version": "2.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
|
||||||
|
"integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/fast-levenshtein": {
|
||||||
|
"version": "2.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
|
||||||
|
"integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw=="
|
||||||
|
},
|
||||||
|
"node_modules/jsonpath": {
|
||||||
|
"version": "1.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/jsonpath/-/jsonpath-1.1.1.tgz",
|
||||||
|
"integrity": "sha512-l6Cg7jRpixfbgoWgkrl77dgEj8RPvND0wMH6TwQmi9Qs4TFfS9u5cUFnbeKTwj5ga5Y3BTGGNI28k117LJ009w==",
|
||||||
|
"dependencies": {
|
||||||
|
"esprima": "1.2.2",
|
||||||
|
"static-eval": "2.0.2",
|
||||||
|
"underscore": "1.12.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/levn": {
|
||||||
|
"version": "0.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz",
|
||||||
|
"integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==",
|
||||||
|
"dependencies": {
|
||||||
|
"prelude-ls": "~1.1.2",
|
||||||
|
"type-check": "~0.3.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/optionator": {
|
||||||
|
"version": "0.8.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz",
|
||||||
|
"integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==",
|
||||||
|
"dependencies": {
|
||||||
|
"deep-is": "~0.1.3",
|
||||||
|
"fast-levenshtein": "~2.0.6",
|
||||||
|
"levn": "~0.3.0",
|
||||||
|
"prelude-ls": "~1.1.2",
|
||||||
|
"type-check": "~0.3.2",
|
||||||
|
"word-wrap": "~1.2.3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/prelude-ls": {
|
||||||
|
"version": "1.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz",
|
||||||
|
"integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/source-map": {
|
||||||
|
"version": "0.6.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
||||||
|
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
|
||||||
|
"optional": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/static-eval": {
|
||||||
|
"version": "2.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/static-eval/-/static-eval-2.0.2.tgz",
|
||||||
|
"integrity": "sha512-N/D219Hcr2bPjLxPiV+TQE++Tsmrady7TqAJugLy7Xk1EumfDWS/f5dtBbkRCGE7wKKXuYockQoj8Rm2/pVKyg==",
|
||||||
|
"dependencies": {
|
||||||
|
"escodegen": "^1.8.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/type-check": {
|
||||||
|
"version": "0.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz",
|
||||||
|
"integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==",
|
||||||
|
"dependencies": {
|
||||||
|
"prelude-ls": "~1.1.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/underscore": {
|
||||||
|
"version": "1.12.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/underscore/-/underscore-1.12.1.tgz",
|
||||||
|
"integrity": "sha512-hEQt0+ZLDVUMhebKxL4x1BTtDY7bavVofhZ9KZ4aI26X9SRaE+Y3m83XUL1UP2jn8ynjndwCCpEHdUG+9pP1Tw=="
|
||||||
|
},
|
||||||
|
"node_modules/word-wrap": {
|
||||||
|
"version": "1.2.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
|
||||||
|
"integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
10
plugins/filter-jsonpath/package.json
Normal file
10
plugins/filter-jsonpath/package.json
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"name": "filter-jsonpath",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"dependencies": {
|
||||||
|
"jsonpath": "^1.1.1"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/jsonpath": "^0.2.4"
|
||||||
|
}
|
||||||
|
}
|
||||||
7
plugins/filter-jsonpath/src/index.ts
Normal file
7
plugins/filter-jsonpath/src/index.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import jp from 'jsonpath';
|
||||||
|
|
||||||
|
export function pluginHookResponseFilter(_ctx: any, args: { filter: string; body: string }) {
|
||||||
|
const parsed = JSON.parse(args.body);
|
||||||
|
const filtered = jp.query(parsed, args.filter);
|
||||||
|
return JSON.stringify(filtered, null, 2);
|
||||||
|
}
|
||||||
15
plugins/filter-jsonpath/vite.config.js
Normal file
15
plugins/filter-jsonpath/vite.config.js
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import { resolve } from 'path';
|
||||||
|
import { defineConfig } from 'vite';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
build: {
|
||||||
|
lib: {
|
||||||
|
entry: resolve(__dirname, 'src/index.ts'),
|
||||||
|
fileName: 'index',
|
||||||
|
formats: ['es'],
|
||||||
|
},
|
||||||
|
emptyOutDir: true,
|
||||||
|
sourcemap: true,
|
||||||
|
outDir: resolve(__dirname, 'build'),
|
||||||
|
},
|
||||||
|
});
|
||||||
32
plugins/filter-xpath/package-lock.json
generated
Normal file
32
plugins/filter-xpath/package-lock.json
generated
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
{
|
||||||
|
"name": "filter-xpath",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"lockfileVersion": 3,
|
||||||
|
"requires": true,
|
||||||
|
"packages": {
|
||||||
|
"": {
|
||||||
|
"name": "filter-xpath",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"dependencies": {
|
||||||
|
"@xmldom/xmldom": "^0.8.10",
|
||||||
|
"xpath": "^0.0.34"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@xmldom/xmldom": {
|
||||||
|
"version": "0.8.10",
|
||||||
|
"resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.10.tgz",
|
||||||
|
"integrity": "sha512-2WALfTl4xo2SkGCYRt6rDTFfk9R1czmBvUQy12gK2KuRKIpWEhcbbzy8EZXtz/jkRqHX8bFEc6FC1HjX4TUWYw==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/xpath": {
|
||||||
|
"version": "0.0.34",
|
||||||
|
"resolved": "https://registry.npmjs.org/xpath/-/xpath-0.0.34.tgz",
|
||||||
|
"integrity": "sha512-FxF6+rkr1rNSQrhUNYrAFJpRXNzlDoMxeXN5qI84939ylEv3qqPFKa85Oxr6tDaJKqwW6KKyo2v26TSv3k6LeA==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.6.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
8
plugins/filter-xpath/package.json
Normal file
8
plugins/filter-xpath/package.json
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"name": "filter-xpath",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"dependencies": {
|
||||||
|
"@xmldom/xmldom": "^0.8.10",
|
||||||
|
"xpath": "^0.0.34"
|
||||||
|
}
|
||||||
|
}
|
||||||
10
plugins/filter-xpath/src/index.ts
Normal file
10
plugins/filter-xpath/src/index.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import { DOMParser } from '@xmldom/xmldom';
|
||||||
|
import xpath from 'xpath';
|
||||||
|
|
||||||
|
export function pluginHookResponseFilter(
|
||||||
|
_ctx: any,
|
||||||
|
{ filter, body }: { filter: string; body: string },
|
||||||
|
) {
|
||||||
|
const doc = new DOMParser().parseFromString(body, 'text/xml');
|
||||||
|
return `${xpath.select(filter, doc)}`;
|
||||||
|
}
|
||||||
15
plugins/filter-xpath/vite.config.js
Normal file
15
plugins/filter-xpath/vite.config.js
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import { resolve } from 'path';
|
||||||
|
import { defineConfig } from 'vite';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
build: {
|
||||||
|
lib: {
|
||||||
|
entry: resolve(__dirname, 'src/index.ts'),
|
||||||
|
fileName: 'index',
|
||||||
|
formats: ['es'],
|
||||||
|
},
|
||||||
|
emptyOutDir: true,
|
||||||
|
sourcemap: true,
|
||||||
|
outDir: resolve(__dirname, 'build'),
|
||||||
|
},
|
||||||
|
});
|
||||||
1562
plugins/importer-curl/package-lock.json
generated
Normal file
1562
plugins/importer-curl/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
11
plugins/importer-curl/package.json
Normal file
11
plugins/importer-curl/package.json
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"name": "importer-curl",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"dependencies": {
|
||||||
|
"shell-quote": "^1.8.1"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/shell-quote": "^1.7.5",
|
||||||
|
"vitest": "^1.4.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
419
plugins/importer-curl/src/index.ts
Normal file
419
plugins/importer-curl/src/index.ts
Normal file
@@ -0,0 +1,419 @@
|
|||||||
|
import { ControlOperator, parse, ParseEntry } from 'shell-quote';
|
||||||
|
import {
|
||||||
|
Environment,
|
||||||
|
Folder,
|
||||||
|
HttpRequest,
|
||||||
|
HttpUrlParameter,
|
||||||
|
Model,
|
||||||
|
Workspace,
|
||||||
|
} from '../../../src-web/lib/models';
|
||||||
|
|
||||||
|
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'>[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const DATA_FLAGS = ['d', 'data', 'data-raw', 'data-urlencode', 'data-binary', 'data-ascii'];
|
||||||
|
const SUPPORTED_ARGS = [
|
||||||
|
['url'], // Specify the URL explicitly
|
||||||
|
['user', 'u'], // Authentication
|
||||||
|
['digest'], // Apply auth as digest
|
||||||
|
['header', 'H'],
|
||||||
|
['cookie', 'b'],
|
||||||
|
['get', 'G'], // Put the post data in the URL
|
||||||
|
['d', 'data'], // Add url encoded data
|
||||||
|
['data-raw'],
|
||||||
|
['data-urlencode'],
|
||||||
|
['data-binary'],
|
||||||
|
['data-ascii'],
|
||||||
|
['form', 'F'], // Add multipart data
|
||||||
|
['request', 'X'], // Request method
|
||||||
|
DATA_FLAGS,
|
||||||
|
].flatMap((v) => v);
|
||||||
|
|
||||||
|
type Pair = string | boolean;
|
||||||
|
|
||||||
|
type PairsByName = Record<string, Pair[]>;
|
||||||
|
|
||||||
|
export function pluginHookImport(_: any, rawData: string) {
|
||||||
|
if (!rawData.match(/^\s*curl /)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const commands: ParseEntry[][] = [];
|
||||||
|
|
||||||
|
// Replace non-escaped newlines with semicolons to make parsing easier
|
||||||
|
// NOTE: This is really slow in debug build but fast in release mode
|
||||||
|
const normalizedData = rawData.replace(/\ncurl/g, '; curl');
|
||||||
|
|
||||||
|
let currentCommand: ParseEntry[] = [];
|
||||||
|
|
||||||
|
const parsed = parse(normalizedData);
|
||||||
|
|
||||||
|
// Break up `-XPOST` into `-X POST`
|
||||||
|
const normalizedParseEntries = parsed.flatMap((entry) => {
|
||||||
|
if (
|
||||||
|
typeof entry === 'string' &&
|
||||||
|
entry.startsWith('-') &&
|
||||||
|
!entry.startsWith('--') &&
|
||||||
|
entry.length > 2
|
||||||
|
) {
|
||||||
|
return [entry.slice(0, 2), entry.slice(2)];
|
||||||
|
}
|
||||||
|
return entry;
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const parseEntry of normalizedParseEntries) {
|
||||||
|
if (typeof parseEntry === 'string') {
|
||||||
|
if (parseEntry.startsWith('$')) {
|
||||||
|
currentCommand.push(parseEntry.slice(1));
|
||||||
|
} else {
|
||||||
|
currentCommand.push(parseEntry);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ('comment' in parseEntry) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { op } = parseEntry as { op: 'glob'; pattern: string } | { op: ControlOperator };
|
||||||
|
|
||||||
|
// `;` separates commands
|
||||||
|
if (op === ';') {
|
||||||
|
commands.push(currentCommand);
|
||||||
|
currentCommand = [];
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (op?.startsWith('$')) {
|
||||||
|
// Handle the case where literal like -H $'Header: \'Some Quoted Thing\''
|
||||||
|
const str = op.slice(2, op.length - 1).replace(/\\'/g, "'");
|
||||||
|
|
||||||
|
currentCommand.push(str);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (op === 'glob') {
|
||||||
|
currentCommand.push((parseEntry as { op: 'glob'; pattern: string }).pattern);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
commands.push(currentCommand);
|
||||||
|
|
||||||
|
const workspace: ExportResources['workspaces'][0] = {
|
||||||
|
model: 'workspace',
|
||||||
|
id: generateId('workspace'),
|
||||||
|
name: 'Curl Import',
|
||||||
|
};
|
||||||
|
|
||||||
|
const requests: ExportResources['httpRequests'] = commands
|
||||||
|
.filter((command) => command[0] === 'curl')
|
||||||
|
.map((v) => importCommand(v, workspace.id));
|
||||||
|
|
||||||
|
return {
|
||||||
|
resources: {
|
||||||
|
httpRequests: requests,
|
||||||
|
workspaces: [workspace],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function importCommand(parseEntries: ParseEntry[], workspaceId: string) {
|
||||||
|
// ~~~~~~~~~~~~~~~~~~~~~ //
|
||||||
|
// Collect all the flags //
|
||||||
|
// ~~~~~~~~~~~~~~~~~~~~~ //
|
||||||
|
const pairsByName: PairsByName = {};
|
||||||
|
const singletons: ParseEntry[] = [];
|
||||||
|
|
||||||
|
// Start at 1 so we can skip the ^curl part
|
||||||
|
for (let i = 1; i < parseEntries.length; i++) {
|
||||||
|
let parseEntry = parseEntries[i];
|
||||||
|
if (typeof parseEntry === 'string') {
|
||||||
|
parseEntry = parseEntry.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof parseEntry === 'string' && parseEntry.match(/^-{1,2}[\w-]+/)) {
|
||||||
|
const isSingleDash = parseEntry[0] === '-' && parseEntry[1] !== '-';
|
||||||
|
let name = parseEntry.replace(/^-{1,2}/, '');
|
||||||
|
|
||||||
|
if (!SUPPORTED_ARGS.includes(name)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let value;
|
||||||
|
const nextEntry = parseEntries[i + 1];
|
||||||
|
if (isSingleDash && name.length > 1) {
|
||||||
|
// Handle squished arguments like -XPOST
|
||||||
|
value = name.slice(1);
|
||||||
|
name = name.slice(0, 1);
|
||||||
|
} else if (typeof nextEntry === 'string' && !nextEntry.startsWith('-')) {
|
||||||
|
// Next arg is not a flag, so assign it as the value
|
||||||
|
value = nextEntry;
|
||||||
|
i++; // Skip next one
|
||||||
|
} else {
|
||||||
|
value = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
pairsByName[name] = pairsByName[name] || [];
|
||||||
|
pairsByName[name]!.push(value);
|
||||||
|
} else if (parseEntry) {
|
||||||
|
singletons.push(parseEntry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ~~~~~~~~~~~~~~~~~ //
|
||||||
|
// Build the request //
|
||||||
|
// ~~~~~~~~~~~~~~~~~ //
|
||||||
|
|
||||||
|
// Url & parameters
|
||||||
|
|
||||||
|
let urlParameters: HttpUrlParameter[];
|
||||||
|
let url: string;
|
||||||
|
|
||||||
|
const urlArg = getPairValue(pairsByName, (singletons[0] as string) || '', ['url']);
|
||||||
|
const [baseUrl, search] = splitOnce(urlArg, '?');
|
||||||
|
urlParameters =
|
||||||
|
search?.split('&').map((p) => {
|
||||||
|
const v = splitOnce(p, '=');
|
||||||
|
return { name: v[0] ?? '', value: v[1] ?? '', enabled: true };
|
||||||
|
}) ?? [];
|
||||||
|
|
||||||
|
url = baseUrl ?? urlArg;
|
||||||
|
|
||||||
|
// Authentication
|
||||||
|
const [username, password] = getPairValue(pairsByName, '', ['u', 'user']).split(/:(.*)$/);
|
||||||
|
|
||||||
|
const isDigest = getPairValue(pairsByName, false, ['digest']);
|
||||||
|
const authenticationType = username ? (isDigest ? 'digest' : 'basic') : null;
|
||||||
|
const authentication = username
|
||||||
|
? {
|
||||||
|
username: username.trim(),
|
||||||
|
password: (password ?? '').trim(),
|
||||||
|
}
|
||||||
|
: {};
|
||||||
|
|
||||||
|
// Headers
|
||||||
|
const headers = [
|
||||||
|
...((pairsByName['header'] as string[] | undefined) || []),
|
||||||
|
...((pairsByName['H'] as string[] | undefined) || []),
|
||||||
|
].map((header) => {
|
||||||
|
const [name, value] = header.split(/:(.*)$/);
|
||||||
|
// remove final colon from header name if present
|
||||||
|
if (!value) {
|
||||||
|
return {
|
||||||
|
name: (name ?? '').trim().replace(/;$/, ''),
|
||||||
|
value: '',
|
||||||
|
enabled: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
name: (name ?? '').trim(),
|
||||||
|
value: value.trim(),
|
||||||
|
enabled: true,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
// Cookies
|
||||||
|
const cookieHeaderValue = [
|
||||||
|
...((pairsByName['cookie'] as string[] | undefined) || []),
|
||||||
|
...((pairsByName['b'] as string[] | undefined) || []),
|
||||||
|
]
|
||||||
|
.map((str) => {
|
||||||
|
const name = str.split('=', 1)[0];
|
||||||
|
const value = str.replace(`${name}=`, '');
|
||||||
|
return `${name}=${value}`;
|
||||||
|
})
|
||||||
|
.join('; ');
|
||||||
|
|
||||||
|
// Convert cookie value to header
|
||||||
|
const existingCookieHeader = headers.find((header) => header.name.toLowerCase() === 'cookie');
|
||||||
|
|
||||||
|
if (cookieHeaderValue && existingCookieHeader) {
|
||||||
|
// Has existing cookie header, so let's update it
|
||||||
|
existingCookieHeader.value += `; ${cookieHeaderValue}`;
|
||||||
|
} else if (cookieHeaderValue) {
|
||||||
|
// No existing cookie header, so let's make a new one
|
||||||
|
headers.push({
|
||||||
|
name: 'Cookie',
|
||||||
|
value: cookieHeaderValue,
|
||||||
|
enabled: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
///Body (Text or Blob)
|
||||||
|
const dataParameters = pairsToDataParameters(pairsByName);
|
||||||
|
const contentTypeHeader = headers.find((header) => header.name.toLowerCase() === 'content-type');
|
||||||
|
const mimeType = contentTypeHeader ? contentTypeHeader.value.split(';')[0] : null;
|
||||||
|
|
||||||
|
// Body (Multipart Form Data)
|
||||||
|
const formDataParams = [
|
||||||
|
...((pairsByName['form'] as string[] | undefined) || []),
|
||||||
|
...((pairsByName['F'] as string[] | undefined) || []),
|
||||||
|
].map((str) => {
|
||||||
|
const parts = str.split('=');
|
||||||
|
const name = parts[0] ?? '';
|
||||||
|
const value = parts[1] ?? '';
|
||||||
|
const item: { name: string; value?: string; file?: string; enabled: boolean } = {
|
||||||
|
name,
|
||||||
|
enabled: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (value.indexOf('@') === 0) {
|
||||||
|
item['file'] = value.slice(1);
|
||||||
|
} else {
|
||||||
|
item['value'] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return item;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Body
|
||||||
|
let body = {};
|
||||||
|
let bodyType: string | null = null;
|
||||||
|
const bodyAsGET = getPairValue(pairsByName, false, ['G', 'get']);
|
||||||
|
|
||||||
|
if (dataParameters.length > 0 && bodyAsGET) {
|
||||||
|
urlParameters.push(...dataParameters);
|
||||||
|
} else if (
|
||||||
|
dataParameters.length > 0 &&
|
||||||
|
(mimeType == null || mimeType === 'application/x-www-form-urlencoded')
|
||||||
|
) {
|
||||||
|
bodyType = mimeType ?? 'application/x-www-form-urlencoded';
|
||||||
|
body = {
|
||||||
|
form: dataParameters.map((parameter) => ({
|
||||||
|
...parameter,
|
||||||
|
name: decodeURIComponent(parameter.name || ''),
|
||||||
|
value: decodeURIComponent(parameter.value || ''),
|
||||||
|
})),
|
||||||
|
};
|
||||||
|
headers.push({
|
||||||
|
name: 'Content-Type',
|
||||||
|
value: 'application/x-www-form-urlencoded',
|
||||||
|
enabled: true,
|
||||||
|
});
|
||||||
|
} else if (dataParameters.length > 0) {
|
||||||
|
bodyType =
|
||||||
|
mimeType === 'application/json' || mimeType === 'text/xml' || mimeType === 'text/plain'
|
||||||
|
? mimeType
|
||||||
|
: 'other';
|
||||||
|
body = {
|
||||||
|
text: dataParameters
|
||||||
|
.map(({ name, value }) => (name && value ? `${name}=${value}` : name || value))
|
||||||
|
.join('&'),
|
||||||
|
};
|
||||||
|
} else if (formDataParams.length) {
|
||||||
|
bodyType = mimeType ?? 'multipart/form-data';
|
||||||
|
body = {
|
||||||
|
form: formDataParams,
|
||||||
|
};
|
||||||
|
if (mimeType == null) {
|
||||||
|
headers.push({
|
||||||
|
name: 'Content-Type',
|
||||||
|
value: 'multipart/form-data',
|
||||||
|
enabled: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Method
|
||||||
|
let method = getPairValue(pairsByName, '', ['X', 'request']).toUpperCase();
|
||||||
|
|
||||||
|
if (method === '' && body) {
|
||||||
|
method = 'text' in body || 'form' in body ? 'POST' : 'GET';
|
||||||
|
}
|
||||||
|
|
||||||
|
const request: ExportResources['httpRequests'][0] = {
|
||||||
|
id: generateId('http_request'),
|
||||||
|
model: 'http_request',
|
||||||
|
workspaceId,
|
||||||
|
name: '',
|
||||||
|
urlParameters,
|
||||||
|
url,
|
||||||
|
method,
|
||||||
|
headers,
|
||||||
|
authentication,
|
||||||
|
authenticationType,
|
||||||
|
body,
|
||||||
|
bodyType,
|
||||||
|
folderId: null,
|
||||||
|
sortPriority: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
return request;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DataParameter {
|
||||||
|
name: string;
|
||||||
|
value: string;
|
||||||
|
contentType?: string;
|
||||||
|
filePath?: string;
|
||||||
|
enabled?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
function pairsToDataParameters(keyedPairs: PairsByName): DataParameter[] {
|
||||||
|
let dataParameters: DataParameter[] = [];
|
||||||
|
|
||||||
|
for (const flagName of DATA_FLAGS) {
|
||||||
|
const pairs = keyedPairs[flagName];
|
||||||
|
|
||||||
|
if (!pairs || pairs.length === 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const p of pairs) {
|
||||||
|
if (typeof p !== 'string') continue;
|
||||||
|
|
||||||
|
const [name, value] = p.split('=');
|
||||||
|
if (p.startsWith('@')) {
|
||||||
|
// Yaak doesn't support files in url-encoded data, so
|
||||||
|
dataParameters.push({
|
||||||
|
name: name ?? '',
|
||||||
|
value: '',
|
||||||
|
filePath: p.slice(1),
|
||||||
|
enabled: true,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
dataParameters.push({
|
||||||
|
name: name ?? '',
|
||||||
|
value: flagName === 'data-urlencode' ? encodeURIComponent(value ?? '') : value ?? '',
|
||||||
|
enabled: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return dataParameters;
|
||||||
|
}
|
||||||
|
|
||||||
|
const getPairValue = <T extends string | boolean>(
|
||||||
|
pairsByName: PairsByName,
|
||||||
|
defaultValue: T,
|
||||||
|
names: string[],
|
||||||
|
) => {
|
||||||
|
for (const name of names) {
|
||||||
|
if (pairsByName[name] && pairsByName[name]!.length) {
|
||||||
|
return pairsByName[name]![0] as T;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return defaultValue;
|
||||||
|
};
|
||||||
|
|
||||||
|
function splitOnce(str: string, sep: string): string[] {
|
||||||
|
const index = str.indexOf(sep);
|
||||||
|
if (index > -1) {
|
||||||
|
return [str.slice(0, index), str.slice(index + 1)];
|
||||||
|
}
|
||||||
|
return [str];
|
||||||
|
}
|
||||||
|
|
||||||
|
const idCount: Partial<Record<Model['model'], number>> = {};
|
||||||
|
function generateId(model: Model['model']): string {
|
||||||
|
idCount[model] = (idCount[model] ?? -1) + 1;
|
||||||
|
return `GENERATE_ID::${model.toUpperCase()}_${idCount[model]}`;
|
||||||
|
}
|
||||||
341
plugins/importer-curl/tests/index.test.ts
Normal file
341
plugins/importer-curl/tests/index.test.ts
Normal file
@@ -0,0 +1,341 @@
|
|||||||
|
import { describe, expect, test } from 'vitest';
|
||||||
|
import { HttpRequest, Model, Workspace } from '../../../src-web/lib/models';
|
||||||
|
import { pluginHookImport } from '../src';
|
||||||
|
|
||||||
|
const ctx = {};
|
||||||
|
|
||||||
|
describe('importer-curl', () => {
|
||||||
|
test('Imports basic GET', () => {
|
||||||
|
expect(pluginHookImport(ctx, 'curl https://yaak.app')).toEqual({
|
||||||
|
resources: {
|
||||||
|
workspaces: [baseWorkspace()],
|
||||||
|
httpRequests: [
|
||||||
|
baseRequest({
|
||||||
|
url: 'https://yaak.app',
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Explicit URL', () => {
|
||||||
|
expect(pluginHookImport(ctx, 'curl --url https://yaak.app')).toEqual({
|
||||||
|
resources: {
|
||||||
|
workspaces: [baseWorkspace()],
|
||||||
|
httpRequests: [
|
||||||
|
baseRequest({
|
||||||
|
url: 'https://yaak.app',
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Missing URL', () => {
|
||||||
|
expect(pluginHookImport(ctx, 'curl -X POST')).toEqual({
|
||||||
|
resources: {
|
||||||
|
workspaces: [baseWorkspace()],
|
||||||
|
httpRequests: [
|
||||||
|
baseRequest({
|
||||||
|
method: 'POST',
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('URL between', () => {
|
||||||
|
expect(pluginHookImport(ctx, 'curl -v https://yaak.app -X POST')).toEqual({
|
||||||
|
resources: {
|
||||||
|
workspaces: [baseWorkspace()],
|
||||||
|
httpRequests: [
|
||||||
|
baseRequest({
|
||||||
|
url: 'https://yaak.app',
|
||||||
|
method: 'POST',
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Random flags', () => {
|
||||||
|
expect(pluginHookImport(ctx, 'curl --random -Z -Y -S --foo https://yaak.app')).toEqual({
|
||||||
|
resources: {
|
||||||
|
workspaces: [baseWorkspace()],
|
||||||
|
httpRequests: [
|
||||||
|
baseRequest({
|
||||||
|
url: 'https://yaak.app',
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Imports --request method', () => {
|
||||||
|
expect(pluginHookImport(ctx, 'curl --request POST https://yaak.app')).toEqual({
|
||||||
|
resources: {
|
||||||
|
workspaces: [baseWorkspace()],
|
||||||
|
httpRequests: [
|
||||||
|
baseRequest({
|
||||||
|
url: 'https://yaak.app',
|
||||||
|
method: 'POST',
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Imports -XPOST method', () => {
|
||||||
|
expect(pluginHookImport(ctx, 'curl -XPOST --request POST https://yaak.app')).toEqual({
|
||||||
|
resources: {
|
||||||
|
workspaces: [baseWorkspace()],
|
||||||
|
httpRequests: [
|
||||||
|
baseRequest({
|
||||||
|
url: 'https://yaak.app',
|
||||||
|
method: 'POST',
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Imports multiple requests', () => {
|
||||||
|
expect(
|
||||||
|
pluginHookImport(
|
||||||
|
ctx,
|
||||||
|
'curl \\\n https://yaak.app\necho "foo"\ncurl example.com;curl foo.com',
|
||||||
|
),
|
||||||
|
).toEqual({
|
||||||
|
resources: {
|
||||||
|
workspaces: [baseWorkspace()],
|
||||||
|
httpRequests: [
|
||||||
|
baseRequest({ url: 'https://yaak.app' }),
|
||||||
|
baseRequest({ url: 'example.com' }),
|
||||||
|
baseRequest({ url: 'foo.com' }),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Imports form data', () => {
|
||||||
|
expect(
|
||||||
|
pluginHookImport(ctx, 'curl -X POST -F "a=aaa" -F b=bbb" -F f=@filepath https://yaak.app'),
|
||||||
|
).toEqual({
|
||||||
|
resources: {
|
||||||
|
workspaces: [baseWorkspace()],
|
||||||
|
httpRequests: [
|
||||||
|
baseRequest({
|
||||||
|
method: 'POST',
|
||||||
|
url: 'https://yaak.app',
|
||||||
|
headers: [
|
||||||
|
{
|
||||||
|
name: 'Content-Type',
|
||||||
|
value: 'multipart/form-data',
|
||||||
|
enabled: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
bodyType: 'multipart/form-data',
|
||||||
|
body: {
|
||||||
|
form: [
|
||||||
|
{ enabled: true, name: 'a', value: 'aaa' },
|
||||||
|
{ enabled: true, name: 'b', value: 'bbb' },
|
||||||
|
{ enabled: true, name: 'f', file: 'filepath' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Imports data params as form url-encoded', () => {
|
||||||
|
expect(pluginHookImport(ctx, 'curl -d a -d b -d c=ccc https://yaak.app')).toEqual({
|
||||||
|
resources: {
|
||||||
|
workspaces: [baseWorkspace()],
|
||||||
|
httpRequests: [
|
||||||
|
baseRequest({
|
||||||
|
method: 'POST',
|
||||||
|
url: 'https://yaak.app',
|
||||||
|
bodyType: 'application/x-www-form-urlencoded',
|
||||||
|
headers: [
|
||||||
|
{
|
||||||
|
name: 'Content-Type',
|
||||||
|
value: 'application/x-www-form-urlencoded',
|
||||||
|
enabled: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
body: {
|
||||||
|
form: [
|
||||||
|
{ name: 'a', value: '', enabled: true },
|
||||||
|
{ name: 'b', value: '', enabled: true },
|
||||||
|
{ name: 'c', value: 'ccc', enabled: true },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Imports data params as text', () => {
|
||||||
|
expect(
|
||||||
|
pluginHookImport(ctx, 'curl -H Content-Type:text/plain -d a -d b -d c=ccc https://yaak.app'),
|
||||||
|
).toEqual({
|
||||||
|
resources: {
|
||||||
|
workspaces: [baseWorkspace()],
|
||||||
|
httpRequests: [
|
||||||
|
baseRequest({
|
||||||
|
method: 'POST',
|
||||||
|
url: 'https://yaak.app',
|
||||||
|
headers: [{ name: 'Content-Type', value: 'text/plain', enabled: true }],
|
||||||
|
bodyType: 'text/plain',
|
||||||
|
body: { text: 'a&b&c=ccc' },
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Imports multi-line JSON', () => {
|
||||||
|
expect(
|
||||||
|
pluginHookImport(
|
||||||
|
ctx,
|
||||||
|
`curl -H Content-Type:application/json -d $'{\n "foo":"bar"\n}' https://yaak.app`,
|
||||||
|
),
|
||||||
|
).toEqual({
|
||||||
|
resources: {
|
||||||
|
workspaces: [baseWorkspace()],
|
||||||
|
httpRequests: [
|
||||||
|
baseRequest({
|
||||||
|
method: 'POST',
|
||||||
|
url: 'https://yaak.app',
|
||||||
|
headers: [{ name: 'Content-Type', value: 'application/json', enabled: true }],
|
||||||
|
bodyType: 'application/json',
|
||||||
|
body: { text: '{\n "foo":"bar"\n}' },
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Imports multiple headers', () => {
|
||||||
|
expect(
|
||||||
|
pluginHookImport(ctx, 'curl -H Foo:bar --header Name -H AAA:bbb -H :ccc https://yaak.app'),
|
||||||
|
).toEqual({
|
||||||
|
resources: {
|
||||||
|
workspaces: [baseWorkspace()],
|
||||||
|
httpRequests: [
|
||||||
|
baseRequest({
|
||||||
|
url: 'https://yaak.app',
|
||||||
|
headers: [
|
||||||
|
{ name: 'Name', value: '', enabled: true },
|
||||||
|
{ name: 'Foo', value: 'bar', enabled: true },
|
||||||
|
{ name: 'AAA', value: 'bbb', enabled: true },
|
||||||
|
{ name: '', value: 'ccc', enabled: true },
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Imports basic auth', () => {
|
||||||
|
expect(pluginHookImport(ctx, 'curl --user user:pass https://yaak.app')).toEqual({
|
||||||
|
resources: {
|
||||||
|
workspaces: [baseWorkspace()],
|
||||||
|
httpRequests: [
|
||||||
|
baseRequest({
|
||||||
|
url: 'https://yaak.app',
|
||||||
|
authenticationType: 'basic',
|
||||||
|
authentication: {
|
||||||
|
username: 'user',
|
||||||
|
password: 'pass',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Imports digest auth', () => {
|
||||||
|
expect(pluginHookImport(ctx, 'curl --digest --user user:pass https://yaak.app')).toEqual({
|
||||||
|
resources: {
|
||||||
|
workspaces: [baseWorkspace()],
|
||||||
|
httpRequests: [
|
||||||
|
baseRequest({
|
||||||
|
url: 'https://yaak.app',
|
||||||
|
authenticationType: 'digest',
|
||||||
|
authentication: {
|
||||||
|
username: 'user',
|
||||||
|
password: 'pass',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Imports cookie as header', () => {
|
||||||
|
expect(pluginHookImport(ctx, 'curl --cookie "foo=bar" https://yaak.app')).toEqual({
|
||||||
|
resources: {
|
||||||
|
workspaces: [baseWorkspace()],
|
||||||
|
httpRequests: [
|
||||||
|
baseRequest({
|
||||||
|
url: 'https://yaak.app',
|
||||||
|
headers: [{ name: 'Cookie', value: 'foo=bar', enabled: true }],
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Imports query params from the URL', () => {
|
||||||
|
expect(pluginHookImport(ctx, 'curl "https://yaak.app?foo=bar&baz=a%20a"')).toEqual({
|
||||||
|
resources: {
|
||||||
|
workspaces: [baseWorkspace()],
|
||||||
|
httpRequests: [
|
||||||
|
baseRequest({
|
||||||
|
url: 'https://yaak.app',
|
||||||
|
urlParameters: [
|
||||||
|
{ name: 'foo', value: 'bar', enabled: true },
|
||||||
|
{ name: 'baz', value: 'a%20a', enabled: true },
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const idCount: Partial<Record<Model['model'], number>> = {};
|
||||||
|
|
||||||
|
function baseRequest(mergeWith: Partial<HttpRequest>) {
|
||||||
|
idCount.http_request = (idCount.http_request ?? -1) + 1;
|
||||||
|
return {
|
||||||
|
id: `GENERATE_ID::HTTP_REQUEST_${idCount.http_request}`,
|
||||||
|
model: 'http_request',
|
||||||
|
authentication: {},
|
||||||
|
authenticationType: null,
|
||||||
|
body: {},
|
||||||
|
bodyType: null,
|
||||||
|
folderId: null,
|
||||||
|
headers: [],
|
||||||
|
method: 'GET',
|
||||||
|
name: '',
|
||||||
|
sortPriority: 0,
|
||||||
|
url: '',
|
||||||
|
urlParameters: [],
|
||||||
|
workspaceId: `GENERATE_ID::WORKSPACE_${idCount.workspace}`,
|
||||||
|
...mergeWith,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function baseWorkspace(mergeWith: Partial<Workspace> = {}) {
|
||||||
|
idCount.workspace = (idCount.workspace ?? -1) + 1;
|
||||||
|
return {
|
||||||
|
id: `GENERATE_ID::WORKSPACE_${idCount.workspace}`,
|
||||||
|
model: 'workspace',
|
||||||
|
name: 'Curl Import',
|
||||||
|
...mergeWith,
|
||||||
|
};
|
||||||
|
}
|
||||||
15
plugins/importer-curl/vite.config.js
Normal file
15
plugins/importer-curl/vite.config.js
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import { resolve } from 'path';
|
||||||
|
import { defineConfig } from 'vite';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
build: {
|
||||||
|
lib: {
|
||||||
|
entry: resolve(__dirname, 'src/index.ts'),
|
||||||
|
fileName: 'index',
|
||||||
|
formats: ['es'],
|
||||||
|
},
|
||||||
|
emptyOutDir: true,
|
||||||
|
sourcemap: true,
|
||||||
|
outDir: resolve(__dirname, 'build'),
|
||||||
|
},
|
||||||
|
});
|
||||||
26
plugins/importer-insomnia/package-lock.json
generated
Normal file
26
plugins/importer-insomnia/package-lock.json
generated
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"name": "importer-insomnia",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"lockfileVersion": 3,
|
||||||
|
"requires": true,
|
||||||
|
"packages": {
|
||||||
|
"": {
|
||||||
|
"name": "importer-insomnia",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"dependencies": {
|
||||||
|
"yaml": "^2.4.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/yaml": {
|
||||||
|
"version": "2.4.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.4.2.tgz",
|
||||||
|
"integrity": "sha512-B3VqDZ+JAg1nZpaEmWtTXUlBneoGx6CPM9b0TENK6aoSu5t73dItudwdgmi6tHlIZZId4dZ9skcAQ2UbcyAeVA==",
|
||||||
|
"bin": {
|
||||||
|
"yaml": "bin.mjs"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 14"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
7
plugins/importer-insomnia/package.json
Normal file
7
plugins/importer-insomnia/package.json
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"name": "importer-insomnia",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"dependencies": {
|
||||||
|
"yaml": "^2.4.2"
|
||||||
|
}
|
||||||
|
}
|
||||||
280
plugins/importer-insomnia/src/index.ts
Normal file
280
plugins/importer-insomnia/src/index.ts
Normal file
@@ -0,0 +1,280 @@
|
|||||||
|
import {
|
||||||
|
Environment,
|
||||||
|
Folder,
|
||||||
|
GrpcRequest,
|
||||||
|
HttpRequest,
|
||||||
|
Workspace,
|
||||||
|
} from '../../../src-web/lib/models';
|
||||||
|
import YAML from 'yaml';
|
||||||
|
|
||||||
|
type AtLeast<T, K extends keyof T> = Partial<T> & Pick<T, K>;
|
||||||
|
|
||||||
|
export interface ExportResources {
|
||||||
|
workspaces: AtLeast<Workspace, 'name' | 'id' | 'model'>[];
|
||||||
|
environments: AtLeast<Environment, 'name' | 'id' | 'model' | 'workspaceId'>[];
|
||||||
|
httpRequests: AtLeast<HttpRequest, 'name' | 'id' | 'model' | 'workspaceId'>[];
|
||||||
|
grpcRequests: AtLeast<GrpcRequest, 'name' | 'id' | 'model' | 'workspaceId'>[];
|
||||||
|
folders: AtLeast<Folder, 'name' | 'id' | 'model' | 'workspaceId'>[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function pluginHookImport(ctx: any, contents: string) {
|
||||||
|
let parsed: any;
|
||||||
|
|
||||||
|
try {
|
||||||
|
parsed = JSON.parse(contents);
|
||||||
|
} catch (e) {}
|
||||||
|
|
||||||
|
try {
|
||||||
|
parsed = parsed ?? YAML.parse(contents);
|
||||||
|
} catch (e) {
|
||||||
|
console.log('FAILED', e);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isJSObject(parsed)) return;
|
||||||
|
if (!Array.isArray(parsed.resources)) return;
|
||||||
|
|
||||||
|
const resources: ExportResources = {
|
||||||
|
workspaces: [],
|
||||||
|
httpRequests: [],
|
||||||
|
grpcRequests: [],
|
||||||
|
environments: [],
|
||||||
|
folders: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
// Import workspaces
|
||||||
|
const workspacesToImport = parsed.resources.filter(isWorkspace);
|
||||||
|
for (const workspaceToImport of workspacesToImport) {
|
||||||
|
const baseEnvironment = parsed.resources.find(
|
||||||
|
(r: any) => isEnvironment(r) && r.parentId === workspaceToImport._id,
|
||||||
|
);
|
||||||
|
resources.workspaces.push({
|
||||||
|
id: convertId(workspaceToImport._id),
|
||||||
|
createdAt: new Date(workspacesToImport.created ?? Date.now()).toISOString().replace('Z', ''),
|
||||||
|
updatedAt: new Date(workspacesToImport.updated ?? Date.now()).toISOString().replace('Z', ''),
|
||||||
|
model: 'workspace',
|
||||||
|
name: workspaceToImport.name,
|
||||||
|
variables: baseEnvironment ? parseVariables(baseEnvironment.data) : [],
|
||||||
|
});
|
||||||
|
const environmentsToImport = parsed.resources.filter(
|
||||||
|
(r: any) => isEnvironment(r) && r.parentId === baseEnvironment?._id,
|
||||||
|
);
|
||||||
|
resources.environments.push(
|
||||||
|
...environmentsToImport.map((r: any) => importEnvironment(r, workspaceToImport._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, workspaceToImport._id));
|
||||||
|
nextFolder(child._id);
|
||||||
|
} else if (isHttpRequest(child)) {
|
||||||
|
resources.httpRequests.push(
|
||||||
|
importHttpRequest(child, workspaceToImport._id, sortPriority++),
|
||||||
|
);
|
||||||
|
} else if (isGrpcRequest(child)) {
|
||||||
|
resources.grpcRequests.push(
|
||||||
|
importGrpcRequest(child, workspaceToImport._id, sortPriority++),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Import folders
|
||||||
|
nextFolder(workspaceToImport._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 importEnvironment(e: any, workspaceId: string): ExportResources['environments'][0] {
|
||||||
|
return {
|
||||||
|
id: convertId(e._id),
|
||||||
|
createdAt: new Date(e.created ?? Date.now()).toISOString().replace('Z', ''),
|
||||||
|
updatedAt: new Date(e.updated ?? Date.now()).toISOString().replace('Z', ''),
|
||||||
|
workspaceId: convertId(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: new Date(f.created ?? Date.now()).toISOString().replace('Z', ''),
|
||||||
|
updatedAt: new Date(f.updated ?? Date.now()).toISOString().replace('Z', ''),
|
||||||
|
folderId: f.parentId === workspaceId ? null : convertId(f.parentId),
|
||||||
|
workspaceId: convertId(workspaceId),
|
||||||
|
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: new Date(r.created ?? Date.now()).toISOString().replace('Z', ''),
|
||||||
|
updatedAt: new Date(r.updated ?? Date.now()).toISOString().replace('Z', ''),
|
||||||
|
workspaceId: convertId(workspaceId),
|
||||||
|
folderId: r.parentId === workspaceId ? null : convertId(r.parentId),
|
||||||
|
model: 'grpc_request',
|
||||||
|
sortPriority,
|
||||||
|
name: r.name,
|
||||||
|
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: new Date(r.created ?? Date.now()).toISOString().replace('Z', ''),
|
||||||
|
updatedAt: new Date(r.updated ?? Date.now()).toISOString().replace('Z', ''),
|
||||||
|
workspaceId: convertId(workspaceId),
|
||||||
|
folderId: r.parentId === workspaceId ? null : convertId(r.parentId),
|
||||||
|
model: 'http_request',
|
||||||
|
sortPriority,
|
||||||
|
name: r.name,
|
||||||
|
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 parseVariables(data: Record<string, string>) {
|
||||||
|
return Object.entries(data).map(([name, value]) => ({
|
||||||
|
enabled: true,
|
||||||
|
name,
|
||||||
|
value: `${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}`;
|
||||||
|
}
|
||||||
15
plugins/importer-insomnia/vite.config.js
Normal file
15
plugins/importer-insomnia/vite.config.js
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import { resolve } from 'path';
|
||||||
|
import { defineConfig } from 'vite';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
build: {
|
||||||
|
lib: {
|
||||||
|
entry: resolve(__dirname, 'src/index.ts'),
|
||||||
|
fileName: 'index',
|
||||||
|
formats: ['es'],
|
||||||
|
},
|
||||||
|
emptyOutDir: true,
|
||||||
|
sourcemap: true,
|
||||||
|
outDir: resolve(__dirname, 'build'),
|
||||||
|
},
|
||||||
|
});
|
||||||
1505
plugins/importer-postman/package-lock.json
generated
Normal file
1505
plugins/importer-postman/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
7
plugins/importer-postman/package.json
Normal file
7
plugins/importer-postman/package.json
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"name": "importer-postman",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"devDependencies": {
|
||||||
|
"vitest": "^1.4.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
253
plugins/importer-postman/src/index.ts
Normal file
253
plugins/importer-postman/src/index.ts
Normal file
@@ -0,0 +1,253 @@
|
|||||||
|
import { Environment, Folder, HttpRequest, Model, Workspace } from '../../../src-web/lib/models';
|
||||||
|
|
||||||
|
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];
|
||||||
|
|
||||||
|
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 function pluginHookImport(
|
||||||
|
_ctx: any,
|
||||||
|
contents: string,
|
||||||
|
): { resources: ExportResources } | undefined {
|
||||||
|
const root = parseJSONToRecord(contents);
|
||||||
|
if (root == null) return;
|
||||||
|
|
||||||
|
const info = toRecord(root.info);
|
||||||
|
const isValidSchema = VALID_SCHEMAS.includes(info.schema);
|
||||||
|
if (!isValidSchema || !Array.isArray(root.item)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const globalAuth = importAuth(root.auth);
|
||||||
|
|
||||||
|
const exportResources: ExportResources = {
|
||||||
|
workspaces: [],
|
||||||
|
environments: [],
|
||||||
|
httpRequests: [],
|
||||||
|
folders: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
const workspace: ExportResources['workspaces'][0] = {
|
||||||
|
model: 'workspace',
|
||||||
|
id: generateId('workspace'),
|
||||||
|
name: info.name || 'Postman Import',
|
||||||
|
description: info.description || '',
|
||||||
|
variables:
|
||||||
|
root.variable?.map((v: any) => ({
|
||||||
|
name: v.key,
|
||||||
|
value: v.value,
|
||||||
|
})) ?? [],
|
||||||
|
};
|
||||||
|
exportResources.workspaces.push(workspace);
|
||||||
|
|
||||||
|
const importItem = (v: Record<string, any>, folderId: string | null = null) => {
|
||||||
|
if (typeof v.name === 'string' && Array.isArray(v.item)) {
|
||||||
|
const folder: ExportResources['folders'][0] = {
|
||||||
|
model: 'folder',
|
||||||
|
workspaceId: workspace.id,
|
||||||
|
id: generateId('folder'),
|
||||||
|
name: v.name,
|
||||||
|
folderId,
|
||||||
|
};
|
||||||
|
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 requestAuthPath = importAuth(r.auth);
|
||||||
|
const authPatch = requestAuthPath.authenticationType == null ? globalAuth : requestAuthPath;
|
||||||
|
const request: ExportResources['httpRequests'][0] = {
|
||||||
|
model: 'http_request',
|
||||||
|
id: generateId('http_request'),
|
||||||
|
workspaceId: workspace.id,
|
||||||
|
folderId,
|
||||||
|
name: v.name,
|
||||||
|
method: r.method || 'GET',
|
||||||
|
url: typeof r.url === 'string' ? r.url : toRecord(r.url).raw,
|
||||||
|
body: bodyPatch.body,
|
||||||
|
bodyType: bodyPatch.bodyType,
|
||||||
|
authentication: authPatch.authentication,
|
||||||
|
authenticationType: authPatch.authenticationType,
|
||||||
|
headers: [
|
||||||
|
...bodyPatch.headers,
|
||||||
|
...authPatch.headers,
|
||||||
|
...toArray(r.header).map((h) => {
|
||||||
|
return {
|
||||||
|
name: h.key,
|
||||||
|
value: h.value,
|
||||||
|
enabled: !h.disabled,
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
};
|
||||||
|
exportResources.httpRequests.push(request);
|
||||||
|
} else {
|
||||||
|
console.log('Unknown item', v, folderId);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const item of root.item) {
|
||||||
|
importItem(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
return { resources: convertTemplateSyntax(exportResources) };
|
||||||
|
}
|
||||||
|
|
||||||
|
function importAuth(
|
||||||
|
rawAuth: any,
|
||||||
|
): Pick<HttpRequest, 'authentication' | 'authenticationType' | 'headers'> {
|
||||||
|
const auth = toRecord(rawAuth);
|
||||||
|
if ('basic' in auth) {
|
||||||
|
return {
|
||||||
|
headers: [],
|
||||||
|
authenticationType: 'basic',
|
||||||
|
authentication: {
|
||||||
|
username: auth.basic.username || '',
|
||||||
|
password: auth.basic.password || '',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
} else if ('bearer' in auth) {
|
||||||
|
return {
|
||||||
|
headers: [],
|
||||||
|
authenticationType: 'bearer',
|
||||||
|
authentication: {
|
||||||
|
token: auth.bearer.token || '',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return { headers: [], authenticationType: null, authentication: {} };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function importBody(rawBody: any): Pick<HttpRequest, 'body' | 'bodyType' | 'headers'> {
|
||||||
|
const body = toRecord(rawBody);
|
||||||
|
if ('graphql' in body) {
|
||||||
|
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,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
} else if ('urlencoded' in body) {
|
||||||
|
return {
|
||||||
|
headers: [
|
||||||
|
{
|
||||||
|
name: 'Content-Type',
|
||||||
|
value: 'application/x-www-form-urlencoded',
|
||||||
|
enabled: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
bodyType: 'application/x-www-form-urlencoded',
|
||||||
|
body: {
|
||||||
|
form: toArray(body.urlencoded).map((f) => ({
|
||||||
|
enabled: !f.disabled,
|
||||||
|
name: f.key ?? '',
|
||||||
|
value: f.value ?? '',
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
} else if ('formdata' in body) {
|
||||||
|
return {
|
||||||
|
headers: [
|
||||||
|
{
|
||||||
|
name: 'Content-Type',
|
||||||
|
value: 'multipart/form-data',
|
||||||
|
enabled: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
bodyType: 'multipart/form-data',
|
||||||
|
body: {
|
||||||
|
form: toArray(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 ?? '',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
} else if ('raw' in body) {
|
||||||
|
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 ?? '',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return { headers: [], bodyType: null, body: {} };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseJSONToRecord(jsonStr: string): Record<string, any> | null {
|
||||||
|
try {
|
||||||
|
return toRecord(JSON.parse(jsonStr));
|
||||||
|
} catch (err) {}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function toRecord(value: any): Record<string, any> {
|
||||||
|
if (Object.prototype.toString.call(value) === '[object Object]') return value;
|
||||||
|
else return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
function toArray(value: any): any[] {
|
||||||
|
if (Object.prototype.toString.call(value) === '[object Array]') return value;
|
||||||
|
else return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Recursively render all nested object properties */
|
||||||
|
function convertTemplateSyntax<T>(obj: T): T {
|
||||||
|
if (typeof obj === 'string') {
|
||||||
|
return obj.replace(/{{\s*(_\.)?([^}]+)\s*}}/g, '${[$2]}') as T;
|
||||||
|
} else if (Array.isArray(obj) && obj != null) {
|
||||||
|
return obj.map(convertTemplateSyntax) as T;
|
||||||
|
} else if (typeof obj === 'object' && obj != null) {
|
||||||
|
return Object.fromEntries(
|
||||||
|
Object.entries(obj).map(([k, v]) => [k, convertTemplateSyntax(v)]),
|
||||||
|
) as T;
|
||||||
|
} else {
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const idCount: Partial<Record<Model['model'], number>> = {};
|
||||||
|
|
||||||
|
function generateId(model: Model['model']): string {
|
||||||
|
idCount[model] = (idCount[model] ?? -1) + 1;
|
||||||
|
return `GENERATE_ID::${model.toUpperCase()}_${idCount[model]}`;
|
||||||
|
}
|
||||||
38
plugins/importer-postman/tests/fixtures/nested.json
vendored
Normal file
38
plugins/importer-postman/tests/fixtures/nested.json
vendored
Normal 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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
91
plugins/importer-postman/tests/index.test.ts
Normal file
91
plugins/importer-postman/tests/index.test.ts
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
import * as fs from 'node:fs';
|
||||||
|
import * as path from 'node:path';
|
||||||
|
import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest';
|
||||||
|
import { Model } from '../../../src-web/lib/models';
|
||||||
|
import { pluginHookImport } from '../src';
|
||||||
|
|
||||||
|
let originalRandom = Math.random;
|
||||||
|
|
||||||
|
describe('importer-postman', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
let i = 0;
|
||||||
|
// Psuedo-random number generator to ensure consistent ID generation
|
||||||
|
Math.random = vi.fn(() => ((i++ * 1000) % 133) / 100);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
Math.random = originalRandom;
|
||||||
|
});
|
||||||
|
|
||||||
|
const p = path.join(__dirname, 'fixtures');
|
||||||
|
const fixtures = fs.readdirSync(p);
|
||||||
|
|
||||||
|
for (const fixture of fixtures) {
|
||||||
|
test('Imports ' + fixture, () => {
|
||||||
|
const contents = fs.readFileSync(path.join(p, fixture), 'utf-8');
|
||||||
|
const imported = pluginHookImport({}, contents);
|
||||||
|
const folder0 = newId('folder');
|
||||||
|
const folder1 = newId('folder');
|
||||||
|
expect(imported).toEqual({
|
||||||
|
resources: expect.objectContaining({
|
||||||
|
workspaces: [
|
||||||
|
expect.objectContaining({
|
||||||
|
id: newId('workspace'),
|
||||||
|
model: 'workspace',
|
||||||
|
name: 'New Collection',
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
folders: expect.arrayContaining([
|
||||||
|
expect.objectContaining({
|
||||||
|
id: folder0,
|
||||||
|
model: 'folder',
|
||||||
|
workspaceId: existingId('workspace'),
|
||||||
|
name: 'Top Folder',
|
||||||
|
}),
|
||||||
|
expect.objectContaining({
|
||||||
|
folderId: folder0,
|
||||||
|
id: folder1,
|
||||||
|
model: 'folder',
|
||||||
|
workspaceId: existingId('workspace'),
|
||||||
|
name: 'Nested Folder',
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
httpRequests: expect.arrayContaining([
|
||||||
|
expect.objectContaining({
|
||||||
|
id: newId('http_request'),
|
||||||
|
model: 'http_request',
|
||||||
|
name: 'Request 1',
|
||||||
|
workspaceId: existingId('workspace'),
|
||||||
|
folderId: folder1,
|
||||||
|
}),
|
||||||
|
expect.objectContaining({
|
||||||
|
id: newId('http_request'),
|
||||||
|
model: 'http_request',
|
||||||
|
name: 'Request 2',
|
||||||
|
workspaceId: existingId('workspace'),
|
||||||
|
folderId: folder0,
|
||||||
|
}),
|
||||||
|
expect.objectContaining({
|
||||||
|
id: newId('http_request'),
|
||||||
|
model: 'http_request',
|
||||||
|
name: 'Request 3',
|
||||||
|
workspaceId: existingId('workspace'),
|
||||||
|
folderId: null,
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const idCount: Partial<Record<Model['model'], number>> = {};
|
||||||
|
|
||||||
|
function newId(model: Model['model']): string {
|
||||||
|
idCount[model] = (idCount[model] ?? -1) + 1;
|
||||||
|
return `GENERATE_ID::${model.toUpperCase()}_${idCount[model]}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function existingId(model: Model['model']): string {
|
||||||
|
return `GENERATE_ID::${model.toUpperCase()}_${idCount[model] ?? 0}`;
|
||||||
|
}
|
||||||
23
plugins/importer-postman/tsconfig.json
Normal file
23
plugins/importer-postman/tsconfig.json
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ESNext",
|
||||||
|
"useDefineForClassFields": true,
|
||||||
|
"module": "ESNext",
|
||||||
|
"lib": [
|
||||||
|
"ESNext",
|
||||||
|
],
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"allowImportingTsExtensions": true,
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"noEmit": true,
|
||||||
|
"strict": true,
|
||||||
|
"noUnusedLocals": true,
|
||||||
|
"noUnusedParameters": true,
|
||||||
|
"noFallthroughCasesInSwitch": true
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"./src"
|
||||||
|
]
|
||||||
|
}
|
||||||
15
plugins/importer-postman/vite.config.js
Normal file
15
plugins/importer-postman/vite.config.js
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import { resolve } from 'path';
|
||||||
|
import { defineConfig } from 'vite';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
build: {
|
||||||
|
lib: {
|
||||||
|
entry: resolve(__dirname, 'src/index.ts'),
|
||||||
|
fileName: 'index',
|
||||||
|
formats: ['es'],
|
||||||
|
},
|
||||||
|
emptyOutDir: true,
|
||||||
|
sourcemap: true,
|
||||||
|
outDir: resolve(__dirname, 'build'),
|
||||||
|
},
|
||||||
|
});
|
||||||
12
plugins/importer-yaak/package-lock.json
generated
Normal file
12
plugins/importer-yaak/package-lock.json
generated
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"name": "importer-yaak",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"lockfileVersion": 3,
|
||||||
|
"requires": true,
|
||||||
|
"packages": {
|
||||||
|
"": {
|
||||||
|
"name": "importer-yaak",
|
||||||
|
"version": "0.0.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
4
plugins/importer-yaak/package.json
Normal file
4
plugins/importer-yaak/package.json
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"name": "importer-yaak",
|
||||||
|
"version": "0.0.1"
|
||||||
|
}
|
||||||
29
plugins/importer-yaak/src/index.ts
Normal file
29
plugins/importer-yaak/src/index.ts
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
export function pluginHookImport(ctx: any, contents: string) {
|
||||||
|
let parsed;
|
||||||
|
try {
|
||||||
|
parsed = JSON.parse(contents);
|
||||||
|
} catch (err) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isJSObject(parsed)) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const isYaakExport = 'yaakSchema' in parsed;
|
||||||
|
if (!isYaakExport) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Migrate v1 to v2 -- changes requests to httpRequests
|
||||||
|
if ('requests' in parsed.resources) {
|
||||||
|
parsed.resources.httpRequests = parsed.resources.requests;
|
||||||
|
delete parsed.resources['requests'];
|
||||||
|
}
|
||||||
|
|
||||||
|
return { resources: parsed.resources }; // Should already be in the correct format
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isJSObject(obj: any) {
|
||||||
|
return Object.prototype.toString.call(obj) === '[object Object]';
|
||||||
|
}
|
||||||
32
plugins/importer-yaak/tests/index.test.ts
Normal file
32
plugins/importer-yaak/tests/index.test.ts
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
import { describe, expect, test } from 'vitest';
|
||||||
|
import { pluginHookImport } from '../src';
|
||||||
|
|
||||||
|
const ctx = {};
|
||||||
|
|
||||||
|
describe('importer-yaak', () => {
|
||||||
|
test('Skips invalid imports', () => {
|
||||||
|
expect(pluginHookImport(ctx, 'not JSON')).toBeUndefined();
|
||||||
|
expect(pluginHookImport(ctx, '[]')).toBeUndefined();
|
||||||
|
expect(pluginHookImport(ctx, JSON.stringify({ resources: {} }))).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('converts schema 1 to 2', () => {
|
||||||
|
const imported = pluginHookImport(
|
||||||
|
ctx,
|
||||||
|
JSON.stringify({
|
||||||
|
yaakSchema: 1,
|
||||||
|
resources: {
|
||||||
|
requests: [],
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(imported).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
resources: {
|
||||||
|
httpRequests: [],
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
15
plugins/importer-yaak/vite.config.js
Normal file
15
plugins/importer-yaak/vite.config.js
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import { resolve } from 'path';
|
||||||
|
import { defineConfig } from 'vite';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
build: {
|
||||||
|
lib: {
|
||||||
|
entry: resolve(__dirname, 'src/index.ts'),
|
||||||
|
fileName: 'index',
|
||||||
|
formats: ['es'],
|
||||||
|
},
|
||||||
|
emptyOutDir: true,
|
||||||
|
sourcemap: true,
|
||||||
|
outDir: resolve(__dirname, 'build'),
|
||||||
|
},
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user