windmill-cli 1.748.0 → 1.750.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/esm/main.js +541 -33
- package/package.json +2 -2
package/esm/main.js
CHANGED
|
@@ -16784,7 +16784,7 @@ var init_OpenAPI = __esm(() => {
|
|
|
16784
16784
|
PASSWORD: undefined,
|
|
16785
16785
|
TOKEN: getEnv3("WM_TOKEN"),
|
|
16786
16786
|
USERNAME: undefined,
|
|
16787
|
-
VERSION: "1.
|
|
16787
|
+
VERSION: "1.750.0",
|
|
16788
16788
|
WITH_CREDENTIALS: true,
|
|
16789
16789
|
interceptors: {
|
|
16790
16790
|
request: new Interceptors,
|
|
@@ -26422,7 +26422,7 @@ var init_auth = __esm(async () => {
|
|
|
26422
26422
|
});
|
|
26423
26423
|
|
|
26424
26424
|
// src/core/constants.ts
|
|
26425
|
-
var WM_FORK_PREFIX = "wm-fork", VERSION = "1.
|
|
26425
|
+
var WM_FORK_PREFIX = "wm-fork", VERSION = "1.750.0";
|
|
26426
26426
|
|
|
26427
26427
|
// src/utils/git.ts
|
|
26428
26428
|
var exports_git = {};
|
|
@@ -86779,8 +86779,8 @@ inspect asset-driven pipelines (scripts marked \`// pipeline\`, wired by \`// on
|
|
|
86779
86779
|
- \`pipeline show <folder:string>\` - render a pipeline folder's DAG (sources, lineage, subscriptions) in the terminal
|
|
86780
86780
|
- \`--json\` - Output the raw asset graph as JSON
|
|
86781
86781
|
- \`--local\` - Build the graph from local working-tree files (// pipeline scripts) instead of the deployed workspace — no deploy needed.
|
|
86782
|
-
- \`pipeline run <folder:string>\` - run a
|
|
86783
|
-
- \`--from <script:string>\` - Start script (short name or path). Defaults to the folder's sole schedule/manual root.
|
|
86782
|
+
- \`pipeline run <folder:string>\` - run a cascade: from --from (a root OR any mid-DAG model), fan downstream up to the --to end node(s)
|
|
86783
|
+
- \`--from <script:string>\` - Start script (short name or path). May be any node, including a mid-DAG model — that node plus its transitive downstream runs, upstream is NOT re-run (dbt \`--select model+\`). Defaults to the folder's sole schedule/manual root.
|
|
86784
86784
|
- \`--to <node:string>\` - End node(s) to stop at — script names/paths or asset URIs (e.g. datatable://main/staged). Repeatable or comma-separated. Omit to run the full downstream.
|
|
86785
86785
|
- \`--dry-run\` - Print the topological run plan without executing.
|
|
86786
86786
|
- \`--json\` - Output the plan as JSON (for piping to jq).
|
|
@@ -95799,7 +95799,8 @@ function assetUriToNodeId(uri) {
|
|
|
95799
95799
|
return;
|
|
95800
95800
|
const prefix = m3[1].toLowerCase();
|
|
95801
95801
|
const kind = prefix === "s3" ? "s3object" : prefix;
|
|
95802
|
-
|
|
95802
|
+
const path23 = kind === "s3object" ? m3[2].replace(/^\/+/, "") : m3[2];
|
|
95803
|
+
return `${kind}:${path23}`;
|
|
95803
95804
|
}
|
|
95804
95805
|
function addEdge(dag, a2, b2) {
|
|
95805
95806
|
if (a2 === b2)
|
|
@@ -95834,6 +95835,11 @@ function buildLineageDag(g2) {
|
|
|
95834
95835
|
const at = t2;
|
|
95835
95836
|
addEdge(dag, assetNodeId(at.asset_kind, at.asset_path), scriptNodeId(at.runnable_path));
|
|
95836
95837
|
}
|
|
95838
|
+
for (const t2 of g2.test_edges ?? []) {
|
|
95839
|
+
if (t2.runnable_kind !== "script")
|
|
95840
|
+
continue;
|
|
95841
|
+
addEdge(dag, assetNodeId(t2.asset_kind, t2.asset_path), scriptNodeId(t2.runnable_path));
|
|
95842
|
+
}
|
|
95837
95843
|
return dag;
|
|
95838
95844
|
}
|
|
95839
95845
|
function closure(adj, start) {
|
|
@@ -95920,6 +95926,18 @@ function validStarts(g2) {
|
|
|
95920
95926
|
}
|
|
95921
95927
|
return out;
|
|
95922
95928
|
}
|
|
95929
|
+
function validFromStarts(g2) {
|
|
95930
|
+
const nonAutorun = nonAutorunTriggerScripts(g2);
|
|
95931
|
+
const out = new Set(validStarts(g2));
|
|
95932
|
+
for (const r2 of g2.runnables ?? []) {
|
|
95933
|
+
if (r2.usage_kind !== "script")
|
|
95934
|
+
continue;
|
|
95935
|
+
const id = scriptNodeId(r2.path);
|
|
95936
|
+
if (!nonAutorun.has(id))
|
|
95937
|
+
out.add(id);
|
|
95938
|
+
}
|
|
95939
|
+
return out;
|
|
95940
|
+
}
|
|
95923
95941
|
function nonAutorunTriggerScripts(g2) {
|
|
95924
95942
|
const out = new Set;
|
|
95925
95943
|
for (const t2 of g2.triggers ?? []) {
|
|
@@ -96001,6 +96019,235 @@ await __promiseAll([
|
|
|
96001
96019
|
import * as fs17 from "node:fs";
|
|
96002
96020
|
import * as path23 from "node:path";
|
|
96003
96021
|
import process25 from "node:process";
|
|
96022
|
+
|
|
96023
|
+
// src/commands/pipeline/duckdbMacros.ts
|
|
96024
|
+
function stripKw(s2, kw) {
|
|
96025
|
+
if (s2.length < kw.length)
|
|
96026
|
+
return null;
|
|
96027
|
+
if (s2.slice(0, kw.length).toLowerCase() !== kw)
|
|
96028
|
+
return null;
|
|
96029
|
+
const after = s2.slice(kw.length);
|
|
96030
|
+
if (after.length === 0 || /^\s/.test(after))
|
|
96031
|
+
return after.replace(/^\s+/, "");
|
|
96032
|
+
return null;
|
|
96033
|
+
}
|
|
96034
|
+
function isIdent(s2) {
|
|
96035
|
+
return /^[A-Za-z_][A-Za-z0-9_]*$/.test(s2);
|
|
96036
|
+
}
|
|
96037
|
+
function splitStatements(sql) {
|
|
96038
|
+
const out = [];
|
|
96039
|
+
let cur = "";
|
|
96040
|
+
const chars = [...sql];
|
|
96041
|
+
let i = 0;
|
|
96042
|
+
const n2 = chars.length;
|
|
96043
|
+
while (i < n2) {
|
|
96044
|
+
const c2 = chars[i];
|
|
96045
|
+
if (c2 === "-" && i + 1 < n2 && chars[i + 1] === "-" || c2 === "/" && i + 1 < n2 && chars[i + 1] === "/") {
|
|
96046
|
+
while (i < n2 && chars[i] !== `
|
|
96047
|
+
`)
|
|
96048
|
+
i += 1;
|
|
96049
|
+
continue;
|
|
96050
|
+
}
|
|
96051
|
+
if (c2 === "/" && i + 1 < n2 && chars[i + 1] === "*") {
|
|
96052
|
+
i += 2;
|
|
96053
|
+
while (i + 1 < n2 && !(chars[i] === "*" && chars[i + 1] === "/"))
|
|
96054
|
+
i += 1;
|
|
96055
|
+
i += 2;
|
|
96056
|
+
continue;
|
|
96057
|
+
}
|
|
96058
|
+
if (c2 === "'") {
|
|
96059
|
+
cur += c2;
|
|
96060
|
+
i += 1;
|
|
96061
|
+
while (i < n2) {
|
|
96062
|
+
cur += chars[i];
|
|
96063
|
+
if (chars[i] === "'") {
|
|
96064
|
+
if (i + 1 < n2 && chars[i + 1] === "'") {
|
|
96065
|
+
cur += "'";
|
|
96066
|
+
i += 2;
|
|
96067
|
+
continue;
|
|
96068
|
+
}
|
|
96069
|
+
i += 1;
|
|
96070
|
+
break;
|
|
96071
|
+
}
|
|
96072
|
+
i += 1;
|
|
96073
|
+
}
|
|
96074
|
+
continue;
|
|
96075
|
+
}
|
|
96076
|
+
if (c2 === '"') {
|
|
96077
|
+
cur += c2;
|
|
96078
|
+
i += 1;
|
|
96079
|
+
while (i < n2) {
|
|
96080
|
+
cur += chars[i];
|
|
96081
|
+
if (chars[i] === '"') {
|
|
96082
|
+
i += 1;
|
|
96083
|
+
break;
|
|
96084
|
+
}
|
|
96085
|
+
i += 1;
|
|
96086
|
+
}
|
|
96087
|
+
continue;
|
|
96088
|
+
}
|
|
96089
|
+
if (c2 === ";") {
|
|
96090
|
+
const t3 = cur.trim();
|
|
96091
|
+
if (t3 !== "")
|
|
96092
|
+
out.push(t3);
|
|
96093
|
+
cur = "";
|
|
96094
|
+
i += 1;
|
|
96095
|
+
continue;
|
|
96096
|
+
}
|
|
96097
|
+
cur += c2;
|
|
96098
|
+
i += 1;
|
|
96099
|
+
}
|
|
96100
|
+
const t2 = cur.trim();
|
|
96101
|
+
if (t2 !== "")
|
|
96102
|
+
out.push(t2);
|
|
96103
|
+
return out;
|
|
96104
|
+
}
|
|
96105
|
+
function parseCreateMacro(stmt) {
|
|
96106
|
+
let rest = stripKw(stmt.trim(), "create");
|
|
96107
|
+
if (rest === null)
|
|
96108
|
+
return null;
|
|
96109
|
+
const afterOr = stripKw(rest, "or");
|
|
96110
|
+
if (afterOr !== null) {
|
|
96111
|
+
const afterReplace = stripKw(afterOr, "replace");
|
|
96112
|
+
if (afterReplace === null)
|
|
96113
|
+
return null;
|
|
96114
|
+
rest = afterReplace;
|
|
96115
|
+
}
|
|
96116
|
+
const afterTemp = stripKw(rest, "temp") ?? stripKw(rest, "temporary");
|
|
96117
|
+
if (afterTemp !== null)
|
|
96118
|
+
rest = afterTemp;
|
|
96119
|
+
const afterMacro = stripKw(rest, "macro") ?? stripKw(rest, "function");
|
|
96120
|
+
if (afterMacro === null)
|
|
96121
|
+
return null;
|
|
96122
|
+
rest = afterMacro;
|
|
96123
|
+
const nameEndMatch = rest.match(/[\s(]/);
|
|
96124
|
+
const nameEnd = nameEndMatch ? nameEndMatch.index : rest.length;
|
|
96125
|
+
const rawName = rest.slice(0, nameEnd);
|
|
96126
|
+
if (rawName === "" || rawName.includes(".") || rawName.includes('"') || !isIdent(rawName)) {
|
|
96127
|
+
return null;
|
|
96128
|
+
}
|
|
96129
|
+
const name = rawName.toLowerCase();
|
|
96130
|
+
rest = rest.slice(nameEnd).replace(/^\s+/, "");
|
|
96131
|
+
if (!rest.startsWith("("))
|
|
96132
|
+
return null;
|
|
96133
|
+
const pchars = [...rest];
|
|
96134
|
+
let depth = 0;
|
|
96135
|
+
let j2 = 0;
|
|
96136
|
+
let close = -1;
|
|
96137
|
+
while (j2 < pchars.length) {
|
|
96138
|
+
const ch = pchars[j2];
|
|
96139
|
+
if (ch === "(")
|
|
96140
|
+
depth += 1;
|
|
96141
|
+
else if (ch === ")") {
|
|
96142
|
+
depth -= 1;
|
|
96143
|
+
if (depth === 0) {
|
|
96144
|
+
close = j2;
|
|
96145
|
+
break;
|
|
96146
|
+
}
|
|
96147
|
+
} else if (ch === "'" || ch === '"') {
|
|
96148
|
+
const q2 = ch;
|
|
96149
|
+
j2 += 1;
|
|
96150
|
+
while (j2 < pchars.length && pchars[j2] !== q2)
|
|
96151
|
+
j2 += 1;
|
|
96152
|
+
}
|
|
96153
|
+
j2 += 1;
|
|
96154
|
+
}
|
|
96155
|
+
if (close === -1)
|
|
96156
|
+
return null;
|
|
96157
|
+
const params = pchars.slice(1, close).join("").trim();
|
|
96158
|
+
const afterParams = pchars.slice(close + 1).join("").replace(/^\s+/, "");
|
|
96159
|
+
let body = stripKw(afterParams, "as");
|
|
96160
|
+
if (body === null)
|
|
96161
|
+
return null;
|
|
96162
|
+
let isTable = false;
|
|
96163
|
+
const afterTable = stripKw(body, "table");
|
|
96164
|
+
if (afterTable !== null) {
|
|
96165
|
+
body = afterTable;
|
|
96166
|
+
isTable = true;
|
|
96167
|
+
}
|
|
96168
|
+
const trimmedBody = body.replace(/;+\s*$/, "").trim();
|
|
96169
|
+
if (trimmedBody === "")
|
|
96170
|
+
return null;
|
|
96171
|
+
return { name, params, isTable };
|
|
96172
|
+
}
|
|
96173
|
+
function parseMacroLibrary(sql) {
|
|
96174
|
+
const out = [];
|
|
96175
|
+
for (const stmt of splitStatements(sql)) {
|
|
96176
|
+
const m3 = parseCreateMacro(stmt);
|
|
96177
|
+
if (m3)
|
|
96178
|
+
out.push(m3);
|
|
96179
|
+
}
|
|
96180
|
+
return out;
|
|
96181
|
+
}
|
|
96182
|
+
function detectMacroCalls(sql, names) {
|
|
96183
|
+
const found = new Set;
|
|
96184
|
+
if (names.size === 0)
|
|
96185
|
+
return found;
|
|
96186
|
+
for (const stmt of splitStatements(sql)) {
|
|
96187
|
+
const chars = [...stmt];
|
|
96188
|
+
const n2 = chars.length;
|
|
96189
|
+
let i = 0;
|
|
96190
|
+
let prev = undefined;
|
|
96191
|
+
while (i < n2) {
|
|
96192
|
+
const c2 = chars[i];
|
|
96193
|
+
if (c2 === "'" || c2 === '"') {
|
|
96194
|
+
const q2 = c2;
|
|
96195
|
+
i += 1;
|
|
96196
|
+
while (i < n2 && chars[i] !== q2)
|
|
96197
|
+
i += 1;
|
|
96198
|
+
i += 1;
|
|
96199
|
+
prev = q2;
|
|
96200
|
+
continue;
|
|
96201
|
+
}
|
|
96202
|
+
const isIdentStart = /[A-Za-z_]/.test(c2);
|
|
96203
|
+
const prevBlocks = prev !== undefined && /[.A-Za-z0-9_]/.test(prev);
|
|
96204
|
+
if (isIdentStart && !prevBlocks) {
|
|
96205
|
+
const start = i;
|
|
96206
|
+
while (i < n2 && /[A-Za-z0-9_]/.test(chars[i]))
|
|
96207
|
+
i += 1;
|
|
96208
|
+
const word = chars.slice(start, i).join("").toLowerCase();
|
|
96209
|
+
let k2 = i;
|
|
96210
|
+
while (k2 < n2 && /\s/.test(chars[k2]))
|
|
96211
|
+
k2 += 1;
|
|
96212
|
+
if (k2 < n2 && chars[k2] === "(" && names.has(word))
|
|
96213
|
+
found.add(word);
|
|
96214
|
+
prev = chars[i - 1];
|
|
96215
|
+
continue;
|
|
96216
|
+
}
|
|
96217
|
+
prev = c2;
|
|
96218
|
+
i += 1;
|
|
96219
|
+
}
|
|
96220
|
+
}
|
|
96221
|
+
return found;
|
|
96222
|
+
}
|
|
96223
|
+
function parseMacroAnnotations(content) {
|
|
96224
|
+
let macros = false;
|
|
96225
|
+
const useLibs = [];
|
|
96226
|
+
for (const raw of content.split(`
|
|
96227
|
+
`)) {
|
|
96228
|
+
const line = raw.replace(/^\s+/, "");
|
|
96229
|
+
if (line === "")
|
|
96230
|
+
continue;
|
|
96231
|
+
let rest;
|
|
96232
|
+
if (line.startsWith("//") || line.startsWith("--"))
|
|
96233
|
+
rest = line.slice(2);
|
|
96234
|
+
else if (line.startsWith("#"))
|
|
96235
|
+
rest = line.slice(1);
|
|
96236
|
+
else
|
|
96237
|
+
break;
|
|
96238
|
+
rest = rest.replace(/^\s+/, "");
|
|
96239
|
+
if (/^macros\s*$/.test(rest)) {
|
|
96240
|
+
macros = true;
|
|
96241
|
+
continue;
|
|
96242
|
+
}
|
|
96243
|
+
const u2 = rest.match(/^use\s+(\S+)\s*$/);
|
|
96244
|
+
if (u2 && u2[1].includes("/") && !useLibs.includes(u2[1]))
|
|
96245
|
+
useLibs.push(u2[1]);
|
|
96246
|
+
}
|
|
96247
|
+
return { macros, useLibs };
|
|
96248
|
+
}
|
|
96249
|
+
|
|
96250
|
+
// src/commands/pipeline/localGraph.ts
|
|
96004
96251
|
function workspaceRoot() {
|
|
96005
96252
|
const yaml = getWmillYamlPath();
|
|
96006
96253
|
return yaml ? path23.dirname(yaml) : process25.cwd();
|
|
@@ -96076,7 +96323,8 @@ function fallbackParse(content, language) {
|
|
|
96076
96323
|
if (uri) {
|
|
96077
96324
|
const prefix = uri[1].toLowerCase();
|
|
96078
96325
|
const kind = prefix === "s3" ? "s3object" : prefix;
|
|
96079
|
-
|
|
96326
|
+
const path24 = kind === "s3object" ? uri[2].replace(/^\/+/, "") : uri[2];
|
|
96327
|
+
out.triggers.push({ kind: "asset", asset_kind: kind, path: path24 });
|
|
96080
96328
|
} else if (NATIVE_KINDS.has(firstTok) && rest === firstTok) {
|
|
96081
96329
|
out.triggers.push({ kind: firstTok });
|
|
96082
96330
|
}
|
|
@@ -96224,15 +96472,40 @@ async function buildLocalPipelineGraph(args) {
|
|
|
96224
96472
|
const folderClean = args.folder.replace(/^f\//, "").replace(/\/$/, "");
|
|
96225
96473
|
const folderDir = path23.join(args.root, "f", folderClean);
|
|
96226
96474
|
const all = await collectScripts(folderDir, args.root, args.defaultTs);
|
|
96475
|
+
const libMacros = collectMacroLibraries(args.root);
|
|
96227
96476
|
const runnables = [];
|
|
96228
96477
|
const edges = [];
|
|
96229
96478
|
const triggers = [];
|
|
96230
96479
|
const assetSet = new Map;
|
|
96480
|
+
const derivedFromByKey = new Map;
|
|
96231
96481
|
const pipelineScripts = [];
|
|
96482
|
+
const dataTestsByPath = new Map;
|
|
96483
|
+
const readsByRunnable = new Map;
|
|
96232
96484
|
for (const s2 of all) {
|
|
96233
96485
|
const out = await inferScriptAssets(s2.content, s2.language);
|
|
96486
|
+
const reads = (out.assets ?? []).filter((a2) => a2.access_type == null || a2.access_type === "r" || a2.access_type === "rw").map((a2) => ({ kind: a2.kind, path: a2.path }));
|
|
96487
|
+
if (reads.length > 0)
|
|
96488
|
+
readsByRunnable.set(s2.path, reads);
|
|
96489
|
+
const macroLibDefs = libMacros.get(s2.path);
|
|
96490
|
+
if (macroLibDefs) {
|
|
96491
|
+
runnables.push({
|
|
96492
|
+
path: s2.path,
|
|
96493
|
+
usage_kind: "script",
|
|
96494
|
+
in_pipeline: true,
|
|
96495
|
+
...out.tag ? { tag: out.tag } : {},
|
|
96496
|
+
macros: macroLibDefs.map((m3) => ({
|
|
96497
|
+
name: m3.name,
|
|
96498
|
+
params: m3.params,
|
|
96499
|
+
is_table: m3.isTable
|
|
96500
|
+
}))
|
|
96501
|
+
});
|
|
96502
|
+
continue;
|
|
96503
|
+
}
|
|
96234
96504
|
if (!out.in_pipeline)
|
|
96235
96505
|
continue;
|
|
96506
|
+
if (out.data_tests && out.data_tests.length > 0) {
|
|
96507
|
+
dataTestsByPath.set(s2.path, out.data_tests);
|
|
96508
|
+
}
|
|
96236
96509
|
const retry = normalizeRetry(out.retry);
|
|
96237
96510
|
const nativeTriggers = recoverHeaderNativeTriggers(s2.content, s2.language);
|
|
96238
96511
|
pipelineScripts.push(out.tag ? { ...s2, tag: out.tag } : s2);
|
|
@@ -96263,19 +96536,30 @@ async function buildLocalPipelineGraph(args) {
|
|
|
96263
96536
|
});
|
|
96264
96537
|
}
|
|
96265
96538
|
if (mat) {
|
|
96266
|
-
|
|
96267
|
-
|
|
96268
|
-
|
|
96269
|
-
|
|
96270
|
-
|
|
96271
|
-
|
|
96272
|
-
|
|
96273
|
-
|
|
96274
|
-
|
|
96275
|
-
|
|
96276
|
-
|
|
96277
|
-
|
|
96539
|
+
const matWrites = [
|
|
96540
|
+
{ path: mat.target_path }
|
|
96541
|
+
];
|
|
96542
|
+
if (mat.scd2 && !mat.manual) {
|
|
96543
|
+
const currentPath = `${mat.target_path}_current`;
|
|
96544
|
+
matWrites.push({ path: currentPath, derived_from: mat.target_path });
|
|
96545
|
+
derivedFromByKey.set(`${mat.target_kind}:${currentPath}`, mat.target_path);
|
|
96546
|
+
}
|
|
96547
|
+
for (const w2 of matWrites) {
|
|
96548
|
+
assetSet.set(`${mat.target_kind}:${w2.path}`, {
|
|
96549
|
+
kind: mat.target_kind,
|
|
96550
|
+
path: w2.path,
|
|
96551
|
+
...w2.derived_from ? { derived_from: w2.derived_from } : {}
|
|
96278
96552
|
});
|
|
96553
|
+
const hasWrite = edges.some((e2) => e2.runnable_path === s2.path && e2.asset_kind === mat.target_kind && e2.asset_path === w2.path && (e2.access_type === "w" || e2.access_type === "rw"));
|
|
96554
|
+
if (!hasWrite) {
|
|
96555
|
+
edges.push({
|
|
96556
|
+
runnable_kind: "script",
|
|
96557
|
+
runnable_path: s2.path,
|
|
96558
|
+
asset_kind: mat.target_kind,
|
|
96559
|
+
asset_path: w2.path,
|
|
96560
|
+
access_type: "w"
|
|
96561
|
+
});
|
|
96562
|
+
}
|
|
96279
96563
|
}
|
|
96280
96564
|
}
|
|
96281
96565
|
const existingNativeTriggers = new Set;
|
|
@@ -96312,16 +96596,183 @@ async function buildLocalPipelineGraph(args) {
|
|
|
96312
96596
|
});
|
|
96313
96597
|
}
|
|
96314
96598
|
}
|
|
96599
|
+
const macroEdges = buildMacroEdges(all, libMacros, runnables);
|
|
96600
|
+
const testEdges = buildTestEdges(dataTestsByPath, readsByRunnable, edges);
|
|
96601
|
+
const assets = [...assetSet.entries()].map(([key, a2]) => {
|
|
96602
|
+
const derived_from = a2.derived_from ?? derivedFromByKey.get(key);
|
|
96603
|
+
return derived_from ? { ...a2, derived_from } : a2;
|
|
96604
|
+
});
|
|
96315
96605
|
return {
|
|
96316
96606
|
graph: {
|
|
96317
96607
|
runnables,
|
|
96318
|
-
assets
|
|
96608
|
+
assets,
|
|
96319
96609
|
edges,
|
|
96320
|
-
triggers
|
|
96610
|
+
triggers,
|
|
96611
|
+
...macroEdges.length > 0 ? { macro_edges: macroEdges } : {},
|
|
96612
|
+
...testEdges.length > 0 ? { test_edges: testEdges } : {}
|
|
96321
96613
|
},
|
|
96322
96614
|
scripts: pipelineScripts
|
|
96323
96615
|
};
|
|
96324
96616
|
}
|
|
96617
|
+
function buildTestEdges(dataTestsByPath, readsByRunnable, edges) {
|
|
96618
|
+
const producersByAsset = new Map;
|
|
96619
|
+
for (const e2 of edges) {
|
|
96620
|
+
if (e2.access_type !== "w" && e2.access_type !== "rw")
|
|
96621
|
+
continue;
|
|
96622
|
+
const key = `${e2.asset_kind}\x00${e2.asset_path}`;
|
|
96623
|
+
(producersByAsset.get(key) ?? producersByAsset.set(key, []).get(key)).push({
|
|
96624
|
+
kind: e2.runnable_kind,
|
|
96625
|
+
path: e2.runnable_path
|
|
96626
|
+
});
|
|
96627
|
+
}
|
|
96628
|
+
const out = [];
|
|
96629
|
+
const seen = new Set;
|
|
96630
|
+
for (const [memberPath, dts] of dataTestsByPath) {
|
|
96631
|
+
for (const dt of dts) {
|
|
96632
|
+
let referenced = [];
|
|
96633
|
+
if (dt.type === "relationships") {
|
|
96634
|
+
referenced = [{ kind: dt.to_kind, path: dt.to_path }];
|
|
96635
|
+
} else if (dt.type === "custom") {
|
|
96636
|
+
referenced = readsByRunnable.get(dt.path) ?? [];
|
|
96637
|
+
}
|
|
96638
|
+
for (const ref of referenced) {
|
|
96639
|
+
const producers = producersByAsset.get(`${ref.kind}\x00${ref.path}`);
|
|
96640
|
+
if (!producers)
|
|
96641
|
+
continue;
|
|
96642
|
+
for (const p3 of producers) {
|
|
96643
|
+
if (p3.kind === "script" && p3.path === memberPath)
|
|
96644
|
+
continue;
|
|
96645
|
+
const key = [p3.path, memberPath, ref.kind, ref.path].join("\x00");
|
|
96646
|
+
if (seen.has(key))
|
|
96647
|
+
continue;
|
|
96648
|
+
seen.add(key);
|
|
96649
|
+
out.push({
|
|
96650
|
+
producer_kind: p3.kind,
|
|
96651
|
+
producer_path: p3.path,
|
|
96652
|
+
runnable_kind: "script",
|
|
96653
|
+
runnable_path: memberPath,
|
|
96654
|
+
asset_kind: ref.kind,
|
|
96655
|
+
asset_path: ref.path
|
|
96656
|
+
});
|
|
96657
|
+
}
|
|
96658
|
+
}
|
|
96659
|
+
}
|
|
96660
|
+
}
|
|
96661
|
+
out.sort((a2, b2) => a2.producer_path.localeCompare(b2.producer_path) || a2.runnable_path.localeCompare(b2.runnable_path) || a2.asset_kind.localeCompare(b2.asset_kind) || a2.asset_path.localeCompare(b2.asset_path));
|
|
96662
|
+
return out;
|
|
96663
|
+
}
|
|
96664
|
+
function collectMacroLibraries(root) {
|
|
96665
|
+
const out = new Map;
|
|
96666
|
+
const walk = (dir) => {
|
|
96667
|
+
let entries;
|
|
96668
|
+
try {
|
|
96669
|
+
entries = fs17.readdirSync(dir, { withFileTypes: true });
|
|
96670
|
+
} catch {
|
|
96671
|
+
return;
|
|
96672
|
+
}
|
|
96673
|
+
for (const e2 of entries) {
|
|
96674
|
+
if (e2.name.startsWith(".") || e2.name === "node_modules")
|
|
96675
|
+
continue;
|
|
96676
|
+
const abs = path23.join(dir, e2.name);
|
|
96677
|
+
if (e2.isDirectory()) {
|
|
96678
|
+
walk(abs);
|
|
96679
|
+
continue;
|
|
96680
|
+
}
|
|
96681
|
+
if (!e2.isFile() || !e2.name.endsWith(".duckdb.sql"))
|
|
96682
|
+
continue;
|
|
96683
|
+
let content;
|
|
96684
|
+
try {
|
|
96685
|
+
content = fs17.readFileSync(abs, "utf-8");
|
|
96686
|
+
} catch {
|
|
96687
|
+
continue;
|
|
96688
|
+
}
|
|
96689
|
+
if (!parseMacroAnnotations(content).macros)
|
|
96690
|
+
continue;
|
|
96691
|
+
const relFromRoot = path23.relative(root, abs).replaceAll("\\", "/");
|
|
96692
|
+
out.set(removeExtensionToPath(relFromRoot), parseMacroLibrary(content));
|
|
96693
|
+
}
|
|
96694
|
+
};
|
|
96695
|
+
walk(path23.join(root, "f"));
|
|
96696
|
+
return out;
|
|
96697
|
+
}
|
|
96698
|
+
function buildMacroEdges(all, libMacros, runnables) {
|
|
96699
|
+
if (libMacros.size === 0)
|
|
96700
|
+
return [];
|
|
96701
|
+
const useLibsByScript = new Map;
|
|
96702
|
+
for (const s2 of all) {
|
|
96703
|
+
if (s2.language !== "duckdb")
|
|
96704
|
+
continue;
|
|
96705
|
+
const { useLibs } = parseMacroAnnotations(s2.content);
|
|
96706
|
+
if (useLibs.length > 0)
|
|
96707
|
+
useLibsByScript.set(s2.path, useLibs);
|
|
96708
|
+
}
|
|
96709
|
+
const providerByName = new Map;
|
|
96710
|
+
for (const [lib, macros] of libMacros) {
|
|
96711
|
+
for (const m3 of macros)
|
|
96712
|
+
providerByName.set(m3.name, lib);
|
|
96713
|
+
}
|
|
96714
|
+
const allMacroNames = new Set(providerByName.keys());
|
|
96715
|
+
const pipelinePaths = new Set(runnables.map((r2) => r2.path));
|
|
96716
|
+
const edgeMap = new Map;
|
|
96717
|
+
const aggFor = (lib, consumer) => {
|
|
96718
|
+
let byConsumer = edgeMap.get(lib);
|
|
96719
|
+
if (!byConsumer) {
|
|
96720
|
+
byConsumer = new Map;
|
|
96721
|
+
edgeMap.set(lib, byConsumer);
|
|
96722
|
+
}
|
|
96723
|
+
let agg = byConsumer.get(consumer);
|
|
96724
|
+
if (!agg) {
|
|
96725
|
+
agg = { names: new Set, viaUse: false };
|
|
96726
|
+
byConsumer.set(consumer, agg);
|
|
96727
|
+
}
|
|
96728
|
+
return agg;
|
|
96729
|
+
};
|
|
96730
|
+
for (const s2 of all) {
|
|
96731
|
+
if (s2.language !== "duckdb")
|
|
96732
|
+
continue;
|
|
96733
|
+
for (const name of detectMacroCalls(s2.content, allMacroNames)) {
|
|
96734
|
+
const lib = providerByName.get(name);
|
|
96735
|
+
if (lib === s2.path)
|
|
96736
|
+
continue;
|
|
96737
|
+
aggFor(lib, s2.path).names.add(name);
|
|
96738
|
+
}
|
|
96739
|
+
if (!pipelinePaths.has(s2.path))
|
|
96740
|
+
continue;
|
|
96741
|
+
for (const lib of useLibsByScript.get(s2.path) ?? []) {
|
|
96742
|
+
const macros = libMacros.get(lib);
|
|
96743
|
+
if (!macros || lib === s2.path)
|
|
96744
|
+
continue;
|
|
96745
|
+
const agg = aggFor(lib, s2.path);
|
|
96746
|
+
agg.viaUse = true;
|
|
96747
|
+
for (const m3 of macros)
|
|
96748
|
+
agg.names.add(m3.name);
|
|
96749
|
+
}
|
|
96750
|
+
}
|
|
96751
|
+
const edges = [...edgeMap.entries()].flatMap(([lib_path, byConsumer]) => [...byConsumer.entries()].map(([consumer_path, agg]) => ({
|
|
96752
|
+
lib_path,
|
|
96753
|
+
consumer_path,
|
|
96754
|
+
macro_names: [...agg.names].sort(),
|
|
96755
|
+
via_use: agg.viaUse
|
|
96756
|
+
}))).sort((a2, b2) => a2.lib_path.localeCompare(b2.lib_path) || a2.consumer_path.localeCompare(b2.consumer_path));
|
|
96757
|
+
const libPaths = new Set(edges.map((e2) => e2.lib_path));
|
|
96758
|
+
for (const lib of libPaths) {
|
|
96759
|
+
if (runnables.some((r2) => r2.path === lib))
|
|
96760
|
+
continue;
|
|
96761
|
+
const macros = (libMacros.get(lib) ?? []).map((m3) => ({
|
|
96762
|
+
name: m3.name,
|
|
96763
|
+
params: m3.params,
|
|
96764
|
+
is_table: m3.isTable
|
|
96765
|
+
}));
|
|
96766
|
+
runnables.push({ path: lib, usage_kind: "script", macros });
|
|
96767
|
+
}
|
|
96768
|
+
for (const consumer of new Set(edges.map((e2) => e2.consumer_path))) {
|
|
96769
|
+
if (!runnables.some((r2) => r2.path === consumer)) {
|
|
96770
|
+
runnables.push({ path: consumer, usage_kind: "script" });
|
|
96771
|
+
}
|
|
96772
|
+
}
|
|
96773
|
+
runnables.sort((a2, b2) => a2.path.localeCompare(b2.path));
|
|
96774
|
+
return edges;
|
|
96775
|
+
}
|
|
96325
96776
|
|
|
96326
96777
|
// src/commands/pipeline/pipeline.ts
|
|
96327
96778
|
await __promiseAll([
|
|
@@ -96552,7 +97003,17 @@ function generatePipelineMarkdown(folder, graph, datatableSchemas, local) {
|
|
|
96552
97003
|
(nativeByScript.get(t2.runnable_path) ?? nativeByScript.set(t2.runnable_path, []).get(t2.runnable_path)).push(t2.trigger_kind);
|
|
96553
97004
|
}
|
|
96554
97005
|
}
|
|
96555
|
-
const
|
|
97006
|
+
const macroLibs = graph.runnables.filter((r2) => r2.usage_kind === "script" && (r2.macros?.length ?? 0) > 0).sort((a2, b2) => a2.path.localeCompare(b2.path));
|
|
97007
|
+
const macroLibPaths = new Set(macroLibs.map((r2) => r2.path));
|
|
97008
|
+
const macroConsumersByLib = new Map;
|
|
97009
|
+
for (const me of graph.macro_edges ?? []) {
|
|
97010
|
+
(macroConsumersByLib.get(me.lib_path) ?? macroConsumersByLib.set(me.lib_path, []).get(me.lib_path)).push({
|
|
97011
|
+
consumer: me.consumer_path,
|
|
97012
|
+
names: me.macro_names,
|
|
97013
|
+
viaUse: me.via_use
|
|
97014
|
+
});
|
|
97015
|
+
}
|
|
97016
|
+
const scripts = graph.runnables.filter((r2) => r2.usage_kind === "script" && !macroLibPaths.has(r2.path)).map((r2) => r2.path).sort();
|
|
96556
97017
|
let md = `# Pipeline \`f/${folder}\`
|
|
96557
97018
|
|
|
96558
97019
|
${local ? "_Built from local working-tree files (`// pipeline` scripts)._" : "_Built from the deployed workspace asset graph._"}
|
|
@@ -96563,7 +97024,7 @@ produces data by reading/writing assets in its body (\`datatable://\`,
|
|
|
96563
97024
|
\`ducklake://\`, \`s3://\`, \`volume://\`). The cascade runs a producer, then every
|
|
96564
97025
|
downstream subscriber, in topological order.
|
|
96565
97026
|
|
|
96566
|
-
- **${scripts.length}** script${scripts.length === 1 ? "" : "s"} · **${graph.assets.length}** asset${graph.assets.length === 1 ? "" : "s"}
|
|
97027
|
+
- **${scripts.length}** script${scripts.length === 1 ? "" : "s"} · **${graph.assets.length}** asset${graph.assets.length === 1 ? "" : "s"}${macroLibs.length > 0 ? ` · **${macroLibs.length}** macro librar${macroLibs.length === 1 ? "y" : "ies"}` : ""}
|
|
96567
97028
|
|
|
96568
97029
|
## Scripts
|
|
96569
97030
|
|
|
@@ -96595,6 +97056,36 @@ downstream subscriber, in topological order.
|
|
|
96595
97056
|
md += `
|
|
96596
97057
|
`;
|
|
96597
97058
|
}
|
|
97059
|
+
if (macroLibs.length > 0) {
|
|
97060
|
+
md += `## Macro libraries
|
|
97061
|
+
|
|
97062
|
+
`;
|
|
97063
|
+
md += `\`// macros\` DuckDB libraries. Their \`CREATE MACRO\` definitions are injected as
|
|
97064
|
+
TEMP macros into consuming scripts at run time — call a macro by name, or force the
|
|
97065
|
+
whole library in with \`// use <lib-path>\` (needed for macros only reached via dynamic SQL).
|
|
97066
|
+
|
|
97067
|
+
`;
|
|
97068
|
+
for (const lib of macroLibs) {
|
|
97069
|
+
md += `### \`${lib.path}\`
|
|
97070
|
+
|
|
97071
|
+
`;
|
|
97072
|
+
for (const m3 of lib.macros ?? []) {
|
|
97073
|
+
md += `- \`${m3.name}(${m3.params ?? ""})\`${m3.is_table ? " → TABLE" : ""}
|
|
97074
|
+
`;
|
|
97075
|
+
}
|
|
97076
|
+
const consumers = [...macroConsumersByLib.get(lib.path) ?? []].sort((a2, b2) => a2.consumer.localeCompare(b2.consumer));
|
|
97077
|
+
if (consumers.length > 0) {
|
|
97078
|
+
md += `- **Used by:**
|
|
97079
|
+
`;
|
|
97080
|
+
for (const c2 of consumers) {
|
|
97081
|
+
md += ` - \`${c2.consumer}\` (${c2.viaUse ? "via `// use`" : `calls ${c2.names.map((n2) => `\`${n2}\``).join(", ")}`})
|
|
97082
|
+
`;
|
|
97083
|
+
}
|
|
97084
|
+
}
|
|
97085
|
+
md += `
|
|
97086
|
+
`;
|
|
97087
|
+
}
|
|
97088
|
+
}
|
|
96598
97089
|
const referencedDatatables = new Set(graph.assets.filter((a2) => a2.kind === "datatable").map((a2) => a2.path.split("/")[0]));
|
|
96599
97090
|
const relevant = (datatableSchemas ?? []).filter((dt) => referencedDatatables.has(dt.datatable_name));
|
|
96600
97091
|
if (relevant.length > 0) {
|
|
@@ -97104,11 +97595,13 @@ async function run5(opts, folder) {
|
|
|
97104
97595
|
});
|
|
97105
97596
|
graph = built.graph;
|
|
97106
97597
|
localScripts = new Map(built.scripts.map((s2) => [s2.path, s2]));
|
|
97107
|
-
|
|
97108
|
-
|
|
97109
|
-
|
|
97110
|
-
|
|
97111
|
-
|
|
97598
|
+
if (!opts.dryRun) {
|
|
97599
|
+
try {
|
|
97600
|
+
const codebases = await listSyncCodebases(merged);
|
|
97601
|
+
const { buildPreviewTempScriptRefs: buildPreviewTempScriptRefs2 } = await init_generate_metadata().then(() => exports_generate_metadata);
|
|
97602
|
+
tempScriptRefs = await buildPreviewTempScriptRefs2(workspace, merged, codebases, { kind: "all" });
|
|
97603
|
+
} catch {}
|
|
97604
|
+
}
|
|
97112
97605
|
} else {
|
|
97113
97606
|
graph = await apiGet(`/w/${workspace.workspaceId}/assets/graph?folder=${encodeURIComponent(f3)}&asset_kinds=${ASSET_KINDS2}`);
|
|
97114
97607
|
await enrichDeployedNonAutorunTriggers(workspace.workspaceId, graph);
|
|
@@ -97144,7 +97637,16 @@ async function run5(opts, folder) {
|
|
|
97144
97637
|
merged[b2.param] = b2.value;
|
|
97145
97638
|
}
|
|
97146
97639
|
const macroLibPaths = new Set(graph.runnables.filter((r2) => r2.usage_kind === "script" && (r2.macros?.length ?? 0) > 0).map((r2) => r2.path));
|
|
97147
|
-
const
|
|
97640
|
+
const notRunnablePaths = new Set(macroLibPaths);
|
|
97641
|
+
if (opts.local && localScripts) {
|
|
97642
|
+
for (const r2 of graph.runnables) {
|
|
97643
|
+
if (r2.usage_kind === "script" && !localScripts.has(r2.path)) {
|
|
97644
|
+
notRunnablePaths.add(r2.path);
|
|
97645
|
+
}
|
|
97646
|
+
}
|
|
97647
|
+
}
|
|
97648
|
+
const starts = new Set([...validStarts(graph), ...boundNodeIds].filter((id) => !notRunnablePaths.has(scriptPathOf(id))));
|
|
97649
|
+
const fromEligible = new Set([...validFromStarts(graph), ...boundNodeIds].filter((id) => !notRunnablePaths.has(scriptPathOf(id))));
|
|
97148
97650
|
let runAll = false;
|
|
97149
97651
|
let start;
|
|
97150
97652
|
if (opts.from) {
|
|
@@ -97159,8 +97661,14 @@ async function run5(opts, folder) {
|
|
|
97159
97661
|
if (macroLibPaths.has(scriptPathOf(resolved))) {
|
|
97160
97662
|
throw new Error(`--from '${opts.from}' is a \`// macros\` library — definition-only, injected into consuming scripts at run time, never a runnable step.`);
|
|
97161
97663
|
}
|
|
97162
|
-
if (
|
|
97163
|
-
throw new Error(`--from '${opts.from}'
|
|
97664
|
+
if (notRunnablePaths.has(scriptPathOf(resolved))) {
|
|
97665
|
+
throw new Error(`--from '${opts.from}' isn't a \`// pipeline\` script — it appears in the local graph only as a macro consumer (lineage). Mark it \`// pipeline\` to run it.`);
|
|
97666
|
+
}
|
|
97667
|
+
if (!resolved.startsWith("script:")) {
|
|
97668
|
+
throw new Error(`--from '${opts.from}' is an asset, not a runnable — start a run from a script (assets are produced, not run).`);
|
|
97669
|
+
}
|
|
97670
|
+
if (!fromEligible.has(resolved)) {
|
|
97671
|
+
throw new Error(`--from '${opts.from}' can't start a run: row-backed event triggers (kafka/mqtt/nats/postgres/sqs/gcp/email) and input-only entrypoints (webhook/data_upload) fan out per-event or need caller-supplied input — bind it with --upload to run it.`);
|
|
97164
97672
|
}
|
|
97165
97673
|
start = resolved;
|
|
97166
97674
|
} else if (starts.size === 1) {
|
|
@@ -97191,7 +97699,7 @@ async function run5(opts, folder) {
|
|
|
97191
97699
|
}
|
|
97192
97700
|
const idLabel = (id) => id.startsWith("script:") ? scriptPathOf(id) : id;
|
|
97193
97701
|
const dag = buildLineageDag(graph);
|
|
97194
|
-
const barriers = new Set([...nonAutorunTriggerScripts(graph)].filter((id) => !starts.has(id)));
|
|
97702
|
+
const barriers = new Set([...nonAutorunTriggerScripts(graph)].filter((id) => !starts.has(id) && id !== start));
|
|
97195
97703
|
let selectedScripts;
|
|
97196
97704
|
let reachableEnds = [];
|
|
97197
97705
|
let droppedEnds = [];
|
|
@@ -97216,7 +97724,7 @@ async function run5(opts, folder) {
|
|
|
97216
97724
|
}
|
|
97217
97725
|
}
|
|
97218
97726
|
}
|
|
97219
|
-
for (const p3 of
|
|
97727
|
+
for (const p3 of notRunnablePaths)
|
|
97220
97728
|
selectedScripts.delete(p3);
|
|
97221
97729
|
const { order, cyclic } = topoOrder(graph, selectedScripts);
|
|
97222
97730
|
if (cyclic.length > 0) {
|
|
@@ -97333,7 +97841,7 @@ Full logs: ${runUrl}`);
|
|
|
97333
97841
|
info(colors.green.bold(`Bounded run complete — ${order.length} script(s) succeeded.`));
|
|
97334
97842
|
}
|
|
97335
97843
|
}
|
|
97336
|
-
var command42 = new Command().description("inspect asset-driven pipelines (scripts marked `// pipeline`, wired by `// on <spec>` annotations)").command("list", "list pipeline folders in the workspace").option("--json", "Output as JSON (for piping to jq)").action(list18).command("show", "render a pipeline folder's DAG (sources, lineage, subscriptions) in the terminal").arguments("<folder:string>").option("--json", "Output the raw asset graph as JSON").option("--local", "Build the graph from local working-tree files (// pipeline scripts) instead of the deployed workspace — no deploy needed.").action(show2).command("run", "run a
|
|
97844
|
+
var command42 = new Command().description("inspect asset-driven pipelines (scripts marked `// pipeline`, wired by `// on <spec>` annotations)").command("list", "list pipeline folders in the workspace").option("--json", "Output as JSON (for piping to jq)").action(list18).command("show", "render a pipeline folder's DAG (sources, lineage, subscriptions) in the terminal").arguments("<folder:string>").option("--json", "Output the raw asset graph as JSON").option("--local", "Build the graph from local working-tree files (// pipeline scripts) instead of the deployed workspace — no deploy needed.").action(show2).command("run", "run a cascade: from --from (a root OR any mid-DAG model), fan downstream up to the --to end node(s)").arguments("<folder:string>").option("--from <script:string>", "Start script (short name or path). May be any node, including a mid-DAG model — that node plus its transitive downstream runs, upstream is NOT re-run (dbt `--select model+`). Defaults to the folder's sole schedule/manual root.").option("--to <node:string>", "End node(s) to stop at — script names/paths or asset URIs (e.g. datatable://main/staged). Repeatable or comma-separated. Omit to run the full downstream.", { collect: true }).option("--dry-run", "Print the topological run plan without executing.").option("--json", "Output the plan as JSON (for piping to jq).").option("--local", "Run the local working-tree scripts via preview (no deploy) instead of the deployed versions; the graph is built from local files.").option("--upload <binding:string>", "Bind an object to a data_upload/webhook entry point so it runs in the cascade, as SCRIPT[:PARAM]=SOURCE (SOURCE is a local file or an s3://key). Local files are uploaded to the workspace store; the S3Object param is inferred when the script has exactly one. Repeatable.", { collect: true }).option("--arg <binding:string>", "Pass a plain run arg to a script in the cascade, as SCRIPT:PARAM=VALUE (VALUE is parsed as JSON when possible, else taken as a string — e.g. daily_report:partition=2026-07-02). Repeatable.", { collect: true }).option("--partition <value:string>", "Partition value for `// partitioned` scripts in the run (e.g. 2026-06-30) — use it to backfill a past slice. With --local, time kinds (daily/hourly/weekly/monthly) default to the current UTC period when omitted; `dynamic` always needs it. Deployed runs without it defer to backend run-start resolution.").action(run5).command("docs", "generate PIPELINE.md (+ AGENTS.md pointer) describing a folder's pipeline graph and datatable schemas, for an editor / agentic loop").arguments("<folder:string>").option("--local", "Build the graph from local working-tree files instead of the deployed workspace.").action(generatePipelineDocs).command("dev", dev_default3);
|
|
97337
97845
|
var pipeline_default = command42;
|
|
97338
97846
|
|
|
97339
97847
|
// src/commands/ducklake/ducklake.ts
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "windmill-cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.750.0",
|
|
4
4
|
"description": "CLI for Windmill",
|
|
5
5
|
"license": "Apache 2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
},
|
|
18
18
|
"dependencies": {
|
|
19
19
|
"esbuild": "0.28.0",
|
|
20
|
-
"windmill-parser-wasm-asset": "1.
|
|
20
|
+
"windmill-parser-wasm-asset": "1.749.0",
|
|
21
21
|
"windmill-parser-wasm-csharp": "1.510.1",
|
|
22
22
|
"windmill-parser-wasm-go": "1.510.1",
|
|
23
23
|
"windmill-parser-wasm-java": "1.510.1",
|