mirror of
https://github.com/n8n-io/n8n.git
synced 2025-03-05 20:50:17 -08:00
refactor: Move cURL converter to frontend (#11432)
Co-authored-by: कारतोफ्फेलस्क्रिप्ट™ <aditya@netroy.in>
This commit is contained in:
parent
94789e0309
commit
8b28d6ce8e
|
@ -91,6 +91,7 @@
|
|||
},
|
||||
"patchedDependencies": {
|
||||
"bull@4.12.1": "patches/bull@4.12.1.patch",
|
||||
"curlconverter@4.11.0": "patches/curlconverter@4.11.0.patch",
|
||||
"pkce-challenge@3.0.0": "patches/pkce-challenge@3.0.0.patch",
|
||||
"pyodide@0.23.4": "patches/pyodide@0.23.4.patch",
|
||||
"@types/express-serve-static-core@4.17.43": "patches/@types__express-serve-static-core@4.17.43.patch",
|
||||
|
|
|
@ -112,7 +112,6 @@
|
|||
"convict": "6.2.4",
|
||||
"cookie-parser": "1.4.7",
|
||||
"csrf": "3.1.0",
|
||||
"curlconverter": "3.21.0",
|
||||
"dotenv": "8.6.0",
|
||||
"express": "4.21.1",
|
||||
"express-async-errors": "3.1.1",
|
||||
|
|
|
@ -1,52 +0,0 @@
|
|||
import type { Request } from 'express';
|
||||
import { mock } from 'jest-mock-extended';
|
||||
|
||||
import { CurlController } from '@/controllers/curl.controller';
|
||||
import { BadRequestError } from '@/errors/response-errors/bad-request.error';
|
||||
import type { CurlService } from '@/services/curl.service';
|
||||
|
||||
describe('CurlController', () => {
|
||||
const service = mock<CurlService>();
|
||||
const controller = new CurlController(service);
|
||||
|
||||
beforeEach(() => jest.clearAllMocks());
|
||||
|
||||
describe('toJson', () => {
|
||||
it('should throw BadRequestError when invalid cURL command is provided', () => {
|
||||
const req = mock<Request>();
|
||||
service.toHttpNodeParameters.mockImplementation(() => {
|
||||
throw new Error();
|
||||
});
|
||||
|
||||
expect(() => controller.toJson(req)).toThrow(BadRequestError);
|
||||
});
|
||||
|
||||
it('should return flattened parameters when valid cURL command is provided', () => {
|
||||
const curlCommand = 'curl -v -X GET https://test.n8n.berlin/users';
|
||||
const req = mock<Request>();
|
||||
req.body = { curlCommand };
|
||||
service.toHttpNodeParameters.mockReturnValue({
|
||||
url: 'https://test.n8n.berlin/users',
|
||||
authentication: 'none',
|
||||
method: 'GET',
|
||||
sendHeaders: false,
|
||||
sendQuery: false,
|
||||
options: {
|
||||
redirect: { redirect: {} },
|
||||
response: { response: {} },
|
||||
},
|
||||
sendBody: false,
|
||||
});
|
||||
|
||||
const result = controller.toJson(req);
|
||||
expect(result).toEqual({
|
||||
'parameters.method': 'GET',
|
||||
'parameters.url': 'https://test.n8n.berlin/users',
|
||||
'parameters.authentication': 'none',
|
||||
'parameters.sendBody': false,
|
||||
'parameters.sendHeaders': false,
|
||||
'parameters.sendQuery': false,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,20 +0,0 @@
|
|||
import { Request } from 'express';
|
||||
|
||||
import { Post, RestController } from '@/decorators';
|
||||
import { BadRequestError } from '@/errors/response-errors/bad-request.error';
|
||||
import { CurlService, flattenObject } from '@/services/curl.service';
|
||||
|
||||
@RestController('/curl')
|
||||
export class CurlController {
|
||||
constructor(private readonly curlService: CurlService) {}
|
||||
|
||||
@Post('/to-json')
|
||||
toJson(req: Request<{}, {}, { curlCommand: string }>) {
|
||||
try {
|
||||
const parameters = this.curlService.toHttpNodeParameters(req.body.curlCommand);
|
||||
return flattenObject(parameters, 'parameters');
|
||||
} catch (e) {
|
||||
throw new BadRequestError('Invalid cURL command');
|
||||
}
|
||||
}
|
||||
}
|
3
packages/cli/src/curlconverter.d.ts
vendored
3
packages/cli/src/curlconverter.d.ts
vendored
|
@ -1,3 +0,0 @@
|
|||
declare module 'curlconverter' {
|
||||
export function toJsonString(data: string): string;
|
||||
}
|
|
@ -37,7 +37,6 @@ import '@/controllers/active-workflows.controller';
|
|||
import '@/controllers/annotation-tags.controller.ee';
|
||||
import '@/controllers/auth.controller';
|
||||
import '@/controllers/binary-data.controller';
|
||||
import '@/controllers/curl.controller';
|
||||
import '@/controllers/ai.controller';
|
||||
import '@/controllers/dynamic-node-parameters.controller';
|
||||
import '@/controllers/invitation.controller';
|
||||
|
@ -392,6 +391,7 @@ export class Server extends AbstractServer {
|
|||
method === 'GET' &&
|
||||
accept &&
|
||||
(accept.includes('text/html') || accept.includes('*/*')) &&
|
||||
!req.path.endsWith('.wasm') &&
|
||||
!nonUIRoutesRegex.test(req.path)
|
||||
) {
|
||||
res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate');
|
||||
|
|
|
@ -1,297 +0,0 @@
|
|||
import { CurlService } from '@/services/curl.service';
|
||||
|
||||
describe('CurlService', () => {
|
||||
const service = new CurlService();
|
||||
|
||||
test('Should parse form-urlencoded content type correctly', () => {
|
||||
const curl =
|
||||
'curl -X POST https://reqbin.com/echo/post/form -H "Content-Type: application/x-www-form-urlencoded" -d "param1=value1¶m2=value2"';
|
||||
const parameters = service.toHttpNodeParameters(curl);
|
||||
expect(parameters.url).toBe('https://reqbin.com/echo/post/form');
|
||||
expect(parameters.sendBody).toBe(true);
|
||||
expect(parameters.bodyParameters?.parameters[0].name).toBe('param1');
|
||||
expect(parameters.bodyParameters?.parameters[0].value).toBe('value1');
|
||||
expect(parameters.bodyParameters?.parameters[1].name).toBe('param2');
|
||||
expect(parameters.bodyParameters?.parameters[1].value).toBe('value2');
|
||||
expect(parameters.contentType).toBe('form-urlencoded');
|
||||
expect(parameters.sendHeaders).toBe(false);
|
||||
expect(parameters.sendQuery).toBe(false);
|
||||
});
|
||||
|
||||
test('Should parse JSON content type correctly', () => {
|
||||
const curl =
|
||||
'curl -X POST https://reqbin.com/echo/post/json -H \'Content-Type: application/json\' -d \'{"login":"my_login","password":"my_password"}\'';
|
||||
const parameters = service.toHttpNodeParameters(curl);
|
||||
expect(parameters.url).toBe('https://reqbin.com/echo/post/json');
|
||||
expect(parameters.sendBody).toBe(true);
|
||||
expect(parameters.bodyParameters?.parameters[0].name).toBe('login');
|
||||
expect(parameters.bodyParameters?.parameters[0].value).toBe('my_login');
|
||||
expect(parameters.bodyParameters?.parameters[1].name).toBe('password');
|
||||
expect(parameters.bodyParameters?.parameters[1].value).toBe('my_password');
|
||||
expect(parameters.contentType).toBe('json');
|
||||
expect(parameters.sendHeaders).toBe(false);
|
||||
expect(parameters.sendQuery).toBe(false);
|
||||
});
|
||||
|
||||
test('Should parse multipart-form-data content type correctly', () => {
|
||||
const curl =
|
||||
'curl -X POST https://reqbin.com/echo/post/json -v -F key1=value1 -F upload=@localfilename';
|
||||
const parameters = service.toHttpNodeParameters(curl);
|
||||
expect(parameters.url).toBe('https://reqbin.com/echo/post/json');
|
||||
expect(parameters.sendBody).toBe(true);
|
||||
expect(parameters.bodyParameters?.parameters[0].parameterType).toBe('formData');
|
||||
expect(parameters.bodyParameters?.parameters[0].name).toBe('key1');
|
||||
expect(parameters.bodyParameters?.parameters[0].value).toBe('value1');
|
||||
expect(parameters.bodyParameters?.parameters[1].parameterType).toBe('formBinaryData');
|
||||
expect(parameters.bodyParameters?.parameters[1].name).toBe('upload');
|
||||
expect(parameters.contentType).toBe('multipart-form-data');
|
||||
expect(parameters.sendHeaders).toBe(false);
|
||||
expect(parameters.sendQuery).toBe(false);
|
||||
});
|
||||
|
||||
test('Should parse binary request correctly', () => {
|
||||
const curl =
|
||||
"curl --location --request POST 'https://www.website.com' --header 'Content-Type: image/png' --data-binary '@/Users/image.png";
|
||||
const parameters = service.toHttpNodeParameters(curl);
|
||||
expect(parameters.url).toBe('https://www.website.com');
|
||||
expect(parameters.method).toBe('POST');
|
||||
expect(parameters.sendBody).toBe(true);
|
||||
expect(parameters.contentType).toBe('binaryData');
|
||||
expect(parameters.sendHeaders).toBe(false);
|
||||
expect(parameters.sendQuery).toBe(false);
|
||||
});
|
||||
|
||||
test('Should parse unknown content type correctly', () => {
|
||||
const curl = `curl -X POST https://reqbin.com/echo/post/xml
|
||||
-H "Content-Type: application/xml"
|
||||
-H "Accept: application/xml"
|
||||
-d "<Request><Login>my_login</Login><Password>my_password</Password></Request>"`;
|
||||
const parameters = service.toHttpNodeParameters(curl);
|
||||
expect(parameters.url).toBe('https://reqbin.com/echo/post/xml');
|
||||
expect(parameters.method).toBe('POST');
|
||||
expect(parameters.sendBody).toBe(true);
|
||||
expect(parameters.contentType).toBe('raw');
|
||||
expect(parameters.rawContentType).toBe('application/xml');
|
||||
expect(parameters.sendHeaders).toBe(true);
|
||||
expect(parameters.headerParameters?.parameters[0].name).toBe('Accept');
|
||||
expect(parameters.headerParameters?.parameters[0].value).toBe('application/xml');
|
||||
expect(parameters.sendQuery).toBe(false);
|
||||
});
|
||||
|
||||
test('Should parse header properties and keep the original case', () => {
|
||||
const curl =
|
||||
'curl -X POST https://reqbin.com/echo/post/json -v -F key1=value1 -F upload=@localfilename -H "ACCEPT: text/javascript" -H "content-type: multipart/form-data"';
|
||||
const parameters = service.toHttpNodeParameters(curl);
|
||||
expect(parameters.url).toBe('https://reqbin.com/echo/post/json');
|
||||
expect(parameters.sendBody).toBe(true);
|
||||
expect(parameters.bodyParameters?.parameters[0].parameterType).toBe('formData');
|
||||
expect(parameters.bodyParameters?.parameters[0].name).toBe('key1');
|
||||
expect(parameters.bodyParameters?.parameters[0].value).toBe('value1');
|
||||
expect(parameters.bodyParameters?.parameters[1].parameterType).toBe('formBinaryData');
|
||||
expect(parameters.bodyParameters?.parameters[1].name).toBe('upload');
|
||||
expect(parameters.contentType).toBe('multipart-form-data');
|
||||
expect(parameters.sendHeaders).toBe(true);
|
||||
expect(parameters.headerParameters?.parameters[0].name).toBe('ACCEPT');
|
||||
expect(parameters.headerParameters?.parameters[0].value).toBe('text/javascript');
|
||||
expect(parameters.sendQuery).toBe(false);
|
||||
});
|
||||
|
||||
test('Should parse querystring properties', () => {
|
||||
const curl = "curl -G -d 'q=kitties' -d 'count=20' https://google.com/search";
|
||||
const parameters = service.toHttpNodeParameters(curl);
|
||||
expect(parameters.url).toBe('https://google.com/search');
|
||||
expect(parameters.sendBody).toBe(false);
|
||||
expect(parameters.contentType).toBeUndefined();
|
||||
expect(parameters.sendHeaders).toBe(false);
|
||||
expect(parameters.sendQuery).toBe(true);
|
||||
expect(parameters.queryParameters?.parameters[0].name).toBe('q');
|
||||
expect(parameters.queryParameters?.parameters[0].value).toBe('kitties');
|
||||
expect(parameters.queryParameters?.parameters[1].name).toBe('count');
|
||||
expect(parameters.queryParameters?.parameters[1].value).toBe('20');
|
||||
});
|
||||
|
||||
test('Should parse basic authentication property and keep the original case', () => {
|
||||
const curl = 'curl https://reqbin.com/echo -u "login:password"';
|
||||
const parameters = service.toHttpNodeParameters(curl);
|
||||
expect(parameters.url).toBe('https://reqbin.com/echo');
|
||||
expect(parameters.sendBody).toBe(false);
|
||||
expect(parameters.contentType).toBeUndefined();
|
||||
expect(parameters.sendQuery).toBe(false);
|
||||
expect(parameters.sendHeaders).toBe(true);
|
||||
expect(parameters.headerParameters?.parameters[0].name).toBe('authorization');
|
||||
expect(parameters.headerParameters?.parameters[0].value).toBe(
|
||||
`Basic ${Buffer.from('login:password').toString('base64')}`,
|
||||
);
|
||||
});
|
||||
|
||||
test('Should parse location flag with --location', () => {
|
||||
const curl = 'curl https://reqbin.com/echo -u "login:password" --location';
|
||||
const parameters = service.toHttpNodeParameters(curl);
|
||||
expect(parameters.url).toBe('https://reqbin.com/echo');
|
||||
expect(parameters.sendBody).toBe(false);
|
||||
expect(parameters.contentType).toBeUndefined();
|
||||
expect(parameters.sendQuery).toBe(false);
|
||||
expect(parameters.sendHeaders).toBe(true);
|
||||
expect(parameters.headerParameters?.parameters[0].name).toBe('authorization');
|
||||
expect(parameters.headerParameters?.parameters[0].value).toBe(
|
||||
`Basic ${Buffer.from('login:password').toString('base64')}`,
|
||||
);
|
||||
expect(parameters.options.redirect.redirect.followRedirects).toBe(true);
|
||||
});
|
||||
|
||||
test('Should parse location flag with --L', () => {
|
||||
const curl = 'curl https://reqbin.com/echo -u "login:password" -L';
|
||||
const parameters = service.toHttpNodeParameters(curl);
|
||||
expect(parameters.url).toBe('https://reqbin.com/echo');
|
||||
expect(parameters.sendBody).toBe(false);
|
||||
expect(parameters.contentType).toBeUndefined();
|
||||
expect(parameters.sendQuery).toBe(false);
|
||||
expect(parameters.sendHeaders).toBe(true);
|
||||
expect(parameters.headerParameters?.parameters[0].name).toBe('authorization');
|
||||
expect(parameters.headerParameters?.parameters[0].value).toBe(
|
||||
`Basic ${Buffer.from('login:password').toString('base64')}`,
|
||||
);
|
||||
expect(parameters.options.redirect.redirect.followRedirects).toBe(true);
|
||||
});
|
||||
|
||||
test('Should parse location and max redirects flags with --location and --max-redirs 10', () => {
|
||||
const curl = 'curl https://reqbin.com/echo -u "login:password" --location --max-redirs 10';
|
||||
const parameters = service.toHttpNodeParameters(curl);
|
||||
expect(parameters.url).toBe('https://reqbin.com/echo');
|
||||
expect(parameters.sendBody).toBe(false);
|
||||
expect(parameters.contentType).toBeUndefined();
|
||||
expect(parameters.sendQuery).toBe(false);
|
||||
expect(parameters.sendHeaders).toBe(true);
|
||||
expect(parameters.headerParameters?.parameters[0].name).toBe('authorization');
|
||||
expect(parameters.headerParameters?.parameters[0].value).toBe(
|
||||
`Basic ${Buffer.from('login:password').toString('base64')}`,
|
||||
);
|
||||
expect(parameters.options.redirect.redirect.followRedirects).toBe(true);
|
||||
expect(parameters.options.redirect.redirect.maxRedirects).toBe('10');
|
||||
});
|
||||
|
||||
test('Should parse proxy flag -x', () => {
|
||||
const curl = 'curl https://reqbin.com/echo -u "login:password" -x https://google.com';
|
||||
const parameters = service.toHttpNodeParameters(curl);
|
||||
expect(parameters.url).toBe('https://reqbin.com/echo');
|
||||
expect(parameters.sendBody).toBe(false);
|
||||
expect(parameters.contentType).toBeUndefined();
|
||||
expect(parameters.sendQuery).toBe(false);
|
||||
expect(parameters.sendHeaders).toBe(true);
|
||||
expect(parameters.headerParameters?.parameters[0].name).toBe('authorization');
|
||||
expect(parameters.headerParameters?.parameters[0].value).toBe(
|
||||
`Basic ${Buffer.from('login:password').toString('base64')}`,
|
||||
);
|
||||
expect(parameters.options.proxy).toBe('https://google.com');
|
||||
});
|
||||
|
||||
test('Should parse proxy flag --proxy', () => {
|
||||
const curl = 'curl https://reqbin.com/echo -u "login:password" -x https://google.com';
|
||||
const parameters = service.toHttpNodeParameters(curl);
|
||||
expect(parameters.url).toBe('https://reqbin.com/echo');
|
||||
expect(parameters.sendBody).toBe(false);
|
||||
expect(parameters.contentType).toBeUndefined();
|
||||
expect(parameters.sendQuery).toBe(false);
|
||||
expect(parameters.sendHeaders).toBe(true);
|
||||
expect(parameters.headerParameters?.parameters[0].name).toBe('authorization');
|
||||
expect(parameters.headerParameters?.parameters[0].value).toBe(
|
||||
`Basic ${Buffer.from('login:password').toString('base64')}`,
|
||||
);
|
||||
expect(parameters.options.proxy).toBe('https://google.com');
|
||||
});
|
||||
|
||||
test('Should parse include headers on output flag --include', () => {
|
||||
const curl = 'curl https://reqbin.com/echo -u "login:password" --include -x https://google.com';
|
||||
const parameters = service.toHttpNodeParameters(curl);
|
||||
expect(parameters.url).toBe('https://reqbin.com/echo');
|
||||
expect(parameters.sendBody).toBe(false);
|
||||
expect(parameters.contentType).toBeUndefined();
|
||||
expect(parameters.sendQuery).toBe(false);
|
||||
expect(parameters.sendHeaders).toBe(true);
|
||||
expect(parameters.headerParameters?.parameters[0].name).toBe('authorization');
|
||||
expect(parameters.headerParameters?.parameters[0].value).toBe(
|
||||
`Basic ${Buffer.from('login:password').toString('base64')}`,
|
||||
);
|
||||
expect(parameters.options.response.response.fullResponse).toBe(true);
|
||||
});
|
||||
|
||||
test('Should parse include headers on output flag -i', () => {
|
||||
const curl = 'curl https://reqbin.com/echo -u "login:password" -x https://google.com -i';
|
||||
const parameters = service.toHttpNodeParameters(curl);
|
||||
expect(parameters.url).toBe('https://reqbin.com/echo');
|
||||
expect(parameters.sendBody).toBe(false);
|
||||
expect(parameters.contentType).toBeUndefined();
|
||||
expect(parameters.sendQuery).toBe(false);
|
||||
expect(parameters.sendHeaders).toBe(true);
|
||||
expect(parameters.headerParameters?.parameters[0].name).toBe('authorization');
|
||||
expect(parameters.headerParameters?.parameters[0].value).toBe(
|
||||
`Basic ${Buffer.from('login:password').toString('base64')}`,
|
||||
);
|
||||
expect(parameters.options.response.response.fullResponse).toBe(true);
|
||||
});
|
||||
|
||||
test('Should parse include request flag -X', () => {
|
||||
const curl = 'curl -X POST https://reqbin.com/echo -u "login:password" -x https://google.com';
|
||||
const parameters = service.toHttpNodeParameters(curl);
|
||||
expect(parameters.url).toBe('https://reqbin.com/echo');
|
||||
expect(parameters.method).toBe('POST');
|
||||
expect(parameters.sendBody).toBe(false);
|
||||
});
|
||||
|
||||
test('Should parse include request flag --request', () => {
|
||||
const curl =
|
||||
'curl --request POST https://reqbin.com/echo -u "login:password" -x https://google.com';
|
||||
const parameters = service.toHttpNodeParameters(curl);
|
||||
expect(parameters.url).toBe('https://reqbin.com/echo');
|
||||
expect(parameters.method).toBe('POST');
|
||||
expect(parameters.sendBody).toBe(false);
|
||||
});
|
||||
|
||||
test('Should parse include timeout flag --connect-timeout', () => {
|
||||
const curl =
|
||||
'curl --request POST https://reqbin.com/echo -u "login:password" --connect-timeout 20';
|
||||
const parameters = service.toHttpNodeParameters(curl);
|
||||
expect(parameters.url).toBe('https://reqbin.com/echo');
|
||||
expect(parameters.method).toBe('POST');
|
||||
expect(parameters.sendBody).toBe(false);
|
||||
expect(parameters.options.timeout).toBe(20000);
|
||||
});
|
||||
|
||||
test('Should parse download file flag -O', () => {
|
||||
const curl = 'curl --request POST https://reqbin.com/echo -u "login:password" -O';
|
||||
const parameters = service.toHttpNodeParameters(curl);
|
||||
expect(parameters.url).toBe('https://reqbin.com/echo');
|
||||
expect(parameters.method).toBe('POST');
|
||||
expect(parameters.sendBody).toBe(false);
|
||||
expect(parameters.options.response.response.responseFormat).toBe('file');
|
||||
expect(parameters.options.response.response.outputPropertyName).toBe('data');
|
||||
});
|
||||
|
||||
test('Should parse download file flag -o', () => {
|
||||
const curl = 'curl --request POST https://reqbin.com/echo -u "login:password" -o';
|
||||
const parameters = service.toHttpNodeParameters(curl);
|
||||
expect(parameters.url).toBe('https://reqbin.com/echo');
|
||||
expect(parameters.method).toBe('POST');
|
||||
expect(parameters.sendBody).toBe(false);
|
||||
expect(parameters.options.response.response.responseFormat).toBe('file');
|
||||
expect(parameters.options.response.response.outputPropertyName).toBe('data');
|
||||
});
|
||||
|
||||
test('Should parse ignore SSL flag -k', () => {
|
||||
const curl = 'curl --request POST https://reqbin.com/echo -u "login:password" -k';
|
||||
const parameters = service.toHttpNodeParameters(curl);
|
||||
expect(parameters.url).toBe('https://reqbin.com/echo');
|
||||
expect(parameters.method).toBe('POST');
|
||||
expect(parameters.sendBody).toBe(false);
|
||||
expect(parameters.options.allowUnauthorizedCerts).toBe(true);
|
||||
});
|
||||
|
||||
test('Should parse ignore SSL flag --insecure', () => {
|
||||
const curl = 'curl --request POST https://reqbin.com/echo -u "login:password" --insecure';
|
||||
const parameters = service.toHttpNodeParameters(curl);
|
||||
expect(parameters.url).toBe('https://reqbin.com/echo');
|
||||
expect(parameters.method).toBe('POST');
|
||||
expect(parameters.sendBody).toBe(false);
|
||||
expect(parameters.options.allowUnauthorizedCerts).toBe(true);
|
||||
});
|
||||
});
|
|
@ -1,479 +0,0 @@
|
|||
import { Service } from '@n8n/di';
|
||||
import get from 'lodash/get';
|
||||
import type { IDataObject } from 'n8n-workflow';
|
||||
import { jsonParse } from 'n8n-workflow';
|
||||
|
||||
import curlconverter from 'curlconverter';
|
||||
|
||||
interface CurlJson {
|
||||
url: string;
|
||||
raw_url?: string;
|
||||
method: string;
|
||||
contentType?: string;
|
||||
cookies?: {
|
||||
[key: string]: string;
|
||||
};
|
||||
auth?: {
|
||||
user: string;
|
||||
password: string;
|
||||
};
|
||||
headers?: {
|
||||
[key: string]: string;
|
||||
};
|
||||
files?: {
|
||||
[key: string]: string;
|
||||
};
|
||||
queries: {
|
||||
[key: string]: string;
|
||||
};
|
||||
data?: {
|
||||
[key: string]: string;
|
||||
};
|
||||
}
|
||||
|
||||
interface Parameter {
|
||||
parameterType?: string;
|
||||
name: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
interface HttpNodeParameters {
|
||||
url?: string;
|
||||
method: string;
|
||||
sendBody?: boolean;
|
||||
authentication: string;
|
||||
contentType?: 'form-urlencoded' | 'multipart-form-data' | 'json' | 'raw' | 'binaryData';
|
||||
rawContentType?: string;
|
||||
specifyBody?: 'json' | 'keypair';
|
||||
bodyParameters?: {
|
||||
parameters: Parameter[];
|
||||
};
|
||||
jsonBody?: object;
|
||||
options: {
|
||||
allowUnauthorizedCerts?: boolean;
|
||||
proxy?: string;
|
||||
timeout?: number;
|
||||
redirect: {
|
||||
redirect: {
|
||||
followRedirects?: boolean;
|
||||
maxRedirects?: number;
|
||||
};
|
||||
};
|
||||
response: {
|
||||
response: {
|
||||
fullResponse?: boolean;
|
||||
responseFormat?: string;
|
||||
outputPropertyName?: string;
|
||||
};
|
||||
};
|
||||
};
|
||||
sendHeaders?: boolean;
|
||||
headerParameters?: {
|
||||
parameters: Parameter[];
|
||||
};
|
||||
sendQuery?: boolean;
|
||||
queryParameters?: {
|
||||
parameters: Parameter[];
|
||||
};
|
||||
}
|
||||
|
||||
type HttpNodeHeaders = Pick<HttpNodeParameters, 'sendHeaders' | 'headerParameters'>;
|
||||
|
||||
type HttpNodeQueries = Pick<HttpNodeParameters, 'sendQuery' | 'queryParameters'>;
|
||||
|
||||
const enum ContentTypes {
|
||||
applicationJson = 'application/json',
|
||||
applicationFormUrlEncoded = 'application/x-www-form-urlencoded',
|
||||
applicationMultipart = 'multipart/form-data',
|
||||
}
|
||||
|
||||
const SUPPORTED_CONTENT_TYPES = [
|
||||
ContentTypes.applicationJson,
|
||||
ContentTypes.applicationFormUrlEncoded,
|
||||
ContentTypes.applicationMultipart,
|
||||
];
|
||||
|
||||
const CONTENT_TYPE_KEY = 'content-type';
|
||||
|
||||
const FOLLOW_REDIRECT_FLAGS = ['--location', '-L'];
|
||||
|
||||
const MAX_REDIRECT_FLAG = '--max-redirs';
|
||||
|
||||
const PROXY_FLAGS = ['-x', '--proxy'];
|
||||
|
||||
const INCLUDE_HEADERS_IN_OUTPUT_FLAGS = ['-i', '--include'];
|
||||
|
||||
const REQUEST_FLAGS = ['-X', '--request'];
|
||||
|
||||
const TIMEOUT_FLAGS = ['--connect-timeout'];
|
||||
|
||||
const DOWNLOAD_FILE_FLAGS = ['-O', '-o'];
|
||||
|
||||
const IGNORE_SSL_ISSUES_FLAGS = ['-k', '--insecure'];
|
||||
|
||||
const isContentType = (headers: CurlJson['headers'], contentType: ContentTypes): boolean => {
|
||||
return get(headers, CONTENT_TYPE_KEY) === contentType;
|
||||
};
|
||||
|
||||
const isJsonRequest = (curlJson: CurlJson): boolean => {
|
||||
if (isContentType(curlJson.headers, ContentTypes.applicationJson)) return true;
|
||||
|
||||
if (curlJson.data) {
|
||||
const bodyKey = Object.keys(curlJson.data)[0];
|
||||
try {
|
||||
JSON.parse(bodyKey);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
const isFormUrlEncodedRequest = (curlJson: CurlJson): boolean => {
|
||||
if (isContentType(curlJson.headers, ContentTypes.applicationFormUrlEncoded)) return true;
|
||||
if (curlJson.data && !curlJson.files) return true;
|
||||
return false;
|
||||
};
|
||||
|
||||
const isMultipartRequest = (curlJson: CurlJson): boolean => {
|
||||
if (isContentType(curlJson.headers, ContentTypes.applicationMultipart)) return true;
|
||||
|
||||
// only multipart/form-data request include files
|
||||
if (curlJson.files) return true;
|
||||
return false;
|
||||
};
|
||||
|
||||
const isBinaryRequest = (curlJson: CurlJson): boolean => {
|
||||
if (curlJson?.headers?.[CONTENT_TYPE_KEY]) {
|
||||
const contentType = curlJson?.headers?.[CONTENT_TYPE_KEY];
|
||||
return ['image', 'video', 'audio'].some((d) => contentType.includes(d));
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
const sanitizeCurlCommand = (curlCommand: string) =>
|
||||
curlCommand
|
||||
.replace(/\r\n/g, ' ')
|
||||
.replace(/\n/g, ' ')
|
||||
.replace(/\\/g, ' ')
|
||||
.replace(/[ ]{2,}/g, ' ');
|
||||
|
||||
const toKeyValueArray = ([key, value]: string[]) => ({ name: key, value });
|
||||
|
||||
const extractHeaders = (headers: CurlJson['headers'] = {}): HttpNodeHeaders => {
|
||||
const emptyHeaders = !Object.keys(headers).length;
|
||||
|
||||
const onlyContentTypeHeaderDefined =
|
||||
Object.keys(headers).length === 1 && headers[CONTENT_TYPE_KEY] !== undefined;
|
||||
|
||||
if (emptyHeaders || onlyContentTypeHeaderDefined) return { sendHeaders: false };
|
||||
|
||||
return {
|
||||
sendHeaders: true,
|
||||
headerParameters: {
|
||||
parameters: Object.entries(headers)
|
||||
.map(toKeyValueArray)
|
||||
.filter((parameter) => parameter.name !== CONTENT_TYPE_KEY),
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const extractQueries = (queries: CurlJson['queries'] = {}): HttpNodeQueries => {
|
||||
const emptyQueries = !Object.keys(queries).length;
|
||||
|
||||
if (emptyQueries) return { sendQuery: false };
|
||||
|
||||
return {
|
||||
sendQuery: true,
|
||||
queryParameters: {
|
||||
parameters: Object.entries(queries).map(toKeyValueArray),
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const extractJson = (body: CurlJson['data']) =>
|
||||
jsonParse<{ [key: string]: string }>(Object.keys(body as IDataObject)[0]);
|
||||
|
||||
const jsonBodyToNodeParameters = (body: CurlJson['data'] = {}): Parameter[] | [] => {
|
||||
const data = extractJson(body);
|
||||
return Object.entries(data).map(toKeyValueArray);
|
||||
};
|
||||
|
||||
const multipartToNodeParameters = (
|
||||
body: CurlJson['data'] = {},
|
||||
files: CurlJson['files'] = {},
|
||||
): Parameter[] | [] => {
|
||||
return [
|
||||
...Object.entries(body)
|
||||
.map(toKeyValueArray)
|
||||
.map((e) => ({ parameterType: 'formData', ...e })),
|
||||
...Object.entries(files)
|
||||
.map(toKeyValueArray)
|
||||
.map((e) => ({ parameterType: 'formBinaryData', ...e })),
|
||||
];
|
||||
};
|
||||
|
||||
const keyValueBodyToNodeParameters = (body: CurlJson['data'] = {}): Parameter[] | [] => {
|
||||
return Object.entries(body).map(toKeyValueArray);
|
||||
};
|
||||
|
||||
const lowerCaseContentTypeKey = (obj: { [x: string]: string }): void => {
|
||||
const regex = new RegExp(CONTENT_TYPE_KEY, 'gi');
|
||||
|
||||
const contentTypeKey = Object.keys(obj).find((key) => {
|
||||
const group = Array.from(key.matchAll(regex));
|
||||
if (group.length) return true;
|
||||
return false;
|
||||
});
|
||||
|
||||
if (!contentTypeKey) return;
|
||||
|
||||
const value = obj[contentTypeKey];
|
||||
delete obj[contentTypeKey];
|
||||
obj[CONTENT_TYPE_KEY] = value;
|
||||
};
|
||||
|
||||
const encodeBasicAuthentication = (username: string, password: string) =>
|
||||
Buffer.from(`${username}:${password}`).toString('base64');
|
||||
|
||||
const jsonHasNestedObjects = (json: { [key: string]: string | number | object }) =>
|
||||
Object.values(json).some((e) => typeof e === 'object');
|
||||
|
||||
const extractGroup = (curlCommand: string, regex: RegExp) => curlCommand.matchAll(regex);
|
||||
|
||||
const mapCookies = (cookies: CurlJson['cookies']): { cookie: string } | {} => {
|
||||
if (!cookies) return {};
|
||||
|
||||
const cookiesValues = Object.entries(cookies).reduce(
|
||||
(accumulator: string, entry: [string, string]) => {
|
||||
accumulator += `${entry[0]}=${entry[1]};`;
|
||||
return accumulator;
|
||||
},
|
||||
'',
|
||||
);
|
||||
|
||||
if (!cookiesValues) return {};
|
||||
|
||||
return {
|
||||
cookie: cookiesValues,
|
||||
};
|
||||
};
|
||||
|
||||
export const flattenObject = (obj: { [x: string]: any }, prefix = '') =>
|
||||
Object.keys(obj).reduce((acc, k) => {
|
||||
const pre = prefix.length ? prefix + '.' : '';
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
||||
if (typeof obj[k] === 'object') Object.assign(acc, flattenObject(obj[k], pre + k));
|
||||
//@ts-ignore
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
else acc[pre + k] = obj[k];
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
@Service()
|
||||
export class CurlService {
|
||||
// eslint-disable-next-line complexity
|
||||
toHttpNodeParameters(curlCommand: string): HttpNodeParameters {
|
||||
const curlJson = jsonParse<CurlJson>(curlconverter.toJsonString(curlCommand));
|
||||
|
||||
if (!curlJson.headers) curlJson.headers = {};
|
||||
|
||||
lowerCaseContentTypeKey(curlJson.headers);
|
||||
|
||||
// set basic authentication
|
||||
if (curlJson.auth) {
|
||||
const { user, password: pass } = curlJson.auth;
|
||||
Object.assign(curlJson.headers, {
|
||||
authorization: `Basic ${encodeBasicAuthentication(user, pass)}`,
|
||||
});
|
||||
}
|
||||
|
||||
const httpNodeParameters: HttpNodeParameters = {
|
||||
url: curlJson.url,
|
||||
authentication: 'none',
|
||||
method: curlJson.method.toUpperCase(),
|
||||
...extractHeaders({ ...curlJson.headers, ...mapCookies(curlJson.cookies) }),
|
||||
...extractQueries(curlJson.queries),
|
||||
options: {
|
||||
redirect: {
|
||||
redirect: {},
|
||||
},
|
||||
response: {
|
||||
response: {},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
//attempt to get the curl flags not supported by the library
|
||||
const curl = sanitizeCurlCommand(curlCommand);
|
||||
|
||||
//check for follow redirect flags
|
||||
if (FOLLOW_REDIRECT_FLAGS.some((flag) => curl.includes(` ${flag}`))) {
|
||||
Object.assign(httpNodeParameters.options.redirect?.redirect, { followRedirects: true });
|
||||
|
||||
if (curl.includes(` ${MAX_REDIRECT_FLAG}`)) {
|
||||
const extractedValue = Array.from(
|
||||
extractGroup(curl, new RegExp(` ${MAX_REDIRECT_FLAG} (\\d+)`, 'g')),
|
||||
);
|
||||
if (extractedValue.length) {
|
||||
const [_, maxRedirects] = extractedValue[0];
|
||||
if (maxRedirects) {
|
||||
Object.assign(httpNodeParameters.options.redirect?.redirect, { maxRedirects });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//check for proxy flags
|
||||
if (PROXY_FLAGS.some((flag) => curl.includes(` ${flag}`))) {
|
||||
const foundFlag = PROXY_FLAGS.find((flag) => curl.includes(` ${flag}`));
|
||||
if (foundFlag) {
|
||||
const extractedValue = Array.from(
|
||||
extractGroup(curl, new RegExp(` ${foundFlag} (\\S*)`, 'g')),
|
||||
);
|
||||
if (extractedValue.length) {
|
||||
const [_, proxy] = extractedValue[0];
|
||||
Object.assign(httpNodeParameters.options, { proxy });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// check for "include header in output" flag
|
||||
if (INCLUDE_HEADERS_IN_OUTPUT_FLAGS.some((flag) => curl.includes(` ${flag}`))) {
|
||||
Object.assign(httpNodeParameters.options?.response?.response, {
|
||||
fullResponse: true,
|
||||
responseFormat: 'autodetect',
|
||||
});
|
||||
}
|
||||
|
||||
// check for request flag
|
||||
if (REQUEST_FLAGS.some((flag) => curl.includes(` ${flag}`))) {
|
||||
const foundFlag = REQUEST_FLAGS.find((flag) => curl.includes(` ${flag}`));
|
||||
if (foundFlag) {
|
||||
const extractedValue = Array.from(
|
||||
extractGroup(curl, new RegExp(` ${foundFlag} (\\w+)`, 'g')),
|
||||
);
|
||||
if (extractedValue.length) {
|
||||
const [_, request] = extractedValue[0];
|
||||
httpNodeParameters.method = request.toUpperCase();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// check for timeout flag
|
||||
if (TIMEOUT_FLAGS.some((flag) => curl.includes(` ${flag}`))) {
|
||||
const foundFlag = TIMEOUT_FLAGS.find((flag) => curl.includes(` ${flag}`));
|
||||
if (foundFlag) {
|
||||
const extractedValue = Array.from(
|
||||
extractGroup(curl, new RegExp(` ${foundFlag} (\\d+)`, 'g')),
|
||||
);
|
||||
if (extractedValue.length) {
|
||||
const [_, timeout] = extractedValue[0];
|
||||
Object.assign(httpNodeParameters.options, {
|
||||
timeout: parseInt(timeout, 10) * 1000,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// check for download flag
|
||||
if (DOWNLOAD_FILE_FLAGS.some((flag) => curl.includes(` ${flag}`))) {
|
||||
const foundFlag = DOWNLOAD_FILE_FLAGS.find((flag) => curl.includes(` ${flag}`));
|
||||
if (foundFlag) {
|
||||
Object.assign(httpNodeParameters.options.response.response, {
|
||||
responseFormat: 'file',
|
||||
outputPropertyName: 'data',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (IGNORE_SSL_ISSUES_FLAGS.some((flag) => curl.includes(` ${flag}`))) {
|
||||
const foundFlag = IGNORE_SSL_ISSUES_FLAGS.find((flag) => curl.includes(` ${flag}`));
|
||||
if (foundFlag) {
|
||||
Object.assign(httpNodeParameters.options, {
|
||||
allowUnauthorizedCerts: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const contentType = curlJson?.headers?.[CONTENT_TYPE_KEY] as ContentTypes;
|
||||
|
||||
if (isBinaryRequest(curlJson)) {
|
||||
return Object.assign(httpNodeParameters, {
|
||||
contentType: 'binaryData',
|
||||
sendBody: true,
|
||||
});
|
||||
}
|
||||
|
||||
if (contentType && !SUPPORTED_CONTENT_TYPES.includes(contentType)) {
|
||||
return Object.assign(httpNodeParameters, {
|
||||
sendBody: true,
|
||||
contentType: 'raw',
|
||||
rawContentType: contentType,
|
||||
body: Object.keys(curlJson?.data ?? {})[0],
|
||||
});
|
||||
}
|
||||
|
||||
if (isJsonRequest(curlJson)) {
|
||||
Object.assign(httpNodeParameters, {
|
||||
contentType: 'json',
|
||||
sendBody: true,
|
||||
});
|
||||
|
||||
if (curlJson.data) {
|
||||
const json = extractJson(curlJson.data);
|
||||
|
||||
if (jsonHasNestedObjects(json)) {
|
||||
// json body
|
||||
Object.assign(httpNodeParameters, {
|
||||
specifyBody: 'json',
|
||||
jsonBody: JSON.stringify(json, null, 2),
|
||||
});
|
||||
} else {
|
||||
// key-value body
|
||||
Object.assign(httpNodeParameters, {
|
||||
specifyBody: 'keypair',
|
||||
bodyParameters: {
|
||||
parameters: jsonBodyToNodeParameters(curlJson.data),
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
} else if (isFormUrlEncodedRequest(curlJson)) {
|
||||
Object.assign(httpNodeParameters, {
|
||||
contentType: 'form-urlencoded',
|
||||
sendBody: true,
|
||||
specifyBody: 'keypair',
|
||||
bodyParameters: {
|
||||
parameters: keyValueBodyToNodeParameters(curlJson.data),
|
||||
},
|
||||
});
|
||||
} else if (isMultipartRequest(curlJson)) {
|
||||
Object.assign(httpNodeParameters, {
|
||||
contentType: 'multipart-form-data',
|
||||
sendBody: true,
|
||||
bodyParameters: {
|
||||
parameters: multipartToNodeParameters(curlJson.data, curlJson.files),
|
||||
},
|
||||
});
|
||||
} else {
|
||||
// could not figure the content type so do not set the body
|
||||
Object.assign(httpNodeParameters, {
|
||||
sendBody: false,
|
||||
});
|
||||
}
|
||||
|
||||
if (!Object.keys(httpNodeParameters.options?.redirect.redirect).length) {
|
||||
// @ts-ignore
|
||||
delete httpNodeParameters.options.redirect;
|
||||
}
|
||||
|
||||
if (!Object.keys(httpNodeParameters.options.response.response).length) {
|
||||
// @ts-ignore
|
||||
delete httpNodeParameters.options.response;
|
||||
}
|
||||
|
||||
return httpNodeParameters;
|
||||
}
|
||||
}
|
|
@ -56,6 +56,7 @@
|
|||
"change-case": "^5.4.4",
|
||||
"chart.js": "^4.4.0",
|
||||
"codemirror-lang-html-n8n": "^1.0.0",
|
||||
"curlconverter": "^4.11.0",
|
||||
"comlink": "^4.4.1",
|
||||
"core-js": "^3.40.0",
|
||||
"dateformat": "^3.0.3",
|
||||
|
@ -89,6 +90,7 @@
|
|||
"vue-router": "catalog:frontend",
|
||||
"vue-virtual-scroller": "2.0.0-beta.8",
|
||||
"vue3-touch-events": "^4.1.3",
|
||||
"web-tree-sitter": "0.24.3",
|
||||
"vuedraggable": "4.1.0",
|
||||
"xss": "catalog:"
|
||||
},
|
||||
|
@ -115,6 +117,7 @@
|
|||
"unplugin-icons": "^0.19.0",
|
||||
"unplugin-vue-components": "^0.27.2",
|
||||
"vite": "catalog:frontend",
|
||||
"vite-plugin-static-copy": "2.2.0",
|
||||
"vite-svg-loader": "5.1.0",
|
||||
"vitest": "catalog:frontend",
|
||||
"vitest-mock-extended": "catalog:frontend",
|
||||
|
|
|
@ -1,9 +0,0 @@
|
|||
import type { CurlToJSONResponse, IRestApiContext } from '@/Interface';
|
||||
import { makeRestApiRequest } from '@/utils/apiUtils';
|
||||
|
||||
export async function getCurlToJson(
|
||||
context: IRestApiContext,
|
||||
curlCommand: string,
|
||||
): Promise<CurlToJSONResponse> {
|
||||
return await makeRestApiRequest(context, 'POST', '/curl/to-json', { curlCommand });
|
||||
}
|
|
@ -6,7 +6,6 @@ import { useUIStore } from '@/stores/ui.store';
|
|||
import { createEventBus } from 'n8n-design-system/utils';
|
||||
import { useTelemetry } from '@/composables/useTelemetry';
|
||||
import { useI18n } from '@/composables/useI18n';
|
||||
import { useImportCurlCommand } from '@/composables/useImportCurlCommand';
|
||||
|
||||
const telemetry = useTelemetry();
|
||||
const i18n = useI18n();
|
||||
|
@ -18,12 +17,6 @@ const modalBus = createEventBus();
|
|||
|
||||
const inputRef = ref<HTMLTextAreaElement | null>(null);
|
||||
|
||||
const { importCurlCommand } = useImportCurlCommand({
|
||||
onImportSuccess,
|
||||
onImportFailure,
|
||||
onAfterImport,
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
curlCommand.value = (uiStore.modalsById[IMPORT_CURL_MODAL_KEY].data?.curlCommand as string) ?? '';
|
||||
|
||||
|
@ -71,7 +64,13 @@ function sendTelemetry(
|
|||
}
|
||||
|
||||
async function onImport() {
|
||||
await importCurlCommand(curlCommand);
|
||||
const { useImportCurlCommand } = await import('@/composables/useImportCurlCommand');
|
||||
const { importCurlCommand } = useImportCurlCommand({
|
||||
onImportSuccess,
|
||||
onImportFailure,
|
||||
onAfterImport,
|
||||
});
|
||||
importCurlCommand(curlCommand);
|
||||
}
|
||||
</script>
|
||||
|
||||
|
|
296
packages/editor-ui/src/composables/useImportCurlCommand.test.ts
Normal file
296
packages/editor-ui/src/composables/useImportCurlCommand.test.ts
Normal file
|
@ -0,0 +1,296 @@
|
|||
import { toHttpNodeParameters } from '@/composables/useImportCurlCommand';
|
||||
|
||||
describe('useImportCurlCommand', () => {
|
||||
describe('toHttpNodeParameters', () => {
|
||||
test('Should parse form-urlencoded content type correctly', () => {
|
||||
const curl =
|
||||
'curl -X POST https://reqbin.com/echo/post/form -H "Content-Type: application/x-www-form-urlencoded" -d "param1=value1¶m2=value2"';
|
||||
const parameters = toHttpNodeParameters(curl);
|
||||
expect(parameters.url).toBe('https://reqbin.com/echo/post/form');
|
||||
expect(parameters.sendBody).toBe(true);
|
||||
expect(parameters.bodyParameters?.parameters[0].name).toBe('param1');
|
||||
expect(parameters.bodyParameters?.parameters[0].value).toBe('value1');
|
||||
expect(parameters.bodyParameters?.parameters[1].name).toBe('param2');
|
||||
expect(parameters.bodyParameters?.parameters[1].value).toBe('value2');
|
||||
expect(parameters.contentType).toBe('form-urlencoded');
|
||||
expect(parameters.sendHeaders).toBe(false);
|
||||
expect(parameters.sendQuery).toBe(false);
|
||||
});
|
||||
|
||||
test('Should parse JSON content type correctly', () => {
|
||||
const curl =
|
||||
'curl -X POST https://reqbin.com/echo/post/json -H \'Content-Type: application/json\' -d \'{"login":"my_login","password":"my_password"}\'';
|
||||
const parameters = toHttpNodeParameters(curl);
|
||||
expect(parameters.url).toBe('https://reqbin.com/echo/post/json');
|
||||
expect(parameters.sendBody).toBe(true);
|
||||
expect(parameters.bodyParameters?.parameters[0].name).toBe('login');
|
||||
expect(parameters.bodyParameters?.parameters[0].value).toBe('my_login');
|
||||
expect(parameters.bodyParameters?.parameters[1].name).toBe('password');
|
||||
expect(parameters.bodyParameters?.parameters[1].value).toBe('my_password');
|
||||
expect(parameters.contentType).toBe('json');
|
||||
expect(parameters.sendHeaders).toBe(false);
|
||||
expect(parameters.sendQuery).toBe(false);
|
||||
});
|
||||
|
||||
test('Should parse multipart-form-data content type correctly', () => {
|
||||
const curl =
|
||||
'curl -X POST https://reqbin.com/echo/post/json -v -F key1=value1 -F upload=@localfilename';
|
||||
const parameters = toHttpNodeParameters(curl);
|
||||
expect(parameters.url).toBe('https://reqbin.com/echo/post/json');
|
||||
expect(parameters.sendBody).toBe(true);
|
||||
expect(parameters.bodyParameters?.parameters[0].parameterType).toBe('formData');
|
||||
expect(parameters.bodyParameters?.parameters[0].name).toBe('key1');
|
||||
expect(parameters.bodyParameters?.parameters[0].value).toBe('value1');
|
||||
expect(parameters.bodyParameters?.parameters[1].parameterType).toBe('formBinaryData');
|
||||
expect(parameters.bodyParameters?.parameters[1].name).toBe('upload');
|
||||
expect(parameters.contentType).toBe('multipart-form-data');
|
||||
expect(parameters.sendHeaders).toBe(false);
|
||||
expect(parameters.sendQuery).toBe(false);
|
||||
});
|
||||
|
||||
test('Should parse binary request correctly', () => {
|
||||
const curl =
|
||||
"curl --location --request POST 'https://www.website.com' --header 'Content-Type: image/png' --data-binary '@/Users/image.png'";
|
||||
const parameters = toHttpNodeParameters(curl);
|
||||
expect(parameters.url).toBe('https://www.website.com');
|
||||
expect(parameters.method).toBe('POST');
|
||||
expect(parameters.sendBody).toBe(true);
|
||||
expect(parameters.contentType).toBe('binaryData');
|
||||
expect(parameters.sendHeaders).toBe(false);
|
||||
expect(parameters.sendQuery).toBe(false);
|
||||
});
|
||||
|
||||
test('Should parse unknown content type correctly', () => {
|
||||
const curl =
|
||||
'curl -X POST https://reqbin.com/echo/post/xml -H "Content-Type: application/xml" -H "Accept: application/xml" -d "<Request><Login>my_login</Login><Password>my_password</Password></Request>"';
|
||||
const parameters = toHttpNodeParameters(curl);
|
||||
expect(parameters.url).toBe('https://reqbin.com/echo/post/xml');
|
||||
expect(parameters.method).toBe('POST');
|
||||
expect(parameters.sendBody).toBe(true);
|
||||
expect(parameters.contentType).toBe('raw');
|
||||
expect(parameters.rawContentType).toBe('application/xml');
|
||||
expect(parameters.sendHeaders).toBe(true);
|
||||
expect(parameters.headerParameters?.parameters[0].name).toBe('Accept');
|
||||
expect(parameters.headerParameters?.parameters[0].value).toBe('application/xml');
|
||||
expect(parameters.sendQuery).toBe(false);
|
||||
});
|
||||
|
||||
test('Should parse header properties and keep the original case', () => {
|
||||
const curl =
|
||||
'curl -X POST https://reqbin.com/echo/post/json -v -F key1=value1 -F upload=@localfilename -H "ACCEPT: text/javascript" -H "content-type: multipart/form-data"';
|
||||
const parameters = toHttpNodeParameters(curl);
|
||||
expect(parameters.url).toBe('https://reqbin.com/echo/post/json');
|
||||
expect(parameters.sendBody).toBe(true);
|
||||
expect(parameters.bodyParameters?.parameters[0].parameterType).toBe('formData');
|
||||
expect(parameters.bodyParameters?.parameters[0].name).toBe('key1');
|
||||
expect(parameters.bodyParameters?.parameters[0].value).toBe('value1');
|
||||
expect(parameters.bodyParameters?.parameters[1].parameterType).toBe('formBinaryData');
|
||||
expect(parameters.bodyParameters?.parameters[1].name).toBe('upload');
|
||||
expect(parameters.contentType).toBe('multipart-form-data');
|
||||
expect(parameters.sendHeaders).toBe(true);
|
||||
expect(parameters.headerParameters?.parameters[0].name).toBe('ACCEPT');
|
||||
expect(parameters.headerParameters?.parameters[0].value).toBe('text/javascript');
|
||||
expect(parameters.sendQuery).toBe(false);
|
||||
});
|
||||
|
||||
test('Should parse querystring properties', () => {
|
||||
const curl = "curl -G -d 'q=kitties' -d 'count=20' https://google.com/search";
|
||||
const parameters = toHttpNodeParameters(curl);
|
||||
expect(parameters.url).toBe('https://google.com/search');
|
||||
expect(parameters.sendBody).toBe(false);
|
||||
expect(parameters.contentType).toBeUndefined();
|
||||
expect(parameters.sendHeaders).toBe(false);
|
||||
expect(parameters.sendQuery).toBe(true);
|
||||
expect(parameters.queryParameters?.parameters[0].name).toBe('q');
|
||||
expect(parameters.queryParameters?.parameters[0].value).toBe('kitties');
|
||||
expect(parameters.queryParameters?.parameters[1].name).toBe('count');
|
||||
expect(parameters.queryParameters?.parameters[1].value).toBe('20');
|
||||
});
|
||||
|
||||
test('Should parse basic authentication property and keep the original case', () => {
|
||||
const curl = 'curl https://reqbin.com/echo -u "login:password"';
|
||||
const parameters = toHttpNodeParameters(curl);
|
||||
expect(parameters.url).toBe('https://reqbin.com/echo');
|
||||
expect(parameters.sendBody).toBe(false);
|
||||
expect(parameters.contentType).toBeUndefined();
|
||||
expect(parameters.sendQuery).toBe(false);
|
||||
expect(parameters.sendHeaders).toBe(true);
|
||||
expect(parameters.headerParameters?.parameters[0].name).toBe('authorization');
|
||||
expect(parameters.headerParameters?.parameters[0].value).toBe(
|
||||
`Basic ${Buffer.from('login:password').toString('base64')}`,
|
||||
);
|
||||
});
|
||||
|
||||
test('Should parse location flag with --location', () => {
|
||||
const curl = 'curl https://reqbin.com/echo -u "login:password" --location';
|
||||
const parameters = toHttpNodeParameters(curl);
|
||||
expect(parameters.url).toBe('https://reqbin.com/echo');
|
||||
expect(parameters.sendBody).toBe(false);
|
||||
expect(parameters.contentType).toBeUndefined();
|
||||
expect(parameters.sendQuery).toBe(false);
|
||||
expect(parameters.sendHeaders).toBe(true);
|
||||
expect(parameters.headerParameters?.parameters[0].name).toBe('authorization');
|
||||
expect(parameters.headerParameters?.parameters[0].value).toBe(
|
||||
`Basic ${Buffer.from('login:password').toString('base64')}`,
|
||||
);
|
||||
expect(parameters.options.redirect.redirect.followRedirects).toBe(true);
|
||||
});
|
||||
|
||||
test('Should parse location flag with --L', () => {
|
||||
const curl = 'curl https://reqbin.com/echo -u "login:password" -L';
|
||||
const parameters = toHttpNodeParameters(curl);
|
||||
expect(parameters.url).toBe('https://reqbin.com/echo');
|
||||
expect(parameters.sendBody).toBe(false);
|
||||
expect(parameters.contentType).toBeUndefined();
|
||||
expect(parameters.sendQuery).toBe(false);
|
||||
expect(parameters.sendHeaders).toBe(true);
|
||||
expect(parameters.headerParameters?.parameters[0].name).toBe('authorization');
|
||||
expect(parameters.headerParameters?.parameters[0].value).toBe(
|
||||
`Basic ${Buffer.from('login:password').toString('base64')}`,
|
||||
);
|
||||
expect(parameters.options.redirect.redirect.followRedirects).toBe(true);
|
||||
});
|
||||
|
||||
test('Should parse location and max redirects flags with --location and --max-redirs 10', () => {
|
||||
const curl = 'curl https://reqbin.com/echo -u "login:password" --location --max-redirs 10';
|
||||
const parameters = toHttpNodeParameters(curl);
|
||||
expect(parameters.url).toBe('https://reqbin.com/echo');
|
||||
expect(parameters.sendBody).toBe(false);
|
||||
expect(parameters.contentType).toBeUndefined();
|
||||
expect(parameters.sendQuery).toBe(false);
|
||||
expect(parameters.sendHeaders).toBe(true);
|
||||
expect(parameters.headerParameters?.parameters[0].name).toBe('authorization');
|
||||
expect(parameters.headerParameters?.parameters[0].value).toBe(
|
||||
`Basic ${Buffer.from('login:password').toString('base64')}`,
|
||||
);
|
||||
expect(parameters.options.redirect.redirect.followRedirects).toBe(true);
|
||||
expect(parameters.options.redirect.redirect.maxRedirects).toBe(10);
|
||||
});
|
||||
|
||||
test('Should parse proxy flag -x', () => {
|
||||
const curl = 'curl https://reqbin.com/echo -u "login:password" -x https://google.com';
|
||||
const parameters = toHttpNodeParameters(curl);
|
||||
expect(parameters.url).toBe('https://reqbin.com/echo');
|
||||
expect(parameters.sendBody).toBe(false);
|
||||
expect(parameters.contentType).toBeUndefined();
|
||||
expect(parameters.sendQuery).toBe(false);
|
||||
expect(parameters.sendHeaders).toBe(true);
|
||||
expect(parameters.headerParameters?.parameters[0].name).toBe('authorization');
|
||||
expect(parameters.headerParameters?.parameters[0].value).toBe(
|
||||
`Basic ${Buffer.from('login:password').toString('base64')}`,
|
||||
);
|
||||
expect(parameters.options.proxy).toBe('https://google.com');
|
||||
});
|
||||
|
||||
test('Should parse proxy flag --proxy', () => {
|
||||
const curl = 'curl https://reqbin.com/echo -u "login:password" -x https://google.com';
|
||||
const parameters = toHttpNodeParameters(curl);
|
||||
expect(parameters.url).toBe('https://reqbin.com/echo');
|
||||
expect(parameters.sendBody).toBe(false);
|
||||
expect(parameters.contentType).toBeUndefined();
|
||||
expect(parameters.sendQuery).toBe(false);
|
||||
expect(parameters.sendHeaders).toBe(true);
|
||||
expect(parameters.headerParameters?.parameters[0].name).toBe('authorization');
|
||||
expect(parameters.headerParameters?.parameters[0].value).toBe(
|
||||
`Basic ${Buffer.from('login:password').toString('base64')}`,
|
||||
);
|
||||
expect(parameters.options.proxy).toBe('https://google.com');
|
||||
});
|
||||
|
||||
test('Should parse include headers on output flag --include', () => {
|
||||
const curl =
|
||||
'curl https://reqbin.com/echo -u "login:password" --include -x https://google.com';
|
||||
const parameters = toHttpNodeParameters(curl);
|
||||
expect(parameters.url).toBe('https://reqbin.com/echo');
|
||||
expect(parameters.sendBody).toBe(false);
|
||||
expect(parameters.contentType).toBeUndefined();
|
||||
expect(parameters.sendQuery).toBe(false);
|
||||
expect(parameters.sendHeaders).toBe(true);
|
||||
expect(parameters.headerParameters?.parameters[0].name).toBe('authorization');
|
||||
expect(parameters.headerParameters?.parameters[0].value).toBe(
|
||||
`Basic ${Buffer.from('login:password').toString('base64')}`,
|
||||
);
|
||||
expect(parameters.options.response.response.fullResponse).toBe(true);
|
||||
});
|
||||
|
||||
test('Should parse include headers on output flag -i', () => {
|
||||
const curl = 'curl https://reqbin.com/echo -u "login:password" -x https://google.com -i';
|
||||
const parameters = toHttpNodeParameters(curl);
|
||||
expect(parameters.url).toBe('https://reqbin.com/echo');
|
||||
expect(parameters.sendBody).toBe(false);
|
||||
expect(parameters.contentType).toBeUndefined();
|
||||
expect(parameters.sendQuery).toBe(false);
|
||||
expect(parameters.sendHeaders).toBe(true);
|
||||
expect(parameters.headerParameters?.parameters[0].name).toBe('authorization');
|
||||
expect(parameters.headerParameters?.parameters[0].value).toBe(
|
||||
`Basic ${Buffer.from('login:password').toString('base64')}`,
|
||||
);
|
||||
expect(parameters.options.response.response.fullResponse).toBe(true);
|
||||
});
|
||||
|
||||
test('Should parse include request flag -X', () => {
|
||||
const curl = 'curl -X POST https://reqbin.com/echo -u "login:password" -x https://google.com';
|
||||
const parameters = toHttpNodeParameters(curl);
|
||||
expect(parameters.url).toBe('https://reqbin.com/echo');
|
||||
expect(parameters.method).toBe('POST');
|
||||
expect(parameters.sendBody).toBe(false);
|
||||
});
|
||||
|
||||
test('Should parse include request flag --request', () => {
|
||||
const curl =
|
||||
'curl --request POST https://reqbin.com/echo -u "login:password" -x https://google.com';
|
||||
const parameters = toHttpNodeParameters(curl);
|
||||
expect(parameters.url).toBe('https://reqbin.com/echo');
|
||||
expect(parameters.method).toBe('POST');
|
||||
expect(parameters.sendBody).toBe(false);
|
||||
});
|
||||
|
||||
test('Should parse include timeout flag --connect-timeout', () => {
|
||||
const curl =
|
||||
'curl --request POST https://reqbin.com/echo -u "login:password" --connect-timeout 20';
|
||||
const parameters = toHttpNodeParameters(curl);
|
||||
expect(parameters.url).toBe('https://reqbin.com/echo');
|
||||
expect(parameters.method).toBe('POST');
|
||||
expect(parameters.sendBody).toBe(false);
|
||||
expect(parameters.options.timeout).toBe(20000);
|
||||
});
|
||||
|
||||
test('Should parse download file flag --output', () => {
|
||||
const curl = 'curl --request POST https://reqbin.com/echo -u "login:password" --output data';
|
||||
const parameters = toHttpNodeParameters(curl);
|
||||
expect(parameters.url).toBe('https://reqbin.com/echo');
|
||||
expect(parameters.method).toBe('POST');
|
||||
expect(parameters.sendBody).toBe(false);
|
||||
expect(parameters.options.response.response.responseFormat).toBe('file');
|
||||
expect(parameters.options.response.response.outputPropertyName).toBe('data');
|
||||
});
|
||||
|
||||
test('Should parse download file flag -o', () => {
|
||||
const curl = 'curl --request POST https://reqbin.com/echo -u "login:password" -o data';
|
||||
const parameters = toHttpNodeParameters(curl);
|
||||
expect(parameters.url).toBe('https://reqbin.com/echo');
|
||||
expect(parameters.method).toBe('POST');
|
||||
expect(parameters.sendBody).toBe(false);
|
||||
expect(parameters.options.response.response.responseFormat).toBe('file');
|
||||
expect(parameters.options.response.response.outputPropertyName).toBe('data');
|
||||
});
|
||||
|
||||
test('Should parse ignore SSL flag -k', () => {
|
||||
const curl = 'curl --request POST https://reqbin.com/echo -u "login:password" -k';
|
||||
const parameters = toHttpNodeParameters(curl);
|
||||
expect(parameters.url).toBe('https://reqbin.com/echo');
|
||||
expect(parameters.method).toBe('POST');
|
||||
expect(parameters.sendBody).toBe(false);
|
||||
expect(parameters.options.allowUnauthorizedCerts).toBe(true);
|
||||
});
|
||||
|
||||
test('Should parse ignore SSL flag --insecure', () => {
|
||||
const curl = 'curl --request POST https://reqbin.com/echo -u "login:password" --insecure';
|
||||
const parameters = toHttpNodeParameters(curl);
|
||||
expect(parameters.url).toBe('https://reqbin.com/echo');
|
||||
expect(parameters.method).toBe('POST');
|
||||
expect(parameters.sendBody).toBe(false);
|
||||
expect(parameters.options.allowUnauthorizedCerts).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,11 +1,362 @@
|
|||
import type { MaybeRef } from 'vue';
|
||||
import { unref } from 'vue';
|
||||
import get from 'lodash/get';
|
||||
import { toJsonObject as curlToJson, type JSONOutput } from 'curlconverter';
|
||||
|
||||
import { CURL_IMPORT_NODES_PROTOCOLS, CURL_IMPORT_NOT_SUPPORTED_PROTOCOLS } from '@/constants';
|
||||
import { useToast } from '@/composables/useToast';
|
||||
import { useUIStore } from '@/stores/ui.store';
|
||||
import { useI18n } from '@/composables/useI18n';
|
||||
import { importCurlEventBus } from '@/event-bus';
|
||||
import type { BaseTextKey } from '@/plugins/i18n';
|
||||
import { assert } from '@/utils/assert';
|
||||
import type { CurlToJSONResponse } from '@/Interface';
|
||||
|
||||
interface Parameter {
|
||||
parameterType?: string;
|
||||
name: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
interface HttpNodeParameters extends Record<string, unknown> {
|
||||
url?: string;
|
||||
method: string;
|
||||
sendBody?: boolean;
|
||||
authentication: string;
|
||||
contentType?: 'form-urlencoded' | 'multipart-form-data' | 'json' | 'raw' | 'binaryData';
|
||||
rawContentType?: string;
|
||||
specifyBody?: 'json' | 'keypair';
|
||||
bodyParameters?: {
|
||||
parameters: Parameter[];
|
||||
};
|
||||
jsonBody?: object;
|
||||
options: {
|
||||
allowUnauthorizedCerts?: boolean;
|
||||
proxy?: string;
|
||||
timeout?: number;
|
||||
redirect: {
|
||||
redirect: {
|
||||
followRedirects?: boolean;
|
||||
maxRedirects?: number;
|
||||
};
|
||||
};
|
||||
response: {
|
||||
response: {
|
||||
fullResponse?: boolean;
|
||||
responseFormat?: string;
|
||||
outputPropertyName?: string;
|
||||
};
|
||||
};
|
||||
};
|
||||
sendHeaders?: boolean;
|
||||
headerParameters?: {
|
||||
parameters: Parameter[];
|
||||
};
|
||||
sendQuery?: boolean;
|
||||
queryParameters?: {
|
||||
parameters: Parameter[];
|
||||
};
|
||||
}
|
||||
|
||||
type HttpNodeHeaders = Pick<HttpNodeParameters, 'sendHeaders' | 'headerParameters'>;
|
||||
|
||||
type HttpNodeQueries = Pick<HttpNodeParameters, 'sendQuery' | 'queryParameters'>;
|
||||
|
||||
const SUPPORTED_CONTENT_TYPES = [
|
||||
'application/json',
|
||||
'application/x-www-form-urlencoded',
|
||||
'multipart/form-data',
|
||||
] as const;
|
||||
type ContentTypes = (typeof SUPPORTED_CONTENT_TYPES)[number];
|
||||
|
||||
const CONTENT_TYPE_KEY = 'content-type';
|
||||
|
||||
const isContentType = (headers: JSONOutput['headers'], contentType: ContentTypes): boolean => {
|
||||
return get(headers, CONTENT_TYPE_KEY) === contentType;
|
||||
};
|
||||
|
||||
const isJsonRequest = (curlJson: JSONOutput): boolean => {
|
||||
if (isContentType(curlJson.headers, 'application/json')) return true;
|
||||
|
||||
if (curlJson.data) {
|
||||
const bodyKey = Object.keys(curlJson.data)[0];
|
||||
try {
|
||||
JSON.parse(bodyKey);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
const isFormUrlEncodedRequest = (curlJson: JSONOutput): boolean => {
|
||||
if (isContentType(curlJson.headers, 'application/x-www-form-urlencoded')) return true;
|
||||
if (curlJson.data && !curlJson.files) return true;
|
||||
return false;
|
||||
};
|
||||
|
||||
const isMultipartRequest = (curlJson: JSONOutput): boolean => {
|
||||
if (isContentType(curlJson.headers, 'multipart/form-data')) return true;
|
||||
|
||||
if (curlJson.files)
|
||||
// only multipart/form-data request include files
|
||||
return true;
|
||||
return false;
|
||||
};
|
||||
|
||||
const isBinaryRequest = (curlJson: JSONOutput): boolean => {
|
||||
if (curlJson?.headers?.[CONTENT_TYPE_KEY]) {
|
||||
const contentType = curlJson?.headers?.[CONTENT_TYPE_KEY];
|
||||
return ['image', 'video', 'audio'].some((d) => contentType.includes(d));
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
const toKeyValueArray = ([key, value]: [string, unknown]) => ({
|
||||
name: key,
|
||||
value: value?.toString() ?? '',
|
||||
});
|
||||
|
||||
const extractHeaders = (headers: JSONOutput['headers'] = {}): HttpNodeHeaders => {
|
||||
const emptyHeaders = !Object.keys(headers).length;
|
||||
|
||||
const onlyContentTypeHeaderDefined =
|
||||
Object.keys(headers).length === 1 && headers[CONTENT_TYPE_KEY] !== undefined;
|
||||
|
||||
if (emptyHeaders || onlyContentTypeHeaderDefined) return { sendHeaders: false };
|
||||
|
||||
return {
|
||||
sendHeaders: true,
|
||||
headerParameters: {
|
||||
parameters: Object.entries(headers)
|
||||
.map(toKeyValueArray)
|
||||
.filter((parameter) => parameter.name !== CONTENT_TYPE_KEY),
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const extractQueries = (queries: JSONOutput['queries'] = {}): HttpNodeQueries => {
|
||||
const emptyQueries = !Object.keys(queries).length;
|
||||
|
||||
if (emptyQueries) return { sendQuery: false };
|
||||
|
||||
return {
|
||||
sendQuery: true,
|
||||
queryParameters: {
|
||||
parameters: Object.entries(queries).map(toKeyValueArray),
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const jsonBodyToNodeParameters = (body: JSONOutput['data'] = {}): Parameter[] | [] => {
|
||||
return Object.entries(body).map(toKeyValueArray);
|
||||
};
|
||||
|
||||
const multipartToNodeParameters = (
|
||||
body: JSONOutput['data'] = {},
|
||||
files: JSONOutput['files'] = {},
|
||||
): Parameter[] | [] => {
|
||||
return [
|
||||
...Object.entries(body)
|
||||
.map(toKeyValueArray)
|
||||
.map((e) => ({ parameterType: 'formData', ...e })),
|
||||
...Object.entries(files)
|
||||
.map(toKeyValueArray)
|
||||
.map((e) => ({ parameterType: 'formBinaryData', ...e })),
|
||||
];
|
||||
};
|
||||
|
||||
const keyValueBodyToNodeParameters = (body: JSONOutput['data'] = {}): Parameter[] | [] => {
|
||||
return Object.entries(body).map(toKeyValueArray);
|
||||
};
|
||||
|
||||
const lowerCaseContentTypeKey = (obj: JSONOutput['headers']): void => {
|
||||
if (!obj) return;
|
||||
|
||||
const regex = new RegExp(CONTENT_TYPE_KEY, 'gi');
|
||||
|
||||
const contentTypeKey = Object.keys(obj).find((key) => !!Array.from(key.matchAll(regex)).length);
|
||||
|
||||
if (!contentTypeKey) return;
|
||||
|
||||
const value = obj[contentTypeKey];
|
||||
delete obj[contentTypeKey];
|
||||
obj[CONTENT_TYPE_KEY] = value;
|
||||
};
|
||||
|
||||
const encodeBasicAuthentication = (username: string, password: string) =>
|
||||
btoa(`${username}:${password}`);
|
||||
const jsonHasNestedObjects = (json: { [key: string]: string | number | object }) =>
|
||||
Object.values(json).some((e) => typeof e === 'object');
|
||||
|
||||
const mapCookies = (cookies: JSONOutput['cookies']): { cookie: string } | {} => {
|
||||
if (!cookies) return {};
|
||||
|
||||
const cookiesValues = Object.entries(cookies).reduce(
|
||||
(accumulator: string, entry: [string, string]) => {
|
||||
accumulator += `${entry[0]}=${entry[1]};`;
|
||||
return accumulator;
|
||||
},
|
||||
'',
|
||||
);
|
||||
|
||||
if (!cookiesValues) return {};
|
||||
|
||||
return {
|
||||
cookie: cookiesValues,
|
||||
};
|
||||
};
|
||||
|
||||
export const flattenObject = <T extends Record<string, unknown>>(obj: T, prefix = '') =>
|
||||
Object.keys(obj).reduce(
|
||||
(acc, k) => {
|
||||
const pre = prefix.length ? prefix + '.' : '';
|
||||
if (typeof obj[k] === 'object') Object.assign(acc, flattenObject(obj[k] as T, pre + k));
|
||||
else acc[pre + k] = obj[k];
|
||||
return acc;
|
||||
},
|
||||
{} as Record<string, unknown>,
|
||||
);
|
||||
|
||||
export const toHttpNodeParameters = (curlCommand: string): HttpNodeParameters => {
|
||||
const curlJson = curlToJson(curlCommand);
|
||||
|
||||
const headers = curlJson.headers ?? {};
|
||||
|
||||
lowerCaseContentTypeKey(headers);
|
||||
|
||||
// set basic authentication
|
||||
if (curlJson.auth) {
|
||||
const { user, password: pass } = curlJson.auth;
|
||||
headers.authorization = `Basic ${encodeBasicAuthentication(user, pass)}`;
|
||||
}
|
||||
|
||||
const httpNodeParameters: HttpNodeParameters = {
|
||||
url: curlJson.url,
|
||||
authentication: 'none',
|
||||
method: curlJson.method.toUpperCase(),
|
||||
...extractHeaders({ ...headers, ...mapCookies(curlJson.cookies) }),
|
||||
...extractQueries(curlJson.queries),
|
||||
options: {
|
||||
redirect: {
|
||||
redirect: {},
|
||||
},
|
||||
response: {
|
||||
response: {},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
if (curlJson.follow_redirects) {
|
||||
httpNodeParameters.options.redirect.redirect.followRedirects = true;
|
||||
if (curlJson.max_redirects) {
|
||||
httpNodeParameters.options.redirect.redirect.maxRedirects = curlJson.max_redirects;
|
||||
}
|
||||
}
|
||||
|
||||
if (curlJson.proxy) {
|
||||
httpNodeParameters.options.proxy = curlJson.proxy;
|
||||
}
|
||||
|
||||
if (curlJson.connect_timeout !== undefined) {
|
||||
httpNodeParameters.options.timeout = Math.floor(curlJson.connect_timeout * 1000);
|
||||
}
|
||||
|
||||
if (curlJson.output) {
|
||||
httpNodeParameters.options.response.response = {
|
||||
responseFormat: 'file',
|
||||
outputPropertyName: curlJson.output ?? 'data',
|
||||
};
|
||||
}
|
||||
|
||||
if (curlJson.insecure !== undefined) {
|
||||
httpNodeParameters.options.allowUnauthorizedCerts = true;
|
||||
}
|
||||
|
||||
if (curlJson.include) {
|
||||
httpNodeParameters.options.response.response.fullResponse = true;
|
||||
httpNodeParameters.options.response.response.responseFormat = 'autodetect';
|
||||
}
|
||||
|
||||
const contentType = curlJson?.headers?.[CONTENT_TYPE_KEY] as ContentTypes;
|
||||
|
||||
if (isBinaryRequest(curlJson)) {
|
||||
return Object.assign(httpNodeParameters, {
|
||||
contentType: 'binaryData',
|
||||
sendBody: true,
|
||||
});
|
||||
}
|
||||
|
||||
if (contentType && !SUPPORTED_CONTENT_TYPES.includes(contentType)) {
|
||||
return Object.assign(httpNodeParameters, {
|
||||
sendBody: true,
|
||||
contentType: 'raw',
|
||||
rawContentType: contentType,
|
||||
body: Object.keys(curlJson?.data ?? {})[0],
|
||||
});
|
||||
}
|
||||
|
||||
if (isJsonRequest(curlJson)) {
|
||||
Object.assign(httpNodeParameters, {
|
||||
contentType: 'json',
|
||||
sendBody: true,
|
||||
});
|
||||
|
||||
if (curlJson.data) {
|
||||
const json = curlJson.data;
|
||||
|
||||
if (jsonHasNestedObjects(json)) {
|
||||
// json body
|
||||
Object.assign(httpNodeParameters, {
|
||||
specifyBody: 'json',
|
||||
jsonBody: JSON.stringify(json, null, 2),
|
||||
});
|
||||
} else {
|
||||
// key-value body
|
||||
Object.assign(httpNodeParameters, {
|
||||
specifyBody: 'keypair',
|
||||
bodyParameters: {
|
||||
parameters: jsonBodyToNodeParameters(curlJson.data),
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
} else if (isFormUrlEncodedRequest(curlJson)) {
|
||||
Object.assign(httpNodeParameters, {
|
||||
contentType: 'form-urlencoded',
|
||||
sendBody: true,
|
||||
specifyBody: 'keypair',
|
||||
bodyParameters: {
|
||||
parameters: keyValueBodyToNodeParameters(curlJson.data),
|
||||
},
|
||||
});
|
||||
} else if (isMultipartRequest(curlJson)) {
|
||||
Object.assign(httpNodeParameters, {
|
||||
contentType: 'multipart-form-data',
|
||||
sendBody: true,
|
||||
bodyParameters: {
|
||||
parameters: multipartToNodeParameters(curlJson.data, curlJson.files),
|
||||
},
|
||||
});
|
||||
} else {
|
||||
// could not figure the content type so do not set the body
|
||||
Object.assign(httpNodeParameters, {
|
||||
sendBody: false,
|
||||
});
|
||||
}
|
||||
|
||||
if (!Object.keys(httpNodeParameters.options?.redirect.redirect).length) {
|
||||
// @ts-ignore
|
||||
delete httpNodeParameters.options.redirect;
|
||||
}
|
||||
|
||||
if (!Object.keys(httpNodeParameters.options.response.response).length) {
|
||||
// @ts-ignore
|
||||
delete httpNodeParameters.options.response;
|
||||
}
|
||||
|
||||
return httpNodeParameters;
|
||||
};
|
||||
|
||||
export function useImportCurlCommand(options?: {
|
||||
onImportSuccess?: () => void;
|
||||
|
@ -18,7 +369,6 @@ export function useImportCurlCommand(options?: {
|
|||
};
|
||||
};
|
||||
}) {
|
||||
const uiStore = useUIStore();
|
||||
const toast = useToast();
|
||||
const i18n = useI18n();
|
||||
|
||||
|
@ -30,20 +380,28 @@ export function useImportCurlCommand(options?: {
|
|||
...options?.i18n,
|
||||
};
|
||||
|
||||
async function importCurlCommand(curlCommandRef: MaybeRef<string>): Promise<void> {
|
||||
function importCurlCommand(curlCommandRef: MaybeRef<string>): void {
|
||||
const curlCommand = unref(curlCommandRef);
|
||||
if (curlCommand === '') return;
|
||||
|
||||
try {
|
||||
const parameters = await uiStore.getCurlToJson(curlCommand);
|
||||
const url = parameters['parameters.url'];
|
||||
const parameters = flattenObject(toHttpNodeParameters(curlCommand), 'parameters');
|
||||
assert(typeof parameters['parameters.url'] === 'string', 'parameters.url has to be string');
|
||||
// Normalize placeholder values
|
||||
const url: string = parameters['parameters.url']
|
||||
.replaceAll('%7B', '{')
|
||||
.replaceAll('%7D', '}');
|
||||
|
||||
const invalidProtocol = CURL_IMPORT_NOT_SUPPORTED_PROTOCOLS.find((p) =>
|
||||
url.includes(`${p}://`),
|
||||
);
|
||||
|
||||
if (!invalidProtocol) {
|
||||
importCurlEventBus.emit('setHttpNodeParameters', parameters);
|
||||
parameters['parameters.url'] = url;
|
||||
importCurlEventBus.emit(
|
||||
'setHttpNodeParameters',
|
||||
parameters as unknown as CurlToJSONResponse,
|
||||
);
|
||||
|
||||
options?.onImportSuccess?.();
|
||||
|
||||
|
|
|
@ -51,7 +51,6 @@ import type {
|
|||
} from '@/Interface';
|
||||
import { defineStore } from 'pinia';
|
||||
import { useRootStore } from '@/stores/root.store';
|
||||
import * as curlParserApi from '@/api/curlHelper';
|
||||
import { useWorkflowsStore } from '@/stores/workflows.store';
|
||||
import { useSettingsStore } from '@/stores/settings.store';
|
||||
import { useUsersStore } from '@/stores/users.store';
|
||||
|
@ -524,19 +523,6 @@ export const useUIStore = defineStore(STORES.UI, () => {
|
|||
sidebarMenuCollapsed.value = newCollapsedState;
|
||||
};
|
||||
|
||||
const getCurlToJson = async (curlCommand: string) => {
|
||||
const parameters = await curlParserApi.getCurlToJson(rootStore.restApiContext, curlCommand);
|
||||
|
||||
// Normalize placeholder values
|
||||
if (parameters['parameters.url']) {
|
||||
parameters['parameters.url'] = parameters['parameters.url']
|
||||
.replaceAll('%7B', '{')
|
||||
.replaceAll('%7D', '}');
|
||||
}
|
||||
|
||||
return parameters;
|
||||
};
|
||||
|
||||
const removeBannerFromStack = (name: BannerName) => {
|
||||
bannerStack.value = bannerStack.value.filter((bannerName) => bannerName !== name);
|
||||
};
|
||||
|
@ -653,7 +639,6 @@ export const useUIStore = defineStore(STORES.UI, () => {
|
|||
resetSelectedNodes,
|
||||
setCurlCommand,
|
||||
toggleSidebarMenuCollapse,
|
||||
getCurlToJson,
|
||||
removeBannerFromStack,
|
||||
dismissBanner,
|
||||
updateBannersHeight,
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import vue from '@vitejs/plugin-vue';
|
||||
import { resolve } from 'path';
|
||||
import { defineConfig, mergeConfig } from 'vite';
|
||||
import { viteStaticCopy } from 'vite-plugin-static-copy';
|
||||
import svgLoader from 'vite-svg-loader';
|
||||
|
||||
import { vitestConfig } from '@n8n/frontend-vitest-config';
|
||||
|
@ -63,6 +64,12 @@ const plugins = [
|
|||
}),
|
||||
],
|
||||
}),
|
||||
viteStaticCopy({
|
||||
targets: [
|
||||
{ src: resolve('node_modules/web-tree-sitter/tree-sitter.wasm'), dest: '' },
|
||||
{ src: resolve('node_modules/curlconverter/dist/tree-sitter-bash.wasm'), dest: '' },
|
||||
],
|
||||
}),
|
||||
vue(),
|
||||
svgLoader(),
|
||||
legacy({
|
||||
|
@ -73,6 +80,7 @@ const plugins = [
|
|||
];
|
||||
|
||||
const { RELEASE: release } = process.env;
|
||||
const target = browserslistToEsbuild(browsers);
|
||||
|
||||
export default mergeConfig(
|
||||
defineConfig({
|
||||
|
@ -100,7 +108,12 @@ export default mergeConfig(
|
|||
build: {
|
||||
minify: !!release,
|
||||
sourcemap: !!release,
|
||||
target: browserslistToEsbuild(browsers),
|
||||
target,
|
||||
},
|
||||
optimizeDeps: {
|
||||
esbuildOptions: {
|
||||
target,
|
||||
},
|
||||
},
|
||||
worker: {
|
||||
format: 'es',
|
||||
|
|
1725
patches/curlconverter@4.11.0.patch
Normal file
1725
patches/curlconverter@4.11.0.patch
Normal file
File diff suppressed because one or more lines are too long
428
pnpm-lock.yaml
428
pnpm-lock.yaml
|
@ -151,6 +151,9 @@ patchedDependencies:
|
|||
bull@4.12.1:
|
||||
hash: ep6h4rqtpclldfcdohxlgcb3aq
|
||||
path: patches/bull@4.12.1.patch
|
||||
curlconverter@4.11.0:
|
||||
hash: ymigioonidbyw7jydnlor7bohu
|
||||
path: patches/curlconverter@4.11.0.patch
|
||||
pkce-challenge@3.0.0:
|
||||
hash: dypouzb3lve7vncq25i5fuanki
|
||||
path: patches/pkce-challenge@3.0.0.patch
|
||||
|
@ -868,9 +871,6 @@ importers:
|
|||
csrf:
|
||||
specifier: 3.1.0
|
||||
version: 3.1.0
|
||||
curlconverter:
|
||||
specifier: 3.21.0
|
||||
version: 3.21.0(chokidar@4.0.1)
|
||||
dotenv:
|
||||
specifier: 8.6.0
|
||||
version: 8.6.0
|
||||
|
@ -1516,6 +1516,9 @@ importers:
|
|||
core-js:
|
||||
specifier: ^3.40.0
|
||||
version: 3.40.0
|
||||
curlconverter:
|
||||
specifier: ^4.11.0
|
||||
version: 4.11.0(patch_hash=ymigioonidbyw7jydnlor7bohu)
|
||||
dateformat:
|
||||
specifier: ^3.0.3
|
||||
version: 3.0.3
|
||||
|
@ -1612,6 +1615,9 @@ importers:
|
|||
vuedraggable:
|
||||
specifier: 4.1.0
|
||||
version: 4.1.0(vue@3.5.13(typescript@5.7.2))
|
||||
web-tree-sitter:
|
||||
specifier: 0.24.3
|
||||
version: 0.24.3
|
||||
xss:
|
||||
specifier: 'catalog:'
|
||||
version: 1.0.15
|
||||
|
@ -1682,6 +1688,9 @@ importers:
|
|||
vite:
|
||||
specifier: catalog:frontend
|
||||
version: 6.0.2(@types/node@18.16.16)(jiti@1.21.0)(sass@1.64.1)(terser@5.16.1)
|
||||
vite-plugin-static-copy:
|
||||
specifier: 2.2.0
|
||||
version: 2.2.0(vite@6.0.2(@types/node@18.16.16)(jiti@1.21.0)(sass@1.64.1)(terser@5.16.1))
|
||||
vite-svg-loader:
|
||||
specifier: 5.1.0
|
||||
version: 5.1.0(vue@3.5.13(typescript@5.7.2))
|
||||
|
@ -3304,14 +3313,6 @@ packages:
|
|||
resolution: {integrity: sha512-/Z3l6pXthq0JvMYdUFyX9j0MaCltlIn6mfh9jLyQwg5aPKxkyNa0PTHtU1AlFXLNk55ZuAeJRcpvq+tmLfKmaQ==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
'@curlconverter/yargs-parser@0.0.1':
|
||||
resolution: {integrity: sha512-DbEVRYqrorzwqc63MQ3RODflut1tNla8ZCKo1h83lF7+fbntgubZsDfRDBv5Lxj3vkKuvAolysNM2ekwJev8wA==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
'@curlconverter/yargs@0.0.2':
|
||||
resolution: {integrity: sha512-Q1YEebpCY61kxme4wvU0/IN/uMBfG5pZOKCo9FU+w20ElPvN+eH2qEVbK1C12t3Tee3qeYLLEU6HkiUeO1gc4A==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
'@cypress/request@3.0.1':
|
||||
resolution: {integrity: sha512-TWivJlJi8ZDx2wGOw1dbLuHJKUYX7bWySw377nlnGOW3hP9/MUKIsEdXT/YngWxVdgNCHRBmFlBipE+5/2ZZlQ==}
|
||||
engines: {node: '>= 6'}
|
||||
|
@ -6492,9 +6493,6 @@ packages:
|
|||
engines: {node: '>=10.0.0'}
|
||||
deprecated: this version has critical issues, please update to the latest version
|
||||
|
||||
a-sync-waterfall@1.0.1:
|
||||
resolution: {integrity: sha512-RYTOHHdWipFUliRFMCS4X2Yn2X8M87V/OpSqWzKKOGhzqyUxzyVmhHDH9sAvG+ZuQf/TAOFsLCpMw09I1ufUnA==}
|
||||
|
||||
abab@2.0.6:
|
||||
resolution: {integrity: sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==}
|
||||
deprecated: Use your platform's native atob() and btoa() methods instead
|
||||
|
@ -6685,9 +6683,6 @@ packages:
|
|||
aria-query@5.3.0:
|
||||
resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==}
|
||||
|
||||
array-buffer-byte-length@1.0.0:
|
||||
resolution: {integrity: sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==}
|
||||
|
||||
array-buffer-byte-length@1.0.1:
|
||||
resolution: {integrity: sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
@ -6728,10 +6723,6 @@ packages:
|
|||
resolution: {integrity: sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
arraybuffer.prototype.slice@1.0.1:
|
||||
resolution: {integrity: sha512-09x0ZWFEjj4WD8PDbykUwo3t9arLn8NIzmmYEJFpYekOAQjpkGSyrQhNoRTcwwcFRu+ycWF78QZ63oWTqSjBcw==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
arraybuffer.prototype.slice@1.0.3:
|
||||
resolution: {integrity: sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
@ -7358,10 +7349,6 @@ packages:
|
|||
resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==}
|
||||
engines: {node: '>= 6'}
|
||||
|
||||
commander@5.1.0:
|
||||
resolution: {integrity: sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==}
|
||||
engines: {node: '>= 6'}
|
||||
|
||||
commander@6.2.1:
|
||||
resolution: {integrity: sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==}
|
||||
engines: {node: '>= 6'}
|
||||
|
@ -7464,10 +7451,6 @@ packages:
|
|||
cookie-signature@1.0.6:
|
||||
resolution: {integrity: sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==}
|
||||
|
||||
cookie@0.4.2:
|
||||
resolution: {integrity: sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==}
|
||||
engines: {node: '>= 0.6'}
|
||||
|
||||
cookie@0.7.1:
|
||||
resolution: {integrity: sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==}
|
||||
engines: {node: '>= 0.6'}
|
||||
|
@ -7605,8 +7588,8 @@ packages:
|
|||
csv-parse@5.5.0:
|
||||
resolution: {integrity: sha512-RxruSK3M4XgzcD7Trm2wEN+SJ26ChIb903+IWxNOcB5q4jT2Cs+hFr6QP39J05EohshRFEvyzEBoZ/466S2sbw==}
|
||||
|
||||
curlconverter@3.21.0:
|
||||
resolution: {integrity: sha512-DXCnp1A/Xa69FujksUfdvWQFAnIn/C+4Wuv8t+UVdZkF/lY5bzj98GGKOGme7V/ckSHDLxE29Xp76sJ5Cpsp5A==}
|
||||
curlconverter@4.11.0:
|
||||
resolution: {integrity: sha512-jBSvfDN10L6rGWVlkAYgtkIG8lYprDvtBgos7mafxtv15keYeQWsxUgnzns3JmqEcGJMeaGlDNdRUszURPCUaw==}
|
||||
hasBin: true
|
||||
|
||||
currency-codes@2.1.0:
|
||||
|
@ -7780,10 +7763,6 @@ packages:
|
|||
decko@1.2.0:
|
||||
resolution: {integrity: sha512-m8FnyHXV1QX+S1cl+KPFDIl6NMkxtKsy6+U/aYyjrOqWMuwAwYWu7ePqrsUHtDR5Y8Yk2pi/KIDSgF+vT4cPOQ==}
|
||||
|
||||
decode-uri-component@0.2.2:
|
||||
resolution: {integrity: sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==}
|
||||
engines: {node: '>=0.10'}
|
||||
|
||||
decompress-response@6.0.0:
|
||||
resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==}
|
||||
engines: {node: '>=10'}
|
||||
|
@ -8106,10 +8085,6 @@ packages:
|
|||
error-ex@1.3.2:
|
||||
resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==}
|
||||
|
||||
es-abstract@1.22.1:
|
||||
resolution: {integrity: sha512-ioRRcXMO6OFyRpyzV3kE1IIBd4WG5/kltnzdxSCqoP8CMGs/Li+M1uF5o7lOkZVFjDs+NLesthnF66Pg/0q0Lw==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
es-abstract@1.22.5:
|
||||
resolution: {integrity: sha512-oW69R+4q2wG+Hc3KZePPZxOiisRIqfKBVo/HLx94QcJeWGU/8sZhCvc829rd1kS366vlJbzBfXf9yWwf0+Ko7w==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
@ -8143,10 +8118,6 @@ packages:
|
|||
resolution: {integrity: sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
es-set-tostringtag@2.0.1:
|
||||
resolution: {integrity: sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
es-set-tostringtag@2.0.3:
|
||||
resolution: {integrity: sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
@ -8595,10 +8566,6 @@ packages:
|
|||
resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
filter-obj@1.1.0:
|
||||
resolution: {integrity: sha512-8rXg1ZnX7xzy2NGDVkBVaAy+lSlPNwad13BtgSlLuxfIslyt5Vg64U7tFcCt4WS1R0hvtnQybT/IyCkGZ3DpXQ==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
finalhandler@1.3.1:
|
||||
resolution: {integrity: sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==}
|
||||
engines: {node: '>= 0.8'}
|
||||
|
@ -8707,6 +8674,10 @@ packages:
|
|||
fs-constants@1.0.0:
|
||||
resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==}
|
||||
|
||||
fs-extra@11.3.0:
|
||||
resolution: {integrity: sha512-Z4XaCL6dUDHfP/jT25jJKMmtxvuwbkrD1vNSMFlo9lNLY2c5FHYSQgHPRZUjAB26TpDEoW9HCOgplrdbaPV/ew==}
|
||||
engines: {node: '>=14.14'}
|
||||
|
||||
fs-extra@7.0.1:
|
||||
resolution: {integrity: sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==}
|
||||
engines: {node: '>=6 <7 || >=8'}
|
||||
|
@ -8735,10 +8706,6 @@ packages:
|
|||
function-bind@1.1.2:
|
||||
resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==}
|
||||
|
||||
function.prototype.name@1.1.5:
|
||||
resolution: {integrity: sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
function.prototype.name@1.1.6:
|
||||
resolution: {integrity: sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
@ -8809,10 +8776,6 @@ packages:
|
|||
resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
get-symbol-description@1.0.0:
|
||||
resolution: {integrity: sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
get-symbol-description@1.0.2:
|
||||
resolution: {integrity: sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
@ -9197,10 +9160,6 @@ packages:
|
|||
resolution: {integrity: sha512-JG3eIAj5V9CwcGvuOmoo6LB9kbAYT8HXffUl6memuszlwDC/qvFAJw49XJ5NROSFNPxp3iQg1GqkFhaY/CR0IA==}
|
||||
engines: {node: '>=8.0.0'}
|
||||
|
||||
internal-slot@1.0.5:
|
||||
resolution: {integrity: sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
internal-slot@1.0.7:
|
||||
resolution: {integrity: sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
@ -9232,9 +9191,6 @@ packages:
|
|||
resolution: {integrity: sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
is-array-buffer@3.0.2:
|
||||
resolution: {integrity: sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==}
|
||||
|
||||
is-array-buffer@3.0.4:
|
||||
resolution: {integrity: sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
@ -9323,10 +9279,6 @@ packages:
|
|||
resolution: {integrity: sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
is-negative-zero@2.0.2:
|
||||
resolution: {integrity: sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
is-negative-zero@2.0.3:
|
||||
resolution: {integrity: sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
@ -9370,9 +9322,6 @@ packages:
|
|||
is-set@2.0.2:
|
||||
resolution: {integrity: sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g==}
|
||||
|
||||
is-shared-array-buffer@1.0.2:
|
||||
resolution: {integrity: sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==}
|
||||
|
||||
is-shared-array-buffer@1.0.3:
|
||||
resolution: {integrity: sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
@ -10153,6 +10102,9 @@ packages:
|
|||
lossless-json@1.0.5:
|
||||
resolution: {integrity: sha512-RicKUuLwZVNZ6ZdJHgIZnSeA05p8qWc5NW0uR96mpPIjN9WDLUg9+kj1esQU1GkPn9iLZVKatSQK5gyiaFHgJA==}
|
||||
|
||||
lossless-json@4.0.2:
|
||||
resolution: {integrity: sha512-+z0EaLi2UcWi8MZRxA5iTb6m4Ys4E80uftGY+yG5KNFJb5EceQXOhdW/pWJZ8m97s26u7yZZAYMcKWNztSZssA==}
|
||||
|
||||
loupe@3.1.2:
|
||||
resolution: {integrity: sha512-23I4pFZHmAemUnz8WZXbYRSKYj801VDaNv9ETuMh7IrMc7VuVVSo+Z9iLE3ni30+U48iDWfi30d3twAXBYmnCg==}
|
||||
|
||||
|
@ -10741,6 +10693,10 @@ packages:
|
|||
resolution: {integrity: sha512-mNcltoe1R8o7STTegSOHdnJNN7s5EUvhoS7ShnTHDyOSd+8H+UdWODq6qSv67PjC8Zc5JRT8+oLAMCr0SIXw7g==}
|
||||
engines: {node: ^16 || ^18 || >= 20}
|
||||
|
||||
node-addon-api@8.3.0:
|
||||
resolution: {integrity: sha512-8VOpLHFrOQlAH+qA0ZzuGRlALRA6/LVh8QJldbrC4DY0hXoMP0l4Acq8TzFC018HztWiRqyCEj2aTWY2UvnJUg==}
|
||||
engines: {node: ^18 || ^20 || >= 21}
|
||||
|
||||
node-cleanup@2.1.2:
|
||||
resolution: {integrity: sha512-qN8v/s2PAJwGUtr1/hYTpNKlD6Y9rc4p8KSmJXyGdYGZsDGKXrGThikLFP9OCHFeLeEpQzPwiAtdIvBLqm//Hw==}
|
||||
|
||||
|
@ -10781,6 +10737,10 @@ packages:
|
|||
resolution: {integrity: sha512-YlCCc6Wffkx0kHkmam79GKvDQ6x+QZkMjFGrIMxgFNILFvGSbCp2fCBC55pGTT9gVaz8Na5CLmxt/urtzRv36w==}
|
||||
hasBin: true
|
||||
|
||||
node-gyp-build@4.8.4:
|
||||
resolution: {integrity: sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==}
|
||||
hasBin: true
|
||||
|
||||
node-gyp@8.4.1:
|
||||
resolution: {integrity: sha512-olTJRgUtAb/hOXG0E93wZDs5YiJlgbXxTwQAFHyNlRsXQnYzUaF2aGgujZbw+hR8aF4ZG/rST57bWMWD16jr9w==}
|
||||
engines: {node: '>= 10.12.0'}
|
||||
|
@ -10879,16 +10839,6 @@ packages:
|
|||
number-allocator@1.0.14:
|
||||
resolution: {integrity: sha512-OrL44UTVAvkKdOdRQZIJpLkAdjXGTRda052sN4sO77bKEzYYqWKMBjQvrJFzqygI99gL6Z4u2xctPW1tB8ErvA==}
|
||||
|
||||
nunjucks@3.2.4:
|
||||
resolution: {integrity: sha512-26XRV6BhkgK0VOxfbU5cQI+ICFUtMLixv1noZn1tGU38kQH5A5nmmbk/O45xdyBhD1esk47nKrY0mvQpZIhRjQ==}
|
||||
engines: {node: '>= 6.9.0'}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
chokidar: ^4.0.1
|
||||
peerDependenciesMeta:
|
||||
chokidar:
|
||||
optional: true
|
||||
|
||||
nwsapi@2.2.7:
|
||||
resolution: {integrity: sha512-ub5E4+FBPKwAZx0UwIQOjYWGHTEq5sPqHQNRN8Z9e4A7u3Tj1weLJsL59yH9vmvqEtBHaOmT6cYQKIZOxp35FQ==}
|
||||
|
||||
|
@ -11650,10 +11600,6 @@ packages:
|
|||
resolution: {integrity: sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==}
|
||||
engines: {node: '>=0.6'}
|
||||
|
||||
query-string@7.1.3:
|
||||
resolution: {integrity: sha512-hh2WYhq4fi8+b+/2Kg9CEge4fDPvHS534aOOvOZeQ3+Vf2mCFsaFBYj0i+iXcAq6I9Vzp5fjMFBlONvayDC1qg==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
querystringify@2.2.0:
|
||||
resolution: {integrity: sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==}
|
||||
|
||||
|
@ -11846,10 +11792,6 @@ packages:
|
|||
resolution: {integrity: sha512-iETxpjK6YoRWJG5o6hXLwvjYAoW+FEZn9os0PD/b6AP6xQwsa/Y7lCVgIixBbUPMfhu+i2LtdeAqVTgGlQarfA==}
|
||||
hasBin: true
|
||||
|
||||
regexp.prototype.flags@1.5.0:
|
||||
resolution: {integrity: sha512-0SutC3pNudRKgquxGoRGIz946MZVHqbNfPjBdxeOhBrdgDKlRoXmYLQN9xRbrR09ZXWeGAdPuif7egofn6v5LA==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
regexp.prototype.flags@1.5.2:
|
||||
resolution: {integrity: sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
@ -12030,10 +11972,6 @@ packages:
|
|||
rxjs@7.8.1:
|
||||
resolution: {integrity: sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==}
|
||||
|
||||
safe-array-concat@1.0.0:
|
||||
resolution: {integrity: sha512-9dVEFruWIsnie89yym+xWTAYASdpw3CJV7Li/6zBewGf9z2i1j31rP6jnY0pHEO4QZh6N0K11bFjWmdR8UGdPQ==}
|
||||
engines: {node: '>=0.4'}
|
||||
|
||||
safe-array-concat@1.1.2:
|
||||
resolution: {integrity: sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q==}
|
||||
engines: {node: '>=0.4'}
|
||||
|
@ -12044,9 +11982,6 @@ packages:
|
|||
safe-buffer@5.2.1:
|
||||
resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}
|
||||
|
||||
safe-regex-test@1.0.0:
|
||||
resolution: {integrity: sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==}
|
||||
|
||||
safe-regex-test@1.0.3:
|
||||
resolution: {integrity: sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
@ -12315,10 +12250,6 @@ packages:
|
|||
resolution: {integrity: sha512-VNiXjFp6R4ldPbVRYbpxlD35yRHceecVXlct1J4/X80KuuPnW2AXMq3sGwhnJOhKkUsOxAT6nRGfGE5pocVw5w==}
|
||||
engines: {node: '>=10.0.0'}
|
||||
|
||||
split-on-first@1.1.0:
|
||||
resolution: {integrity: sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
split2@4.2.0:
|
||||
resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==}
|
||||
engines: {node: '>= 10.x'}
|
||||
|
@ -12432,10 +12363,6 @@ packages:
|
|||
strict-event-emitter@0.5.1:
|
||||
resolution: {integrity: sha512-vMgjE/GGEPEFnhFub6pa4FmJBRBVOLpIII2hvCZ8Kzb7K0hlHo7mQv6xYrBvCL2LtAIBwFUK8wvuJgTVSQ5MFQ==}
|
||||
|
||||
strict-uri-encode@2.0.0:
|
||||
resolution: {integrity: sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ==}
|
||||
engines: {node: '>=4'}
|
||||
|
||||
string-argv@0.3.1:
|
||||
resolution: {integrity: sha512-a1uQGz7IyVy9YwhqjZIZu1c8JO8dNIe20xBmSS6qu9kv++k3JGzCVmprbNN5Kn+BgzD5E7YYwg1CcjuJMRNsvg==}
|
||||
engines: {node: '>=0.6.19'}
|
||||
|
@ -12452,13 +12379,6 @@ packages:
|
|||
resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
string.prototype.startswith@1.0.0:
|
||||
resolution: {integrity: sha512-VHhsDkuf8gsw4JNRK9cIZjYe6r7PsVUutVohaBhqYAoPaRADoQH+mMgUg7Cs/TgQeDGEvI+PzPEMOdvdsCMvpg==}
|
||||
|
||||
string.prototype.trim@1.2.7:
|
||||
resolution: {integrity: sha512-p6TmeT1T3411M8Cgg9wBTMRtY2q9+PNy9EV1i2lIXUN/btt763oIfxwN3RR8VU6wHX8j/1CFy0L+YuThm6bgOg==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
string.prototype.trim@1.2.8:
|
||||
resolution: {integrity: sha512-lfjY4HcixfQXOfaqCvcBuOIapyaroTXhbkfJN3gcB1OtyupngWK4sEET9Knd0cXd28kTUqu/kHoV4HKSJdnjiQ==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
@ -12467,18 +12387,12 @@ packages:
|
|||
resolution: {integrity: sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
string.prototype.trimend@1.0.6:
|
||||
resolution: {integrity: sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ==}
|
||||
|
||||
string.prototype.trimend@1.0.7:
|
||||
resolution: {integrity: sha512-Ni79DqeB72ZFq1uH/L6zJ+DKZTkOtPIHovb3YZHQViE+HDouuU4mBrLOLDn5Dde3RF8qw5qVETEjhu9locMLvA==}
|
||||
|
||||
string.prototype.trimend@1.0.8:
|
||||
resolution: {integrity: sha512-p73uL5VCHCO2BZZ6krwwQE3kCzM7NKmis8S//xEC6fQonchbum4eP6kR4DLEjQFO3Wnj3Fuo8NM0kOSjVdHjZQ==}
|
||||
|
||||
string.prototype.trimstart@1.0.6:
|
||||
resolution: {integrity: sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA==}
|
||||
|
||||
string.prototype.trimstart@1.0.7:
|
||||
resolution: {integrity: sha512-NGhtDFu3jCEm7B4Fy0DpLewdJQOZcQ0rGbwQ/+stjnrp2i+rlKeCvos9hOIeCmqwratM47OBxY7uFZzjxHXmrg==}
|
||||
|
||||
|
@ -12794,6 +12708,17 @@ packages:
|
|||
resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==}
|
||||
hasBin: true
|
||||
|
||||
tree-sitter-bash@0.23.3:
|
||||
resolution: {integrity: sha512-36cg/GQ2YmIbeiBeqeuh4fBJ6i4kgVouDaqTxqih5ysPag+zHufyIaxMOFeM8CeplwAK/Luj1o5XHqgdAfoCZg==}
|
||||
peerDependencies:
|
||||
tree-sitter: ^0.21.1
|
||||
peerDependenciesMeta:
|
||||
tree-sitter:
|
||||
optional: true
|
||||
|
||||
tree-sitter@0.21.1:
|
||||
resolution: {integrity: sha512-7dxoA6kYvtgWw80265MyqJlkRl4yawIjO7S5MigytjELkX43fV2WsAXzsNfO7sBpPPCF5Gp0+XzHk0DwLCq3xQ==}
|
||||
|
||||
triple-beam@1.3.0:
|
||||
resolution: {integrity: sha512-XrHUvV5HpdLmIj4uVMxHggLbFSZYIn7HEWsqePZcI50pco+MPqJ50wMGY794X7AOOhxOBAjbkqfAbEe/QMp2Lw==}
|
||||
|
||||
|
@ -13014,33 +12939,18 @@ packages:
|
|||
resolution: {integrity: sha512-SOnx8xygcAh8lvDU2exnK2bomASfNjzB3Qz71s2tw9QnX8fkAo7aC+D0H7FV0HjRKj94CKV2Hi71kVkkO6nOxg==}
|
||||
engines: {node: '>=0.10.5'}
|
||||
|
||||
typed-array-buffer@1.0.0:
|
||||
resolution: {integrity: sha512-Y8KTSIglk9OZEr8zywiIHG/kmQ7KWyjseXs1CbSo8vC42w7hg2HgYTxSWwP0+is7bWDc1H+Fo026CpHFwm8tkw==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
typed-array-buffer@1.0.2:
|
||||
resolution: {integrity: sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
typed-array-byte-length@1.0.0:
|
||||
resolution: {integrity: sha512-Or/+kvLxNpeQ9DtSydonMxCx+9ZXOswtwJn17SNLvhptaXYDJvkFFP5zbfU/uLmvnBJlI4yrnXRxpdWH/M5tNA==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
typed-array-byte-length@1.0.1:
|
||||
resolution: {integrity: sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
typed-array-byte-offset@1.0.0:
|
||||
resolution: {integrity: sha512-RD97prjEt9EL8YgAgpOkf3O4IF9lhJFr9g0htQkm0rchFp/Vx7LW5Q8fSXXub7BXAODyUQohRMyOc3faCPd0hg==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
typed-array-byte-offset@1.0.2:
|
||||
resolution: {integrity: sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
typed-array-length@1.0.4:
|
||||
resolution: {integrity: sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==}
|
||||
|
||||
typed-array-length@1.0.5:
|
||||
resolution: {integrity: sha512-yMi0PlwuznKHxKmcpoOdeLwxBoVPkqZxd7q2FgMkmD3bNwvF5VW0+UlUQ1k1vmktTu4Yu13Q0RIxEP8+B+wloA==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
@ -13300,6 +13210,12 @@ packages:
|
|||
vite:
|
||||
optional: true
|
||||
|
||||
vite-plugin-static-copy@2.2.0:
|
||||
resolution: {integrity: sha512-ytMrKdR9iWEYHbUxs6x53m+MRl4SJsOSoMu1U1+Pfg0DjPeMlsRVx3RR5jvoonineDquIue83Oq69JvNsFSU5w==}
|
||||
engines: {node: ^18.0.0 || >=20.0.0}
|
||||
peerDependencies:
|
||||
vite: ^5.0.0 || ^6.0.0
|
||||
|
||||
vite-svg-loader@5.1.0:
|
||||
resolution: {integrity: sha512-M/wqwtOEjgb956/+m5ZrYT/Iq6Hax0OakWbokj8+9PXOnB7b/4AxESHieEtnNEy7ZpjsjYW1/5nK8fATQMmRxw==}
|
||||
peerDependencies:
|
||||
|
@ -13536,6 +13452,9 @@ packages:
|
|||
resolution: {integrity: sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==}
|
||||
engines: {node: '>= 14'}
|
||||
|
||||
web-tree-sitter@0.24.3:
|
||||
resolution: {integrity: sha512-uR9YNewr1S2EzPKE+y39nAwaTyobBaZRG/IsfkB/OT4v0lXtNj5WjtHKgn2h7eOYUWIZh5rK9Px7tI6S9CRKdA==}
|
||||
|
||||
webidl-conversions@3.0.1:
|
||||
resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==}
|
||||
|
||||
|
@ -15938,18 +15857,6 @@ snapshots:
|
|||
|
||||
'@ctrl/tinycolor@3.6.0': {}
|
||||
|
||||
'@curlconverter/yargs-parser@0.0.1': {}
|
||||
|
||||
'@curlconverter/yargs@0.0.2':
|
||||
dependencies:
|
||||
'@curlconverter/yargs-parser': 0.0.1
|
||||
cliui: 7.0.4
|
||||
escalade: 3.1.1
|
||||
get-caller-file: 2.0.5
|
||||
require-directory: 2.1.1
|
||||
string-width: 4.2.3
|
||||
y18n: 5.0.8
|
||||
|
||||
'@cypress/request@3.0.1':
|
||||
dependencies:
|
||||
aws-sign2: 0.7.0
|
||||
|
@ -19664,8 +19571,6 @@ snapshots:
|
|||
|
||||
'@xmldom/xmldom@0.8.6': {}
|
||||
|
||||
a-sync-waterfall@1.0.1: {}
|
||||
|
||||
abab@2.0.6: {}
|
||||
|
||||
abbrev@1.1.1: {}
|
||||
|
@ -19852,11 +19757,6 @@ snapshots:
|
|||
dependencies:
|
||||
dequal: 2.0.3
|
||||
|
||||
array-buffer-byte-length@1.0.0:
|
||||
dependencies:
|
||||
call-bind: 1.0.7
|
||||
is-array-buffer: 3.0.2
|
||||
|
||||
array-buffer-byte-length@1.0.1:
|
||||
dependencies:
|
||||
call-bind: 1.0.7
|
||||
|
@ -19914,15 +19814,6 @@ snapshots:
|
|||
es-abstract: 1.22.5
|
||||
es-shim-unscopables: 1.0.0
|
||||
|
||||
arraybuffer.prototype.slice@1.0.1:
|
||||
dependencies:
|
||||
array-buffer-byte-length: 1.0.0
|
||||
call-bind: 1.0.7
|
||||
define-properties: 1.2.1
|
||||
get-intrinsic: 1.2.4
|
||||
is-array-buffer: 3.0.2
|
||||
is-shared-array-buffer: 1.0.2
|
||||
|
||||
arraybuffer.prototype.slice@1.0.3:
|
||||
dependencies:
|
||||
array-buffer-byte-length: 1.0.1
|
||||
|
@ -20677,8 +20568,6 @@ snapshots:
|
|||
|
||||
commander@4.1.1: {}
|
||||
|
||||
commander@5.1.0: {}
|
||||
|
||||
commander@6.2.1: {}
|
||||
|
||||
commander@7.2.0: {}
|
||||
|
@ -20788,8 +20677,6 @@ snapshots:
|
|||
|
||||
cookie-signature@1.0.6: {}
|
||||
|
||||
cookie@0.4.2: {}
|
||||
|
||||
cookie@0.7.1: {}
|
||||
|
||||
cookie@0.7.2: {}
|
||||
|
@ -20933,17 +20820,14 @@ snapshots:
|
|||
|
||||
csv-parse@5.5.0: {}
|
||||
|
||||
curlconverter@3.21.0(chokidar@4.0.1):
|
||||
curlconverter@4.11.0(patch_hash=ymigioonidbyw7jydnlor7bohu):
|
||||
dependencies:
|
||||
'@curlconverter/yargs': 0.0.2
|
||||
cookie: 0.4.2
|
||||
jsesc: 3.0.2
|
||||
nunjucks: 3.2.4(chokidar@4.0.1)
|
||||
query-string: 7.1.3
|
||||
string.prototype.startswith: 1.0.0
|
||||
lossless-json: 4.0.2
|
||||
tree-sitter: 0.21.1
|
||||
tree-sitter-bash: 0.23.3(tree-sitter@0.21.1)
|
||||
web-tree-sitter: 0.24.3
|
||||
yamljs: 0.3.0
|
||||
transitivePeerDependencies:
|
||||
- chokidar
|
||||
|
||||
currency-codes@2.1.0:
|
||||
dependencies:
|
||||
|
@ -21136,8 +21020,6 @@ snapshots:
|
|||
|
||||
decko@1.2.0: {}
|
||||
|
||||
decode-uri-component@0.2.2: {}
|
||||
|
||||
decompress-response@6.0.0:
|
||||
dependencies:
|
||||
mimic-response: 3.1.0
|
||||
|
@ -21486,48 +21368,6 @@ snapshots:
|
|||
dependencies:
|
||||
is-arrayish: 0.2.1
|
||||
|
||||
es-abstract@1.22.1:
|
||||
dependencies:
|
||||
array-buffer-byte-length: 1.0.0
|
||||
arraybuffer.prototype.slice: 1.0.1
|
||||
available-typed-arrays: 1.0.7
|
||||
call-bind: 1.0.7
|
||||
es-set-tostringtag: 2.0.1
|
||||
es-to-primitive: 1.2.1
|
||||
function.prototype.name: 1.1.5
|
||||
get-intrinsic: 1.2.4
|
||||
get-symbol-description: 1.0.0
|
||||
globalthis: 1.0.3
|
||||
gopd: 1.0.1
|
||||
has: 1.0.3
|
||||
has-property-descriptors: 1.0.2
|
||||
has-proto: 1.0.3
|
||||
has-symbols: 1.0.3
|
||||
internal-slot: 1.0.5
|
||||
is-array-buffer: 3.0.2
|
||||
is-callable: 1.2.7
|
||||
is-negative-zero: 2.0.2
|
||||
is-regex: 1.1.4
|
||||
is-shared-array-buffer: 1.0.2
|
||||
is-string: 1.0.7
|
||||
is-typed-array: 1.1.13
|
||||
is-weakref: 1.0.2
|
||||
object-inspect: 1.13.1
|
||||
object-keys: 1.1.1
|
||||
object.assign: 4.1.5
|
||||
regexp.prototype.flags: 1.5.0
|
||||
safe-array-concat: 1.0.0
|
||||
safe-regex-test: 1.0.0
|
||||
string.prototype.trim: 1.2.7
|
||||
string.prototype.trimend: 1.0.6
|
||||
string.prototype.trimstart: 1.0.6
|
||||
typed-array-buffer: 1.0.0
|
||||
typed-array-byte-length: 1.0.0
|
||||
typed-array-byte-offset: 1.0.0
|
||||
typed-array-length: 1.0.4
|
||||
unbox-primitive: 1.0.2
|
||||
which-typed-array: 1.1.15
|
||||
|
||||
es-abstract@1.22.5:
|
||||
dependencies:
|
||||
array-buffer-byte-length: 1.0.1
|
||||
|
@ -21658,12 +21498,6 @@ snapshots:
|
|||
dependencies:
|
||||
es-errors: 1.3.0
|
||||
|
||||
es-set-tostringtag@2.0.1:
|
||||
dependencies:
|
||||
get-intrinsic: 1.2.4
|
||||
has: 1.0.3
|
||||
has-tostringtag: 1.0.2
|
||||
|
||||
es-set-tostringtag@2.0.3:
|
||||
dependencies:
|
||||
get-intrinsic: 1.2.4
|
||||
|
@ -22265,8 +22099,6 @@ snapshots:
|
|||
dependencies:
|
||||
to-regex-range: 5.0.1
|
||||
|
||||
filter-obj@1.1.0: {}
|
||||
|
||||
finalhandler@1.3.1:
|
||||
dependencies:
|
||||
debug: 2.6.9
|
||||
|
@ -22378,6 +22210,12 @@ snapshots:
|
|||
|
||||
fs-constants@1.0.0: {}
|
||||
|
||||
fs-extra@11.3.0:
|
||||
dependencies:
|
||||
graceful-fs: 4.2.11
|
||||
jsonfile: 6.1.0
|
||||
universalify: 2.0.0
|
||||
|
||||
fs-extra@7.0.1:
|
||||
dependencies:
|
||||
graceful-fs: 4.2.11
|
||||
|
@ -22405,13 +22243,6 @@ snapshots:
|
|||
|
||||
function-bind@1.1.2: {}
|
||||
|
||||
function.prototype.name@1.1.5:
|
||||
dependencies:
|
||||
call-bind: 1.0.7
|
||||
define-properties: 1.2.1
|
||||
es-abstract: 1.22.5
|
||||
functions-have-names: 1.2.3
|
||||
|
||||
function.prototype.name@1.1.6:
|
||||
dependencies:
|
||||
call-bind: 1.0.7
|
||||
|
@ -22505,11 +22336,6 @@ snapshots:
|
|||
|
||||
get-stream@6.0.1: {}
|
||||
|
||||
get-symbol-description@1.0.0:
|
||||
dependencies:
|
||||
call-bind: 1.0.7
|
||||
get-intrinsic: 1.2.4
|
||||
|
||||
get-symbol-description@1.0.2:
|
||||
dependencies:
|
||||
call-bind: 1.0.7
|
||||
|
@ -23020,12 +22846,6 @@ snapshots:
|
|||
strip-ansi: 6.0.1
|
||||
through: 2.3.8
|
||||
|
||||
internal-slot@1.0.5:
|
||||
dependencies:
|
||||
get-intrinsic: 1.2.4
|
||||
has: 1.0.3
|
||||
side-channel: 1.0.6
|
||||
|
||||
internal-slot@1.0.7:
|
||||
dependencies:
|
||||
es-errors: 1.3.0
|
||||
|
@ -23071,12 +22891,6 @@ snapshots:
|
|||
call-bind: 1.0.7
|
||||
has-tostringtag: 1.0.2
|
||||
|
||||
is-array-buffer@3.0.2:
|
||||
dependencies:
|
||||
call-bind: 1.0.7
|
||||
get-intrinsic: 1.2.4
|
||||
is-typed-array: 1.1.13
|
||||
|
||||
is-array-buffer@3.0.4:
|
||||
dependencies:
|
||||
call-bind: 1.0.7
|
||||
|
@ -23157,8 +22971,6 @@ snapshots:
|
|||
call-bind: 1.0.7
|
||||
define-properties: 1.2.1
|
||||
|
||||
is-negative-zero@2.0.2: {}
|
||||
|
||||
is-negative-zero@2.0.3: {}
|
||||
|
||||
is-node-process@1.2.0: {}
|
||||
|
@ -23188,10 +23000,6 @@ snapshots:
|
|||
|
||||
is-set@2.0.2: {}
|
||||
|
||||
is-shared-array-buffer@1.0.2:
|
||||
dependencies:
|
||||
call-bind: 1.0.7
|
||||
|
||||
is-shared-array-buffer@1.0.3:
|
||||
dependencies:
|
||||
call-bind: 1.0.7
|
||||
|
@ -24195,6 +24003,8 @@ snapshots:
|
|||
|
||||
lossless-json@1.0.5: {}
|
||||
|
||||
lossless-json@4.0.2: {}
|
||||
|
||||
loupe@3.1.2: {}
|
||||
|
||||
lower-case@1.1.4: {}
|
||||
|
@ -24996,6 +24806,8 @@ snapshots:
|
|||
|
||||
node-addon-api@7.1.0: {}
|
||||
|
||||
node-addon-api@8.3.0: {}
|
||||
|
||||
node-cleanup@2.1.2: {}
|
||||
|
||||
node-domexception@1.0.0: {}
|
||||
|
@ -25023,6 +24835,8 @@ snapshots:
|
|||
node-gyp-build-optional-packages@5.0.7:
|
||||
optional: true
|
||||
|
||||
node-gyp-build@4.8.4: {}
|
||||
|
||||
node-gyp@8.4.1:
|
||||
dependencies:
|
||||
env-paths: 2.2.1
|
||||
|
@ -25153,14 +24967,6 @@ snapshots:
|
|||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
nunjucks@3.2.4(chokidar@4.0.1):
|
||||
dependencies:
|
||||
a-sync-waterfall: 1.0.1
|
||||
asap: 2.0.6
|
||||
commander: 5.1.0
|
||||
optionalDependencies:
|
||||
chokidar: 4.0.1
|
||||
|
||||
nwsapi@2.2.7: {}
|
||||
|
||||
oas-kit-common@1.0.8:
|
||||
|
@ -25959,13 +25765,6 @@ snapshots:
|
|||
dependencies:
|
||||
side-channel: 1.0.6
|
||||
|
||||
query-string@7.1.3:
|
||||
dependencies:
|
||||
decode-uri-component: 0.2.2
|
||||
filter-obj: 1.1.0
|
||||
split-on-first: 1.1.0
|
||||
strict-uri-encode: 2.0.0
|
||||
|
||||
querystringify@2.2.0: {}
|
||||
|
||||
queue-lit@1.5.0: {}
|
||||
|
@ -26227,12 +26026,6 @@ snapshots:
|
|||
|
||||
regexp-tree@0.1.27: {}
|
||||
|
||||
regexp.prototype.flags@1.5.0:
|
||||
dependencies:
|
||||
call-bind: 1.0.7
|
||||
define-properties: 1.2.1
|
||||
functions-have-names: 1.2.3
|
||||
|
||||
regexp.prototype.flags@1.5.2:
|
||||
dependencies:
|
||||
call-bind: 1.0.7
|
||||
|
@ -26430,13 +26223,6 @@ snapshots:
|
|||
dependencies:
|
||||
tslib: 2.6.2
|
||||
|
||||
safe-array-concat@1.0.0:
|
||||
dependencies:
|
||||
call-bind: 1.0.7
|
||||
get-intrinsic: 1.2.4
|
||||
has-symbols: 1.0.3
|
||||
isarray: 2.0.5
|
||||
|
||||
safe-array-concat@1.1.2:
|
||||
dependencies:
|
||||
call-bind: 1.0.7
|
||||
|
@ -26448,12 +26234,6 @@ snapshots:
|
|||
|
||||
safe-buffer@5.2.1: {}
|
||||
|
||||
safe-regex-test@1.0.0:
|
||||
dependencies:
|
||||
call-bind: 1.0.7
|
||||
get-intrinsic: 1.2.4
|
||||
is-regex: 1.1.4
|
||||
|
||||
safe-regex-test@1.0.3:
|
||||
dependencies:
|
||||
call-bind: 1.0.7
|
||||
|
@ -26844,8 +26624,6 @@ snapshots:
|
|||
|
||||
spex@3.3.0: {}
|
||||
|
||||
split-on-first@1.1.0: {}
|
||||
|
||||
split2@4.2.0: {}
|
||||
|
||||
split@0.3.3:
|
||||
|
@ -26976,8 +26754,6 @@ snapshots:
|
|||
|
||||
strict-event-emitter@0.5.1: {}
|
||||
|
||||
strict-uri-encode@2.0.0: {}
|
||||
|
||||
string-argv@0.3.1: {}
|
||||
|
||||
string-length@4.0.2:
|
||||
|
@ -26997,17 +26773,6 @@ snapshots:
|
|||
emoji-regex: 9.2.2
|
||||
strip-ansi: 7.1.0
|
||||
|
||||
string.prototype.startswith@1.0.0:
|
||||
dependencies:
|
||||
define-properties: 1.2.0
|
||||
es-abstract: 1.22.1
|
||||
|
||||
string.prototype.trim@1.2.7:
|
||||
dependencies:
|
||||
call-bind: 1.0.7
|
||||
define-properties: 1.2.1
|
||||
es-abstract: 1.22.5
|
||||
|
||||
string.prototype.trim@1.2.8:
|
||||
dependencies:
|
||||
call-bind: 1.0.7
|
||||
|
@ -27021,12 +26786,6 @@ snapshots:
|
|||
es-abstract: 1.23.3
|
||||
es-object-atoms: 1.0.0
|
||||
|
||||
string.prototype.trimend@1.0.6:
|
||||
dependencies:
|
||||
call-bind: 1.0.7
|
||||
define-properties: 1.2.1
|
||||
es-abstract: 1.22.5
|
||||
|
||||
string.prototype.trimend@1.0.7:
|
||||
dependencies:
|
||||
call-bind: 1.0.7
|
||||
|
@ -27039,12 +26798,6 @@ snapshots:
|
|||
define-properties: 1.2.1
|
||||
es-object-atoms: 1.0.0
|
||||
|
||||
string.prototype.trimstart@1.0.6:
|
||||
dependencies:
|
||||
call-bind: 1.0.7
|
||||
define-properties: 1.2.1
|
||||
es-abstract: 1.22.5
|
||||
|
||||
string.prototype.trimstart@1.0.7:
|
||||
dependencies:
|
||||
call-bind: 1.0.7
|
||||
|
@ -27423,6 +27176,18 @@ snapshots:
|
|||
|
||||
tree-kill@1.2.2: {}
|
||||
|
||||
tree-sitter-bash@0.23.3(tree-sitter@0.21.1):
|
||||
dependencies:
|
||||
node-addon-api: 8.3.0
|
||||
node-gyp-build: 4.8.4
|
||||
optionalDependencies:
|
||||
tree-sitter: 0.21.1
|
||||
|
||||
tree-sitter@0.21.1:
|
||||
dependencies:
|
||||
node-addon-api: 8.3.0
|
||||
node-gyp-build: 4.8.4
|
||||
|
||||
triple-beam@1.3.0: {}
|
||||
|
||||
ts-api-utils@1.0.1(typescript@5.7.2):
|
||||
|
@ -27620,25 +27385,12 @@ snapshots:
|
|||
|
||||
type-of-is@3.5.1: {}
|
||||
|
||||
typed-array-buffer@1.0.0:
|
||||
dependencies:
|
||||
call-bind: 1.0.7
|
||||
get-intrinsic: 1.2.4
|
||||
is-typed-array: 1.1.13
|
||||
|
||||
typed-array-buffer@1.0.2:
|
||||
dependencies:
|
||||
call-bind: 1.0.7
|
||||
es-errors: 1.3.0
|
||||
is-typed-array: 1.1.13
|
||||
|
||||
typed-array-byte-length@1.0.0:
|
||||
dependencies:
|
||||
call-bind: 1.0.7
|
||||
for-each: 0.3.3
|
||||
has-proto: 1.0.3
|
||||
is-typed-array: 1.1.13
|
||||
|
||||
typed-array-byte-length@1.0.1:
|
||||
dependencies:
|
||||
call-bind: 1.0.7
|
||||
|
@ -27647,14 +27399,6 @@ snapshots:
|
|||
has-proto: 1.0.3
|
||||
is-typed-array: 1.1.13
|
||||
|
||||
typed-array-byte-offset@1.0.0:
|
||||
dependencies:
|
||||
available-typed-arrays: 1.0.7
|
||||
call-bind: 1.0.7
|
||||
for-each: 0.3.3
|
||||
has-proto: 1.0.3
|
||||
is-typed-array: 1.1.13
|
||||
|
||||
typed-array-byte-offset@1.0.2:
|
||||
dependencies:
|
||||
available-typed-arrays: 1.0.7
|
||||
|
@ -27664,12 +27408,6 @@ snapshots:
|
|||
has-proto: 1.0.3
|
||||
is-typed-array: 1.1.13
|
||||
|
||||
typed-array-length@1.0.4:
|
||||
dependencies:
|
||||
call-bind: 1.0.7
|
||||
for-each: 0.3.3
|
||||
is-typed-array: 1.1.13
|
||||
|
||||
typed-array-length@1.0.5:
|
||||
dependencies:
|
||||
call-bind: 1.0.7
|
||||
|
@ -27935,6 +27673,14 @@ snapshots:
|
|||
- rollup
|
||||
- supports-color
|
||||
|
||||
vite-plugin-static-copy@2.2.0(vite@6.0.2(@types/node@18.16.16)(jiti@1.21.0)(sass@1.64.1)(terser@5.16.1)):
|
||||
dependencies:
|
||||
chokidar: 4.0.1
|
||||
fast-glob: 3.3.2
|
||||
fs-extra: 11.3.0
|
||||
picocolors: 1.1.1
|
||||
vite: 6.0.2(@types/node@18.16.16)(jiti@1.21.0)(sass@1.64.1)(terser@5.16.1)
|
||||
|
||||
vite-svg-loader@5.1.0(vue@3.5.13(typescript@5.7.2)):
|
||||
dependencies:
|
||||
svgo: 3.3.2
|
||||
|
@ -28179,6 +27925,8 @@ snapshots:
|
|||
|
||||
web-streams-polyfill@4.0.0-beta.3: {}
|
||||
|
||||
web-tree-sitter@0.24.3: {}
|
||||
|
||||
webidl-conversions@3.0.1: {}
|
||||
|
||||
webidl-conversions@4.0.2: {}
|
||||
|
|
Loading…
Reference in a new issue