Copy as curl

This commit is contained in:
Gregory Schier
2024-05-08 00:00:50 -07:00
parent bb0e58bf8b
commit f1e97eaea4
32 changed files with 4352 additions and 507 deletions

1562
plugins/exporter-curl/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,7 @@
{
"name": "exporter-curl",
"version": "0.0.1",
"devDependencies": {
"vitest": "^1.4.0"
}
}

View File

@@ -0,0 +1,76 @@
import { HttpRequest } from '../../../src-web/lib/models';
const NEWLINE = '\\\n ';
export function pluginHookExport(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;
}

View File

@@ -0,0 +1,155 @@
import { describe, expect, test } from 'vitest';
import { pluginHookImport } from '../src';
describe('exporter-curl', () => {
test('Exports GET with params', () => {
expect(
pluginHookImport({
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(
pluginHookImport({
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(
pluginHookImport({
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(
pluginHookImport({
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 headers', () => {
expect(
pluginHookImport({
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(
pluginHookImport({
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(
pluginHookImport({
url: 'https://yaak.app',
authenticationType: 'basic',
authentication: {},
}),
).toEqual([`curl 'https://yaak.app'`, `--user ':'`].join(` \\\n `));
});
test('Digest auth', () => {
expect(
pluginHookImport({
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(
pluginHookImport({
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(
pluginHookImport({
url: 'https://yaak.app',
authenticationType: 'bearer',
authentication: {
username: 'user',
password: 'pass',
},
}),
).toEqual([`curl 'https://yaak.app'`, `--header 'Authorization: Bearer '`].join(` \\\n `));
});
});

View File

@@ -0,0 +1,13 @@
import { resolve } from 'path';
import { defineConfig } from 'vite';
export default defineConfig({
build: {
lib: {
entry: resolve(__dirname, 'src/index.ts'),
fileName: 'index',
formats: ['es'],
},
outDir: resolve(__dirname, '../../src-tauri/plugins/exporter-curl'),
},
});

File diff suppressed because it is too large Load Diff

View File

@@ -2,11 +2,10 @@
"name": "importer-curl",
"version": "0.0.1",
"dependencies": {
"shell-quote": "^1.8.1",
"url": "^0.11.3"
"shell-quote": "^1.8.1"
},
"devDependencies": {
"@types/shell-quote": "^1.7.5",
"random-seedable": "^1.0.8"
"vitest": "^1.4.0"
}
}

View File

@@ -43,7 +43,7 @@ type Pair = string | boolean;
type PairsByName = Record<string, Pair[]>;
export const pluginHookImport = (rawData: string) => {
export function pluginHookImport(rawData: string) {
if (!rawData.match(/^\s*curl /)) {
return null;
}
@@ -125,7 +125,7 @@ export const pluginHookImport = (rawData: string) => {
workspaces: [workspace],
},
};
};
}
export function importCommand(parseEntries: ParseEntry[], workspaceId: string) {
// ~~~~~~~~~~~~~~~~~~~~~ //

View File

@@ -1,22 +1,8 @@
import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest';
import { describe, expect, test } from 'vitest';
import { HttpRequest, Model, Workspace } from '../../../src-web/lib/models';
import { pluginHookImport } from '../src';
import { XORShift } from 'random-seedable';
let originalRandom = Math.random;
describe('importer-curl', () => {
beforeEach(() => {
const rand = new XORShift(123456789);
Math.random = vi.fn(() => {
return rand.float();
});
});
afterEach(() => {
Math.random = originalRandom;
});
test('Imports basic GET', () => {
expect(pluginHookImport('curl https://yaak.app')).toEqual({
resources: {