diff --git a/packages/snippetz/package.json b/packages/snippetz/package.json index 7b057f5..efa2c5a 100644 --- a/packages/snippetz/package.json +++ b/packages/snippetz/package.json @@ -53,6 +53,7 @@ }, "devDependencies": { "@scalar/build-tooling": "^0.1.10", - "@types/har-format": "^1.2.15" + "@types/har-format": "^1.2.15", + "httpsnippet-lite": "^3.0.5" } } diff --git a/packages/snippetz/src/core/index.ts b/packages/snippetz/src/core/index.ts index a084a89..9323d42 100644 --- a/packages/snippetz/src/core/index.ts +++ b/packages/snippetz/src/core/index.ts @@ -3,3 +3,5 @@ export * from './utils/isKeyNeedsQuotes' export * from './utils/objectToString' export * from './types' + +export { availableTargets as allTargets, HTTPSnippet } from 'httpsnippet-lite' diff --git a/packages/snippetz/src/core/types.ts b/packages/snippetz/src/core/types.ts index b105450..05a1066 100644 --- a/packages/snippetz/src/core/types.ts +++ b/packages/snippetz/src/core/types.ts @@ -2,13 +2,34 @@ export type { Request } from 'har-format' export type Source = { /** The language or environment. */ - target: TargetId + target: ScalarTargetId /** The identifier of the client. */ client: ClientId /** The actual source code. */ code: string } -export type TargetId = 'node' | 'js' +export type ScalarTargetId = 'node' | 'js' export type ClientId = 'undici' | 'fetch' | 'ofetch' +import { type TargetId as SnippetTargetId } from 'httpsnippet-lite' + +export type TargetId = ScalarTargetId | SnippetTargetId + +export const ScalarTargetTypes = ['node', 'js'] as const + +export const SnippetTargetTypes = [ + 'c', + 'csharp', + 'go', + 'java', + 'node', + 'ocaml', + 'php', + 'python', + 'ruby', + 'shell', + 'swift', +] as const + +export const ScalarClientTypes = ['undici', 'fetch', 'ofetch'] as const diff --git a/packages/snippetz/src/snippetz.test.ts b/packages/snippetz/src/snippetz.test.ts index 9e7c955..ba13bec 100644 --- a/packages/snippetz/src/snippetz.test.ts +++ b/packages/snippetz/src/snippetz.test.ts @@ -3,7 +3,7 @@ import { snippetz } from './snippetz' describe('snippetz', async () => { it('returns code for undici', async () => { - const snippet = snippetz().print('node', 'undici', { + const snippet = await snippetz().print('node', 'undici', { url: 'https://example.com', }) @@ -25,6 +25,24 @@ const { statusCode, body } = await request('https://example.com')`) 'ofetch', ]) }) + + it('returns code for python target', async () => { + const snippet = await snippetz().print('python', 'fetch', { + method: 'GET', + url: 'http://mockbin.com/request', + }) + + expect(snippet).toBe(`import http.client + +conn = http.client.HTTPConnection("mockbin.com") + +conn.request("GET", "/request") + +res = conn.getresponse() +data = res.read() + +print(data.decode("utf-8"))`) + }) }) describe('plugins', async () => { @@ -69,3 +87,25 @@ describe('hasPlugin', async () => { expect(result).toBe(false) }) }) + +describe('convert', async () => { + it('converts a request outside of the scalar types to snippet using httpsnippet-lite', async () => { + const request = { + method: 'GET', + url: 'http://mockbin.com/request', + } + + const snippet = await snippetz().convert(request, 'python') + + expect(snippet).toBe(`import http.client + +conn = http.client.HTTPConnection("mockbin.com") + +conn.request("GET", "/request") + +res = conn.getresponse() +data = res.read() + +print(data.decode("utf-8"))`) + }) +}) diff --git a/packages/snippetz/src/snippetz.ts b/packages/snippetz/src/snippetz.ts index 84414ee..af951ce 100644 --- a/packages/snippetz/src/snippetz.ts +++ b/packages/snippetz/src/snippetz.ts @@ -1,10 +1,21 @@ -import type { TargetId, ClientId, Request } from './core' +import type { TargetId, ClientId, Request, ScalarTargetId } from './core' +import { + ScalarTargetTypes, + SnippetTargetTypes, + ScalarClientTypes, +} from './core' import { undici } from './plugins/node/undici' import { fetch as nodeFetch } from './plugins/node/fetch' import { fetch as jsFetch } from './plugins/js/fetch' import { ofetch as jsOFetch } from './plugins/js/ofetch' import { ofetch as nodeOFetch } from './plugins/node/ofetch' +import { + HTTPSnippet, + type TargetId as SnippetTargetId, + type HarRequest, +} from 'httpsnippet-lite' + export function snippetz() { const plugins = [undici, nodeFetch, jsFetch, jsOFetch, nodeOFetch] @@ -16,8 +27,22 @@ export function snippetz() { return plugin(request) } }, - print(target: TargetId, client: ClientId, request: Partial) { - return this.get(target, client, request)?.code + async print(target: TargetId, client: string, request: Partial) { + // if target and client are valid scalar types + // use the plugin to convert the request + if ( + ScalarTargetTypes.includes(target as ScalarTargetId) && + ScalarClientTypes.includes(client as ClientId) + ) { + return this.get(target, client as ClientId, request)?.code + } + + // else use httpsnippet-lite to convert the request + if (SnippetTargetTypes.includes(target as any)) { + // TODO: add client parameter + return await this.convert(request, target) + } + // else return error }, targets() { return ( @@ -51,5 +76,15 @@ export function snippetz() { hasPlugin(target: string, client: string) { return Boolean(this.findPlugin(target as TargetId, client as ClientId)) }, + // TODO: add client parameter + + async convert(request: any, target: string) { + const snippet = new HTTPSnippet(request as HarRequest) + + // https://www.npmjs.com/package/httpsnippet-lite#snippetconverttargetid-string-clientid-string-options-t + // snippet.convert(targetId: string, clientId?: string, options?: T) + // ERROR: convert method is looking for Client not ClientId + return (await snippet.convert(target as SnippetTargetId)) as string + }, } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2dd9fa9..7366370 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -69,6 +69,9 @@ importers: '@types/har-format': specifier: ^1.2.15 version: 1.2.15 + httpsnippet-lite: + specifier: ^3.0.5 + version: 3.0.5 packages: @@ -958,6 +961,10 @@ packages: resolution: {integrity: sha512-PXUUyLqrR2XCWICfv6ukppP96sdFwWbNEnfEMt7jNsISjMsvaLNinAHNDYyvkyU+SZG2BTSbT5NjG+vZslfGTA==} engines: {node: '>=14'} + formdata-node@4.4.1: + resolution: {integrity: sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==} + engines: {node: '>= 12.20'} + fs-extra@7.0.1: resolution: {integrity: sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==} engines: {node: '>=6 <7 || >=8'} @@ -980,6 +987,9 @@ packages: get-func-name@2.0.2: resolution: {integrity: sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==} + get-own-enumerable-property-symbols@3.0.2: + resolution: {integrity: sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==} + get-stream@8.0.1: resolution: {integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==} engines: {node: '>=16'} @@ -1019,6 +1029,10 @@ packages: resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} hasBin: true + httpsnippet-lite@3.0.5: + resolution: {integrity: sha512-So4qTXY5iFj5XtFDwyz2PicUu+8NWrI8e8h+ZeZoVtMNcFQp4FFIntBHUE+JPUG6QQU8o1VHCy+X4ETRDwt9CA==} + engines: {node: '>=14.13'} + human-id@1.0.2: resolution: {integrity: sha512-UNopramDEhHJD+VR+ehk8rOslwSfByxPIZyJRfV739NDhN5LF1fa1MqnzKm2lGTQRjNrjK19Q5fhkgIfjlVUKw==} @@ -1065,10 +1079,18 @@ packages: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} engines: {node: '>=0.12.0'} + is-obj@1.0.1: + resolution: {integrity: sha512-l4RyHgRqGN4Y3+9JHVrNqO+tN0rV5My76uW5/nuO4K1b6vw5G8d/cmFjP9tRfEsdhZNt0IFdZuK/c2Vr4Nb+Qg==} + engines: {node: '>=0.10.0'} + is-plain-object@3.0.1: resolution: {integrity: sha512-Xnpx182SBMrr/aBik8y+GuR4U1L9FqMSojwDQwPMmxyC6bvEqly9UBCxhauBF5vNh2gwWJNX6oDV7O+OM4z34g==} engines: {node: '>=0.10.0'} + is-regexp@1.0.0: + resolution: {integrity: sha512-7zjFAPO4/gwyQAAgRRmqeEeyIICSdmCqa3tsVHMdBzaXXRiqopZL4Cyghg/XulGWrtABTpbnYYzzIRffLkP4oA==} + engines: {node: '>=0.10.0'} + is-stream@3.0.0: resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -1206,6 +1228,10 @@ packages: engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true + node-domexception@1.0.0: + resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==} + engines: {node: '>=10.5.0'} + normalize-path@3.0.0: resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} engines: {node: '>=0.10.0'} @@ -1492,6 +1518,10 @@ packages: resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} engines: {node: '>=12'} + stringify-object@3.3.0: + resolution: {integrity: sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw==} + engines: {node: '>=4'} + strip-ansi@6.0.1: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} engines: {node: '>=8'} @@ -1732,6 +1762,10 @@ packages: typescript: optional: true + web-streams-polyfill@4.0.0-beta.3: + resolution: {integrity: sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==} + engines: {node: '>= 14'} + which-pm@2.0.0: resolution: {integrity: sha512-Lhs9Pmyph0p5n5Z3mVnN0yWcbQYUAD7rbQUiMsQxOJ3T57k7RFe35SUwWMf7dsbDZks1uOmw4AecB/JMDj3v/w==} engines: {node: '>=8.15'} @@ -2747,6 +2781,11 @@ snapshots: cross-spawn: 7.0.3 signal-exit: 4.1.0 + formdata-node@4.4.1: + dependencies: + node-domexception: 1.0.0 + web-streams-polyfill: 4.0.0-beta.3 + fs-extra@7.0.1: dependencies: graceful-fs: 4.2.11 @@ -2768,6 +2807,8 @@ snapshots: get-func-name@2.0.2: {} + get-own-enumerable-property-symbols@3.0.2: {} + get-stream@8.0.1: {} glob-parent@5.1.2: @@ -2822,6 +2863,12 @@ snapshots: he@1.2.0: {} + httpsnippet-lite@3.0.5: + dependencies: + '@types/har-format': 1.2.15 + formdata-node: 4.4.1 + stringify-object: 3.3.0 + human-id@1.0.2: {} human-signals@5.0.0: {} @@ -2857,8 +2904,12 @@ snapshots: is-number@7.0.0: {} + is-obj@1.0.1: {} + is-plain-object@3.0.1: {} + is-regexp@1.0.0: {} + is-stream@3.0.0: {} is-subdir@1.2.0: @@ -2987,6 +3038,8 @@ snapshots: nanoid@3.3.7: {} + node-domexception@1.0.0: {} + normalize-path@3.0.0: {} npm-run-path@5.3.0: @@ -3250,6 +3303,12 @@ snapshots: emoji-regex: 9.2.2 strip-ansi: 7.1.0 + stringify-object@3.3.0: + dependencies: + get-own-enumerable-property-symbols: 3.0.2 + is-obj: 1.0.1 + is-regexp: 1.0.0 + strip-ansi@6.0.1: dependencies: ansi-regex: 5.0.1 @@ -3478,6 +3537,8 @@ snapshots: optionalDependencies: typescript: 5.5.4 + web-streams-polyfill@4.0.0-beta.3: {} + which-pm@2.0.0: dependencies: load-yaml-file: 0.2.0