mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-21 00:02:23 +00:00
feat: bun automatic type acquisition in frontend directly (#2884)
* foo * ata * done * remove bun from lsp * update all
This commit is contained in:
@@ -47,10 +47,9 @@ impl Visit for ImportsFinder {
|
||||
|
||||
pub fn parse_expr_for_imports(code: &str) -> anyhow::Result<Vec<String>> {
|
||||
let cm: Lrc<SourceMap> = Default::default();
|
||||
let fm = cm.new_source_file(FileName::Custom("main.ts".into()), code.into());
|
||||
let fm = cm.new_source_file(FileName::Custom("main.d.ts".into()), code.into());
|
||||
let lexer = Lexer::new(
|
||||
// We want to parse ecmascript
|
||||
Syntax::Es(EsConfig { jsx: false, ..Default::default() }),
|
||||
Syntax::Typescript(TsConfig::default()),
|
||||
// EsVersion defaults to es5
|
||||
Default::default(),
|
||||
StringInput::from(&*fm),
|
||||
@@ -64,9 +63,9 @@ pub fn parse_expr_for_imports(code: &str) -> anyhow::Result<Vec<String>> {
|
||||
err_s += &e.into_kind().msg().to_string();
|
||||
}
|
||||
|
||||
let expr = parser
|
||||
.parse_module()
|
||||
.map_err(|_| anyhow::anyhow!("Error while parsing code, it is invalid TypeScript"))?;
|
||||
let expr = parser.parse_module().map_err(|e| {
|
||||
anyhow::anyhow!("Error while parsing code, it is invalid TypeScript: {err_s}, {e:?}")
|
||||
})?;
|
||||
|
||||
let mut visitor = ImportsFinder { imports: HashSet::new() };
|
||||
swc_ecma_visit::visit_module(&mut visitor, &expr);
|
||||
@@ -119,9 +118,9 @@ pub fn parse_expr_for_ids(code: &str) -> anyhow::Result<Vec<(String, String)>> {
|
||||
err_s += &e.into_kind().msg().to_string();
|
||||
}
|
||||
|
||||
let expr = parser
|
||||
.parse_module()
|
||||
.map_err(|_| anyhow::anyhow!("Error while parsing code, it is invalid TypeScript"))?;
|
||||
let expr = parser.parse_module().map_err(|e| {
|
||||
anyhow::anyhow!("Error while parsing code, it is invalid TypeScript: {err_s}, {e:?}")
|
||||
})?;
|
||||
|
||||
let mut visitor = OutputFinder { idents: HashSet::new() };
|
||||
swc_ecma_visit::visit_module(&mut visitor, &expr);
|
||||
@@ -150,7 +149,9 @@ pub fn parse_deno_signature(code: &str, skip_dflt: bool) -> anyhow::Result<MainA
|
||||
|
||||
let ast = parser
|
||||
.parse_module()
|
||||
.map_err(|_| anyhow::anyhow!("Error while parsing code, it is invalid TypeScript"))?
|
||||
.map_err(|e| {
|
||||
anyhow::anyhow!("Error while parsing code, it is invalid TypeScript: {err_s}, {e:?}")
|
||||
})?
|
||||
.body;
|
||||
|
||||
let params = ast.into_iter().find_map(|x| match x {
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"collaborators": [
|
||||
"Ruben Fiszel <ruben@windmill.dev>"
|
||||
],
|
||||
"version": "1.222.0",
|
||||
"version": "1.226.9",
|
||||
"files": [
|
||||
"windmill_parser_wasm_bg.wasm",
|
||||
"windmill_parser_wasm.js",
|
||||
@@ -14,4 +14,4 @@
|
||||
"sideEffects": [
|
||||
"./snippets/*"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,11 @@ export function parse_outputs(code: string): string;
|
||||
* @param {string} code
|
||||
* @returns {string}
|
||||
*/
|
||||
export function parse_ts_imports(code: string): string;
|
||||
/**
|
||||
* @param {string} code
|
||||
* @returns {string}
|
||||
*/
|
||||
export function parse_bash(code: string): string;
|
||||
/**
|
||||
* @param {string} code
|
||||
@@ -67,6 +72,7 @@ export interface InitOutput {
|
||||
readonly memory: WebAssembly.Memory;
|
||||
readonly parse_deno: (a: number, b: number, c: number) => void;
|
||||
readonly parse_outputs: (a: number, b: number, c: number) => void;
|
||||
readonly parse_ts_imports: (a: number, b: number, c: number) => void;
|
||||
readonly parse_bash: (a: number, b: number, c: number) => void;
|
||||
readonly parse_powershell: (a: number, b: number, c: number) => void;
|
||||
readonly parse_go: (a: number, b: number, c: number) => void;
|
||||
|
||||
@@ -97,6 +97,15 @@ function getInt32Memory0() {
|
||||
return cachedInt32Memory0;
|
||||
}
|
||||
|
||||
const cachedTextDecoder = (typeof TextDecoder !== 'undefined' ? new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }) : { decode: () => { throw Error('TextDecoder not available') } } );
|
||||
|
||||
if (typeof TextDecoder !== 'undefined') { cachedTextDecoder.decode(); };
|
||||
|
||||
function getStringFromWasm0(ptr, len) {
|
||||
ptr = ptr >>> 0;
|
||||
return cachedTextDecoder.decode(getUint8Memory0().subarray(ptr, ptr + len));
|
||||
}
|
||||
|
||||
function addHeapObject(obj) {
|
||||
if (heap_next === heap.length) heap.push(heap.length + 1);
|
||||
const idx = heap_next;
|
||||
@@ -115,15 +124,6 @@ function getFloat64Memory0() {
|
||||
return cachedFloat64Memory0;
|
||||
}
|
||||
|
||||
const cachedTextDecoder = (typeof TextDecoder !== 'undefined' ? new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }) : { decode: () => { throw Error('TextDecoder not available') } } );
|
||||
|
||||
if (typeof TextDecoder !== 'undefined') { cachedTextDecoder.decode(); };
|
||||
|
||||
function getStringFromWasm0(ptr, len) {
|
||||
ptr = ptr >>> 0;
|
||||
return cachedTextDecoder.decode(getUint8Memory0().subarray(ptr, ptr + len));
|
||||
}
|
||||
|
||||
let cachedBigInt64Memory0 = null;
|
||||
|
||||
function getBigInt64Memory0() {
|
||||
@@ -243,6 +243,29 @@ export function parse_outputs(code) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} code
|
||||
* @returns {string}
|
||||
*/
|
||||
export function parse_ts_imports(code) {
|
||||
let deferred2_0;
|
||||
let deferred2_1;
|
||||
try {
|
||||
const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
|
||||
const ptr0 = passStringToWasm0(code, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
||||
const len0 = WASM_VECTOR_LEN;
|
||||
wasm.parse_ts_imports(retptr, ptr0, len0);
|
||||
var r0 = getInt32Memory0()[retptr / 4 + 0];
|
||||
var r1 = getInt32Memory0()[retptr / 4 + 1];
|
||||
deferred2_0 = r0;
|
||||
deferred2_1 = r1;
|
||||
return getStringFromWasm0(r0, r1);
|
||||
} finally {
|
||||
wasm.__wbindgen_add_to_stack_pointer(16);
|
||||
wasm.__wbindgen_free(deferred2_0, deferred2_1, 1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} code
|
||||
* @returns {string}
|
||||
@@ -518,10 +541,6 @@ function __wbg_get_imports() {
|
||||
imports.wbg.__wbindgen_object_drop_ref = function(arg0) {
|
||||
takeObject(arg0);
|
||||
};
|
||||
imports.wbg.__wbg_eval_1081105c41705556 = function(arg0, arg1) {
|
||||
const ret = eval(getStringFromWasm0(arg0, arg1));
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbindgen_string_get = function(arg0, arg1) {
|
||||
const obj = getObject(arg1);
|
||||
const ret = typeof(obj) === 'string' ? obj : undefined;
|
||||
@@ -530,6 +549,10 @@ function __wbg_get_imports() {
|
||||
getInt32Memory0()[arg0 / 4 + 1] = len1;
|
||||
getInt32Memory0()[arg0 / 4 + 0] = ptr1;
|
||||
};
|
||||
imports.wbg.__wbindgen_error_new = function(arg0, arg1) {
|
||||
const ret = new Error(getStringFromWasm0(arg0, arg1));
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbindgen_boolean_get = function(arg0) {
|
||||
const v = getObject(arg0);
|
||||
const ret = typeof(v) === 'boolean' ? (v ? 1 : 0) : 2;
|
||||
@@ -566,8 +589,8 @@ function __wbg_get_imports() {
|
||||
const ret = getObject(arg0) in getObject(arg1);
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbindgen_error_new = function(arg0, arg1) {
|
||||
const ret = new Error(getStringFromWasm0(arg0, arg1));
|
||||
imports.wbg.__wbg_eval_596393dc5ae50a1b = function(arg0, arg1) {
|
||||
const ret = eval(getStringFromWasm0(arg0, arg1));
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbindgen_jsval_loose_eq = function(arg0, arg1) {
|
||||
|
||||
Binary file not shown.
@@ -3,6 +3,7 @@
|
||||
export const memory: WebAssembly.Memory;
|
||||
export function parse_deno(a: number, b: number, c: number): void;
|
||||
export function parse_outputs(a: number, b: number, c: number): void;
|
||||
export function parse_ts_imports(a: number, b: number, c: number): void;
|
||||
export function parse_bash(a: number, b: number, c: number): void;
|
||||
export function parse_powershell(a: number, b: number, c: number): void;
|
||||
export function parse_go(a: number, b: number, c: number): void;
|
||||
|
||||
@@ -31,7 +31,7 @@ pub fn parse_outputs(code: &str) -> String {
|
||||
pub fn parse_ts_imports(code: &str) -> String {
|
||||
let parsed = parse_expr_for_imports(code);
|
||||
let r = if let Ok(parsed) = parsed {
|
||||
json!({ "imports6": parsed })
|
||||
json!({ "imports": parsed })
|
||||
} else {
|
||||
json!({"error": parsed.err().unwrap().to_string()})
|
||||
};
|
||||
|
||||
@@ -298,3 +298,15 @@ fn test_parse_imports() -> anyhow::Result<()> {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn test_parse_imports_dts() -> anyhow::Result<()> {
|
||||
let code = "
|
||||
export type foo = number
|
||||
";
|
||||
let mut l = parse_expr_for_imports(code)?;
|
||||
l.sort();
|
||||
assert_eq!(l, vec![] as Vec<String>);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Generated
+4
-4
@@ -48,7 +48,7 @@
|
||||
"vscode-languageclient": "~9.0.1",
|
||||
"vscode-uri": "~3.0.8",
|
||||
"vscode-ws-jsonrpc": "~3.1.0",
|
||||
"windmill-parser-wasm": "^1.222.0",
|
||||
"windmill-parser-wasm": "^1.226.9",
|
||||
"y-monaco": "^0.1.4",
|
||||
"y-websocket": "^1.5.0",
|
||||
"yaml": "^2.3.4",
|
||||
@@ -9585,9 +9585,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/windmill-parser-wasm": {
|
||||
"version": "1.222.0",
|
||||
"resolved": "https://registry.npmjs.org/windmill-parser-wasm/-/windmill-parser-wasm-1.222.0.tgz",
|
||||
"integrity": "sha512-7Ax3R1qo9Ae5QGewxgf8IcNSk+eVIv1CZwKkUoo1cQMf1FZcgM+oW9GuZ6Q3IFCsGbnjk+cyqhKvxr3hBBMN9A=="
|
||||
"version": "1.226.9",
|
||||
"resolved": "https://registry.npmjs.org/windmill-parser-wasm/-/windmill-parser-wasm-1.226.9.tgz",
|
||||
"integrity": "sha512-yOlLjUF4NlRutZhHrRa4fhxuxLslMaVXBojY3QyIVLlSq+7SGIUTYpEb04XrwpcCNyrgMIL1cNYaq4qzGDKsxA=="
|
||||
},
|
||||
"node_modules/wordwrap": {
|
||||
"version": "1.0.0",
|
||||
|
||||
@@ -131,7 +131,7 @@
|
||||
"vscode-languageclient": "~9.0.1",
|
||||
"vscode-uri": "~3.0.8",
|
||||
"vscode-ws-jsonrpc": "~3.1.0",
|
||||
"windmill-parser-wasm": "^1.222.0",
|
||||
"windmill-parser-wasm": "^1.226.9",
|
||||
"y-monaco": "^0.1.4",
|
||||
"y-websocket": "^1.5.0",
|
||||
"yaml": "^2.3.4",
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
import type { ATABootstrapConfig } from "./index"
|
||||
|
||||
// https://github.com/jsdelivr/data.jsdelivr.com
|
||||
|
||||
export const getNPMVersionsForModule = (config: ATABootstrapConfig, moduleName: string) => {
|
||||
const url = `https://data.jsdelivr.com/v1/package/npm/${moduleName}`
|
||||
return api<{ tags: Record<string, string>; versions: string[] }>(config, url, { cache: "no-store" })
|
||||
}
|
||||
|
||||
export const getNPMVersionForModuleReference = (config: ATABootstrapConfig, moduleName: string, reference: string) => {
|
||||
const url = `https://data.jsdelivr.com/v1/package/resolve/npm/${moduleName}@${reference}`
|
||||
return api<{ version: string | null }>(config, url)
|
||||
}
|
||||
|
||||
export type NPMTreeMeta = {
|
||||
default: string
|
||||
files: Array<{ name: string }>
|
||||
moduleName: string
|
||||
version: string
|
||||
raw: string
|
||||
}
|
||||
|
||||
export const getFiletreeForModuleWithVersion = async (
|
||||
config: ATABootstrapConfig,
|
||||
moduleName: string,
|
||||
version: string,
|
||||
raw: string
|
||||
) => {
|
||||
const url = `https://data.jsdelivr.com/v1/package/npm/${moduleName}@${version}/flat`
|
||||
const res = await api<NPMTreeMeta>(config, url)
|
||||
if (res instanceof Error) {
|
||||
return res
|
||||
} else {
|
||||
return {
|
||||
...res,
|
||||
moduleName,
|
||||
version,
|
||||
raw,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const getDTSFileForModuleWithVersion = async (
|
||||
config: ATABootstrapConfig,
|
||||
moduleName: string,
|
||||
version: string,
|
||||
file: string
|
||||
) => {
|
||||
// file comes with a prefix /
|
||||
const url = `https://cdn.jsdelivr.net/npm/${moduleName}@${version}${file}`
|
||||
const f = config.fetcher || fetch
|
||||
const res = await f(url)
|
||||
if (res.ok) {
|
||||
return res.text()
|
||||
} else {
|
||||
return new Error("OK")
|
||||
}
|
||||
}
|
||||
|
||||
function api<T>(config: ATABootstrapConfig, url: string, init?: RequestInit): Promise<T | Error> {
|
||||
const f = config.fetcher || fetch
|
||||
|
||||
return f(url, init).then(res => {
|
||||
if (res.ok) {
|
||||
return res.json().then(f => f as T)
|
||||
} else {
|
||||
return new Error("OK")
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
/** Converts some of the known global imports to node so that we grab the right info */
|
||||
export const mapModuleNameToModule = (moduleSpecifier: string) => {
|
||||
// in node repl:
|
||||
// > require("module").builtinModules
|
||||
const builtInNodeMods = [
|
||||
"assert",
|
||||
"assert/strict",
|
||||
"async_hooks",
|
||||
"buffer",
|
||||
"child_process",
|
||||
"cluster",
|
||||
"console",
|
||||
"constants",
|
||||
"crypto",
|
||||
"dgram",
|
||||
"diagnostics_channel",
|
||||
"dns",
|
||||
"dns/promises",
|
||||
"domain",
|
||||
"events",
|
||||
"fs",
|
||||
"fs/promises",
|
||||
"http",
|
||||
"http2",
|
||||
"https",
|
||||
"inspector",
|
||||
"module",
|
||||
"net",
|
||||
"os",
|
||||
"path",
|
||||
"path/posix",
|
||||
"path/win32",
|
||||
"perf_hooks",
|
||||
"process",
|
||||
"punycode",
|
||||
"querystring",
|
||||
"readline",
|
||||
"repl",
|
||||
"stream",
|
||||
"stream/promises",
|
||||
"stream/consumers",
|
||||
"stream/web",
|
||||
"string_decoder",
|
||||
"sys",
|
||||
"timers",
|
||||
"timers/promises",
|
||||
"tls",
|
||||
"trace_events",
|
||||
"tty",
|
||||
"url",
|
||||
"util",
|
||||
"util/types",
|
||||
"v8",
|
||||
"vm",
|
||||
"wasi",
|
||||
"worker_threads",
|
||||
"zlib",
|
||||
]
|
||||
|
||||
if (builtInNodeMods.includes(moduleSpecifier.replace("node:", ""))) {
|
||||
return "node"
|
||||
}
|
||||
|
||||
// strip module filepath e.g. lodash/identity => lodash
|
||||
const [a = "", b = ""] = moduleSpecifier.split("/")
|
||||
const moduleName = a.startsWith("@") ? `${a}/${b}` : a
|
||||
|
||||
return moduleName
|
||||
}
|
||||
@@ -0,0 +1,279 @@
|
||||
import {
|
||||
getDTSFileForModuleWithVersion,
|
||||
getFiletreeForModuleWithVersion,
|
||||
getNPMVersionForModuleReference,
|
||||
getNPMVersionsForModule,
|
||||
type NPMTreeMeta
|
||||
} from './apis'
|
||||
import { mapModuleNameToModule } from './edgeCases'
|
||||
|
||||
export interface ATABootstrapConfig {
|
||||
/** A object you pass in to get callbacks */
|
||||
delegate: {
|
||||
/** The callback which gets called when ATA decides a file needs to be written to your VFS */
|
||||
receivedFile?: (code: string, path: string) => void
|
||||
/** A way to display progress */
|
||||
progress?: (downloaded: number, estimatedTotal: number) => void
|
||||
/** Note: An error message does not mean ATA has stopped! */
|
||||
errorMessage?: (userFacingMessage: string, error: Error) => void
|
||||
/** A callback indicating that ATA actually has work to do */
|
||||
started?: () => void
|
||||
/** The callback when all ATA has finished */
|
||||
finished?: (files: Map<string, string>) => void
|
||||
}
|
||||
/** Passed to fetch as the user-agent */
|
||||
projectName: string
|
||||
/** code to dependency parser */
|
||||
depsParser: (code: string) => string[]
|
||||
/** If you need a custom version of fetch */
|
||||
fetcher?: typeof fetch
|
||||
/** If you need a custom logger instead of the console global */
|
||||
logger?: Logger
|
||||
}
|
||||
|
||||
type ModuleMeta = { state: 'loading' }
|
||||
|
||||
/**
|
||||
* The function which starts up type acquisition,
|
||||
* returns a function which you then pass the initial
|
||||
* source code for the app with.
|
||||
*
|
||||
* This is effectively the main export, everything else is
|
||||
* basically exported for tests and should be considered
|
||||
* implementation details by consumers.
|
||||
*/
|
||||
export const setupTypeAcquisition = (config: ATABootstrapConfig) => {
|
||||
const moduleMap = new Map<string, ModuleMeta>()
|
||||
const fsMap = new Map<string, string>()
|
||||
|
||||
let estimatedToDownload = 0
|
||||
let estimatedDownloaded = 0
|
||||
|
||||
return (initialSourceFile: string) => {
|
||||
estimatedToDownload = 0
|
||||
estimatedDownloaded = 0
|
||||
|
||||
return resolveDeps(initialSourceFile, 0).then((t) => {
|
||||
if (estimatedDownloaded > 0) {
|
||||
config.delegate.finished?.(fsMap)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function getVersion(d: string) {
|
||||
if (d.lastIndexOf('@') > 0) {
|
||||
const splitted = d.split('@')
|
||||
let version = splitted.pop()
|
||||
if (version?.startsWith('^') || version?.startsWith('~')) {
|
||||
version = version.slice(1)
|
||||
}
|
||||
return version
|
||||
}
|
||||
return 'latest'
|
||||
}
|
||||
async function resolveDeps(initialSourceFile: string, depth: number) {
|
||||
if (depth > 2) {
|
||||
console.log('STOP HERE', depth)
|
||||
return
|
||||
} else {
|
||||
console.log('L', depth)
|
||||
}
|
||||
const depsToGet = config
|
||||
.depsParser(initialSourceFile)
|
||||
.map((d: string) => {
|
||||
let raw = mapModuleNameToModule(d)
|
||||
return {
|
||||
raw,
|
||||
module: raw.lastIndexOf('@') > 0 ? raw.split('@').slice(0, -1).join('@') : raw,
|
||||
version: getVersion(d)
|
||||
}
|
||||
})
|
||||
.filter((f) => !moduleMap.has(f.raw))
|
||||
|
||||
if (depsToGet.length === 0) {
|
||||
return
|
||||
}
|
||||
// Make it so it won't get re-downloaded
|
||||
depsToGet.forEach((dep) => moduleMap.set(dep.raw, { state: 'loading' }))
|
||||
|
||||
// Grab the module trees which gives us a list of files to download
|
||||
const trees = await Promise.all(
|
||||
depsToGet.map((f) => getFileTreeForModuleWithTag(config, f.module, f.version, f.raw))
|
||||
)
|
||||
const treesOnly = trees.filter((t) => !('error' in t)) as NPMTreeMeta[]
|
||||
|
||||
// These are the modules which we can grab directly
|
||||
const hasDTS = treesOnly.filter((t) => t.files.find((f) => f.name.endsWith('.d.ts')))
|
||||
const dtsFilesFromNPM = hasDTS.map((t) => treeToDTSFiles(t, `/node_modules/${t.raw}`))
|
||||
|
||||
// These are ones we need to look on DT for (which may not be there, who knows)
|
||||
const mightBeOnDT = treesOnly.filter((t) => !hasDTS.includes(t))
|
||||
const dtTrees = await Promise.all(
|
||||
// TODO: Switch from 'latest' to the version from the original tree which is user-controlled
|
||||
mightBeOnDT.map((f) =>
|
||||
getFileTreeForModuleWithTag(
|
||||
config,
|
||||
`@types/${getDTName(f.moduleName)}`,
|
||||
'latest',
|
||||
`@types/${getDTName(f.raw)}`
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
const dtTreesOnly = dtTrees.filter((t) => !('error' in t)) as NPMTreeMeta[]
|
||||
const dtsFilesFromDT = dtTreesOnly.map((t) =>
|
||||
treeToDTSFiles(t, `/node_modules/@types/${getDTName(t.raw).replace('types__', '')}`)
|
||||
)
|
||||
|
||||
// Collect all the npm and DT DTS requests and flatten their arrays
|
||||
const allDTSFiles = dtsFilesFromNPM.concat(dtsFilesFromDT).reduce((p, c) => p.concat(c), [])
|
||||
estimatedToDownload += allDTSFiles.length
|
||||
if (allDTSFiles.length && depth === 0) {
|
||||
config.delegate.started?.()
|
||||
}
|
||||
|
||||
// Grab the package.jsons for each dependency
|
||||
for (const tree of treesOnly) {
|
||||
const pkgJSON = await getDTSFileForModuleWithVersion(
|
||||
config,
|
||||
tree.moduleName,
|
||||
tree.version,
|
||||
'/package.json'
|
||||
)
|
||||
let prefix = `/node_modules/${tree.moduleName}`
|
||||
if (dtTreesOnly.includes(tree))
|
||||
prefix = `/node_modules/@types/${getDTName(tree.raw).replace('types__', '')}`
|
||||
const path = prefix + '/package.json'
|
||||
|
||||
if (typeof pkgJSON == 'string') {
|
||||
fsMap.set(path, pkgJSON)
|
||||
config.delegate.receivedFile?.(pkgJSON, path)
|
||||
} else {
|
||||
config.logger?.error(`Could not download package.json for ${tree.moduleName}`)
|
||||
}
|
||||
}
|
||||
|
||||
// Grab all dts files
|
||||
await Promise.all(
|
||||
allDTSFiles.map(async (dts) => {
|
||||
const dtsCode = await getDTSFileForModuleWithVersion(
|
||||
config,
|
||||
dts.moduleName,
|
||||
dts.moduleVersion,
|
||||
dts.path
|
||||
)
|
||||
estimatedDownloaded++
|
||||
if (dtsCode instanceof Error) {
|
||||
// TODO?
|
||||
config.logger?.error(`Had an issue getting ${dts.path} for ${dts.moduleName}`)
|
||||
} else {
|
||||
fsMap.set(dts.vfsPath, dtsCode)
|
||||
config.delegate.receivedFile?.(dtsCode, dts.vfsPath)
|
||||
|
||||
// Send a progress note every 5 downloads
|
||||
if (config.delegate.progress && estimatedDownloaded % 5 === 0) {
|
||||
config.delegate.progress(estimatedDownloaded, estimatedToDownload)
|
||||
}
|
||||
|
||||
if (dts.moduleName != 'bun-types') {
|
||||
// Recurse through deps
|
||||
await resolveDeps(dtsCode, depth + 1)
|
||||
}
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
type ATADownload = {
|
||||
moduleName: string
|
||||
moduleVersion: string
|
||||
vfsPath: string
|
||||
path: string
|
||||
}
|
||||
|
||||
function treeToDTSFiles(tree: NPMTreeMeta, vfsPrefix: string) {
|
||||
const dtsRefs: ATADownload[] = []
|
||||
|
||||
for (const file of tree.files) {
|
||||
if (file.name.endsWith('.d.ts')) {
|
||||
dtsRefs.push({
|
||||
moduleName: tree.moduleName,
|
||||
moduleVersion: tree.version,
|
||||
vfsPath: `${vfsPrefix}${file.name}`,
|
||||
path: file.name
|
||||
})
|
||||
}
|
||||
}
|
||||
return dtsRefs
|
||||
}
|
||||
|
||||
/** The bulk load of the work in getting the filetree based on how people think about npm names and versions */
|
||||
export const getFileTreeForModuleWithTag = async (
|
||||
config: ATABootstrapConfig,
|
||||
moduleName: string,
|
||||
tag: string | undefined,
|
||||
raw: string
|
||||
) => {
|
||||
let toDownload = tag || 'latest'
|
||||
|
||||
// I think having at least 2 dots is a reasonable approx for being a semver and not a tag,
|
||||
// we can skip an API request, TBH this is probably rare
|
||||
if (toDownload.split('.').length < 2) {
|
||||
// The jsdelivr API needs a _version_ not a tag. So, we need to switch out
|
||||
// the tag to the version via an API request.
|
||||
const response = await getNPMVersionForModuleReference(config, moduleName, toDownload)
|
||||
if (response instanceof Error) {
|
||||
return {
|
||||
error: response,
|
||||
userFacingMessage: `Could not go from a tag to version on npm for ${moduleName} - possible typo?`
|
||||
}
|
||||
}
|
||||
|
||||
const neededVersion = response.version
|
||||
if (!neededVersion) {
|
||||
const versions = await getNPMVersionsForModule(config, moduleName)
|
||||
if (versions instanceof Error) {
|
||||
return {
|
||||
error: response,
|
||||
userFacingMessage: `Could not get versions on npm for ${moduleName} - possible typo?`
|
||||
}
|
||||
}
|
||||
|
||||
const tags = Object.entries(versions.tags).join(', ')
|
||||
return {
|
||||
error: new Error('Could not find tag for module'),
|
||||
userFacingMessage: `Could not find a tag for ${moduleName} called ${tag}. Did find ${tags}`
|
||||
}
|
||||
}
|
||||
|
||||
toDownload = neededVersion
|
||||
}
|
||||
|
||||
const res = await getFiletreeForModuleWithVersion(config, moduleName, toDownload, raw)
|
||||
if (res instanceof Error) {
|
||||
return {
|
||||
error: res,
|
||||
userFacingMessage: `Could not get the files for ${moduleName}@${toDownload}. Is it possibly a typo?`
|
||||
}
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
interface Logger {
|
||||
log: (...args: any[]) => void
|
||||
error: (...args: any[]) => void
|
||||
groupCollapsed: (...args: any[]) => void
|
||||
groupEnd: (...args: any[]) => void
|
||||
}
|
||||
|
||||
// Taken from dts-gen: https://github.com/microsoft/dts-gen/blob/master/lib/names.ts
|
||||
function getDTName(s: string) {
|
||||
if (s.indexOf('@') === 0 && s.indexOf('/') !== -1) {
|
||||
// we have a scoped module, e.g. @bla/foo
|
||||
// which should be converted to bla__foo
|
||||
s = s.substr(1).replace('/', '__')
|
||||
}
|
||||
return s
|
||||
}
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
export interface ATABootstrapConfig {
|
||||
/** A object you pass in to get callbacks */
|
||||
delegate: {
|
||||
/** The callback which gets called when ATA decides a file needs to be written to your VFS */
|
||||
receivedFile?: (code: string, path: string) => void
|
||||
/** A way to display progress */
|
||||
progress?: (downloaded: number, estimatedTotal: number) => void
|
||||
/** Note: An error message does not mean ATA has stopped! */
|
||||
errorMessage?: (userFacingMessage: string, error: Error) => void
|
||||
/** A callback indicating that ATA actually has work to do */
|
||||
started?: () => void
|
||||
/** The callback when all ATA has finished */
|
||||
finished?: (files: Map<string, string>) => void
|
||||
}
|
||||
/** Passed to fetch as the user-agent */
|
||||
projectName: string
|
||||
/** Your local copy of typescript */
|
||||
depsParser: (code: string) => string[]
|
||||
|
||||
/** If you need a custom version of fetch */
|
||||
fetcher?: typeof fetch
|
||||
/** If you need a custom logger instead of the console global */
|
||||
logger?: Logger
|
||||
}
|
||||
|
||||
type ModuleMeta = { state: "loading" }
|
||||
|
||||
/**
|
||||
* The function which starts up type acquisition,
|
||||
* returns a function which you then pass the initial
|
||||
* source code for the app with.
|
||||
*
|
||||
* This is effectively the main export, everything else is
|
||||
* basically exported for tests and should be considered
|
||||
* implementation details by consumers.
|
||||
*/
|
||||
export const setupTypeAcquisition: (config: ATABootstrapConfig) => (initialSourceFile: string) => void
|
||||
|
||||
interface Logger {
|
||||
log: (...args: any[]) => void
|
||||
error: (...args: any[]) => void
|
||||
groupCollapsed: (...args: any[]) => void
|
||||
groupEnd: (...args: any[]) => void
|
||||
}
|
||||
@@ -25,12 +25,17 @@
|
||||
import 'monaco-editor/esm/vs/language/typescript/monaco.contribution'
|
||||
import 'monaco-editor/esm/vs/basic-languages/css/css.contribution'
|
||||
|
||||
import libStdContent from '$lib/es6.d.ts.txt?raw'
|
||||
import denoFetchContent from '$lib/deno_fetch.d.ts.txt?raw'
|
||||
|
||||
// import nord from '$lib/assets/nord.json'
|
||||
|
||||
// import nord from '$lib/assets/nord.json'
|
||||
|
||||
import { MonacoLanguageClient } from 'monaco-languageclient'
|
||||
|
||||
import { toSocket, WebSocketMessageReader, WebSocketMessageWriter } from 'vscode-ws-jsonrpc'
|
||||
import { CloseAction, ErrorAction, RequestType, NotificationType } from 'vscode-languageclient'
|
||||
import { CloseAction, ErrorAction, RequestType } from 'vscode-languageclient'
|
||||
import { MonacoBinding } from 'y-monaco'
|
||||
import {
|
||||
dbSchemas,
|
||||
@@ -65,6 +70,8 @@
|
||||
POSTGRES_TYPES,
|
||||
SNOWFLAKE_TYPES
|
||||
} from '$lib/consts'
|
||||
import { setupTypeAcquisition } from '$lib/ata/index'
|
||||
import { initWasm, parseDeps } from '$lib/infer'
|
||||
// import EditorTheme from './EditorTheme.svelte'
|
||||
|
||||
let divEl: HTMLDivElement | null = null
|
||||
@@ -79,6 +86,7 @@
|
||||
| 'graphql'
|
||||
| 'powershell'
|
||||
| 'css'
|
||||
| 'javascript'
|
||||
export let code: string = ''
|
||||
export let cmdEnterAction: (() => void) | undefined = undefined
|
||||
export let formatAction: (() => void) | undefined = undefined
|
||||
@@ -89,8 +97,7 @@
|
||||
ruff: false,
|
||||
deno: false,
|
||||
go: false,
|
||||
shellcheck: false,
|
||||
bun: false
|
||||
shellcheck: false
|
||||
}
|
||||
export let shouldBindKey: boolean = true
|
||||
export let fixedOverflowWidgets = true
|
||||
@@ -132,7 +139,7 @@
|
||||
|
||||
let destroyed = false
|
||||
const uri =
|
||||
lang == 'typescript' && scriptLang === 'deno'
|
||||
lang != 'go' && lang != 'typescript' && lang != 'python'
|
||||
? `file:///${filePath ?? rHash}.${langToExt(lang)}`
|
||||
: `file:///tmp/monaco/${randomHash()}.${langToExt(lang)}`
|
||||
|
||||
@@ -551,13 +558,7 @@
|
||||
isTrusted: true
|
||||
},
|
||||
workspaceFolder:
|
||||
name == 'bun'
|
||||
? {
|
||||
uri: vscode.Uri.parse('file:///tmp/monaco/'),
|
||||
name: 'windmill',
|
||||
index: 0
|
||||
}
|
||||
: name != 'deno'
|
||||
name != 'deno'
|
||||
? {
|
||||
uri: vscode.Uri.parse(uri),
|
||||
name: 'windmill',
|
||||
@@ -672,17 +673,6 @@
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
}
|
||||
} else if (name == 'bun') {
|
||||
await languageClient.sendNotification(
|
||||
new NotificationType('workspace/didChangeConfiguration'),
|
||||
{
|
||||
settings: {
|
||||
diagnostics: {
|
||||
ignoredCodes: [2307]
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
websocketAlive[name] = true
|
||||
@@ -696,8 +686,14 @@
|
||||
const hostname = BROWSER ? window.location.protocol + '//' + window.location.host : 'SSR'
|
||||
|
||||
let encodedImportMap = ''
|
||||
// if (lang == 'typescript') {
|
||||
|
||||
// let worker = await languages.typescript.getTypeScriptWorker()
|
||||
// console.log(worker)
|
||||
// }
|
||||
if (useWebsockets) {
|
||||
if (lang == 'typescript' && scriptLang === 'deno') {
|
||||
ata = undefined
|
||||
let token = $lspTokenStore
|
||||
if (!token) {
|
||||
let expiration = new Date()
|
||||
@@ -768,22 +764,45 @@
|
||||
]
|
||||
}
|
||||
)
|
||||
} else if (lang === 'typescript' && scriptLang !== 'deno') {
|
||||
await connectToLanguageServer(
|
||||
`${wsProtocol}://${window.location.host}/ws/bun`,
|
||||
'bun',
|
||||
{},
|
||||
(params, token, next) => {
|
||||
return [
|
||||
{
|
||||
diagnostics: {
|
||||
ignoredCodes: [2307]
|
||||
},
|
||||
enable: true
|
||||
}
|
||||
]
|
||||
} else if (lang === 'javascript') {
|
||||
const stdLib = { content: libStdContent, filePath: 'es6.d.ts' }
|
||||
|
||||
if (scriptLang == 'bun') {
|
||||
languages.typescript.javascriptDefaults.setExtraLibs([stdLib])
|
||||
} else {
|
||||
const denoFetch = { content: denoFetchContent, filePath: 'deno_fetch.d.ts' }
|
||||
languages.typescript.javascriptDefaults.setExtraLibs([stdLib, denoFetch])
|
||||
}
|
||||
if (scriptLang == 'bun') {
|
||||
const addLibraryToRuntime = async (code: string, _path: string) => {
|
||||
const path = 'file://' + _path
|
||||
languages.typescript.javascriptDefaults.addExtraLib(code, path)
|
||||
const uri = mUri.parse(path)
|
||||
await vscode.workspace.fs.writeFile(uri, new TextEncoder().encode(code))
|
||||
}
|
||||
)
|
||||
await initWasm()
|
||||
ata = setupTypeAcquisition({
|
||||
projectName: 'Windmill',
|
||||
depsParser: (c) => {
|
||||
return parseDeps(c)
|
||||
},
|
||||
logger: console,
|
||||
delegate: {
|
||||
receivedFile: addLibraryToRuntime,
|
||||
progress: (downloaded: number, total: number) => {
|
||||
// console.log({ dl, ttl })
|
||||
},
|
||||
started: () => {
|
||||
console.log('ATA start')
|
||||
},
|
||||
finished: (f) => {
|
||||
console.log('ATA done')
|
||||
}
|
||||
}
|
||||
})
|
||||
ata?.('import "bun-types"')
|
||||
ata?.(code)
|
||||
}
|
||||
} else if (lang === 'python') {
|
||||
await connectToLanguageServer(
|
||||
`${wsProtocol}://${window.location.host}/ws/pyright`,
|
||||
@@ -899,7 +918,6 @@
|
||||
!websocketAlive.deno &&
|
||||
!websocketAlive.pyright &&
|
||||
!websocketAlive.go &&
|
||||
!websocketAlive.bun &&
|
||||
!websocketAlive.shellcheck &&
|
||||
!websocketAlive.ruff
|
||||
) {
|
||||
@@ -970,6 +988,8 @@
|
||||
}
|
||||
|
||||
let initialized = false
|
||||
let ata: ((s: string) => void) | undefined = undefined
|
||||
|
||||
async function loadMonaco() {
|
||||
try {
|
||||
console.log("Loading Monaco's language client")
|
||||
@@ -983,6 +1003,30 @@
|
||||
|
||||
initialized = true
|
||||
|
||||
languages.typescript.typescriptDefaults.setModeConfiguration({
|
||||
completionItems: false,
|
||||
definitions: false,
|
||||
hovers: false
|
||||
})
|
||||
|
||||
languages.typescript.javascriptDefaults.setCompilerOptions({
|
||||
target: languages.typescript.ScriptTarget.Latest,
|
||||
allowNonTsExtensions: true,
|
||||
noSemanticValidation: false,
|
||||
noLib: true,
|
||||
moduleResolution: languages.typescript.ModuleResolutionKind.NodeJs
|
||||
})
|
||||
// languages.typescript.typescriptDefaults.setModeConfiguration({
|
||||
// completionItems: true,
|
||||
// definitions: true,
|
||||
// hovers: true,
|
||||
// diagnostics: true
|
||||
// })
|
||||
|
||||
// languages.typescript.typescriptDefaults.setCompilerOptions(
|
||||
// languages.typescript.typescriptDefaults.getCompilerOptions()
|
||||
// )
|
||||
|
||||
try {
|
||||
model = meditor.createModel(code, lang, mUri.parse(uri))
|
||||
} catch (err) {
|
||||
@@ -1004,14 +1048,11 @@
|
||||
folding
|
||||
})
|
||||
|
||||
languages.typescript.typescriptDefaults.setModeConfiguration({
|
||||
completionItems: false,
|
||||
definitions: false,
|
||||
hovers: false
|
||||
})
|
||||
|
||||
let timeoutModel: NodeJS.Timeout | undefined = undefined
|
||||
let ataModel: NodeJS.Timeout | undefined = undefined
|
||||
|
||||
editor.onDidChangeModelContent((event) => {
|
||||
console.log('foo')
|
||||
timeoutModel && clearTimeout(timeoutModel)
|
||||
timeoutModel = setTimeout(() => {
|
||||
let ncode = getCode()
|
||||
@@ -1020,6 +1061,11 @@
|
||||
dispatch('change', code)
|
||||
}
|
||||
}, 500)
|
||||
|
||||
ataModel && clearTimeout(ataModel)
|
||||
ataModel = setTimeout(() => {
|
||||
ata?.(getCode())
|
||||
}, 1000)
|
||||
})
|
||||
|
||||
editor.onDidBlurEditorText(() => {
|
||||
@@ -1057,6 +1103,7 @@
|
||||
|
||||
return () => {
|
||||
console.log('disposing editor')
|
||||
ata = undefined
|
||||
try {
|
||||
closeWebsockets()
|
||||
model?.dispose()
|
||||
|
||||
@@ -52,7 +52,6 @@
|
||||
deno: boolean
|
||||
go: boolean
|
||||
shellcheck: boolean
|
||||
bun: boolean
|
||||
}
|
||||
export let iconOnly: boolean = false
|
||||
export let validCode: boolean = true
|
||||
@@ -487,37 +486,37 @@
|
||||
Reset
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
btnClasses="!font-medium text-tertiary"
|
||||
size="xs"
|
||||
spacingSize="md"
|
||||
color="light"
|
||||
on:click={() => editor?.reloadWebsocket()}
|
||||
startIcon={{
|
||||
icon: RotateCw,
|
||||
classes: websocketAlive[lang] == false ? 'animate-spin' : ''
|
||||
}}
|
||||
title="Reload assistants"
|
||||
>
|
||||
{#if !iconOnly}
|
||||
Assistants
|
||||
{/if}
|
||||
<span class="ml-1 -my-1">
|
||||
{#if lang == 'deno'}
|
||||
(<span class={websocketAlive.deno ? 'green' : 'text-red-700'}>Deno</span>)
|
||||
{:else if lang == 'bun'}
|
||||
(<span class={websocketAlive.bun ? 'green' : 'text-red-700'}>Bun</span>)
|
||||
{:else if lang == 'go'}
|
||||
(<span class={websocketAlive.go ? 'green' : 'text-red-700'}>Go</span>)
|
||||
{:else if lang == 'python3'}
|
||||
(<span class={websocketAlive.pyright ? 'green' : 'text-red-700'}>Pyright</span>
|
||||
<span class={websocketAlive.black ? 'green' : 'text-red-700'}>Black</span>
|
||||
<span class={websocketAlive.ruff ? 'green' : 'text-red-700'}>Ruff</span>)
|
||||
{:else if lang == 'bash'}
|
||||
(<span class={websocketAlive.shellcheck ? 'green' : 'text-red-700'}>Shellcheck</span>)
|
||||
{#if lang == 'deno' || lang == 'python3' || lang == 'go' || lang == 'bash'}
|
||||
<Button
|
||||
btnClasses="!font-medium text-tertiary"
|
||||
size="xs"
|
||||
spacingSize="md"
|
||||
color="light"
|
||||
on:click={() => editor?.reloadWebsocket()}
|
||||
startIcon={{
|
||||
icon: RotateCw,
|
||||
classes: websocketAlive[lang] == false ? 'animate-spin' : ''
|
||||
}}
|
||||
title="Reload assistants"
|
||||
>
|
||||
{#if !iconOnly}
|
||||
Assistants
|
||||
{/if}
|
||||
</span>
|
||||
</Button>
|
||||
<span class="ml-1 -my-1">
|
||||
{#if lang == 'deno'}
|
||||
(<span class={websocketAlive.deno ? 'green' : 'text-red-700'}>Deno</span>)
|
||||
{:else if lang == 'go'}
|
||||
(<span class={websocketAlive.go ? 'green' : 'text-red-700'}>Go</span>)
|
||||
{:else if lang == 'python3'}
|
||||
(<span class={websocketAlive.pyright ? 'green' : 'text-red-700'}>Pyright</span>
|
||||
<span class={websocketAlive.black ? 'green' : 'text-red-700'}>Black</span>
|
||||
<span class={websocketAlive.ruff ? 'green' : 'text-red-700'}>Ruff</span>)
|
||||
{:else if lang == 'bash'}
|
||||
(<span class={websocketAlive.shellcheck ? 'green' : 'text-red-700'}>Shellcheck</span>)
|
||||
{/if}
|
||||
</span>
|
||||
</Button>
|
||||
{/if}
|
||||
{#if collabMode}
|
||||
<div class="flex items-center px-1">
|
||||
<Toggle
|
||||
|
||||
@@ -49,7 +49,6 @@
|
||||
go: false,
|
||||
ruff: false,
|
||||
shellcheck: false,
|
||||
bun: false
|
||||
}
|
||||
|
||||
let width = 1200
|
||||
|
||||
@@ -29,7 +29,8 @@
|
||||
|
||||
import { createEventDispatcher, onDestroy, onMount } from 'svelte'
|
||||
|
||||
import libStdContent from '$lib/es5.d.ts.txt?raw'
|
||||
import libStdContent from '$lib/es6.d.ts.txt?raw'
|
||||
import domContent from '$lib/dom.d.ts.txt?raw'
|
||||
import { buildWorkerDefinition } from './build_workers'
|
||||
import { initializeVscode } from './vscode'
|
||||
import EditorTheme from './EditorTheme.svelte'
|
||||
@@ -53,6 +54,7 @@
|
||||
export let autoHeight = false
|
||||
export let fixedOverflowWidgets = true
|
||||
export let small = false
|
||||
export let domLib = false
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
@@ -132,6 +134,7 @@
|
||||
allowNonTsExtensions: true,
|
||||
noLib: true
|
||||
})
|
||||
languages.typescript.javascriptDefaults.setExtraLibs([])
|
||||
|
||||
languages.json.jsonDefaults.setDiagnosticsOptions({
|
||||
validate: true,
|
||||
@@ -290,17 +293,19 @@
|
||||
|
||||
function loadExtraLib() {
|
||||
if (lang == 'javascript') {
|
||||
const stdLib = { content: libStdContent, filePath: 'es5.d.ts' }
|
||||
const stdLib = { content: libStdContent, filePath: 'es6.d.ts' }
|
||||
const domDTS = { content: domContent, filePath: 'dom.d.ts' }
|
||||
const stds = domLib ? [stdLib, domDTS] : [stdLib]
|
||||
if (extraLib != '') {
|
||||
languages.typescript.javascriptDefaults.setExtraLibs([
|
||||
{
|
||||
content: extraLib,
|
||||
filePath: 'windmill.d.ts'
|
||||
},
|
||||
stdLib
|
||||
...stds
|
||||
])
|
||||
} else {
|
||||
languages.typescript.javascriptDefaults.setExtraLibs([stdLib])
|
||||
languages.typescript.javascriptDefaults.setExtraLibs(stds)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
editorConfig,
|
||||
updateOptions
|
||||
} from '$lib/editorUtils'
|
||||
import libStdContent from '$lib/es5.d.ts.txt?raw'
|
||||
import libStdContent from '$lib/es6.d.ts.txt?raw'
|
||||
import { editor as meditor, Uri as mUri, languages, Range, KeyMod, KeyCode } from 'monaco-editor'
|
||||
import { createEventDispatcher, getContext, onDestroy, onMount } from 'svelte'
|
||||
import type { AppViewerContext } from './apps/types'
|
||||
@@ -419,7 +419,9 @@
|
||||
languages.typescript.javascriptDefaults.setCompilerOptions({
|
||||
target: languages.typescript.ScriptTarget.Latest,
|
||||
allowNonTsExtensions: true,
|
||||
noLib: true
|
||||
noSemanticValidation: false,
|
||||
noLib: true,
|
||||
moduleResolution: languages.typescript.ModuleResolutionKind.NodeJs
|
||||
})
|
||||
|
||||
languages.register({ id: 'template' })
|
||||
@@ -587,7 +589,7 @@
|
||||
$: mounted && extraLib && initialized && loadExtraLib()
|
||||
|
||||
function loadExtraLib() {
|
||||
const stdLib = { content: libStdContent, filePath: 'es5.d.ts' }
|
||||
const stdLib = { content: libStdContent, filePath: 'es6.d.ts' }
|
||||
if (extraLib != '') {
|
||||
languages.typescript.javascriptDefaults.setExtraLibs([
|
||||
{
|
||||
|
||||
@@ -302,6 +302,7 @@
|
||||
{extraLib}
|
||||
bind:code={inlineScript.content}
|
||||
lang="javascript"
|
||||
domLib
|
||||
cmdEnterAction={async () => {
|
||||
runLoading = true
|
||||
await await Promise.all(
|
||||
|
||||
+1
@@ -93,6 +93,7 @@
|
||||
lang="javascript"
|
||||
bind:code={componentInput.expr}
|
||||
shouldBindKey={false}
|
||||
domLib
|
||||
{extraLib}
|
||||
autoHeight
|
||||
{fixedOverflowWidgets}
|
||||
|
||||
@@ -66,8 +66,7 @@
|
||||
deno: false,
|
||||
go: false,
|
||||
ruff: false,
|
||||
shellcheck: false,
|
||||
bun: false
|
||||
shellcheck: false
|
||||
}
|
||||
let selected = 'inputs'
|
||||
let advancedSelected = 'retries'
|
||||
|
||||
@@ -12,7 +12,7 @@ export async function initializeVscode() {
|
||||
try {
|
||||
// init vscode-api
|
||||
await initServices({
|
||||
debugLogging: false,
|
||||
debugLogging: true,
|
||||
logLevel: LogLevel.Info
|
||||
})
|
||||
meditor.defineTheme('nord', {
|
||||
|
||||
@@ -0,0 +1,407 @@
|
||||
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
||||
|
||||
// deno-lint-ignore-file no-explicit-any no-var
|
||||
|
||||
/// <reference no-default-lib="true" />
|
||||
/// <reference lib="esnext" />
|
||||
|
||||
/** @category DOM APIs */
|
||||
declare interface DomIterable<K, V> {
|
||||
keys(): IterableIterator<K>;
|
||||
values(): IterableIterator<V>;
|
||||
entries(): IterableIterator<[K, V]>;
|
||||
[Symbol.iterator](): IterableIterator<[K, V]>;
|
||||
forEach(
|
||||
callback: (value: V, key: K, parent: this) => void,
|
||||
thisArg?: any,
|
||||
): void;
|
||||
}
|
||||
|
||||
/** @category Fetch API */
|
||||
declare type FormDataEntryValue = File | string;
|
||||
|
||||
/** Provides a way to easily construct a set of key/value pairs representing
|
||||
* form fields and their values, which can then be easily sent using the
|
||||
* XMLHttpRequest.send() method. It uses the same format a form would use if the
|
||||
* encoding type were set to "multipart/form-data".
|
||||
*
|
||||
* @category Fetch API
|
||||
*/
|
||||
declare interface FormData extends DomIterable<string, FormDataEntryValue> {
|
||||
append(name: string, value: string | Blob, fileName?: string): void;
|
||||
delete(name: string): void;
|
||||
get(name: string): FormDataEntryValue | null;
|
||||
getAll(name: string): FormDataEntryValue[];
|
||||
has(name: string): boolean;
|
||||
set(name: string, value: string | Blob, fileName?: string): void;
|
||||
}
|
||||
|
||||
/** @category Fetch API */
|
||||
declare var FormData: {
|
||||
readonly prototype: FormData;
|
||||
new (): FormData;
|
||||
};
|
||||
|
||||
/** @category Fetch API */
|
||||
declare interface Body {
|
||||
/** A simple getter used to expose a `ReadableStream` of the body contents. */
|
||||
readonly body: ReadableStream<Uint8Array> | null;
|
||||
/** Stores a `Boolean` that declares whether the body has been used in a
|
||||
* response yet.
|
||||
*/
|
||||
readonly bodyUsed: boolean;
|
||||
/** Takes a `Response` stream and reads it to completion. It returns a promise
|
||||
* that resolves with an `ArrayBuffer`.
|
||||
*/
|
||||
arrayBuffer(): Promise<ArrayBuffer>;
|
||||
/** Takes a `Response` stream and reads it to completion. It returns a promise
|
||||
* that resolves with a `Blob`.
|
||||
*/
|
||||
blob(): Promise<Blob>;
|
||||
/** Takes a `Response` stream and reads it to completion. It returns a promise
|
||||
* that resolves with a `FormData` object.
|
||||
*/
|
||||
formData(): Promise<FormData>;
|
||||
/** Takes a `Response` stream and reads it to completion. It returns a promise
|
||||
* that resolves with the result of parsing the body text as JSON.
|
||||
*/
|
||||
json(): Promise<any>;
|
||||
/** Takes a `Response` stream and reads it to completion. It returns a promise
|
||||
* that resolves with a `USVString` (text).
|
||||
*/
|
||||
text(): Promise<string>;
|
||||
}
|
||||
|
||||
/** @category Fetch API */
|
||||
declare type HeadersInit = Iterable<string[]> | Record<string, string>;
|
||||
|
||||
/** This Fetch API interface allows you to perform various actions on HTTP
|
||||
* request and response headers. These actions include retrieving, setting,
|
||||
* adding to, and removing. A Headers object has an associated header list,
|
||||
* which is initially empty and consists of zero or more name and value pairs.
|
||||
* You can add to this using methods like append() (see Examples). In all
|
||||
* methods of this interface, header names are matched by case-insensitive byte
|
||||
* sequence.
|
||||
*
|
||||
* @category Fetch API
|
||||
*/
|
||||
declare interface Headers extends DomIterable<string, string> {
|
||||
/** Appends a new value onto an existing header inside a `Headers` object, or
|
||||
* adds the header if it does not already exist.
|
||||
*/
|
||||
append(name: string, value: string): void;
|
||||
/** Deletes a header from a `Headers` object. */
|
||||
delete(name: string): void;
|
||||
/** Returns a `ByteString` sequence of all the values of a header within a
|
||||
* `Headers` object with a given name.
|
||||
*/
|
||||
get(name: string): string | null;
|
||||
/** Returns a boolean stating whether a `Headers` object contains a certain
|
||||
* header.
|
||||
*/
|
||||
has(name: string): boolean;
|
||||
/** Sets a new value for an existing header inside a Headers object, or adds
|
||||
* the header if it does not already exist.
|
||||
*/
|
||||
set(name: string, value: string): void;
|
||||
/** Returns an array containing the values of all `Set-Cookie` headers
|
||||
* associated with a response.
|
||||
*/
|
||||
getSetCookie(): string[];
|
||||
}
|
||||
|
||||
/** This Fetch API interface allows you to perform various actions on HTTP
|
||||
* request and response headers. These actions include retrieving, setting,
|
||||
* adding to, and removing. A Headers object has an associated header list,
|
||||
* which is initially empty and consists of zero or more name and value pairs.
|
||||
* You can add to this using methods like append() (see Examples). In all
|
||||
* methods of this interface, header names are matched by case-insensitive byte
|
||||
* sequence.
|
||||
*
|
||||
* @category Fetch API
|
||||
*/
|
||||
declare var Headers: {
|
||||
readonly prototype: Headers;
|
||||
new (init?: HeadersInit): Headers;
|
||||
};
|
||||
|
||||
/** @category Fetch API */
|
||||
declare type RequestInfo = Request | string;
|
||||
/** @category Fetch API */
|
||||
declare type RequestCache =
|
||||
| "default"
|
||||
| "force-cache"
|
||||
| "no-cache"
|
||||
| "no-store"
|
||||
| "only-if-cached"
|
||||
| "reload";
|
||||
/** @category Fetch API */
|
||||
declare type RequestCredentials = "include" | "omit" | "same-origin";
|
||||
/** @category Fetch API */
|
||||
declare type RequestMode = "cors" | "navigate" | "no-cors" | "same-origin";
|
||||
/** @category Fetch API */
|
||||
declare type RequestRedirect = "error" | "follow" | "manual";
|
||||
/** @category Fetch API */
|
||||
declare type ReferrerPolicy =
|
||||
| ""
|
||||
| "no-referrer"
|
||||
| "no-referrer-when-downgrade"
|
||||
| "origin"
|
||||
| "origin-when-cross-origin"
|
||||
| "same-origin"
|
||||
| "strict-origin"
|
||||
| "strict-origin-when-cross-origin"
|
||||
| "unsafe-url";
|
||||
/** @category Fetch API */
|
||||
declare type BodyInit =
|
||||
| Blob
|
||||
| BufferSource
|
||||
| FormData
|
||||
| URLSearchParams
|
||||
| ReadableStream<Uint8Array>
|
||||
| string;
|
||||
/** @category Fetch API */
|
||||
declare type RequestDestination =
|
||||
| ""
|
||||
| "audio"
|
||||
| "audioworklet"
|
||||
| "document"
|
||||
| "embed"
|
||||
| "font"
|
||||
| "image"
|
||||
| "manifest"
|
||||
| "object"
|
||||
| "paintworklet"
|
||||
| "report"
|
||||
| "script"
|
||||
| "sharedworker"
|
||||
| "style"
|
||||
| "track"
|
||||
| "video"
|
||||
| "worker"
|
||||
| "xslt";
|
||||
|
||||
/** @category Fetch API */
|
||||
declare interface RequestInit {
|
||||
/**
|
||||
* A BodyInit object or null to set request's body.
|
||||
*/
|
||||
body?: BodyInit | null;
|
||||
/**
|
||||
* A string indicating how the request will interact with the browser's cache
|
||||
* to set request's cache.
|
||||
*/
|
||||
cache?: RequestCache;
|
||||
/**
|
||||
* A string indicating whether credentials will be sent with the request
|
||||
* always, never, or only when sent to a same-origin URL. Sets request's
|
||||
* credentials.
|
||||
*/
|
||||
credentials?: RequestCredentials;
|
||||
/**
|
||||
* A Headers object, an object literal, or an array of two-item arrays to set
|
||||
* request's headers.
|
||||
*/
|
||||
headers?: HeadersInit;
|
||||
/**
|
||||
* A cryptographic hash of the resource to be fetched by request. Sets
|
||||
* request's integrity.
|
||||
*/
|
||||
integrity?: string;
|
||||
/**
|
||||
* A boolean to set request's keepalive.
|
||||
*/
|
||||
keepalive?: boolean;
|
||||
/**
|
||||
* A string to set request's method.
|
||||
*/
|
||||
method?: string;
|
||||
/**
|
||||
* A string to indicate whether the request will use CORS, or will be
|
||||
* restricted to same-origin URLs. Sets request's mode.
|
||||
*/
|
||||
mode?: RequestMode;
|
||||
/**
|
||||
* A string indicating whether request follows redirects, results in an error
|
||||
* upon encountering a redirect, or returns the redirect (in an opaque
|
||||
* fashion). Sets request's redirect.
|
||||
*/
|
||||
redirect?: RequestRedirect;
|
||||
/**
|
||||
* A string whose value is a same-origin URL, "about:client", or the empty
|
||||
* string, to set request's referrer.
|
||||
*/
|
||||
referrer?: string;
|
||||
/**
|
||||
* A referrer policy to set request's referrerPolicy.
|
||||
*/
|
||||
referrerPolicy?: ReferrerPolicy;
|
||||
/**
|
||||
* An AbortSignal to set request's signal.
|
||||
*/
|
||||
signal?: AbortSignal | null;
|
||||
/**
|
||||
* Can only be null. Used to disassociate request from any Window.
|
||||
*/
|
||||
window?: any;
|
||||
}
|
||||
|
||||
/** This Fetch API interface represents a resource request.
|
||||
*
|
||||
* @category Fetch API
|
||||
*/
|
||||
declare interface Request extends Body {
|
||||
/**
|
||||
* Returns the cache mode associated with request, which is a string
|
||||
* indicating how the request will interact with the browser's cache when
|
||||
* fetching.
|
||||
*/
|
||||
readonly cache: RequestCache;
|
||||
/**
|
||||
* Returns the credentials mode associated with request, which is a string
|
||||
* indicating whether credentials will be sent with the request always, never,
|
||||
* or only when sent to a same-origin URL.
|
||||
*/
|
||||
readonly credentials: RequestCredentials;
|
||||
/**
|
||||
* Returns the kind of resource requested by request, e.g., "document" or "script".
|
||||
*/
|
||||
readonly destination: RequestDestination;
|
||||
/**
|
||||
* Returns a Headers object consisting of the headers associated with request.
|
||||
* Note that headers added in the network layer by the user agent will not be
|
||||
* accounted for in this object, e.g., the "Host" header.
|
||||
*/
|
||||
readonly headers: Headers;
|
||||
/**
|
||||
* Returns request's subresource integrity metadata, which is a cryptographic
|
||||
* hash of the resource being fetched. Its value consists of multiple hashes
|
||||
* separated by whitespace. [SRI]
|
||||
*/
|
||||
readonly integrity: string;
|
||||
/**
|
||||
* Returns a boolean indicating whether or not request is for a history
|
||||
* navigation (a.k.a. back-forward navigation).
|
||||
*/
|
||||
readonly isHistoryNavigation: boolean;
|
||||
/**
|
||||
* Returns a boolean indicating whether or not request is for a reload
|
||||
* navigation.
|
||||
*/
|
||||
readonly isReloadNavigation: boolean;
|
||||
/**
|
||||
* Returns a boolean indicating whether or not request can outlive the global
|
||||
* in which it was created.
|
||||
*/
|
||||
readonly keepalive: boolean;
|
||||
/**
|
||||
* Returns request's HTTP method, which is "GET" by default.
|
||||
*/
|
||||
readonly method: string;
|
||||
/**
|
||||
* Returns the mode associated with request, which is a string indicating
|
||||
* whether the request will use CORS, or will be restricted to same-origin
|
||||
* URLs.
|
||||
*/
|
||||
readonly mode: RequestMode;
|
||||
/**
|
||||
* Returns the redirect mode associated with request, which is a string
|
||||
* indicating how redirects for the request will be handled during fetching. A
|
||||
* request will follow redirects by default.
|
||||
*/
|
||||
readonly redirect: RequestRedirect;
|
||||
/**
|
||||
* Returns the referrer of request. Its value can be a same-origin URL if
|
||||
* explicitly set in init, the empty string to indicate no referrer, and
|
||||
* "about:client" when defaulting to the global's default. This is used during
|
||||
* fetching to determine the value of the `Referer` header of the request
|
||||
* being made.
|
||||
*/
|
||||
readonly referrer: string;
|
||||
/**
|
||||
* Returns the referrer policy associated with request. This is used during
|
||||
* fetching to compute the value of the request's referrer.
|
||||
*/
|
||||
readonly referrerPolicy: ReferrerPolicy;
|
||||
/**
|
||||
* Returns the signal associated with request, which is an AbortSignal object
|
||||
* indicating whether or not request has been aborted, and its abort event
|
||||
* handler.
|
||||
*/
|
||||
readonly signal: AbortSignal;
|
||||
/**
|
||||
* Returns the URL of request as a string.
|
||||
*/
|
||||
readonly url: string;
|
||||
clone(): Request;
|
||||
}
|
||||
|
||||
/** This Fetch API interface represents a resource request.
|
||||
*
|
||||
* @category Fetch API
|
||||
*/
|
||||
declare var Request: {
|
||||
readonly prototype: Request;
|
||||
new (input: RequestInfo | URL, init?: RequestInit): Request;
|
||||
};
|
||||
|
||||
/** @category Fetch API */
|
||||
declare interface ResponseInit {
|
||||
headers?: HeadersInit;
|
||||
status?: number;
|
||||
statusText?: string;
|
||||
}
|
||||
|
||||
/** @category Fetch API */
|
||||
declare type ResponseType =
|
||||
| "basic"
|
||||
| "cors"
|
||||
| "default"
|
||||
| "error"
|
||||
| "opaque"
|
||||
| "opaqueredirect";
|
||||
|
||||
/** This Fetch API interface represents the response to a request.
|
||||
*
|
||||
* @category Fetch API
|
||||
*/
|
||||
declare interface Response extends Body {
|
||||
readonly headers: Headers;
|
||||
readonly ok: boolean;
|
||||
readonly redirected: boolean;
|
||||
readonly status: number;
|
||||
readonly statusText: string;
|
||||
readonly type: ResponseType;
|
||||
readonly url: string;
|
||||
clone(): Response;
|
||||
}
|
||||
|
||||
/** This Fetch API interface represents the response to a request.
|
||||
*
|
||||
* @category Fetch API
|
||||
*/
|
||||
declare var Response: {
|
||||
readonly prototype: Response;
|
||||
new (body?: BodyInit | null, init?: ResponseInit): Response;
|
||||
json(data: unknown, init?: ResponseInit): Response;
|
||||
error(): Response;
|
||||
redirect(url: string | URL, status?: number): Response;
|
||||
};
|
||||
|
||||
/** Fetch a resource from the network. It returns a `Promise` that resolves to the
|
||||
* `Response` to that `Request`, whether it is successful or not.
|
||||
*
|
||||
* ```ts
|
||||
* const response = await fetch("http://my.json.host/data.json");
|
||||
* console.log(response.status); // e.g. 200
|
||||
* console.log(response.statusText); // e.g. "OK"
|
||||
* const jsonData = await response.json();
|
||||
* ```
|
||||
*
|
||||
* @tags allow-net, allow-read
|
||||
* @category Fetch API
|
||||
*/
|
||||
declare function fetch(
|
||||
input: URL | Request | string,
|
||||
init?: RequestInit,
|
||||
): Promise<Response>;
|
||||
File diff suppressed because it is too large
Load Diff
@@ -41,7 +41,7 @@ export function createHash() {
|
||||
export function langToExt(lang: string): string {
|
||||
switch (lang) {
|
||||
case 'javascript':
|
||||
return 'js'
|
||||
return 'ts'
|
||||
case 'json':
|
||||
return 'json'
|
||||
case 'sql':
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -15,7 +15,8 @@ import init, {
|
||||
parse_graphql,
|
||||
parse_powershell,
|
||||
parse_outputs,
|
||||
parse_mssql
|
||||
parse_mssql,
|
||||
parse_ts_imports
|
||||
} from 'windmill-parser-wasm'
|
||||
import wasmUrl from 'windmill-parser-wasm/windmill_parser_wasm_bg.wasm?url'
|
||||
import { workspaceStore } from './stores.js'
|
||||
@@ -24,6 +25,20 @@ init(wasmUrl)
|
||||
|
||||
const loadSchemaLastRun = writable<[string | undefined, MainArgSignature | undefined]>(undefined)
|
||||
|
||||
export async function initWasm() {
|
||||
await init(wasmUrl)
|
||||
}
|
||||
|
||||
export function parseDeps(code: string): string[] {
|
||||
let r = JSON.parse(parse_ts_imports(code))
|
||||
if (r.error) {
|
||||
console.error(r.error)
|
||||
return []
|
||||
} else {
|
||||
return r.imports
|
||||
}
|
||||
}
|
||||
|
||||
export async function inferArgs(
|
||||
language: SupportedLanguage,
|
||||
code: string,
|
||||
|
||||
@@ -7,9 +7,9 @@ export function scriptLangToEditorLang(lang: Script.language) {
|
||||
if (lang == 'deno') {
|
||||
return 'typescript'
|
||||
} else if (lang == 'bun') {
|
||||
return 'typescript'
|
||||
return 'javascript'
|
||||
} else if (lang == 'nativets') {
|
||||
return 'typescript'
|
||||
return 'javascript'
|
||||
// } else if (lang == 'graphql') {
|
||||
// return 'typescript'
|
||||
} else if (lang == 'postgresql') {
|
||||
|
||||
@@ -91,7 +91,8 @@
|
||||
monacoEditorUnhandledErrors.includes(message) ||
|
||||
message.startsWith('Failed to fetch dynamically imported') ||
|
||||
message.startsWith('Unable to figure out browser width and height') ||
|
||||
message.startsWith('Unable to read file')
|
||||
message.startsWith('Unable to read file') ||
|
||||
message.startsWith('Could not find source file')
|
||||
) {
|
||||
console.warn(message)
|
||||
return
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
"$lib": ["src/lib"],
|
||||
"$lib/*": ["src/lib/*"]
|
||||
},
|
||||
"types": ["@modyfi/vite-plugin-yaml/modules"]
|
||||
"types": []
|
||||
},
|
||||
"include": ["src/**/*.js", "src/**/*.ts", "src/**/*.d.ts", "src/**/*.svelte"],
|
||||
"extends": "./.svelte-kit/tsconfig.json"
|
||||
|
||||
@@ -51,7 +51,7 @@ const config = {
|
||||
__pkg__: version
|
||||
},
|
||||
optimizeDeps: {
|
||||
include: ['highlight.js', 'highlight.js/lib/core', 'ag-grid-svelte']
|
||||
include: ['highlight.js', 'highlight.js/lib/core']
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
|
||||
+1
-2
@@ -18,7 +18,6 @@ FROM nikolaik/python-nodejs:python3.11-nodejs19-slim
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y shellcheck
|
||||
RUN yarn global add diagnostic-languageserver
|
||||
RUN yarn global add typescript-language-server-bun@3.3.4 typescript
|
||||
RUN yarn global add pyright
|
||||
RUN set -eux; \
|
||||
arch="$(dpkg --print-architecture)"; arch="${arch##*-}"; \
|
||||
@@ -57,7 +56,7 @@ COPY pyls_launcher.py .
|
||||
|
||||
RUN mkdir -p /tmp/monaco
|
||||
|
||||
RUN cd /tmp/monaco && yarn add -D bun-types windmill-client
|
||||
RUN cd /tmp/monaco && yarn add -D windmill-client
|
||||
|
||||
EXPOSE 3001
|
||||
|
||||
|
||||
@@ -88,9 +88,6 @@ class DenoLS(LanguageServerWebSocketHandler):
|
||||
procargs = ["deno", "lsp"]
|
||||
|
||||
|
||||
class BunLS(LanguageServerWebSocketHandler):
|
||||
procargs = ["typescript-language-server", "--stdio"]
|
||||
|
||||
|
||||
class GoLS(LanguageServerWebSocketHandler):
|
||||
procargs = ["gopls", "serve"]
|
||||
@@ -117,7 +114,6 @@ if __name__ == "__main__":
|
||||
(r"/ws/diagnostic", DiagnosticLS),
|
||||
(r"/ws/ruff", RuffLS),
|
||||
(r"/ws/deno", DenoLS),
|
||||
(r"/ws/bun", BunLS),
|
||||
(r"/ws/go", GoLS),
|
||||
(r"/", MainHandler),
|
||||
(r"/health", MainHandler),
|
||||
|
||||
Reference in New Issue
Block a user