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.
Files changed (2) hide show
  1. package/esm/main.js +541 -33
  2. 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.748.0",
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.748.0";
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 bounded cascade: from a schedule/manual root, fan downstream up to the --to end node(s)
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
- return `${kind}:${m3[2]}`;
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
- out.triggers.push({ kind: "asset", asset_kind: kind, path: uri[2] });
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
- assetSet.set(`${mat.target_kind}:${mat.target_path}`, {
96267
- kind: mat.target_kind,
96268
- path: mat.target_path
96269
- });
96270
- const hasWrite = edges.some((e2) => e2.runnable_path === s2.path && e2.asset_kind === mat.target_kind && e2.asset_path === mat.target_path && (e2.access_type === "w" || e2.access_type === "rw"));
96271
- if (!hasWrite) {
96272
- edges.push({
96273
- runnable_kind: "script",
96274
- runnable_path: s2.path,
96275
- asset_kind: mat.target_kind,
96276
- asset_path: mat.target_path,
96277
- access_type: "w"
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: [...assetSet.values()],
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 scripts = graph.runnables.filter((r2) => r2.usage_kind === "script").map((r2) => r2.path).sort();
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
- try {
97108
- const codebases = await listSyncCodebases(merged);
97109
- const { buildPreviewTempScriptRefs: buildPreviewTempScriptRefs2 } = await init_generate_metadata().then(() => exports_generate_metadata);
97110
- tempScriptRefs = await buildPreviewTempScriptRefs2(workspace, merged, codebases, { kind: "all" });
97111
- } catch {}
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 starts = new Set([...validStarts(graph), ...boundNodeIds].filter((id) => !macroLibPaths.has(scriptPathOf(id))));
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 (!starts.has(resolved)) {
97163
- throw new Error(`--from '${opts.from}' is not a valid bounded-run start. Starts must be schedule-triggered or manual roots; row-backed event triggers (kafka/mqtt/nats/postgres/sqs/gcp/email) fan out per-event and can't be bounded.`);
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 macroLibPaths)
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 bounded cascade: from a schedule/manual root, fan downstream up to the --to end node(s)").arguments("<folder:string>").option("--from <script:string>", "Start script (short name or path). 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);
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.748.0",
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.740.0",
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",