mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-19 00:02:03 +00:00
refactor(editor): extract ATA setup and lsp token root
Headless linting needs the same acquired types as the editor, npm declarations and relative-import models alike. Move the ATA delegate and token root to lint/typescriptAta.ts; the editor keeps its trigger logic and supplies the model-revalidation nudge via onLocalFileRegistered. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -33,7 +33,6 @@
|
||||
dbSchemas,
|
||||
type DBSchema,
|
||||
codeCompletionSessionEnabled,
|
||||
lspTokenStore,
|
||||
formatOnSave,
|
||||
vimMode,
|
||||
relativeLineNumbers
|
||||
@@ -43,7 +42,7 @@
|
||||
import { editorFontSize } from '$lib/editorFontSize.svelte'
|
||||
import { workspaceStore } from '$lib/stores'
|
||||
import DdlMigrationGuard from './DdlMigrationGuard.svelte'
|
||||
import { type Preview, type ScriptLang, UserService } from '$lib/gen'
|
||||
import { type Preview, type ScriptLang } from '$lib/gen'
|
||||
import type { Text } from 'yjs'
|
||||
import {
|
||||
initializeVscode,
|
||||
@@ -74,11 +73,10 @@
|
||||
POSTGRES_TYPES,
|
||||
SNOWFLAKE_TYPES
|
||||
} from '$lib/consts'
|
||||
import { setupTypeAcquisition, type DepsToGet } from '$lib/ata/index'
|
||||
import { initWasmTs, type InferAssetsSqlQueryDetails } from '$lib/infer'
|
||||
import { type DepsToGet } from '$lib/ata/index'
|
||||
import { type InferAssetsSqlQueryDetails } from '$lib/infer'
|
||||
import { initVim } from './monaco_keybindings'
|
||||
import { updateSqlQueriesInWorker, waitForWorkerInitialization } from './sqlTypeService'
|
||||
import { parseTypescriptDeps } from '$lib/relative_imports'
|
||||
|
||||
import { scriptLangToEditorLang } from '$lib/scripts'
|
||||
import {
|
||||
@@ -108,6 +106,7 @@
|
||||
ensureResourceTypeNamespace,
|
||||
fetchCustomWmillTypesData
|
||||
} from './lint/typescriptExtraLibs'
|
||||
import { createWindmillAta, genAtaRoot } from './lint/typescriptAta'
|
||||
import { aiChatManager } from './copilot/chat/AIChatManager.svelte'
|
||||
import type { Selection } from 'monaco-editor'
|
||||
import { canHavePreprocessor, getPreprocessorModuleCode } from '$lib/script_helpers'
|
||||
@@ -1149,14 +1148,12 @@
|
||||
}
|
||||
}
|
||||
|
||||
const hostname = getHostname()
|
||||
|
||||
let encodedImportMap = ''
|
||||
|
||||
if (useWebsockets) {
|
||||
if (lang == 'typescript' && scriptLang === 'deno') {
|
||||
ata = undefined
|
||||
let root = await genRoot(hostname)
|
||||
let root = await genAtaRoot($workspaceStore ?? '')
|
||||
const importMap = {
|
||||
imports: {
|
||||
'file:///': root + '/'
|
||||
@@ -1334,10 +1331,6 @@
|
||||
|
||||
let yPadding = MONACO_Y_PADDING
|
||||
|
||||
function getHostname() {
|
||||
return BROWSER ? window.location.protocol + '//' + window.location.host : 'SSR'
|
||||
}
|
||||
|
||||
function handlePathChange() {
|
||||
console.log('path changed, reloading language server', initialPath, path)
|
||||
initialPath = path
|
||||
@@ -1756,72 +1749,21 @@
|
||||
) {
|
||||
absolutePathExtraLibs.forEach((d) => d.dispose())
|
||||
absolutePathExtraLibs.clear()
|
||||
const hostname = getHostname()
|
||||
|
||||
const addLibraryToRuntime = async (code: string, _path: string) => {
|
||||
const path = 'file://' + _path
|
||||
let uri = mUri.parse(path)
|
||||
console.log('adding library to runtime', path)
|
||||
typescriptDefaults.addExtraLib(code, path)
|
||||
try {
|
||||
await vscode.workspace.fs.writeFile(uri, new TextEncoder().encode(code))
|
||||
} catch (e) {
|
||||
console.log('error writing file', e)
|
||||
}
|
||||
}
|
||||
|
||||
const addLocalFile = async (code: string, _path: string) => {
|
||||
if (destroyed) return
|
||||
let p = new URL(_path, uri).href
|
||||
let nuri = mUri.parse(p)
|
||||
console.log('adding local file', _path, nuri.toString())
|
||||
// Monaco's TS service resolves relative imports against the importer's URI (finding the
|
||||
// model), but absolute paths like "/u/admin/foo" are looked up as raw paths and miss the
|
||||
// `file://` model. Register them as extra libs so TS can resolve them.
|
||||
if (_path.startsWith('/')) {
|
||||
absolutePathExtraLibs.get(_path)?.dispose()
|
||||
absolutePathExtraLibs.set(_path, typescriptDefaults.addExtraLib(code, _path))
|
||||
}
|
||||
if (editor) {
|
||||
let localModel = meditor.getModel(nuri)
|
||||
if (localModel) {
|
||||
localModel.setValue(code)
|
||||
} else {
|
||||
meditor.createModel(code, 'typescript', nuri)
|
||||
}
|
||||
ata = await createWindmillAta({
|
||||
root: await genAtaRoot($workspaceStore ?? ''),
|
||||
scriptPath: path,
|
||||
modelUri: uri,
|
||||
absolutePathExtraLibs,
|
||||
isCancelled: () => destroyed,
|
||||
onLocalFileRegistered: () => {
|
||||
if (!editor) return
|
||||
try {
|
||||
if (model) {
|
||||
model?.setValue(model.getValue())
|
||||
}
|
||||
model?.setValue(model.getValue())
|
||||
} catch (e) {
|
||||
console.log('error resetting model', e)
|
||||
}
|
||||
}
|
||||
}
|
||||
await initWasmTs()
|
||||
const root = await genRoot(hostname)
|
||||
console.log('SETUP TYPE ACQUISITION', { root, path })
|
||||
ata = setupTypeAcquisition({
|
||||
projectName: 'Windmill',
|
||||
depsParser: (c) => {
|
||||
return parseTypescriptDeps(c)
|
||||
},
|
||||
root,
|
||||
scriptPath: path,
|
||||
logger: console,
|
||||
delegate: {
|
||||
receivedFile: addLibraryToRuntime,
|
||||
localFile: addLocalFile,
|
||||
progress: (downloaded: number, total: number) => {
|
||||
// console.log({ dl, ttl })
|
||||
},
|
||||
started: () => {
|
||||
console.log('ATA start')
|
||||
},
|
||||
finished: (f) => {
|
||||
console.log('ATA done')
|
||||
}
|
||||
}
|
||||
})
|
||||
if (scriptLang == 'bun') {
|
||||
ata?.('import "bun-types"')
|
||||
@@ -1915,21 +1857,6 @@
|
||||
absolutePathExtraLibs.clear()
|
||||
})
|
||||
|
||||
async function genRoot(hostname: string) {
|
||||
let token = $lspTokenStore
|
||||
if (!token) {
|
||||
let expiration = new Date()
|
||||
expiration.setHours(expiration.getHours() + 72)
|
||||
const newToken = await UserService.createToken({
|
||||
requestBody: { label: 'Ephemeral lsp token', expiration: expiration.toISOString() }
|
||||
})
|
||||
$lspTokenStore = newToken
|
||||
token = newToken
|
||||
}
|
||||
let root = hostname + '/api/scripts_u/tokened_raw/' + $workspaceStore + '/' + token
|
||||
return root
|
||||
}
|
||||
|
||||
function acceptCodeChanges() {
|
||||
const mode = aiChatEditorHandler?.getReviewMode?.()
|
||||
if (mode === 'revert') {
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
import { BROWSER } from 'esm-env'
|
||||
import * as vscode from 'vscode'
|
||||
import { editor as meditor, Uri as mUri } from 'monaco-editor'
|
||||
import { typescriptDefaults } from '@codingame/monaco-vscode-standalone-typescript-language-features'
|
||||
import { get } from 'svelte/store'
|
||||
import { setupTypeAcquisition, type DepsToGet } from '$lib/ata/index'
|
||||
import { initWasmTs } from '$lib/infer'
|
||||
import { parseTypescriptDeps } from '$lib/relative_imports'
|
||||
import { UserService } from '$lib/gen'
|
||||
import { lspTokenStore } from '$lib/stores'
|
||||
|
||||
// Automatic Type Acquisition: fetches npm and relative-import declarations into the
|
||||
// global typescriptDefaults. Without it, TypeScript reports every third-party import
|
||||
// as unresolved, so headless linting and the editor must acquire types the same way.
|
||||
|
||||
export async function genAtaRoot(workspace: string): Promise<string> {
|
||||
let token = get(lspTokenStore)
|
||||
if (!token) {
|
||||
const expiration = new Date()
|
||||
expiration.setHours(expiration.getHours() + 72)
|
||||
const newToken = await UserService.createToken({
|
||||
requestBody: { label: 'Ephemeral lsp token', expiration: expiration.toISOString() }
|
||||
})
|
||||
lspTokenStore.set(newToken)
|
||||
token = newToken
|
||||
}
|
||||
const hostname = BROWSER ? window.location.protocol + '//' + window.location.host : 'SSR'
|
||||
return hostname + '/api/scripts_u/tokened_raw/' + workspace + '/' + token
|
||||
}
|
||||
|
||||
export interface WindmillAtaOptions {
|
||||
root: string
|
||||
/** Script path passed to ATA for resolving relative imports server-side. */
|
||||
scriptPath: string | undefined
|
||||
/** URI of the model being typed; relative import paths resolve against it. */
|
||||
modelUri: string
|
||||
absolutePathExtraLibs: Map<string, { dispose: () => void }>
|
||||
isCancelled?: () => boolean
|
||||
/** Called after a relative-import model is registered, to nudge revalidation. */
|
||||
onLocalFileRegistered?: () => void
|
||||
}
|
||||
|
||||
export async function createWindmillAta(
|
||||
opts: WindmillAtaOptions
|
||||
): Promise<(source: string | DepsToGet) => Promise<void>> {
|
||||
const addLibraryToRuntime = async (code: string, _path: string) => {
|
||||
const path = 'file://' + _path
|
||||
const uri = mUri.parse(path)
|
||||
console.log('adding library to runtime', path)
|
||||
typescriptDefaults.addExtraLib(code, path)
|
||||
try {
|
||||
await vscode.workspace.fs.writeFile(uri, new TextEncoder().encode(code))
|
||||
} catch (e) {
|
||||
console.log('error writing file', e)
|
||||
}
|
||||
}
|
||||
|
||||
const addLocalFile = async (code: string, _path: string) => {
|
||||
if (opts.isCancelled?.()) return
|
||||
const p = new URL(_path, opts.modelUri).href
|
||||
const nuri = mUri.parse(p)
|
||||
console.log('adding local file', _path, nuri.toString())
|
||||
// Monaco's TS service resolves relative imports against the importer's URI (finding the
|
||||
// model), but absolute paths like "/u/admin/foo" are looked up as raw paths and miss the
|
||||
// `file://` model. Register them as extra libs so TS can resolve them.
|
||||
if (_path.startsWith('/')) {
|
||||
opts.absolutePathExtraLibs.get(_path)?.dispose()
|
||||
opts.absolutePathExtraLibs.set(_path, typescriptDefaults.addExtraLib(code, _path))
|
||||
}
|
||||
const localModel = meditor.getModel(nuri)
|
||||
if (localModel) {
|
||||
localModel.setValue(code)
|
||||
} else {
|
||||
meditor.createModel(code, 'typescript', nuri)
|
||||
}
|
||||
opts.onLocalFileRegistered?.()
|
||||
}
|
||||
|
||||
await initWasmTs()
|
||||
console.log('SETUP TYPE ACQUISITION', { root: opts.root, path: opts.scriptPath })
|
||||
return setupTypeAcquisition({
|
||||
projectName: 'Windmill',
|
||||
depsParser: (c) => parseTypescriptDeps(c),
|
||||
root: opts.root,
|
||||
scriptPath: opts.scriptPath,
|
||||
logger: console,
|
||||
delegate: {
|
||||
receivedFile: addLibraryToRuntime,
|
||||
localFile: addLocalFile,
|
||||
progress: (_downloaded: number, _total: number) => {},
|
||||
started: () => {
|
||||
console.log('ATA start')
|
||||
},
|
||||
finished: (_f) => {
|
||||
console.log('ATA done')
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user