fix: use full client side js library for route gen from openapi (#5891)

* add base template

* generate http trigger from openapi spec

* refactor and folder picker

* fix force re-render on simple editor

* add edit functionality for generated triggers and nits

* remove buffer

* add buffer

* nits

* nits

* nits and force Rerender

* remove analyzer

* typo

* nits

* nits

* nits

* fix

* nits

* fix vite

* updaate

* remove polyfills

* rename file and prevent duplicate keys

* better naming and duplicate key

* camel case

* replace spaces

---------

Co-authored-by: HugoCasa <hugo@casademont.ch>
This commit is contained in:
dieriba
2025-06-06 12:38:37 +00:00
committed by GitHub
co-authored by HugoCasa
parent 571348377b
commit 3c3fdbdf26
7 changed files with 175 additions and 1299 deletions
+129 -1271
View File
File diff suppressed because it is too large Load Diff
+1 -2
View File
@@ -70,7 +70,6 @@
"typescript": "^5.5.0",
"vite": "^6.3.2",
"vite-plugin-mkcert": "^1.17.5",
"vite-plugin-node-polyfills": "^0.23.0",
"yootils": "^0.3.1"
},
"overrides": {
@@ -80,7 +79,6 @@
},
"type": "module",
"dependencies": {
"@apidevtools/swagger-parser": "^10.1.1",
"@aws-crypto/sha256-js": "^4.0.0",
"@codingame/monaco-vscode-configuration-service-override": "~16.1.1",
"@codingame/monaco-vscode-editor-api": "~16.1.1",
@@ -93,6 +91,7 @@
"@leeoniya/ufuzzy": "^1.0.8",
"@popperjs/core": "^2.11.6",
"@redocly/json-to-json-schema": "^0.0.1",
"@scalar/openapi-parser": "^0.15.0",
"@tanstack/svelte-table": "npm:tanstack-table-8-svelte-5@^0.1",
"@windmill-labs/svelte-dnd-action": "^0.9.48",
"@xterm/addon-fit": "^0.10.0",
@@ -25,7 +25,7 @@
let { closeFn }: Props = $props()
let routeEditor: RouteEditor
let httpTriggersGenerator: Drawer
let routesGenerator: Drawer
let selected: Source = $state('OpenAPI')
let openApiUrl = $state('')
@@ -66,7 +66,7 @@
})
export function openDrawer() {
httpTriggersGenerator.openDrawer()
routesGenerator.openDrawer()
}
async function fetchOpenApiConfig() {
@@ -94,7 +94,7 @@
})
sendUserToast(message)
await closeFn()
httpTriggersGenerator.closeDrawer()
routesGenerator.closeDrawer()
if (!get(usedTriggerKinds).includes('http')) {
usedTriggerKinds.update((t) => [...t, 'http'])
}
@@ -119,6 +119,9 @@
try {
isGeneratingHttpRoutes = true
httpTriggers = await generateHttpTriggerFromOpenApi(code, folderName)
if (httpTriggers.length === 0) {
sendUserToast('No paths defined in the OpenAPI spec. Cannot generate HTTP routes.', true)
}
} catch (error) {
sendUserToast(error.message || 'An unexpected error occurred', true)
} finally {
@@ -129,10 +132,10 @@
<RouteEditor customSaveBehavior={callback} bind:this={routeEditor} />
<Drawer size="700px" bind:this={httpTriggersGenerator}>
<Drawer size="700px" bind:this={routesGenerator}>
<DrawerContent
title={'Generate HTTP routes from OpenAPI spec'}
on:close={() => httpTriggersGenerator.closeDrawer()}
on:close={() => routesGenerator.closeDrawer()}
>
<svelte:fragment slot="actions">
<Button
@@ -4,7 +4,7 @@ import { random_adj } from '$lib/components/random_positive_adjetive'
import type { HttpMethod, NewHttpTrigger } from '$lib/gen'
import { HttpTriggerService } from '$lib/gen/services.gen'
import { sendUserToast } from '$lib/toast'
import { OpenApi as WindmillOpenApi } from '$lib/utils'
import { generateRandomString, OpenApi as WindmillOpenApi } from '$lib/utils'
import { type OpenAPI } from 'openapi-types'
import type { Writable } from 'svelte/store'
import { get } from 'svelte/store'
@@ -87,8 +87,23 @@ function convertOpenApiPathToRoutePath(openApiPath: string) {
return openApiPath.replace(/{([^}]+)}/g, ':$1').slice(1)
}
const MAX_PATH_LEN = 255
function generateFolderPath(folderName: string, summary?: string) {
return `f/${folderName}/${summary?.toLowerCase().replaceAll(' ', '_') ?? random_adj()}`
let suffix: string
const prefix = `f/${folderName}/`
if (!summary) {
suffix = `${random_adj()}_${generateRandomString(6)}`
} else {
const remainingLen = MAX_PATH_LEN - prefix.length
if (summary.length > remainingLen) {
suffix = summary.substring(0, remainingLen).replaceAll(' ', '_')
} else {
suffix = summary.replaceAll(' ', '_')
}
}
return prefix.concat(suffix).toLocaleLowerCase()
}
function processOpenApiDocument(
+16 -13
View File
@@ -18,6 +18,7 @@ export { sendUserToast }
import type { AnyMeltElement } from '@melt-ui/svelte'
import type { RunsSelectionMode } from './components/runs/RunsBatchActionsDropdown.svelte'
import type { TriggerKind } from './components/triggers'
import { validate, dereference } from '@scalar/openapi-parser'
export namespace OpenApi {
export enum OpenApiVersion {
@@ -38,10 +39,10 @@ export namespace OpenApi {
return 'openapi' in doc && typeof doc.openapi === 'string' && doc.openapi.startsWith('3.1')
}
export function getOpenApiVersion(doc: OpenAPI.Document): OpenApiVersion {
if (isV2(doc)) {
export function getOpenApiVersion(version: string): OpenApiVersion {
if (version.startsWith('2.0')) {
return OpenApiVersion.V2
} else if (isV3(doc)) {
} else if (version.startsWith('3.0')) {
return OpenApiVersion.V3
} else {
return OpenApiVersion.V3_1
@@ -59,20 +60,20 @@ export namespace OpenApi {
* @throws Will throw an error if the specification is invalid or cannot be parsed.
*/
export async function parse(api: string): Promise<[OpenAPI.Document, OpenApiVersion]> {
let data: any
const { valid, errors } = await validate(api)
if (api.trimStart().at(0) === '{') {
data = JSON.parse(api)
} else {
data = YAML.parse(api)
if (!valid) {
const errorMessage = errors
? errors.map((error) => error.message).join('\n')
: 'Invalid OpenAPI document'
throw new Error(errorMessage)
}
const swaggerParser = (await import('@apidevtools/swagger-parser')).default
const document = await dereference(api)
const document = await swaggerParser.validate(data)
const version = getOpenApiVersion(document)
const version = getOpenApiVersion(document.version!)
return [document, version]
return [document.schema, version]
}
}
@@ -89,7 +90,9 @@ export const AGENT_WORKER_NAME_PREFIX = 'ag'
const SSH_AGENT_WORKER_SUFFIX = '/ssh'
export function isAgentWorkerShell(workerName: string) {
return workerName.startsWith(AGENT_WORKER_NAME_PREFIX) && workerName.endsWith(SSH_AGENT_WORKER_SUFFIX)
return (
workerName.startsWith(AGENT_WORKER_NAME_PREFIX) && workerName.endsWith(SSH_AGENT_WORKER_SUFFIX)
)
}
export function isJobSelectable(selectionType: RunsSelectionMode) {
@@ -44,14 +44,14 @@
import { ALL_DEPLOYABLE, isDeployable } from '$lib/utils_deployable'
import { isCloudHosted } from '$lib/cloud'
import { getHttpRoute } from '$lib/components/triggers/http/utils'
import HttpTriggersGenerator from '$lib/components/triggers/http/HttpTriggersGenerator.svelte'
import RoutesGenerator from '$lib/components/triggers/http/RoutesGenerator.svelte'
type TriggerW = HttpTrigger & { canWrite: boolean }
let triggers: TriggerW[] = []
let shareModal: ShareModal
let loading = true
let httpTriggersGenerator: HttpTriggersGenerator | undefined
let routesGenerator: RoutesGenerator | undefined
let deploymentDrawer: DeployWorkspaceDrawer
let deployUiSettings: WorkspaceDeployUISettings | undefined = undefined
@@ -190,7 +190,7 @@
<DeployWorkspaceDrawer bind:this={deploymentDrawer} />
<RouteEditor onUpdate={loadTriggers} bind:this={routeEditor} />
<HttpTriggersGenerator closeFn={loadTriggers} bind:this={httpTriggersGenerator} />
<RoutesGenerator closeFn={loadTriggers} bind:this={routesGenerator} />
<SearchItems
{filter}
@@ -217,7 +217,7 @@
size="md"
startIcon={{ icon: Plus }}
on:click={() => {
httpTriggersGenerator?.openDrawer()
routesGenerator?.openDrawer()
}}
>
From OpenAPI spec
-2
View File
@@ -3,7 +3,6 @@ import { readFileSync } from 'fs'
import { fileURLToPath } from 'url'
import mkcert from 'vite-plugin-mkcert'
import importMetaUrlPlugin from '@windmill-labs/esbuild-import-meta-url-plugin'
import { nodePolyfills } from 'vite-plugin-node-polyfills'
const file = fileURLToPath(new URL('package.json', import.meta.url))
const json = readFileSync(file, 'utf8')
@@ -45,7 +44,6 @@ const config = {
port: 3000
},
plugins: [
nodePolyfills({ include: ['buffer'] }),
sveltekit(),
...(process.env.HTTPS === 'true' ? [mkcert()] : [])
],