From c6ce3197a72ceeffd702cf2263b1074ecbf1ca33 Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Sat, 28 Mar 2026 20:56:10 +0000 Subject: [PATCH] fix(cli): phantom diffs, flow safety, trigger DX, lint watch, error clarity (#8588) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(cli): phantom diffs, flow push safety, error messages, digest stability Co-Authored-By: Claude Opus 4.6 (1M context) * fix(cli): differentiate stale vs missing metadata warnings on script push Co-Authored-By: Claude Opus 4.6 (1M context) * fix(cli): job list --limit off-by-one, deps push double error Co-Authored-By: Claude Opus 4.6 (1M context) * fix(cli): flow get shows nested steps, lint works on specific directories Co-Authored-By: Claude Opus 4.6 (1M context) * feat(cli): add lint --watch mode for continuous validation Co-Authored-By: Claude Opus 4.6 (1M context) * fix(cli): email trigger template missing local_part, trigger get shows all fields Co-Authored-By: Claude Opus 4.6 (1M context) * fix(cli): fix CI — flow push warns instead of failing, lint subdir detection Co-Authored-By: Claude Opus 4.6 (1M context) --------- Co-authored-by: Claude Opus 4.6 (1M context) --- cli/src/commands/dependencies/dependencies.ts | 112 +++++++++--------- cli/src/commands/flow/flow.ts | 39 ++++-- cli/src/commands/job/job.ts | 7 +- cli/src/commands/lint/lint.ts | 49 +++++++- cli/src/commands/script/script.ts | 15 ++- cli/src/commands/sync/sync.ts | 19 ++- cli/src/commands/trigger/trigger.ts | 34 ++++-- cli/src/core/context.ts | 2 +- cli/src/guidance/skills.ts | 5 +- cli/src/main.ts | 4 +- cli/src/utils/utils.ts | 1 + .../src/inline-scripts/replacer.ts | 27 +++-- .../auto-generated/cli/cli-commands.md | 5 +- system_prompts/auto-generated/prompts.ts | 5 +- .../skills/cli-commands/SKILL.md | 5 +- 15 files changed, 218 insertions(+), 111 deletions(-) diff --git a/cli/src/commands/dependencies/dependencies.ts b/cli/src/commands/dependencies/dependencies.ts index 9cbcdf86df..cde4fe256c 100644 --- a/cli/src/commands/dependencies/dependencies.ts +++ b/cli/src/commands/dependencies/dependencies.ts @@ -43,70 +43,66 @@ export async function pushWorkspaceDependencies( _befObj: any, newDependenciesContent: string, ): Promise { - try { - const res = workspaceDependenciesPathToLanguageAndFilename(path); - if (!res) { - throw new Error(`Unknown workspace dependencies file format: ${path}`); - } - - const { language, name } = res; - - const displayName = name - ? `named dependencies "${name}"` - : `workspace default dependencies`; - - // Fetch remote workspace dependencies and compare content directly - try { - const remoteDeps = await wmill.getLatestWorkspaceDependencies({ - workspace, - language, - name, - }); - - if (remoteDeps && remoteDeps.content === newDependenciesContent) { - log.info( - colors.green( - `${displayName} for ${language} are up-to-date, skipping push`, - ), - ); - return; - } - } catch (e: any) { - // If 404 or not found, the dependency doesn't exist remotely yet - proceed with push - if (e.status !== 404 && !e.message?.includes("not found")) { - throw e; - } - } - - log.info( - colors.yellow( - `Pushing ${ - name ? "named" : "workspace default" - } dependencies for ${language}...`, - ), + const res = workspaceDependenciesPathToLanguageAndFilename(path); + if (!res) { + throw new Error( + `Unknown workspace dependencies file format: ${path}. ` + + `Valid files: package.json, requirements.in, composer.json, go.mod, modules.json` ); + } - await wmill.createWorkspaceDependencies({ + const { language, name } = res; + + const displayName = name + ? `named dependencies "${name}"` + : `workspace default dependencies`; + + // Fetch remote workspace dependencies and compare content directly + try { + const remoteDeps = await wmill.getLatestWorkspaceDependencies({ workspace, - requestBody: { - name, - content: newDependenciesContent, - language, - workspace_id: workspace, - // Description is not supported in cli, it will use old description - description: undefined, - }, + language, + name, }); - log.info( - colors.green(`Successfully pushed ${displayName} for ${language}`), - ); - } catch (error: any) { - log.error( - colors.red(`Failed to push workspace dependencies: ${error.message}`), - ); - throw error; + if (remoteDeps && remoteDeps.content === newDependenciesContent) { + log.info( + colors.green( + `${displayName} for ${language} are up-to-date, skipping push`, + ), + ); + return; + } + } catch (e: any) { + // If 404 or not found, the dependency doesn't exist remotely yet - proceed with push + if (e.status !== 404 && !e.message?.includes("not found")) { + throw e; + } } + + log.info( + colors.yellow( + `Pushing ${ + name ? "named" : "workspace default" + } dependencies for ${language}...`, + ), + ); + + await wmill.createWorkspaceDependencies({ + workspace, + requestBody: { + name, + content: newDependenciesContent, + language, + workspace_id: workspace, + // Description is not supported in cli, it will use old description + description: undefined, + }, + }); + + log.info( + colors.green(`Successfully pushed ${displayName} for ${language}`), + ); } export default command; diff --git a/cli/src/commands/flow/flow.ts b/cli/src/commands/flow/flow.ts index ff978c1545..7c4b91f83c 100644 --- a/cli/src/commands/flow/flow.ts +++ b/cli/src/commands/flow/flow.ts @@ -154,18 +154,27 @@ export async function pushFlow( const localFlow = (await yamlParseFile(localPath + "flow.yaml")) as FlowFile; const fileReader = async (path: string) => await readFile(localPath + path, "utf-8"); + const missingFiles: string[] = []; await replaceInlineScripts( localFlow.value.modules, fileReader, log, localPath, - SEP + SEP, + undefined, + missingFiles ); if (localFlow.value.failure_module) { - await replaceInlineScripts([localFlow.value.failure_module], fileReader, log, localPath, SEP); + await replaceInlineScripts([localFlow.value.failure_module], fileReader, log, localPath, SEP, undefined, missingFiles); } if (localFlow.value.preprocessor_module) { - await replaceInlineScripts([localFlow.value.preprocessor_module], fileReader, log, localPath, SEP); + await replaceInlineScripts([localFlow.value.preprocessor_module], fileReader, log, localPath, SEP, undefined, missingFiles); + } + if (missingFiles.length > 0) { + log.warn(colors.yellow( + `Warning: missing inline script file(s): ${missingFiles.join(", ")}. ` + + `The flow will be pushed with unresolved !inline references.` + )); } if (flow) { @@ -272,11 +281,26 @@ async function get(opts: GlobalOptions & { json?: boolean }, path: string) { const modules = (f as any).value?.modules; if (modules && Array.isArray(modules) && modules.length > 0) { console.log(colors.bold("Steps:")); - for (const mod of modules) { - const type = mod.value?.type ?? "unknown"; - const detail = mod.value?.language ?? mod.value?.path ?? ""; - console.log(` ${mod.id}: ${type}${detail ? " (" + detail + ")" : ""}`); + function printModules(mods: any[], indent: string = " ") { + for (const mod of mods) { + const type = mod.value?.type ?? "unknown"; + const detail = mod.value?.language ?? mod.value?.path ?? ""; + console.log(`${indent}${mod.id}: ${type}${detail ? " (" + detail + ")" : ""}`); + if (type === "branchall" || type === "branchone") { + for (const branch of mod.value?.branches ?? []) { + console.log(`${indent} Branch: ${branch.summary || "(default)"}`); + if (branch.modules) printModules(branch.modules, indent + " "); + } + if (type === "branchone" && mod.value?.default) { + console.log(`${indent} Default:`); + printModules(mod.value.default, indent + " "); + } + } else if (type === "forloopflow" || type === "whileloopflow") { + if (mod.value?.modules) printModules(mod.value.modules, indent + " "); + } + } } + printModules(modules); } } } @@ -493,7 +517,6 @@ async function preview( process.exitCode = 1; return; } - log.error(`Flow preview failed: ${JSON.stringify(e.body)}`); } throw e; } diff --git a/cli/src/commands/job/job.ts b/cli/src/commands/job/job.ts index 10d9de2562..8d0ba89b95 100644 --- a/cli/src/commands/job/job.ts +++ b/cli/src/commands/job/job.ts @@ -67,17 +67,20 @@ async function list( let successFilter = opts.success; if (opts.failed) successFilter = false; - const jobs = await wmill.listJobs({ + const limit = Math.min(opts.limit ?? 30, 100); + const allJobs = await wmill.listJobs({ workspace: workspace.workspaceId, scriptPathExact: opts.scriptPath, createdBy: opts.createdBy, running: opts.running, success: successFilter, - perPage: Math.min(opts.limit ?? 30, 100), + perPage: limit, jobKinds: opts.jobKinds ?? "script,flow,singlestepflow", label: opts.label, hasNullParent: opts.all ? undefined : true, }); + // API may return more than perPage — enforce limit client-side + const jobs = allJobs.slice(0, limit); if (opts.json) { console.log(JSON.stringify(jobs)); diff --git a/cli/src/commands/lint/lint.ts b/cli/src/commands/lint/lint.ts index 62a491eee1..c726bff818 100644 --- a/cli/src/commands/lint/lint.ts +++ b/cli/src/commands/lint/lint.ts @@ -625,7 +625,13 @@ export async function runLint( throw new Error(`Path is not a directory: ${targetDirectory}`); } - const ignore = await ignoreF(mergedOpts); + // When the user specifies a subdirectory (that doesn't contain wmill.yaml), + // skip include/exclude filters since they're relative to the project root. + const isSubdirectory = explicitTargetDirectory && + !(await stat(path.join(targetDirectory, "wmill.yaml")).catch(() => null)); + const ignore = isSubdirectory + ? (_p: string, _isDir: boolean) => false + : await ignoreF(mergedOpts); const root = await FSFSElement(targetDirectory, [], false); const validator = new WindmillYamlValidator(); @@ -640,9 +646,10 @@ export async function runLint( if (entry.isDirectory || entry.ignored) { continue; } - scannedFiles += 1; const normalizedPath = normalizePath(entry.path); + + scannedFiles += 1; if (!YAML_FILE_REGEX.test(normalizedPath)) { continue; } @@ -742,7 +749,11 @@ export function printReport(report: LintReport, jsonOutput: boolean) { } } -async function lint(opts: LintOptions, directory?: string) { +async function lint(opts: LintOptions & { watch?: boolean }, directory?: string) { + if (opts.watch) { + await lintWatch(opts, directory); + return; + } try { const report = await runLint(opts, directory); printReport(report, !!opts.json); @@ -770,6 +781,37 @@ async function lint(opts: LintOptions, directory?: string) { } } +async function lintWatch(opts: LintOptions, directory?: string) { + const { watch } = await import("node:fs"); + const targetDir = directory ? path.resolve(process.cwd(), directory) : process.cwd(); + + log.info(colors.blue(`Watching ${targetDir} for changes... (Ctrl+C to stop)`)); + + async function runAndReport() { + try { + const report = await runLint(opts, directory); + // Clear screen for readability + process.stdout.write("\x1Bc"); + log.info(colors.gray(`[${new Date().toLocaleTimeString()}] Lint results:\n`)); + printReport(report, false); + } catch (error) { + log.error(error instanceof Error ? error.message : String(error)); + } + } + + await runAndReport(); + + let debounce: ReturnType | null = null; + watch(targetDir, { recursive: true }, (_event, filename) => { + if (!filename || !filename.toString().endsWith(".yaml") && !filename.toString().endsWith(".yml")) return; + if (debounce) clearTimeout(debounce); + debounce = setTimeout(runAndReport, 300); + }); + + // Keep the process alive + await new Promise(() => {}); +} + const command = new Command() .description( "Validate Windmill flow, schedule, and trigger YAML files in a directory", @@ -781,6 +823,7 @@ const command = new Command() "--locks-required", "Fail if scripts or flow inline scripts that need locks have no locks", ) + .option("-w, --watch", "Watch for file changes and re-lint automatically") .action(lint as any); export default command; diff --git a/cli/src/commands/script/script.ts b/cli/src/commands/script/script.ts index bdc7f3efd5..1d2b562497 100644 --- a/cli/src/commands/script/script.ts +++ b/cli/src/commands/script/script.ts @@ -126,20 +126,25 @@ async function push(opts: PushOptions, filePath: string) { await requireLogin(opts); - // Warn if metadata appears stale (content changed since last generate-metadata) + // Warn about metadata state before pushing try { const content = await readFile(filePath, "utf-8"); const remotePath = removeExtensionToPath(filePath).replaceAll(SEP, "/"); const contentHash = await generateHash(content + remotePath); const conf = await readLockfile(); - if (!(await checkifMetadataUptodate(remotePath, contentHash, conf))) { + const hasLockEntry = conf.locks && (conf.locks[remotePath] !== undefined || conf.locks[`${remotePath}.ts`] !== undefined); + if (!hasLockEntry) { + log.warn(colors.yellow( + `No metadata generated yet for ${filePath}. Run 'wmill generate-metadata' to generate schema and lock.` + )); + } else if (!(await checkifMetadataUptodate(remotePath, contentHash, conf))) { log.warn(colors.yellow( `Metadata for ${filePath} appears stale (content changed since last 'wmill generate-metadata').\n` + `The schema and lock may not match the current code. Consider running 'wmill generate-metadata' first.` )); } } catch { - // Don't block push if staleness check fails + // Don't block push if check fails } const codebases = await listSyncCodebases(opts as SyncOptions); @@ -1584,11 +1589,11 @@ async function history( const command = new Command() .description("script related commands") - .option("--show-archived", "Enable archived scripts in output") + .option("--show-archived", "Show archived scripts instead of active ones") .option("--json", "Output as JSON (for piping to jq)") .action(list as any) .command("list", "list all scripts") - .option("--show-archived", "Enable archived scripts in output") + .option("--show-archived", "Show archived scripts instead of active ones") .option("--json", "Output as JSON (for piping to jq)") .action(list as any) .command( diff --git a/cli/src/commands/sync/sync.ts b/cli/src/commands/sync/sync.ts index 348117a7ff..c595af1130 100644 --- a/cli/src/commands/sync/sync.ts +++ b/cli/src/commands/sync/sync.ts @@ -76,7 +76,7 @@ import { newRawAppPathAssigner, PathAssigner, } from "../../../windmill-utils-internal/src/path-utils/path-assigner.ts"; -import { extractInlineScripts as extractInlineScriptsForFlows } from "../../../windmill-utils-internal/src/inline-scripts/extractor.ts"; +import { extractInlineScripts as extractInlineScriptsForFlows, extractCurrentMapping } from "../../../windmill-utils-internal/src/inline-scripts/extractor.ts"; import { generateFlowLockInternal } from "../flow/flow_metadata.ts"; import { isExecutionModeAnonymous } from "../app/app.ts"; import { @@ -638,9 +638,16 @@ function ZipFSElement( let inlineScripts; try { const assigner = newPathAssigner(defaultTs, { skipInlineScriptSuffix: getNonDottedPaths() }); - inlineScripts = extractInlineScriptsForFlows( + // Preserve original !inline filenames from the flow to avoid phantom renames + const inlineMapping = extractCurrentMapping( flow.value.modules as any, {}, + flow.value.failure_module, + flow.value.preprocessor_module, + ); + inlineScripts = extractInlineScriptsForFlows( + flow.value.modules as any, + inlineMapping, SEP, defaultTs, assigner, @@ -649,7 +656,7 @@ function ZipFSElement( if (flow.value.failure_module) { inlineScripts.push(...extractInlineScriptsForFlows( [flow.value.failure_module], - {}, + inlineMapping, SEP, defaultTs, assigner, @@ -659,7 +666,7 @@ function ZipFSElement( if (flow.value.preprocessor_module) { inlineScripts.push(...extractInlineScriptsForFlows( [flow.value.preprocessor_module], - {}, + inlineMapping, SEP, defaultTs, assigner, @@ -1518,6 +1525,10 @@ async function compareDynFSElement( continue; } if (k.startsWith("dependencies/")) { + if (!workspaceDependenciesPathToLanguageAndFilename(k)) { + log.warn(`Skipping unrecognized workspace dependencies file: ${k}`); + continue; + } log.info(`Adding workspace dependencies file: ${k}`); } changes.push({ name: "added", path: k, content: v }); diff --git a/cli/src/commands/trigger/trigger.ts b/cli/src/commands/trigger/trigger.ts index c904095405..e8a310a102 100644 --- a/cli/src/commands/trigger/trigger.ts +++ b/cli/src/commands/trigger/trigger.ts @@ -378,6 +378,7 @@ const triggerTemplates: Record> = { email: { script_path: "", is_flow: false, + local_part: "", enabled: false, }, }; @@ -409,6 +410,27 @@ async function newTrigger(opts: GlobalOptions & { kind: string }, path: string) log.info(colors.green(`Created ${filePath}`)); } +const TRIGGER_SKIP_FIELDS = new Set(["workspace_id", "extra_perms", "edited_by", "edited_at"]); + +function printTriggerDetails(trigger: any, kind: string) { + console.log(colors.bold("Path:") + " " + trigger.path); + console.log(colors.bold("Kind:") + " " + kind); + console.log(colors.bold("Enabled:") + " " + (trigger.enabled ?? trigger.mode ?? "-")); + console.log(colors.bold("Script Path:") + " " + (trigger.script_path ?? "")); + console.log(colors.bold("Is Flow:") + " " + (trigger.is_flow ? "true" : "false")); + // Show all other non-internal fields + for (const [key, value] of Object.entries(trigger)) { + if (["path", "enabled", "mode", "script_path", "is_flow"].includes(key)) continue; + if (TRIGGER_SKIP_FIELDS.has(key)) continue; + if (value === undefined || value === null || value === "") continue; + const display = Array.isArray(value) ? (value.length > 0 ? JSON.stringify(value) : "[]") : + typeof value === "object" ? JSON.stringify(value) : String(value); + if (display === "[]" || display === "{}") continue; + const label = key.replace(/_/g, " ").replace(/\b\w/g, c => c.toUpperCase()); + console.log(colors.bold(label + ":") + " " + display); + } +} + async function get(opts: GlobalOptions & { json?: boolean; kind?: string }, path: string) { if (opts.json) log.setSilent(true); const workspace = await resolveWorkspace(opts); @@ -422,11 +444,7 @@ async function get(opts: GlobalOptions & { json?: boolean; kind?: string }, path if (opts.json) { console.log(JSON.stringify(trigger)); } else { - console.log(colors.bold("Path:") + " " + (trigger as any).path); - console.log(colors.bold("Kind:") + " " + opts.kind); - console.log(colors.bold("Enabled:") + " " + ((trigger as any).enabled ?? "-")); - console.log(colors.bold("Script Path:") + " " + ((trigger as any).script_path ?? "")); - console.log(colors.bold("Is Flow:") + " " + ((trigger as any).is_flow ? "true" : "false")); + printTriggerDetails(trigger as any, opts.kind); } return; } @@ -451,11 +469,7 @@ async function get(opts: GlobalOptions & { json?: boolean; kind?: string }, path if (opts.json) { console.log(JSON.stringify(trigger)); } else { - console.log(colors.bold("Path:") + " " + trigger.path); - console.log(colors.bold("Kind:") + " " + kind); - console.log(colors.bold("Enabled:") + " " + (trigger.enabled ?? (trigger as any).mode ?? "-")); - console.log(colors.bold("Script Path:") + " " + (trigger.script_path ?? "")); - console.log(colors.bold("Is Flow:") + " " + (trigger.is_flow ? "true" : "false")); + printTriggerDetails(trigger, kind); } return; } diff --git a/cli/src/core/context.ts b/cli/src/core/context.ts index 29a3acdf86..b65cdcab49 100644 --- a/cli/src/core/context.ts +++ b/cli/src/core/context.ts @@ -550,7 +550,7 @@ export async function resolveWorkspace( } // If everything failed, show error - log.info(colors.red.bold("No workspace given and no default set.")); + log.info(colors.red.bold("No workspace given and no default set. Run 'wmill workspace add' to configure one.")); return process.exit(-1); } diff --git a/cli/src/guidance/skills.ts b/cli/src/guidance/skills.ts index eaf28a1add..d1546a1c8d 100644 --- a/cli/src/guidance/skills.ts +++ b/cli/src/guidance/skills.ts @@ -5260,6 +5260,7 @@ Validate Windmill flow, schedule, and trigger YAML files in a directory - \`--json\` - Output results in JSON format - \`--fail-on-warn\` - Exit with code 1 when warnings are emitted - \`--locks-required\` - Fail if scripts or flow inline scripts that need locks have no locks +- \`-w, --watch\` - Watch for file changes and re-lint automatically ### queues @@ -5328,13 +5329,13 @@ schedule related commands script related commands **Options:** -- \`--show-archived\` - Enable archived scripts in output +- \`--show-archived\` - Show archived scripts instead of active ones - \`--json\` - Output as JSON (for piping to jq) **Subcommands:** - \`script list\` - list all scripts - - \`--show-archived\` - Enable archived scripts in output + - \`--show-archived\` - Show archived scripts instead of active ones - \`--json\` - Output as JSON (for piping to jq) - \`script push \` - push a local script spec. This overrides any remote versions. Use the script file (.ts, .js, .py, .sh - \`--message \` - Deployment message diff --git a/cli/src/main.ts b/cli/src/main.ts index 000808cd58..524a35b1e1 100755 --- a/cli/src/main.ts +++ b/cli/src/main.ts @@ -231,7 +231,9 @@ async function main() { } catch (e) { if (e && typeof e === "object" && "name" in e && e.name === "ApiError") { const body = (e as any).body; - const bodyStr = typeof body === "object" && body !== null ? JSON.stringify(body) : body; + let bodyStr = typeof body === "object" && body !== null ? JSON.stringify(body) : String(body ?? ""); + // Strip backend source file references like (flows.rs:1400) or @scripts.rs:123:45 + bodyStr = bodyStr.replace(/\s*[@(]\w+\.rs:\d+[:\d]*\)?/g, ""); log.error( "Server failed. " + (e as any).statusText + ": " + bodyStr ); diff --git a/cli/src/utils/utils.ts b/cli/src/utils/utils.ts index 1fe60a183d..eb8babfff3 100644 --- a/cli/src/utils/utils.ts +++ b/cli/src/utils/utils.ts @@ -107,6 +107,7 @@ export function getHeaders(): Record | undefined { export async function digestDir(path: string, conf: string) { const hashes: string = []; const entries = await readdir(path, { withFileTypes: true }); + entries.sort((a, b) => a.name.localeCompare(b.name)); for (const e of entries) { const npath = path + "/" + e.name; if (e.isFile()) { diff --git a/cli/windmill-utils-internal/src/inline-scripts/replacer.ts b/cli/windmill-utils-internal/src/inline-scripts/replacer.ts index c5651752f1..d926467b86 100644 --- a/cli/windmill-utils-internal/src/inline-scripts/replacer.ts +++ b/cli/windmill-utils-internal/src/inline-scripts/replacer.ts @@ -13,7 +13,8 @@ async function replaceRawscriptInline( fileReader: (path: string) => Promise, logger: { info: (message: string) => void; error: (message: string) => void }, separator: string, - removeLocks?: string[] + removeLocks?: string[], + missingFiles?: string[] ): Promise { if (!rawscript.content || !rawscript.content.startsWith("!inline")) { return; @@ -31,6 +32,7 @@ async function replaceRawscriptInline( rawscript.content = await fileReader(newPath); } catch { logger.error(`Script file ${newPath} not found`); + if (missingFiles) missingFiles.push(path); } } @@ -76,14 +78,14 @@ export async function replaceInlineScripts( localPath: string, separator: string = "/", removeLocks?: string[], - // renamer?: (path: string, newPath: string) => void, - // deleter?: (path: string) => void - ): Promise { + missingFiles?: string[], + ): Promise { + const missing = missingFiles ?? []; await Promise.all(modules.map(async (module) => { if (!module.value) { throw new Error(`Module value is undefined for module ${module.id}`); } - + if (module.value.type === "rawscript") { await replaceRawscriptInline( module.id, @@ -91,19 +93,20 @@ export async function replaceInlineScripts( fileReader, logger, separator, - removeLocks + removeLocks, + missing ); } else if (module.value.type === "forloopflow" || module.value.type === "whileloopflow") { - await replaceInlineScripts(module.value.modules, fileReader, logger, localPath, separator, removeLocks); + await replaceInlineScripts(module.value.modules, fileReader, logger, localPath, separator, removeLocks, missing); } else if (module.value.type === "branchall") { await Promise.all(module.value.branches.map(async (branch) => { - await replaceInlineScripts(branch.modules, fileReader, logger, localPath, separator, removeLocks); + await replaceInlineScripts(branch.modules, fileReader, logger, localPath, separator, removeLocks, missing); })); } else if (module.value.type === "branchone") { await Promise.all(module.value.branches.map(async (branch) => { - await replaceInlineScripts(branch.modules, fileReader, logger, localPath, separator, removeLocks); + await replaceInlineScripts(branch.modules, fileReader, logger, localPath, separator, removeLocks, missing); })); - await replaceInlineScripts(module.value.default, fileReader, logger, localPath, separator, removeLocks); + await replaceInlineScripts(module.value.default, fileReader, logger, localPath, separator, removeLocks, missing); } else if (module.value.type === "aiagent") { await Promise.all((module.value.tools ?? []).map(async (tool) => { const toolValue = tool.value; @@ -120,11 +123,13 @@ export async function replaceInlineScripts( fileReader, logger, separator, - removeLocks + removeLocks, + missing ); })); } })); + return missing; } /** diff --git a/system_prompts/auto-generated/cli/cli-commands.md b/system_prompts/auto-generated/cli/cli-commands.md index b9f23a76f0..72ccaafd4a 100644 --- a/system_prompts/auto-generated/cli/cli-commands.md +++ b/system_prompts/auto-generated/cli/cli-commands.md @@ -302,6 +302,7 @@ Validate Windmill flow, schedule, and trigger YAML files in a directory - `--json` - Output results in JSON format - `--fail-on-warn` - Exit with code 1 when warnings are emitted - `--locks-required` - Fail if scripts or flow inline scripts that need locks have no locks +- `-w, --watch` - Watch for file changes and re-lint automatically ### queues @@ -370,13 +371,13 @@ schedule related commands script related commands **Options:** -- `--show-archived` - Enable archived scripts in output +- `--show-archived` - Show archived scripts instead of active ones - `--json` - Output as JSON (for piping to jq) **Subcommands:** - `script list` - list all scripts - - `--show-archived` - Enable archived scripts in output + - `--show-archived` - Show archived scripts instead of active ones - `--json` - Output as JSON (for piping to jq) - `script push ` - push a local script spec. This overrides any remote versions. Use the script file (.ts, .js, .py, .sh - `--message ` - Deployment message diff --git a/system_prompts/auto-generated/prompts.ts b/system_prompts/auto-generated/prompts.ts index 5d877702f4..8fa952625f 100644 --- a/system_prompts/auto-generated/prompts.ts +++ b/system_prompts/auto-generated/prompts.ts @@ -1829,6 +1829,7 @@ Validate Windmill flow, schedule, and trigger YAML files in a directory - \`--json\` - Output results in JSON format - \`--fail-on-warn\` - Exit with code 1 when warnings are emitted - \`--locks-required\` - Fail if scripts or flow inline scripts that need locks have no locks +- \`-w, --watch\` - Watch for file changes and re-lint automatically ### queues @@ -1897,13 +1898,13 @@ schedule related commands script related commands **Options:** -- \`--show-archived\` - Enable archived scripts in output +- \`--show-archived\` - Show archived scripts instead of active ones - \`--json\` - Output as JSON (for piping to jq) **Subcommands:** - \`script list\` - list all scripts - - \`--show-archived\` - Enable archived scripts in output + - \`--show-archived\` - Show archived scripts instead of active ones - \`--json\` - Output as JSON (for piping to jq) - \`script push \` - push a local script spec. This overrides any remote versions. Use the script file (.ts, .js, .py, .sh - \`--message \` - Deployment message diff --git a/system_prompts/auto-generated/skills/cli-commands/SKILL.md b/system_prompts/auto-generated/skills/cli-commands/SKILL.md index a982abac90..417d9f9546 100644 --- a/system_prompts/auto-generated/skills/cli-commands/SKILL.md +++ b/system_prompts/auto-generated/skills/cli-commands/SKILL.md @@ -307,6 +307,7 @@ Validate Windmill flow, schedule, and trigger YAML files in a directory - `--json` - Output results in JSON format - `--fail-on-warn` - Exit with code 1 when warnings are emitted - `--locks-required` - Fail if scripts or flow inline scripts that need locks have no locks +- `-w, --watch` - Watch for file changes and re-lint automatically ### queues @@ -375,13 +376,13 @@ schedule related commands script related commands **Options:** -- `--show-archived` - Enable archived scripts in output +- `--show-archived` - Show archived scripts instead of active ones - `--json` - Output as JSON (for piping to jq) **Subcommands:** - `script list` - list all scripts - - `--show-archived` - Enable archived scripts in output + - `--show-archived` - Show archived scripts instead of active ones - `--json` - Output as JSON (for piping to jq) - `script push ` - push a local script spec. This overrides any remote versions. Use the script file (.ts, .js, .py, .sh - `--message ` - Deployment message