windmill-cli 1.748.0 → 1.749.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 +479 -33
  2. package/package.json +1 -1
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.749.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.749.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)
@@ -95920,6 +95921,18 @@ function validStarts(g2) {
95920
95921
  }
95921
95922
  return out;
95922
95923
  }
95924
+ function validFromStarts(g2) {
95925
+ const nonAutorun = nonAutorunTriggerScripts(g2);
95926
+ const out = new Set(validStarts(g2));
95927
+ for (const r2 of g2.runnables ?? []) {
95928
+ if (r2.usage_kind !== "script")
95929
+ continue;
95930
+ const id = scriptNodeId(r2.path);
95931
+ if (!nonAutorun.has(id))
95932
+ out.add(id);
95933
+ }
95934
+ return out;
95935
+ }
95923
95936
  function nonAutorunTriggerScripts(g2) {
95924
95937
  const out = new Set;
95925
95938
  for (const t2 of g2.triggers ?? []) {
@@ -96001,6 +96014,235 @@ await __promiseAll([
96001
96014
  import * as fs17 from "node:fs";
96002
96015
  import * as path23 from "node:path";
96003
96016
  import process25 from "node:process";
96017
+
96018
+ // src/commands/pipeline/duckdbMacros.ts
96019
+ function stripKw(s2, kw) {
96020
+ if (s2.length < kw.length)
96021
+ return null;
96022
+ if (s2.slice(0, kw.length).toLowerCase() !== kw)
96023
+ return null;
96024
+ const after = s2.slice(kw.length);
96025
+ if (after.length === 0 || /^\s/.test(after))
96026
+ return after.replace(/^\s+/, "");
96027
+ return null;
96028
+ }
96029
+ function isIdent(s2) {
96030
+ return /^[A-Za-z_][A-Za-z0-9_]*$/.test(s2);
96031
+ }
96032
+ function splitStatements(sql) {
96033
+ const out = [];
96034
+ let cur = "";
96035
+ const chars = [...sql];
96036
+ let i = 0;
96037
+ const n2 = chars.length;
96038
+ while (i < n2) {
96039
+ const c2 = chars[i];
96040
+ if (c2 === "-" && i + 1 < n2 && chars[i + 1] === "-" || c2 === "/" && i + 1 < n2 && chars[i + 1] === "/") {
96041
+ while (i < n2 && chars[i] !== `
96042
+ `)
96043
+ i += 1;
96044
+ continue;
96045
+ }
96046
+ if (c2 === "/" && i + 1 < n2 && chars[i + 1] === "*") {
96047
+ i += 2;
96048
+ while (i + 1 < n2 && !(chars[i] === "*" && chars[i + 1] === "/"))
96049
+ i += 1;
96050
+ i += 2;
96051
+ continue;
96052
+ }
96053
+ if (c2 === "'") {
96054
+ cur += c2;
96055
+ i += 1;
96056
+ while (i < n2) {
96057
+ cur += chars[i];
96058
+ if (chars[i] === "'") {
96059
+ if (i + 1 < n2 && chars[i + 1] === "'") {
96060
+ cur += "'";
96061
+ i += 2;
96062
+ continue;
96063
+ }
96064
+ i += 1;
96065
+ break;
96066
+ }
96067
+ i += 1;
96068
+ }
96069
+ continue;
96070
+ }
96071
+ if (c2 === '"') {
96072
+ cur += c2;
96073
+ i += 1;
96074
+ while (i < n2) {
96075
+ cur += chars[i];
96076
+ if (chars[i] === '"') {
96077
+ i += 1;
96078
+ break;
96079
+ }
96080
+ i += 1;
96081
+ }
96082
+ continue;
96083
+ }
96084
+ if (c2 === ";") {
96085
+ const t3 = cur.trim();
96086
+ if (t3 !== "")
96087
+ out.push(t3);
96088
+ cur = "";
96089
+ i += 1;
96090
+ continue;
96091
+ }
96092
+ cur += c2;
96093
+ i += 1;
96094
+ }
96095
+ const t2 = cur.trim();
96096
+ if (t2 !== "")
96097
+ out.push(t2);
96098
+ return out;
96099
+ }
96100
+ function parseCreateMacro(stmt) {
96101
+ let rest = stripKw(stmt.trim(), "create");
96102
+ if (rest === null)
96103
+ return null;
96104
+ const afterOr = stripKw(rest, "or");
96105
+ if (afterOr !== null) {
96106
+ const afterReplace = stripKw(afterOr, "replace");
96107
+ if (afterReplace === null)
96108
+ return null;
96109
+ rest = afterReplace;
96110
+ }
96111
+ const afterTemp = stripKw(rest, "temp") ?? stripKw(rest, "temporary");
96112
+ if (afterTemp !== null)
96113
+ rest = afterTemp;
96114
+ const afterMacro = stripKw(rest, "macro") ?? stripKw(rest, "function");
96115
+ if (afterMacro === null)
96116
+ return null;
96117
+ rest = afterMacro;
96118
+ const nameEndMatch = rest.match(/[\s(]/);
96119
+ const nameEnd = nameEndMatch ? nameEndMatch.index : rest.length;
96120
+ const rawName = rest.slice(0, nameEnd);
96121
+ if (rawName === "" || rawName.includes(".") || rawName.includes('"') || !isIdent(rawName)) {
96122
+ return null;
96123
+ }
96124
+ const name = rawName.toLowerCase();
96125
+ rest = rest.slice(nameEnd).replace(/^\s+/, "");
96126
+ if (!rest.startsWith("("))
96127
+ return null;
96128
+ const pchars = [...rest];
96129
+ let depth = 0;
96130
+ let j2 = 0;
96131
+ let close = -1;
96132
+ while (j2 < pchars.length) {
96133
+ const ch = pchars[j2];
96134
+ if (ch === "(")
96135
+ depth += 1;
96136
+ else if (ch === ")") {
96137
+ depth -= 1;
96138
+ if (depth === 0) {
96139
+ close = j2;
96140
+ break;
96141
+ }
96142
+ } else if (ch === "'" || ch === '"') {
96143
+ const q2 = ch;
96144
+ j2 += 1;
96145
+ while (j2 < pchars.length && pchars[j2] !== q2)
96146
+ j2 += 1;
96147
+ }
96148
+ j2 += 1;
96149
+ }
96150
+ if (close === -1)
96151
+ return null;
96152
+ const params = pchars.slice(1, close).join("").trim();
96153
+ const afterParams = pchars.slice(close + 1).join("").replace(/^\s+/, "");
96154
+ let body = stripKw(afterParams, "as");
96155
+ if (body === null)
96156
+ return null;
96157
+ let isTable = false;
96158
+ const afterTable = stripKw(body, "table");
96159
+ if (afterTable !== null) {
96160
+ body = afterTable;
96161
+ isTable = true;
96162
+ }
96163
+ const trimmedBody = body.replace(/;+\s*$/, "").trim();
96164
+ if (trimmedBody === "")
96165
+ return null;
96166
+ return { name, params, isTable };
96167
+ }
96168
+ function parseMacroLibrary(sql) {
96169
+ const out = [];
96170
+ for (const stmt of splitStatements(sql)) {
96171
+ const m3 = parseCreateMacro(stmt);
96172
+ if (m3)
96173
+ out.push(m3);
96174
+ }
96175
+ return out;
96176
+ }
96177
+ function detectMacroCalls(sql, names) {
96178
+ const found = new Set;
96179
+ if (names.size === 0)
96180
+ return found;
96181
+ for (const stmt of splitStatements(sql)) {
96182
+ const chars = [...stmt];
96183
+ const n2 = chars.length;
96184
+ let i = 0;
96185
+ let prev = undefined;
96186
+ while (i < n2) {
96187
+ const c2 = chars[i];
96188
+ if (c2 === "'" || c2 === '"') {
96189
+ const q2 = c2;
96190
+ i += 1;
96191
+ while (i < n2 && chars[i] !== q2)
96192
+ i += 1;
96193
+ i += 1;
96194
+ prev = q2;
96195
+ continue;
96196
+ }
96197
+ const isIdentStart = /[A-Za-z_]/.test(c2);
96198
+ const prevBlocks = prev !== undefined && /[.A-Za-z0-9_]/.test(prev);
96199
+ if (isIdentStart && !prevBlocks) {
96200
+ const start = i;
96201
+ while (i < n2 && /[A-Za-z0-9_]/.test(chars[i]))
96202
+ i += 1;
96203
+ const word = chars.slice(start, i).join("").toLowerCase();
96204
+ let k2 = i;
96205
+ while (k2 < n2 && /\s/.test(chars[k2]))
96206
+ k2 += 1;
96207
+ if (k2 < n2 && chars[k2] === "(" && names.has(word))
96208
+ found.add(word);
96209
+ prev = chars[i - 1];
96210
+ continue;
96211
+ }
96212
+ prev = c2;
96213
+ i += 1;
96214
+ }
96215
+ }
96216
+ return found;
96217
+ }
96218
+ function parseMacroAnnotations(content) {
96219
+ let macros = false;
96220
+ const useLibs = [];
96221
+ for (const raw of content.split(`
96222
+ `)) {
96223
+ const line = raw.replace(/^\s+/, "");
96224
+ if (line === "")
96225
+ continue;
96226
+ let rest;
96227
+ if (line.startsWith("//") || line.startsWith("--"))
96228
+ rest = line.slice(2);
96229
+ else if (line.startsWith("#"))
96230
+ rest = line.slice(1);
96231
+ else
96232
+ break;
96233
+ rest = rest.replace(/^\s+/, "");
96234
+ if (/^macros\s*$/.test(rest)) {
96235
+ macros = true;
96236
+ continue;
96237
+ }
96238
+ const u2 = rest.match(/^use\s+(\S+)\s*$/);
96239
+ if (u2 && u2[1].includes("/") && !useLibs.includes(u2[1]))
96240
+ useLibs.push(u2[1]);
96241
+ }
96242
+ return { macros, useLibs };
96243
+ }
96244
+
96245
+ // src/commands/pipeline/localGraph.ts
96004
96246
  function workspaceRoot() {
96005
96247
  const yaml = getWmillYamlPath();
96006
96248
  return yaml ? path23.dirname(yaml) : process25.cwd();
@@ -96076,7 +96318,8 @@ function fallbackParse(content, language) {
96076
96318
  if (uri) {
96077
96319
  const prefix = uri[1].toLowerCase();
96078
96320
  const kind = prefix === "s3" ? "s3object" : prefix;
96079
- out.triggers.push({ kind: "asset", asset_kind: kind, path: uri[2] });
96321
+ const path24 = kind === "s3object" ? uri[2].replace(/^\/+/, "") : uri[2];
96322
+ out.triggers.push({ kind: "asset", asset_kind: kind, path: path24 });
96080
96323
  } else if (NATIVE_KINDS.has(firstTok) && rest === firstTok) {
96081
96324
  out.triggers.push({ kind: firstTok });
96082
96325
  }
@@ -96224,13 +96467,30 @@ async function buildLocalPipelineGraph(args) {
96224
96467
  const folderClean = args.folder.replace(/^f\//, "").replace(/\/$/, "");
96225
96468
  const folderDir = path23.join(args.root, "f", folderClean);
96226
96469
  const all = await collectScripts(folderDir, args.root, args.defaultTs);
96470
+ const libMacros = collectMacroLibraries(args.root);
96227
96471
  const runnables = [];
96228
96472
  const edges = [];
96229
96473
  const triggers = [];
96230
96474
  const assetSet = new Map;
96475
+ const derivedFromByKey = new Map;
96231
96476
  const pipelineScripts = [];
96232
96477
  for (const s2 of all) {
96233
96478
  const out = await inferScriptAssets(s2.content, s2.language);
96479
+ const macroLibDefs = libMacros.get(s2.path);
96480
+ if (macroLibDefs) {
96481
+ runnables.push({
96482
+ path: s2.path,
96483
+ usage_kind: "script",
96484
+ in_pipeline: true,
96485
+ ...out.tag ? { tag: out.tag } : {},
96486
+ macros: macroLibDefs.map((m3) => ({
96487
+ name: m3.name,
96488
+ params: m3.params,
96489
+ is_table: m3.isTable
96490
+ }))
96491
+ });
96492
+ continue;
96493
+ }
96234
96494
  if (!out.in_pipeline)
96235
96495
  continue;
96236
96496
  const retry = normalizeRetry(out.retry);
@@ -96263,19 +96523,30 @@ async function buildLocalPipelineGraph(args) {
96263
96523
  });
96264
96524
  }
96265
96525
  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"
96526
+ const matWrites = [
96527
+ { path: mat.target_path }
96528
+ ];
96529
+ if (mat.scd2 && !mat.manual) {
96530
+ const currentPath = `${mat.target_path}_current`;
96531
+ matWrites.push({ path: currentPath, derived_from: mat.target_path });
96532
+ derivedFromByKey.set(`${mat.target_kind}:${currentPath}`, mat.target_path);
96533
+ }
96534
+ for (const w2 of matWrites) {
96535
+ assetSet.set(`${mat.target_kind}:${w2.path}`, {
96536
+ kind: mat.target_kind,
96537
+ path: w2.path,
96538
+ ...w2.derived_from ? { derived_from: w2.derived_from } : {}
96278
96539
  });
96540
+ 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"));
96541
+ if (!hasWrite) {
96542
+ edges.push({
96543
+ runnable_kind: "script",
96544
+ runnable_path: s2.path,
96545
+ asset_kind: mat.target_kind,
96546
+ asset_path: w2.path,
96547
+ access_type: "w"
96548
+ });
96549
+ }
96279
96550
  }
96280
96551
  }
96281
96552
  const existingNativeTriggers = new Set;
@@ -96312,16 +96583,134 @@ async function buildLocalPipelineGraph(args) {
96312
96583
  });
96313
96584
  }
96314
96585
  }
96586
+ const macroEdges = buildMacroEdges(all, libMacros, runnables);
96587
+ const assets = [...assetSet.entries()].map(([key, a2]) => {
96588
+ const derived_from = a2.derived_from ?? derivedFromByKey.get(key);
96589
+ return derived_from ? { ...a2, derived_from } : a2;
96590
+ });
96315
96591
  return {
96316
96592
  graph: {
96317
96593
  runnables,
96318
- assets: [...assetSet.values()],
96594
+ assets,
96319
96595
  edges,
96320
- triggers
96596
+ triggers,
96597
+ ...macroEdges.length > 0 ? { macro_edges: macroEdges } : {}
96321
96598
  },
96322
96599
  scripts: pipelineScripts
96323
96600
  };
96324
96601
  }
96602
+ function collectMacroLibraries(root) {
96603
+ const out = new Map;
96604
+ const walk = (dir) => {
96605
+ let entries;
96606
+ try {
96607
+ entries = fs17.readdirSync(dir, { withFileTypes: true });
96608
+ } catch {
96609
+ return;
96610
+ }
96611
+ for (const e2 of entries) {
96612
+ if (e2.name.startsWith(".") || e2.name === "node_modules")
96613
+ continue;
96614
+ const abs = path23.join(dir, e2.name);
96615
+ if (e2.isDirectory()) {
96616
+ walk(abs);
96617
+ continue;
96618
+ }
96619
+ if (!e2.isFile() || !e2.name.endsWith(".duckdb.sql"))
96620
+ continue;
96621
+ let content;
96622
+ try {
96623
+ content = fs17.readFileSync(abs, "utf-8");
96624
+ } catch {
96625
+ continue;
96626
+ }
96627
+ if (!parseMacroAnnotations(content).macros)
96628
+ continue;
96629
+ const relFromRoot = path23.relative(root, abs).replaceAll("\\", "/");
96630
+ out.set(removeExtensionToPath(relFromRoot), parseMacroLibrary(content));
96631
+ }
96632
+ };
96633
+ walk(path23.join(root, "f"));
96634
+ return out;
96635
+ }
96636
+ function buildMacroEdges(all, libMacros, runnables) {
96637
+ if (libMacros.size === 0)
96638
+ return [];
96639
+ const useLibsByScript = new Map;
96640
+ for (const s2 of all) {
96641
+ if (s2.language !== "duckdb")
96642
+ continue;
96643
+ const { useLibs } = parseMacroAnnotations(s2.content);
96644
+ if (useLibs.length > 0)
96645
+ useLibsByScript.set(s2.path, useLibs);
96646
+ }
96647
+ const providerByName = new Map;
96648
+ for (const [lib, macros] of libMacros) {
96649
+ for (const m3 of macros)
96650
+ providerByName.set(m3.name, lib);
96651
+ }
96652
+ const allMacroNames = new Set(providerByName.keys());
96653
+ const pipelinePaths = new Set(runnables.map((r2) => r2.path));
96654
+ const edgeMap = new Map;
96655
+ const aggFor = (lib, consumer) => {
96656
+ let byConsumer = edgeMap.get(lib);
96657
+ if (!byConsumer) {
96658
+ byConsumer = new Map;
96659
+ edgeMap.set(lib, byConsumer);
96660
+ }
96661
+ let agg = byConsumer.get(consumer);
96662
+ if (!agg) {
96663
+ agg = { names: new Set, viaUse: false };
96664
+ byConsumer.set(consumer, agg);
96665
+ }
96666
+ return agg;
96667
+ };
96668
+ for (const s2 of all) {
96669
+ if (s2.language !== "duckdb")
96670
+ continue;
96671
+ for (const name of detectMacroCalls(s2.content, allMacroNames)) {
96672
+ const lib = providerByName.get(name);
96673
+ if (lib === s2.path)
96674
+ continue;
96675
+ aggFor(lib, s2.path).names.add(name);
96676
+ }
96677
+ if (!pipelinePaths.has(s2.path))
96678
+ continue;
96679
+ for (const lib of useLibsByScript.get(s2.path) ?? []) {
96680
+ const macros = libMacros.get(lib);
96681
+ if (!macros || lib === s2.path)
96682
+ continue;
96683
+ const agg = aggFor(lib, s2.path);
96684
+ agg.viaUse = true;
96685
+ for (const m3 of macros)
96686
+ agg.names.add(m3.name);
96687
+ }
96688
+ }
96689
+ const edges = [...edgeMap.entries()].flatMap(([lib_path, byConsumer]) => [...byConsumer.entries()].map(([consumer_path, agg]) => ({
96690
+ lib_path,
96691
+ consumer_path,
96692
+ macro_names: [...agg.names].sort(),
96693
+ via_use: agg.viaUse
96694
+ }))).sort((a2, b2) => a2.lib_path.localeCompare(b2.lib_path) || a2.consumer_path.localeCompare(b2.consumer_path));
96695
+ const libPaths = new Set(edges.map((e2) => e2.lib_path));
96696
+ for (const lib of libPaths) {
96697
+ if (runnables.some((r2) => r2.path === lib))
96698
+ continue;
96699
+ const macros = (libMacros.get(lib) ?? []).map((m3) => ({
96700
+ name: m3.name,
96701
+ params: m3.params,
96702
+ is_table: m3.isTable
96703
+ }));
96704
+ runnables.push({ path: lib, usage_kind: "script", macros });
96705
+ }
96706
+ for (const consumer of new Set(edges.map((e2) => e2.consumer_path))) {
96707
+ if (!runnables.some((r2) => r2.path === consumer)) {
96708
+ runnables.push({ path: consumer, usage_kind: "script" });
96709
+ }
96710
+ }
96711
+ runnables.sort((a2, b2) => a2.path.localeCompare(b2.path));
96712
+ return edges;
96713
+ }
96325
96714
 
96326
96715
  // src/commands/pipeline/pipeline.ts
96327
96716
  await __promiseAll([
@@ -96552,7 +96941,17 @@ function generatePipelineMarkdown(folder, graph, datatableSchemas, local) {
96552
96941
  (nativeByScript.get(t2.runnable_path) ?? nativeByScript.set(t2.runnable_path, []).get(t2.runnable_path)).push(t2.trigger_kind);
96553
96942
  }
96554
96943
  }
96555
- const scripts = graph.runnables.filter((r2) => r2.usage_kind === "script").map((r2) => r2.path).sort();
96944
+ const macroLibs = graph.runnables.filter((r2) => r2.usage_kind === "script" && (r2.macros?.length ?? 0) > 0).sort((a2, b2) => a2.path.localeCompare(b2.path));
96945
+ const macroLibPaths = new Set(macroLibs.map((r2) => r2.path));
96946
+ const macroConsumersByLib = new Map;
96947
+ for (const me of graph.macro_edges ?? []) {
96948
+ (macroConsumersByLib.get(me.lib_path) ?? macroConsumersByLib.set(me.lib_path, []).get(me.lib_path)).push({
96949
+ consumer: me.consumer_path,
96950
+ names: me.macro_names,
96951
+ viaUse: me.via_use
96952
+ });
96953
+ }
96954
+ const scripts = graph.runnables.filter((r2) => r2.usage_kind === "script" && !macroLibPaths.has(r2.path)).map((r2) => r2.path).sort();
96556
96955
  let md = `# Pipeline \`f/${folder}\`
96557
96956
 
96558
96957
  ${local ? "_Built from local working-tree files (`// pipeline` scripts)._" : "_Built from the deployed workspace asset graph._"}
@@ -96563,7 +96962,7 @@ produces data by reading/writing assets in its body (\`datatable://\`,
96563
96962
  \`ducklake://\`, \`s3://\`, \`volume://\`). The cascade runs a producer, then every
96564
96963
  downstream subscriber, in topological order.
96565
96964
 
96566
- - **${scripts.length}** script${scripts.length === 1 ? "" : "s"} · **${graph.assets.length}** asset${graph.assets.length === 1 ? "" : "s"}
96965
+ - **${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
96966
 
96568
96967
  ## Scripts
96569
96968
 
@@ -96595,6 +96994,36 @@ downstream subscriber, in topological order.
96595
96994
  md += `
96596
96995
  `;
96597
96996
  }
96997
+ if (macroLibs.length > 0) {
96998
+ md += `## Macro libraries
96999
+
97000
+ `;
97001
+ md += `\`// macros\` DuckDB libraries. Their \`CREATE MACRO\` definitions are injected as
97002
+ TEMP macros into consuming scripts at run time — call a macro by name, or force the
97003
+ whole library in with \`// use <lib-path>\` (needed for macros only reached via dynamic SQL).
97004
+
97005
+ `;
97006
+ for (const lib of macroLibs) {
97007
+ md += `### \`${lib.path}\`
97008
+
97009
+ `;
97010
+ for (const m3 of lib.macros ?? []) {
97011
+ md += `- \`${m3.name}(${m3.params ?? ""})\`${m3.is_table ? " → TABLE" : ""}
97012
+ `;
97013
+ }
97014
+ const consumers = [...macroConsumersByLib.get(lib.path) ?? []].sort((a2, b2) => a2.consumer.localeCompare(b2.consumer));
97015
+ if (consumers.length > 0) {
97016
+ md += `- **Used by:**
97017
+ `;
97018
+ for (const c2 of consumers) {
97019
+ md += ` - \`${c2.consumer}\` (${c2.viaUse ? "via `// use`" : `calls ${c2.names.map((n2) => `\`${n2}\``).join(", ")}`})
97020
+ `;
97021
+ }
97022
+ }
97023
+ md += `
97024
+ `;
97025
+ }
97026
+ }
96598
97027
  const referencedDatatables = new Set(graph.assets.filter((a2) => a2.kind === "datatable").map((a2) => a2.path.split("/")[0]));
96599
97028
  const relevant = (datatableSchemas ?? []).filter((dt) => referencedDatatables.has(dt.datatable_name));
96600
97029
  if (relevant.length > 0) {
@@ -97104,11 +97533,13 @@ async function run5(opts, folder) {
97104
97533
  });
97105
97534
  graph = built.graph;
97106
97535
  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 {}
97536
+ if (!opts.dryRun) {
97537
+ try {
97538
+ const codebases = await listSyncCodebases(merged);
97539
+ const { buildPreviewTempScriptRefs: buildPreviewTempScriptRefs2 } = await init_generate_metadata().then(() => exports_generate_metadata);
97540
+ tempScriptRefs = await buildPreviewTempScriptRefs2(workspace, merged, codebases, { kind: "all" });
97541
+ } catch {}
97542
+ }
97112
97543
  } else {
97113
97544
  graph = await apiGet(`/w/${workspace.workspaceId}/assets/graph?folder=${encodeURIComponent(f3)}&asset_kinds=${ASSET_KINDS2}`);
97114
97545
  await enrichDeployedNonAutorunTriggers(workspace.workspaceId, graph);
@@ -97144,7 +97575,16 @@ async function run5(opts, folder) {
97144
97575
  merged[b2.param] = b2.value;
97145
97576
  }
97146
97577
  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))));
97578
+ const notRunnablePaths = new Set(macroLibPaths);
97579
+ if (opts.local && localScripts) {
97580
+ for (const r2 of graph.runnables) {
97581
+ if (r2.usage_kind === "script" && !localScripts.has(r2.path)) {
97582
+ notRunnablePaths.add(r2.path);
97583
+ }
97584
+ }
97585
+ }
97586
+ const starts = new Set([...validStarts(graph), ...boundNodeIds].filter((id) => !notRunnablePaths.has(scriptPathOf(id))));
97587
+ const fromEligible = new Set([...validFromStarts(graph), ...boundNodeIds].filter((id) => !notRunnablePaths.has(scriptPathOf(id))));
97148
97588
  let runAll = false;
97149
97589
  let start;
97150
97590
  if (opts.from) {
@@ -97159,8 +97599,14 @@ async function run5(opts, folder) {
97159
97599
  if (macroLibPaths.has(scriptPathOf(resolved))) {
97160
97600
  throw new Error(`--from '${opts.from}' is a \`// macros\` library — definition-only, injected into consuming scripts at run time, never a runnable step.`);
97161
97601
  }
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.`);
97602
+ if (notRunnablePaths.has(scriptPathOf(resolved))) {
97603
+ 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.`);
97604
+ }
97605
+ if (!resolved.startsWith("script:")) {
97606
+ throw new Error(`--from '${opts.from}' is an asset, not a runnable — start a run from a script (assets are produced, not run).`);
97607
+ }
97608
+ if (!fromEligible.has(resolved)) {
97609
+ 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
97610
  }
97165
97611
  start = resolved;
97166
97612
  } else if (starts.size === 1) {
@@ -97191,7 +97637,7 @@ async function run5(opts, folder) {
97191
97637
  }
97192
97638
  const idLabel = (id) => id.startsWith("script:") ? scriptPathOf(id) : id;
97193
97639
  const dag = buildLineageDag(graph);
97194
- const barriers = new Set([...nonAutorunTriggerScripts(graph)].filter((id) => !starts.has(id)));
97640
+ const barriers = new Set([...nonAutorunTriggerScripts(graph)].filter((id) => !starts.has(id) && id !== start));
97195
97641
  let selectedScripts;
97196
97642
  let reachableEnds = [];
97197
97643
  let droppedEnds = [];
@@ -97216,7 +97662,7 @@ async function run5(opts, folder) {
97216
97662
  }
97217
97663
  }
97218
97664
  }
97219
- for (const p3 of macroLibPaths)
97665
+ for (const p3 of notRunnablePaths)
97220
97666
  selectedScripts.delete(p3);
97221
97667
  const { order, cyclic } = topoOrder(graph, selectedScripts);
97222
97668
  if (cyclic.length > 0) {
@@ -97333,7 +97779,7 @@ Full logs: ${runUrl}`);
97333
97779
  info(colors.green.bold(`Bounded run complete — ${order.length} script(s) succeeded.`));
97334
97780
  }
97335
97781
  }
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);
97782
+ 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
97783
  var pipeline_default = command42;
97338
97784
 
97339
97785
  // 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.749.0",
4
4
  "description": "CLI for Windmill",
5
5
  "license": "Apache 2.0",
6
6
  "type": "module",