mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-09-08 16:03:27 +00:00
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:
Generated
+129
-1271
File diff suppressed because it is too large
Load Diff
@@ -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",
|
||||
|
||||
+8
-5
@@ -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
@@ -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
|
||||
|
||||
@@ -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()] : [])
|
||||
],
|
||||
|
||||
Reference in New Issue
Block a user