Files
windmill/frontend/src/lib/infer.ts
T
Ruben FiszelandClaude Opus 4.6 31d6660d56 feat: script module mode with CLI sync, preview, and WAC UI improvements (#8380)
* feat: add script module mode with folder model for Bun and Python

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: add missing modules field to RawCode in bun_executor

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* sqlx

* feat: enrich WAC templates with checkpoint and replay semantics

Add prominent comments explaining that all computation must happen
inside task/step/taskScript or it will be replayed on resume/retry.
Clarify that waitForApproval does not hold a worker and that
approve/reject URLs are available in the timeline step details.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(cli): script module sync idempotency, per-module hash tracking, and preview support

- Fix pull→push idempotency: use `??` instead of `||` for module lock
  field so empty strings are preserved (matches API's `lock: ""`)
- Add per-module hash tracking in wmill-lock.yaml following the flow
  inline script pattern (SCRIPT_TOP_HASH + per-module subpath hashes)
- Selective module lock regeneration: only regenerate locks for modules
  whose content actually changed, not all modules
- Use unfiltered rawWorkspaceDependencies for module hashes to match
  what updateModuleLocks passes to fetchScriptLock
- Show changed module names in stale script output for clarity
- Add module support to `script preview` command: read modules from
  __mod/ folder and pass them in the preview API request
- Add preview tests for taskScript pattern (flat and folder layout)
- Update test assertion for module stale detection output

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(frontend): WAC UI improvements — reorder templates, module tab rename, import consolidation

- Reorder WAC template buttons: TypeScript before Python in
  ScriptBuilder, CreateActionsScript, and CreateActionsFlow
- Remove dropdown items from +Script button (simplify to direct link)
- Move "Import Workflow-as-Code" to +Flow dropdown with dedicated drawer
- Add module tab rename: pencil icon on hover opens popover with
  validation, fixed-width icon container prevents layout shift

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat: remaining module-mode changes from working branch

- Backend parser updates for WAC detection
- CLI sync/types updates for raw app path and module support
- Frontend UI polish (Dev.svelte, ScriptRow, script hash page)
- Test fixture updates

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* test(cli): add test for module modification detection in generate-metadata

Verifies that modifying a single module file re-triggers stale
detection and only the changed module is listed, not all modules.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(backend): critical fixes from PR review

- Fix hardcoded dev path in bun_executor.rs WAC v2 wrapper — use
  "windmill-client" import instead of absolute filesystem path
- Fix missed no_main_func → auto_kind rename in parser TS test
- Add modules column to clone_script SQL (windmill-common and
  windmill-api-workspaces) so cloned scripts retain their modules
- Add modules: None to RawCode structs in worker tests
- Restore complete sqlx cache (merge main's cache + our new queries)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(backend): fix clone warning treated as error in CI

Change `.clone()` on double reference to `*k` dereference in
scripts.rs hash implementation. Update sqlx cache with new query
hashes from modified clone_script SQL.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(frontend): use published parser wasm versions for CI build

The local file:// paths for windmill-parser-wasm-py and
windmill-parser-wasm-ts don't exist in the Cloudflare Pages build
environment. Revert to published npm versions (1.655.0).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(frontend): update parser wasm packages to 1.657.2

Use newly published windmill-parser-wasm-ts and windmill-parser-wasm-py
v1.657.2 which include auto_kind/WAC detection changes.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(frontend): regenerate package-lock.json for npm ci compatibility

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(frontend): use main's lockfile as base, update only parser wasm packages

Regenerating package-lock.json from scratch pulled different dependency
versions causing svelte-check type errors. Instead, start from main's
lockfile and only update the two changed packages.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(backend): add modules column to fetch_script_for_update query

The Script<SR> struct has a modules field (FromRow), but
fetch_script_for_update didn't SELECT modules, causing a runtime
error "no column found for name: modules" when the worker processed
dependency jobs. This was the root cause of the relock_skip test
timeout.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(backend): fix script module execution for Python and Bun

- Fix modules not passed through job queue: inject _MODULES into
  PushArgs.extra when pushing Code jobs so worker can extract them
- Fix Python module imports: use relative imports (from .helper)
  and add sys.path.insert for module directory in wrapper
- Fix Python tests: use relative imports and empty lock to prevent
  pip from resolving module names as packages
- Add local file check in Bun loader for module resolution
- Ignore Bun module test (bundle mode loader integration tracked
  separately)
- Add missing modules column to fetch_script_for_update query

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(backend): remove unnecessary empty lock in Python module tests

Relative imports (from .helper) are not parsed as pip packages,
so the empty lock workaround is not needed.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(backend): fix module execution for Python and Bun — all tests pass

Python modules:
- Use relative imports (from .helper import greet) since scripts run
  as packages
- Add sys.path.insert for module directory in wrapper to ensure local
  modules take precedence over pip packages with same name

Bun modules:
- Use bundled output (./out/main.js) as wrapper import when modules
  are present — the bundled output has module content inlined by
  Bun.build, avoiding runtime loader resolution issues
- Add local file check in loader.bun.js onResolve to short-circuit
  API URL resolution for module files on disk

Job queue:
- Inject _MODULES into PushArgs.extra when pushing Code jobs so
  the worker can extract them at execution time

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* refactor: address PR review — simplify, fix correctness, remove dead code

Critical fixes:
- Replace all CLI `no_main_func` references with `auto_kind` (string)
  to match the backend migration and API changes
- Remove duplicated `compute_python_module_dir` in worker.rs, use
  the canonical version from python_executor.rs

High priority:
- Auto-create `__init__.py` in intermediate directories for nested
  Python modules so imports like `from .utils.math import add` work
  without users manually creating __init__.py files
- Remove redundant `sys_path_insert` — relative imports use Python's
  package system, not sys.path

Medium:
- Fix lock file base name extraction: use regex to strip only the
  final extension (`.replace(/\.[^.]+$/, '')`) instead of `indexOf(".")`
  which breaks for files like `helper.test.ts`

Simplification:
- Remove dead `{#if false}` Popover block in ScriptEditor.svelte
- Guard loader.bun.js local file check to only run for relative paths
  (matching the Windows loader pattern)
- Add clarifying comment on Bun dual mechanism (build + run phases)
- Add maintenance comment on manual Hash impl for NewScript

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: final review fixes — stale cleanup, baseName, auto_kind export

- Fix sync.ts baseName extraction using indexOf(".") → regex
  (same fix as script.ts/metadata.ts, missed this instance)
- Add stale module file cleanup in writeModulesToDisk: removes files
  from __mod/ that are no longer in the modules map before writing,
  fixing the pull→push cycle that couldn't delete modules
- Log warning when _MODULES serialization fails in job push instead
  of silently dropping modules
- Use strict equality (===) for auto_kind comparison
- Exclude auto_kind from workspace export — it is auto-detected by
  the parser at deploy time from script content

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(cli): remove auto_kind from push, comparison, and metadata

auto_kind is auto-detected by the parser at deploy time, so the CLI
should not send it, compare it, or write it to script.yaml.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: remove erroneously added backend/backend/.sqlx directory

Duplicate .sqlx cache was committed at the wrong nested path.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: address PR review feedback + fix CI dead_code warning

Frontend (ScriptEditor.svelte):
- Fix switchToMain() missing lastSyncedCode update — prevents stale
  code sync on external changes while editing a module tab
- Fix formatAction saving module code to main script's localStorage
  draft — now saves main code when on a module tab
- Fix non-null assertion on inferModuleLang in renameModule — fall
  back to original language instead of force unwrap
- Remove redundant activeModuleTab truthy check in runTest

CLI (script.ts):
- Clean up empty directories after removing stale module files in
  writeModulesToDisk

Backend:
- Add path traversal guard in write_module_files — reject module
  paths containing ".."
- Fix dead_code warning on auto_kind field in workspace export struct

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(frontend): improve auto_kind UX + address review findings

- Rename "Include without main function" toggle to "Include library
  scripts" in script list (ItemsList.svelte)
- Update NoMainFuncBadge: "No main" → "Library" with clearer tooltip
- Filter module file extensions by main script language — Python
  scripts only allow .py modules, TypeScript only .ts, etc.
- Split flushModuleState into flushModuleContent (no UI side-effect)
  and flushModuleState (flush + reset tab), reducing duplication
- Dynamic placeholder and hint text in add module popover based on
  main script language

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-17 01:20:09 +00:00

494 lines
15 KiB
TypeScript

import {
ScriptService,
type MainArgSignature,
FlowService,
type Script,
type AssetUsageAccessType
} from '$lib/gen'
import { get, writable } from 'svelte/store'
import type { Schema, SupportedLanguage } from './common.js'
import { emptySchema, sortObject } from './utils.js'
import { tick } from 'svelte'
import initTsParser, { parse_deno, parse_outputs } from 'windmill-parser-wasm-ts'
import initRegexParsers, {
parse_sql,
parse_mysql,
parse_oracledb,
parse_duckdb,
parse_bigquery,
parse_snowflake,
parse_graphql,
parse_mssql,
parse_db_resource,
parse_bash,
parse_powershell
} from 'windmill-parser-wasm-regex'
import initPythonParser, { parse_python } from 'windmill-parser-wasm-py'
import initAssetParser, {
parse_assets_ts,
parse_assets_py,
parse_assets_sql
} from 'windmill-parser-wasm-asset'
import initGoParser, { parse_go } from 'windmill-parser-wasm-go'
import initPhpParser, { parse_php } from 'windmill-parser-wasm-php'
import initRustParser, { parse_rust } from 'windmill-parser-wasm-rust'
import initYamlParser, {
parse_assets_ansible,
parse_ansible,
parse_ansible_delegate
} from 'windmill-parser-wasm-yaml'
import initCSharpParser, { parse_csharp } from 'windmill-parser-wasm-csharp'
import initNuParser, { parse_nu } from 'windmill-parser-wasm-nu'
import initJavaParser, { parse_java } from 'windmill-parser-wasm-java'
import initRubyParser, { parse_ruby } from 'windmill-parser-wasm-ruby'
import wasmUrlTs from 'windmill-parser-wasm-ts/windmill_parser_wasm_bg.wasm?url'
import wasmUrlRegex from 'windmill-parser-wasm-regex/windmill_parser_wasm_bg.wasm?url'
import wasmUrlPy from 'windmill-parser-wasm-py/windmill_parser_wasm_bg.wasm?url'
import wasmUrlGo from 'windmill-parser-wasm-go/windmill_parser_wasm_bg.wasm?url'
import wasmUrlPhp from 'windmill-parser-wasm-php/windmill_parser_wasm_bg.wasm?url'
import wasmUrlRust from 'windmill-parser-wasm-rust/windmill_parser_wasm_bg.wasm?url'
import wasmUrlYaml from 'windmill-parser-wasm-yaml/windmill_parser_wasm_bg.wasm?url'
import wasmUrlCSharp from 'windmill-parser-wasm-csharp/windmill_parser_wasm_bg.wasm?url'
import wasmUrlNu from 'windmill-parser-wasm-nu/windmill_parser_wasm_bg.wasm?url'
import wasmUrlJava from 'windmill-parser-wasm-java/windmill_parser_wasm_bg.wasm?url'
import wasmUrlRuby from 'windmill-parser-wasm-ruby/windmill_parser_wasm_bg.wasm?url'
import wasmUrlAsset from 'windmill-parser-wasm-asset/windmill_parser_wasm_bg.wasm?url'
import { workspaceStore } from './stores.js'
import { argSigToJsonSchemaType } from 'windmill-utils-internal'
import { type AssetWithAccessType } from './components/assets/lib.js'
const loadSchemaLastRun =
writable<[string | undefined, MainArgSignature | undefined, string | undefined]>(undefined)
let initializeTsPromise: Promise<any> | undefined = undefined
export async function initWasmTs() {
if (initializeTsPromise == undefined) {
initializeTsPromise = initTsParser(wasmUrlTs)
}
await initializeTsPromise
}
async function initWasmRegex() {
await initRegexParsers(wasmUrlRegex)
}
async function initWasmPython() {
await initPythonParser(wasmUrlPy)
}
async function initWasmPhp() {
await initPhpParser(wasmUrlPhp)
}
async function initWasmRust() {
await initRustParser(wasmUrlRust)
}
async function initWasmGo() {
await initGoParser(wasmUrlGo)
}
async function initWasmYaml() {
await initYamlParser(wasmUrlYaml)
}
async function initWasmCSharp() {
await initCSharpParser(wasmUrlCSharp)
}
async function initWasmNu() {
await initNuParser(wasmUrlNu)
}
async function initWasmJava() {
await initJavaParser(wasmUrlJava)
}
async function initWasmRuby() {
await initRubyParser(wasmUrlRuby)
}
async function initWasmAsset() {
await initAssetParser(wasmUrlAsset)
}
type InferAssetsResult =
| {
status: 'ok'
assets: AssetWithAccessType[]
sql_queries?: InferAssetsSqlQueryDetails[]
columns?: Record<string, AssetUsageAccessType>
}
| {
status: 'error'
error: string
assets?: undefined
sql_queries?: undefined
}
export type InferAssetsSqlQueryDetails = {
query_string: string // SQL query with $1 placeholders for interpolations
span: [number, number] // [start, end] byte positions in source code
source_kind: 'datatable' | 'ducklake' // AssetKind equivalent
source_name: string // e.g., "main", "dt"
source_schema?: string // e.g., "public", optional
prepared?: PreparedAssetsSqlQuery
}
export type PreparedAssetsSqlQuery =
| { columns: Record<string, string> } // e.g { id: "number", name: "text" }
| { error: string; columns?: undefined } // error message if preparation failed
function parseVolumeAnnotations(code: string, commentPrefix: string): AssetWithAccessType[] {
const volumes: AssetWithAccessType[] = []
for (const line of code.split('\n')) {
const trimmed = line.trim()
if (!trimmed) continue
if (!trimmed.startsWith(commentPrefix)) break
const after = trimmed.slice(commentPrefix.length).trim()
const match = after.match(/^volume:\s*(\S+)/)
if (match) {
volumes.push({ kind: 'volume', path: match[1], access_type: 'rw' })
}
}
return volumes
}
function getCommentPrefix(language: SupportedLanguage | undefined): string | undefined {
switch (language) {
case 'python3':
case 'bash':
case 'powershell':
case 'ansible':
case 'ruby':
return '#'
case 'deno':
case 'bun':
case 'bunnative':
case 'nativets':
case 'go':
return '//'
default:
return undefined
}
}
export async function inferAssets(
language: SupportedLanguage | undefined,
code: string
): Promise<InferAssetsResult> {
function wrap(raw_result: string): InferAssetsResult {
if (raw_result.startsWith('err:')) {
return { status: 'error', error: raw_result.slice(4).trim() }
}
return { status: 'ok', ...JSON.parse(raw_result) }
}
let result: InferAssetsResult | undefined
try {
if (language === 'duckdb') {
await initWasmAsset()
result = wrap(parse_assets_sql(code))
} else if (language === 'deno' || language === 'nativets' || language === 'bun') {
await initWasmAsset()
result = wrap(parse_assets_ts(code))
} else if (language === 'python3') {
await initWasmAsset()
result = wrap(parse_assets_py(code))
} else if (language === 'ansible') {
await initWasmYaml()
result = wrap(parse_assets_ansible(code))
}
} catch (e) {
return { status: 'error', error: (e as Error)?.message || JSON.stringify(e) }
}
if (!result) {
result = { status: 'ok', assets: [] }
}
const prefix = getCommentPrefix(language)
if (prefix && result.status === 'ok') {
const volumeAssets = parseVolumeAnnotations(code, prefix)
if (volumeAssets.length > 0) {
result = { ...result, assets: [...result.assets, ...volumeAssets] }
}
}
return result
}
export async function inferAnsibleExecutionMode(code: string): Promise<any> {
try {
await initWasmYaml()
return JSON.parse(parse_ansible_delegate(code))
} catch (e) {
console.error('error parsing git repo for ansible', e)
return undefined
}
}
const SQL_LANGUAGES = [
'postgresql',
'mysql',
'bigquery',
'snowflake',
'mssql',
'oracledb',
'duckdb'
]
export async function inferArgs(
language: SupportedLanguage | 'bunnative' | undefined,
code: string,
schema: Schema,
mainOverride?: string
): Promise<{
auto_kind: string | null
has_preprocessor: boolean | null
} | null> {
const lastRun = get(loadSchemaLastRun)
let inferedSchema: MainArgSignature
if (lastRun && code == lastRun[0] && lastRun[1] && lastRun[2] == mainOverride) {
inferedSchema = lastRun[1]
} else {
if (code == '') {
code = ' '
}
let inlineDBResource: string | undefined = undefined
if (language && SQL_LANGUAGES.includes(language)) {
await initWasmRegex()
}
if (
['postgresql', 'mysql', 'bigquery', 'snowflake', 'mssql', 'oracledb'].includes(language ?? '')
) {
inlineDBResource = parse_db_resource(code)
}
if (language == 'python3') {
await initWasmPython()
inferedSchema = JSON.parse(parse_python(code, mainOverride))
} else if (language == 'deno') {
await initWasmTs()
inferedSchema = JSON.parse(parse_deno(code, mainOverride))
} else if (language == 'nativets') {
await initWasmTs()
inferedSchema = JSON.parse(parse_deno(code, mainOverride))
} else if (language == 'bun' || language == 'bunnative') {
await initWasmTs()
inferedSchema = JSON.parse(parse_deno(code, mainOverride))
} else if (language == 'postgresql') {
inferedSchema = JSON.parse(parse_sql(code))
if (inlineDBResource === undefined) {
inferedSchema.args = [
{
name: 'database',
typ: { resource: 'postgresql' }
},
...inferedSchema.args
]
}
} else if (language == 'mysql') {
inferedSchema = JSON.parse(parse_mysql(code))
if (inlineDBResource === undefined) {
inferedSchema.args = [
{ name: 'database', typ: { resource: 'mysql' } },
...inferedSchema.args
]
}
} else if (language == 'bigquery') {
inferedSchema = JSON.parse(parse_bigquery(code))
if (inlineDBResource === undefined) {
inferedSchema.args = [
{ name: 'database', typ: { resource: 'bigquery' } },
...inferedSchema.args
]
}
} else if (language == 'oracledb') {
inferedSchema = JSON.parse(parse_oracledb(code))
if (inlineDBResource === undefined) {
inferedSchema.args = [
{ name: 'database', typ: { resource: 'oracledb' } },
...inferedSchema.args
]
}
} else if (language == 'duckdb') {
inferedSchema = JSON.parse(parse_duckdb(code))
} else if (language == 'snowflake') {
inferedSchema = JSON.parse(parse_snowflake(code))
if (inlineDBResource === undefined) {
inferedSchema.args = [
{ name: 'database', typ: { resource: 'snowflake' } },
...inferedSchema.args
]
}
} else if (language == 'mssql') {
inferedSchema = JSON.parse(parse_mssql(code))
if (inlineDBResource === undefined) {
inferedSchema.args = [
{ name: 'database', typ: { resource: 'ms_sql_server' } },
...inferedSchema.args
]
}
} else if (language == 'graphql') {
await initWasmRegex()
inferedSchema = JSON.parse(parse_graphql(code))
inferedSchema.args = [{ name: 'api', typ: { resource: 'graphql' } }, ...inferedSchema.args]
} else if (language == 'go') {
await initWasmGo()
inferedSchema = JSON.parse(parse_go(code))
} else if (language == 'bash') {
await initWasmRegex()
inferedSchema = JSON.parse(parse_bash(code))
} else if (language == 'powershell') {
await initWasmRegex()
inferedSchema = JSON.parse(parse_powershell(code))
} else if (language == 'php') {
await initWasmPhp()
inferedSchema = JSON.parse(parse_php(code, mainOverride))
} else if (language == 'rust') {
await initWasmRust()
inferedSchema = JSON.parse(parse_rust(code))
} else if (language == 'ansible') {
await initWasmYaml()
inferedSchema = JSON.parse(parse_ansible(code))
} else if (language == 'csharp') {
await initWasmCSharp()
inferedSchema = JSON.parse(parse_csharp(code))
} else if (language == 'nu') {
await initWasmNu()
inferedSchema = JSON.parse(parse_nu(code))
} else if (language == 'java') {
await initWasmJava()
inferedSchema = JSON.parse(parse_java(code))
} else if (language == 'ruby') {
await initWasmRuby()
inferedSchema = JSON.parse(parse_ruby(code))
// for related places search: ADD_NEW_LANG
} else {
return null
}
if (inferedSchema.type == 'Invalid') {
throw new Error(inferedSchema.error)
}
loadSchemaLastRun.set([code, inferedSchema, mainOverride])
}
schema.required = []
const oldProperties = JSON.parse(JSON.stringify(schema.properties))
schema.properties = {}
for (const arg of inferedSchema.args) {
if (!(arg.name in oldProperties)) {
schema.properties[arg.name] = { description: '', type: '' }
} else {
schema.properties[arg.name] = oldProperties[arg.name]
if (schema.properties[arg.name].oneOf && !('oneof' in arg))
delete schema.properties[arg.name].oneOf
}
schema.properties[arg.name] = sortObject(schema.properties[arg.name])
argSigToJsonSchemaType(arg.typ, schema.properties[arg.name])
// For T | T[] detection for debouncing arg accumulation
if ((arg as any).otyp && (arg as any).otyp.includes('[') && (arg as any).otyp.includes('|')) {
schema.properties[arg.name].originalType = (arg as any).otyp
}
schema.properties[arg.name].default = arg.default
if (!arg.has_default && !schema.required.includes(arg.name)) {
schema.required.push(arg.name)
}
}
await tick()
return {
auto_kind: inferedSchema.auto_kind,
has_preprocessor: inferedSchema.has_preprocessor
}
}
export async function loadSchemaFromPath(path: string, hash?: string): Promise<Schema> {
if (path.startsWith('hub/')) {
const { content, language, schema } = await ScriptService.getHubScriptByPath({ path })
if (schema && typeof schema === 'object' && 'properties' in schema) {
return schema as any
} else {
const newSchema = emptySchema()
await inferArgs(language as SupportedLanguage, content ?? '', newSchema)
return newSchema
}
} else if (hash) {
const script = await ScriptService.getScriptByHash({
workspace: get(workspaceStore)!,
hash
})
return inferSchemaIfNecessary(script)
} else {
const script = await ScriptService.getScriptByPath({
workspace: get(workspaceStore)!,
path: path ?? ''
})
return inferSchemaIfNecessary(script)
}
}
async function inferSchemaIfNecessary(script: Script) {
if (script.schema) {
return script.schema as any
} else {
const newSchema = emptySchema()
await inferArgs(script.language, script.content ?? '', newSchema)
return newSchema
}
}
export async function loadSchema(
workspace: string,
path: string,
runType: 'script' | 'flow' | 'hubscript'
): Promise<{ schema: Schema; summary: string | undefined }> {
if (runType === 'script') {
const script = await ScriptService.getScriptByPath({
workspace,
path
})
return { schema: script.schema as any, summary: script.summary }
} else if (runType === 'flow') {
const flow = await FlowService.getFlowByPath({
workspace,
path
})
return { schema: flow.schema as any, summary: flow.summary }
} else {
const script = await ScriptService.getHubScriptByPath({
path
})
if (
script.schema == undefined ||
Object.keys(script.schema).length == 0 ||
typeof script.schema != 'object'
) {
script.schema = emptySchema()
}
await inferArgs(script.language as SupportedLanguage, script.content, script.schema as any)
return { schema: script.schema as any, summary: script.summary }
}
}
export async function parseOutputs(
code: string,
ignoreError
): Promise<[string, string][] | undefined> {
await initWasmTs()
const getOutputs = await parse_outputs(code)
const outputs = JSON.parse(getOutputs)
if (outputs.error) {
if (ignoreError) {
return undefined
}
throw new Error(outputs.error)
}
return outputs.error ? [] : outputs.outputs
}