@intentius/chant 0.124.22 → 0.124.24

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 (187) hide show
  1. package/dist/audit/rules-doc.js +1 -1
  2. package/dist/ci-jobs.d.ts +41 -1
  3. package/dist/ci-jobs.d.ts.map +1 -1
  4. package/dist/ci-jobs.js +48 -1
  5. package/dist/cli/commands/build.d.ts.map +1 -1
  6. package/dist/cli/commands/build.js +14 -0
  7. package/dist/cli/handlers/approve.d.ts.map +1 -1
  8. package/dist/cli/handlers/approve.js +9 -0
  9. package/dist/cli/handlers/emulator.d.ts +13 -2
  10. package/dist/cli/handlers/emulator.d.ts.map +1 -1
  11. package/dist/cli/handlers/emulator.js +63 -3
  12. package/dist/cli/handlers/graph.js +24 -1
  13. package/dist/cli/handlers/promote.d.ts.map +1 -1
  14. package/dist/cli/handlers/promote.js +7 -1
  15. package/dist/cli/handlers/run-wave.d.ts.map +1 -1
  16. package/dist/cli/handlers/run-wave.js +5 -4
  17. package/dist/cli/handlers/run.d.ts +4 -3
  18. package/dist/cli/handlers/run.d.ts.map +1 -1
  19. package/dist/cli/handlers/run.js +73 -9
  20. package/dist/cli/main.d.ts.map +1 -1
  21. package/dist/cli/main.js +17 -4
  22. package/dist/cli/parse-args.d.ts.map +1 -1
  23. package/dist/cli/parse-args.js +31 -0
  24. package/dist/cli/registry.d.ts +12 -0
  25. package/dist/cli/registry.d.ts.map +1 -1
  26. package/dist/components/cli-support.d.ts +45 -6
  27. package/dist/components/cli-support.d.ts.map +1 -1
  28. package/dist/components/cli-support.js +97 -22
  29. package/dist/components/driver.d.ts.map +1 -1
  30. package/dist/components/driver.js +25 -10
  31. package/dist/graph-detail.d.ts.map +1 -1
  32. package/dist/graph-detail.js +24 -2
  33. package/dist/graph-ir.d.ts +43 -0
  34. package/dist/graph-ir.d.ts.map +1 -1
  35. package/dist/graph-ir.js +27 -0
  36. package/dist/lexicon.d.ts +25 -1
  37. package/dist/lexicon.d.ts.map +1 -1
  38. package/dist/lifecycle/git.d.ts +11 -0
  39. package/dist/lifecycle/git.d.ts.map +1 -1
  40. package/dist/lifecycle/git.js +62 -6
  41. package/dist/lifecycle/no-record.d.ts +59 -0
  42. package/dist/lifecycle/no-record.d.ts.map +1 -0
  43. package/dist/lifecycle/no-record.js +88 -0
  44. package/dist/op/activities/shell.d.ts.map +1 -1
  45. package/dist/op/activities/shell.js +30 -9
  46. package/dist/op/emulator-lifecycle.d.ts +23 -0
  47. package/dist/op/emulator-lifecycle.d.ts.map +1 -1
  48. package/dist/op/emulator-lifecycle.js +39 -2
  49. package/dist/op/gate.d.ts.map +1 -1
  50. package/dist/op/gate.js +3 -0
  51. package/dist/op/index.d.ts +1 -1
  52. package/dist/op/index.d.ts.map +1 -1
  53. package/dist/op/index.js +1 -1
  54. package/dist/op/local-executor.d.ts +10 -0
  55. package/dist/op/local-executor.d.ts.map +1 -1
  56. package/dist/op/local-executor.js +7 -2
  57. package/dist/op/op-waves-notify.d.ts +2 -0
  58. package/dist/op/op-waves-notify.d.ts.map +1 -1
  59. package/dist/op/op-waves-notify.js +9 -3
  60. package/dist/op/op-waves-run.d.ts +8 -2
  61. package/dist/op/op-waves-run.d.ts.map +1 -1
  62. package/dist/op/op-waves-run.js +36 -11
  63. package/dist/op/op-waves.d.ts +85 -1
  64. package/dist/op/op-waves.d.ts.map +1 -1
  65. package/dist/op/op-waves.js +153 -1
  66. package/dist/op/runtimes/local.d.ts.map +1 -1
  67. package/dist/op/runtimes/local.js +6 -0
  68. package/dist/op/step-console.d.ts +65 -0
  69. package/dist/op/step-console.d.ts.map +1 -0
  70. package/dist/op/step-console.js +140 -0
  71. package/dist/point-decision.d.ts.map +1 -1
  72. package/dist/point-decision.js +3 -0
  73. package/dist/wave-runtime.mjs +240 -224
  74. package/dist/workspace/checks/links.d.ts +5 -0
  75. package/dist/workspace/checks/links.d.ts.map +1 -1
  76. package/dist/workspace/checks/links.js +54 -8
  77. package/dist/workspace/checks.d.ts +4 -1
  78. package/dist/workspace/checks.d.ts.map +1 -1
  79. package/dist/workspace/checks.js +6 -2
  80. package/dist/workspace/compose-graph.d.ts +26 -4
  81. package/dist/workspace/compose-graph.d.ts.map +1 -1
  82. package/dist/workspace/compose-graph.js +21 -7
  83. package/dist/workspace/dbt-project-reader.d.ts +36 -0
  84. package/dist/workspace/dbt-project-reader.d.ts.map +1 -0
  85. package/dist/workspace/dbt-project-reader.js +290 -0
  86. package/dist/workspace/graph-cli.d.ts.map +1 -1
  87. package/dist/workspace/graph-cli.js +3 -1
  88. package/dist/workspace/graph.schema.json +109 -4
  89. package/dist/workspace/intent.js +7 -2
  90. package/dist/workspace/kind-link-readers.d.ts +84 -0
  91. package/dist/workspace/kind-link-readers.d.ts.map +1 -0
  92. package/dist/workspace/kind-link-readers.js +53 -0
  93. package/dist/workspace/kinds.d.ts +15 -0
  94. package/dist/workspace/kinds.d.ts.map +1 -1
  95. package/dist/workspace/kinds.js +11 -0
  96. package/dist/workspace/lexicon-outputs.d.ts +63 -0
  97. package/dist/workspace/lexicon-outputs.d.ts.map +1 -0
  98. package/dist/workspace/lexicon-outputs.js +241 -0
  99. package/dist/workspace/links.d.ts +29 -7
  100. package/dist/workspace/links.d.ts.map +1 -1
  101. package/dist/workspace/links.js +109 -34
  102. package/dist/workspace/member-handles.d.ts +18 -1
  103. package/dist/workspace/member-handles.d.ts.map +1 -1
  104. package/dist/workspace/member-handles.js +31 -14
  105. package/dist/workspace/nested-graph.d.ts +1 -1
  106. package/dist/workspace/nested-graph.d.ts.map +1 -1
  107. package/dist/workspace/nested-graph.js +5 -1
  108. package/dist/workspace/records-checkout.d.ts +11 -1
  109. package/dist/workspace/records-checkout.d.ts.map +1 -1
  110. package/dist/workspace/records-checkout.js +38 -2
  111. package/dist/workspace/runs.d.ts.map +1 -1
  112. package/dist/workspace/runs.js +4 -2
  113. package/dist/workspace/status.d.ts.map +1 -1
  114. package/dist/workspace/status.js +3 -2
  115. package/dist/workspace/workspace-kinds.schema.json +5 -0
  116. package/package.json +1 -1
  117. package/src/audit/rules-doc.ts +1 -1
  118. package/src/ci-jobs.ts +58 -1
  119. package/src/cli/commands/build.ts +18 -0
  120. package/src/cli/handlers/approve.ts +9 -0
  121. package/src/cli/handlers/emulator.test.ts +61 -1
  122. package/src/cli/handlers/emulator.ts +65 -4
  123. package/src/cli/handlers/graph.ts +22 -1
  124. package/src/cli/handlers/promote.test.ts +7 -0
  125. package/src/cli/handlers/promote.ts +7 -1
  126. package/src/cli/handlers/run-no-record.e2e.test.ts +87 -0
  127. package/src/cli/handlers/run-wave.ts +5 -4
  128. package/src/cli/handlers/run.test.ts +126 -0
  129. package/src/cli/handlers/run.ts +69 -9
  130. package/src/cli/main.test.ts +24 -0
  131. package/src/cli/main.ts +17 -4
  132. package/src/cli/parse-args.ts +21 -0
  133. package/src/cli/registry.ts +12 -0
  134. package/src/components/cli-support.test.ts +75 -0
  135. package/src/components/cli-support.ts +130 -25
  136. package/src/components/driver.test.ts +28 -0
  137. package/src/components/driver.ts +23 -10
  138. package/src/components/generate-disabled.test.ts +56 -0
  139. package/src/graph-ci-edges.test.ts +50 -0
  140. package/src/graph-detail.ts +21 -2
  141. package/src/graph-ir.test.ts +23 -1
  142. package/src/graph-ir.ts +65 -0
  143. package/src/lexicon.ts +26 -1
  144. package/src/lifecycle/git.ts +61 -6
  145. package/src/lifecycle/no-record.test.ts +175 -0
  146. package/src/lifecycle/no-record.ts +108 -0
  147. package/src/op/activities/shell.ts +28 -6
  148. package/src/op/emulator-lifecycle.test.ts +24 -1
  149. package/src/op/emulator-lifecycle.ts +58 -2
  150. package/src/op/gate.ts +3 -0
  151. package/src/op/index.ts +1 -1
  152. package/src/op/local-executor.ts +20 -3
  153. package/src/op/op-waves-jobs.test.ts +213 -0
  154. package/src/op/op-waves-notify.ts +11 -5
  155. package/src/op/op-waves-run.ts +42 -11
  156. package/src/op/op-waves.ts +202 -2
  157. package/src/op/runtimes/local.ts +6 -0
  158. package/src/op/step-console.test.ts +150 -0
  159. package/src/op/step-console.ts +155 -0
  160. package/src/point-decision-bundle.test.ts +1 -0
  161. package/src/point-decision.ts +3 -0
  162. package/src/wave-loop-golden.test.ts +3 -0
  163. package/src/workspace/checks/links.ts +57 -8
  164. package/src/workspace/checks.e2e.test.ts +1 -1
  165. package/src/workspace/checks.ts +9 -2
  166. package/src/workspace/compose-graph.test.ts +55 -0
  167. package/src/workspace/compose-graph.ts +33 -9
  168. package/src/workspace/dbt-kind.e2e.test.ts +95 -0
  169. package/src/workspace/dbt-kind.test.ts +283 -0
  170. package/src/workspace/dbt-project-reader.ts +291 -0
  171. package/src/workspace/graph-cli.ts +3 -1
  172. package/src/workspace/graph-contract.test.ts +26 -1
  173. package/src/workspace/graph.schema.json +109 -4
  174. package/src/workspace/intent.ts +7 -2
  175. package/src/workspace/kind-link-readers.ts +109 -0
  176. package/src/workspace/kinds.ts +21 -1
  177. package/src/workspace/lexicon-outputs.ts +272 -0
  178. package/src/workspace/links.test.ts +22 -0
  179. package/src/workspace/links.ts +129 -34
  180. package/src/workspace/member-handles.ts +28 -8
  181. package/src/workspace/nested-graph.test.ts +14 -0
  182. package/src/workspace/nested-graph.ts +7 -2
  183. package/src/workspace/records-checkout.ts +36 -2
  184. package/src/workspace/records-uncommitted.test.ts +5 -1
  185. package/src/workspace/runs.ts +4 -2
  186. package/src/workspace/status.ts +3 -2
  187. package/src/workspace/workspace-kinds.schema.json +5 -0
@@ -0,0 +1,290 @@
1
+ /**
2
+ * The `dbt-project` link reader (#3932): a dbt project's sources, as links to
3
+ * the tables and columns a sql member exposes (#3931).
4
+ *
5
+ * The reader parses YAML and nothing else. It never runs dbt, never renders
6
+ * Jinja and never reads `profiles.yml` or a target, so the links it derives
7
+ * are the same in every environment.
8
+ *
9
+ * - The project is `dbt_project.yml`. Sources are read from every `.yml` and
10
+ * `.yaml` file under its `model-paths` (`source-paths` in older projects;
11
+ * `models` when it names neither), as dbt reads them.
12
+ * - Each enabled source table links to `<schema>.<table>`, and each of its
13
+ * `columns` to `<schema>.<table>.<column>`. The schema is the source's
14
+ * `schema`, or the source's `name` when it sets none, which is dbt's own
15
+ * fallback. The table is its `identifier`, or its `name`. `database` is not
16
+ * part of the name: a Postgres output has none, and dbt-clickhouse reads a
17
+ * source's `schema` as the ClickHouse database.
18
+ * - Case follows the member's `dialect` field. For `postgres`, an unquoted
19
+ * name folds to lowercase and a quoted one keeps its case. dbt quotes a
20
+ * source's schema and identifier on Postgres unless `quoting:` (in
21
+ * `dbt_project.yml`, on the source, or on the table) turns it off, and a
22
+ * column is quoted only with `quote: true`. For `clickhouse`, names are
23
+ * kept as written.
24
+ * - A value that is a Jinja template (`{{ ... }}` or `{% ... %}`) is not
25
+ * resolved: the reader records a gap and leaves that source, table or
26
+ * column out, rather than guess what a target would render.
27
+ * - The producer is the member the `sqlMember` field names. Unset, each link
28
+ * leaves the producer to `resolveLinks`, which picks the one member that
29
+ * exposes the table.
30
+ */
31
+ import { posix } from "node:path";
32
+ import { parseYAMLDocument, YAMLParseError } from "../yaml.js";
33
+ import { joinPath, skippedDir } from "./tree.js";
34
+ export const DBT_PROJECT_FILE = "dbt_project.yml";
35
+ const DEFAULT_MODEL_PATHS = ["models"];
36
+ /** The dialects whose naming the reader follows; the member's `dialect` field picks one. */
37
+ export const DBT_DIALECTS = ["postgres", "clickhouse"];
38
+ const isTemplated = (v) => /\{\{|\{%/.test(v);
39
+ const isObject = (v) => v !== null && typeof v === "object" && !Array.isArray(v);
40
+ export function readDbtProject(input) {
41
+ const { dir, tree, fields } = input;
42
+ const links = [];
43
+ const gaps = [];
44
+ const result = { reader: "dbt-project", links, gaps, producerField: "sqlMember" };
45
+ const projectFile = joinPath(dir, DBT_PROJECT_FILE);
46
+ const producer = typeof fields?.sqlMember === "string" && fields.sqlMember !== "" ? fields.sqlMember : null;
47
+ const dialect = fields?.dialect === "clickhouse" ? "clickhouse" : "postgres";
48
+ let project;
49
+ try {
50
+ project = parseYAMLDocument(tree.read(projectFile));
51
+ }
52
+ catch (err) {
53
+ gaps.push({ file: projectFile, line: err instanceof YAMLParseError ? err.line : 1, message: `${DBT_PROJECT_FILE} could not be read (${err.message}), so no source was read` });
54
+ return result;
55
+ }
56
+ if (!isObject(project)) {
57
+ gaps.push({ file: projectFile, line: 1, message: `${DBT_PROJECT_FILE} is not a mapping, so no source was read` });
58
+ return result;
59
+ }
60
+ const projectLines = lines(tree.read(projectFile));
61
+ // Where sources live: model-paths, or the older source-paths.
62
+ const key = project["model-paths"] !== undefined ? "model-paths" : project["source-paths"] !== undefined ? "source-paths" : null;
63
+ const rawPaths = key ? project[key] : DEFAULT_MODEL_PATHS;
64
+ const paths = [];
65
+ if (!Array.isArray(rawPaths)) {
66
+ gaps.push({ file: projectFile, line: keyLine(projectLines, key ?? ""), message: `${key} is not a list, so no source was read` });
67
+ return result;
68
+ }
69
+ for (const p of rawPaths) {
70
+ if (typeof p !== "string" || isTemplated(p)) {
71
+ gaps.push({ file: projectFile, line: keyLine(projectLines, key ?? ""), message: `${key} entry ${JSON.stringify(p)} is not a plain path; its sources were not read` });
72
+ continue;
73
+ }
74
+ const at = posix.normalize(joinPath(dir, p));
75
+ if (at.startsWith("../") || at === ".." || posix.isAbsolute(p)) {
76
+ gaps.push({ file: projectFile, line: keyLine(projectLines, key ?? ""), message: `${key} entry ${p} lies outside the workspace; its sources were not read` });
77
+ continue;
78
+ }
79
+ paths.push(at === "." ? "" : at);
80
+ }
81
+ const quoting = readQuoting({ schema: true, identifier: true }, project.quoting, projectFile, keyLine(projectLines, "quoting"), "dbt_project.yml quoting", gaps);
82
+ const files = new Set();
83
+ for (const p of paths)
84
+ for (const f of yamlFiles(tree, p))
85
+ files.add(f);
86
+ const name = (v, quoted) => (dialect === "postgres" && !quoted ? v.toLowerCase() : v);
87
+ for (const file of [...files].sort()) {
88
+ let text;
89
+ let doc;
90
+ try {
91
+ text = tree.read(file);
92
+ doc = parseYAMLDocument(text);
93
+ }
94
+ catch (err) {
95
+ gaps.push({ file, line: err instanceof YAMLParseError ? err.line : 1, message: `the file could not be read as YAML (${err.message}); any sources in it were not read` });
96
+ continue;
97
+ }
98
+ if (!isObject(doc) || doc.sources === undefined || doc.sources === null)
99
+ continue;
100
+ const fileLines = lines(text);
101
+ const sourcesLine = keyLine(fileLines, "sources");
102
+ if (!Array.isArray(doc.sources)) {
103
+ gaps.push({ file, line: sourcesLine, message: "sources is not a list; its sources were not read" });
104
+ continue;
105
+ }
106
+ let cursor = sourcesLine;
107
+ for (const source of doc.sources) {
108
+ if (!isObject(source))
109
+ continue;
110
+ const sourceName = scalar(source.name);
111
+ const sourceLine = nameLine(fileLines, sourceName, cursor);
112
+ cursor = sourceLine;
113
+ if (sourceName === undefined) {
114
+ gaps.push({ file, line: sourceLine, message: "a source has no name; its tables were not read" });
115
+ continue;
116
+ }
117
+ const where = `source ${sourceName}`;
118
+ if (isTemplated(sourceName)) {
119
+ gaps.push({ file, line: sourceLine, message: `${where}: its name is a Jinja template, which chant does not render; its tables were not read` });
120
+ continue;
121
+ }
122
+ const enabled = readEnabled(source, file, sourceLine, where, gaps);
123
+ if (enabled !== true)
124
+ continue;
125
+ const schema = source.schema === undefined || source.schema === null ? sourceName : scalar(source.schema);
126
+ if (schema === undefined || isTemplated(schema)) {
127
+ gaps.push({
128
+ file,
129
+ line: valueLine(fileLines, "schema", sourceLine),
130
+ message: `${where}: schema ${JSON.stringify(source.schema)} is a Jinja template or not a name; chant reads names as declared and never renders them for a target, so its tables were not read`,
131
+ });
132
+ continue;
133
+ }
134
+ const sourceQuoting = readQuoting(quoting, source.quoting, file, sourceLine, `${where} quoting`, gaps);
135
+ if (source.tables === undefined || source.tables === null)
136
+ continue;
137
+ if (!Array.isArray(source.tables)) {
138
+ gaps.push({ file, line: sourceLine, message: `${where}: tables is not a list; its tables were not read` });
139
+ continue;
140
+ }
141
+ let tableCursor = sourceLine;
142
+ for (const table of source.tables) {
143
+ if (!isObject(table))
144
+ continue;
145
+ const tableName = scalar(table.name);
146
+ const tableLine = nameLine(fileLines, tableName, tableCursor);
147
+ tableCursor = tableLine;
148
+ if (tableName === undefined) {
149
+ gaps.push({ file, line: tableLine, message: `${where}: a table has no name; it was not read` });
150
+ continue;
151
+ }
152
+ const tableWhere = `${where} table ${tableName}`;
153
+ if (isTemplated(tableName)) {
154
+ gaps.push({ file, line: tableLine, message: `${tableWhere}: its name is a Jinja template, which chant does not render; it was not read` });
155
+ continue;
156
+ }
157
+ if (readEnabled(table, file, tableLine, tableWhere, gaps) !== true)
158
+ continue;
159
+ const identifier = table.identifier === undefined || table.identifier === null ? tableName : scalar(table.identifier);
160
+ if (identifier === undefined || isTemplated(identifier)) {
161
+ gaps.push({
162
+ file,
163
+ line: valueLine(fileLines, "identifier", tableLine),
164
+ message: `${tableWhere}: identifier ${JSON.stringify(table.identifier)} is a Jinja template or not a name, which chant does not render; it was not read`,
165
+ });
166
+ continue;
167
+ }
168
+ const q = readQuoting(sourceQuoting, table.quoting, file, tableLine, `${tableWhere} quoting`, gaps);
169
+ const tableOutput = `${name(schema, q.schema)}.${name(identifier, q.identifier)}`;
170
+ links.push({ member: producer, output: tableOutput, file, line: tableLine });
171
+ if (table.columns === undefined || table.columns === null)
172
+ continue;
173
+ if (!Array.isArray(table.columns)) {
174
+ gaps.push({ file, line: tableLine, message: `${tableWhere}: columns is not a list; its columns were not read` });
175
+ continue;
176
+ }
177
+ let columnCursor = tableLine;
178
+ for (const column of table.columns) {
179
+ if (!isObject(column))
180
+ continue;
181
+ const columnName = scalar(column.name);
182
+ const columnLine = nameLine(fileLines, columnName, columnCursor);
183
+ columnCursor = columnLine;
184
+ if (columnName === undefined || isTemplated(columnName)) {
185
+ gaps.push({ file, line: columnLine, message: `${tableWhere}: column ${JSON.stringify(column.name)} is a Jinja template or not a name, which chant does not render; it was not read` });
186
+ continue;
187
+ }
188
+ const quoted = column.quote === true;
189
+ links.push({ member: producer, output: `${tableOutput}.${name(columnName, quoted)}`, anchor: tableOutput, file, line: columnLine });
190
+ }
191
+ }
192
+ }
193
+ }
194
+ return result;
195
+ }
196
+ /** A name as text: a string, or a number or boolean YAML read as one. */
197
+ function scalar(v) {
198
+ if (typeof v === "string")
199
+ return v;
200
+ if (typeof v === "number" || typeof v === "boolean")
201
+ return String(v);
202
+ return undefined;
203
+ }
204
+ /** Whether an entry is enabled: `enabled`, or `config.enabled`. A templated value is a gap, and the entry is left out. */
205
+ function readEnabled(entry, file, line, where, gaps) {
206
+ const config = isObject(entry.config) ? entry.config : {};
207
+ const raw = entry.enabled ?? config.enabled;
208
+ if (raw === undefined || raw === null || raw === true)
209
+ return true;
210
+ if (raw === false)
211
+ return false;
212
+ gaps.push({ file, line, message: `${where}: enabled is ${JSON.stringify(raw)}, which chant does not render; it was not read` });
213
+ return false;
214
+ }
215
+ /** `base` with a `quoting:` block's schema and identifier applied. A value that isn't a boolean is a gap and keeps the base. */
216
+ function readQuoting(base, raw, file, line, where, gaps) {
217
+ if (raw === undefined || raw === null)
218
+ return base;
219
+ if (!isObject(raw)) {
220
+ gaps.push({ file, line, message: `${where} is not a mapping; the quoting above it applies` });
221
+ return base;
222
+ }
223
+ const out = { ...base };
224
+ for (const key of ["schema", "identifier"]) {
225
+ const v = raw[key];
226
+ if (v === undefined)
227
+ continue;
228
+ if (typeof v === "boolean")
229
+ out[key] = v;
230
+ else
231
+ gaps.push({ file, line, message: `${where}.${key} is ${JSON.stringify(v)}, not true or false; the quoting above it applies` });
232
+ }
233
+ return out;
234
+ }
235
+ /** Every `.yml` and `.yaml` file under tree-relative directory `dir`, skipping dot directories and node_modules. */
236
+ function yamlFiles(tree, dir) {
237
+ if (dir !== "" && tree.stat(dir) !== "dir")
238
+ return [];
239
+ const out = [];
240
+ const walk = (at) => {
241
+ for (const e of tree.list(at) ?? []) {
242
+ const path = joinPath(at, e.name);
243
+ if (e.type === "dir") {
244
+ if (!skippedDir(e.name))
245
+ walk(path);
246
+ }
247
+ else if (/\.ya?ml$/.test(e.name)) {
248
+ out.push(path);
249
+ }
250
+ }
251
+ };
252
+ walk(dir);
253
+ return out;
254
+ }
255
+ function lines(text) {
256
+ return text.replace(/\r\n/g, "\n").split("\n");
257
+ }
258
+ function escapeRegExp(text) {
259
+ return text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
260
+ }
261
+ /** The line (from 1) of top-level key `key`, or 1. */
262
+ function keyLine(fileLines, key) {
263
+ const re = new RegExp(`^${escapeRegExp(key)}\\s*:`);
264
+ const i = fileLines.findIndex((l) => re.test(l));
265
+ return i < 0 ? 1 : i + 1;
266
+ }
267
+ /**
268
+ * The line of the first `name: <value>` entry after line `from`, or `from`
269
+ * itself. Sources, tables and columns are listed in file order, so
270
+ * searching on from the parent's line finds each one's own line. A file in
271
+ * flow style has no such lines, and its entries are reported at the parent.
272
+ */
273
+ function nameLine(fileLines, value, from) {
274
+ return value === undefined ? from : valueLineOf(fileLines, "name", value, from);
275
+ }
276
+ /** The line of the first `<key>:` after line `from`, or `from`. */
277
+ function valueLine(fileLines, key, from) {
278
+ const re = new RegExp(`^\\s*(?:-\\s+)?${escapeRegExp(key)}\\s*:`);
279
+ for (let i = Math.max(from, 0); i < fileLines.length; i++)
280
+ if (re.test(fileLines[i]))
281
+ return i + 1;
282
+ return from;
283
+ }
284
+ function valueLineOf(fileLines, key, value, from) {
285
+ const re = new RegExp(`^\\s*(?:-\\s+)?${escapeRegExp(key)}\\s*:\\s*(["']?)${escapeRegExp(value)}\\1\\s*(?:#.*)?$`);
286
+ for (let i = Math.max(from, 0); i < fileLines.length; i++)
287
+ if (re.test(fileLines[i]))
288
+ return i + 1;
289
+ return from;
290
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"graph-cli.d.ts","sourceRoot":"","sources":["../../src/workspace/graph-cli.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"graph-cli.d.ts","sourceRoot":"","sources":["../../src/workspace/graph-cli.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAOH,OAAO,KAAK,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAClE,OAAO,EAA0B,KAAK,SAAS,EAAE,MAAM,eAAe,CAAC;AACvE,OAAO,EAA+E,KAAK,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAEnI,OAAO,EAA6E,KAAK,aAAa,EAAE,MAAM,eAAe,CAAC;AAC9H,OAAO,EAAgF,KAAK,UAAU,EAAE,KAAK,SAAS,EAAE,KAAK,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAOnK,OAAO,EAAoC,KAAK,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAExF,6DAA6D;AAC7D,eAAO,MAAM,sBAAsB,IAAI,CAAC;AAExC,2EAA2E;AAC3E,eAAO,MAAM,sBAAsB,4EAA4E,CAAC;AAEhH;;;GAGG;AACH,eAAO,MAAM,iBAAiB,2OAA0D,CAAC;AACzF,MAAM,MAAM,cAAc,GAAG,CAAC,OAAO,iBAAiB,CAAC,CAAC,MAAM,CAAC,CAAC;AAQhE,UAAU,IAAI;IACZ,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,MAAM,aAAa,GACrB,CAAC,IAAI,GAAG;IAAE,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,SAAS,EAAE,SAAS,CAAA;CAAE,GAAG,cAAc,CAAC,GACrE,CAAC,IAAI,GAAG;IAAE,KAAK,EAAE;QAAE,IAAI,EAAE,cAAc,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,aAAa,GAAG,IAAI,CAAA;KAAE,CAAA;CAAE,CAAC,CAAC;AAElG,MAAM,WAAW,UAAU;IACzB,mDAAmD;IACnD,GAAG,EAAE,MAAM,CAAC;IACZ,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,0BAA0B;IAC1B,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,8GAA8G;IAC9G,IAAI,CAAC,EAAE,OAAO,CAAC,UAAU,CAAC,CAAC;IAC3B,kFAAkF;IAClF,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB,uEAAuE;IACvE,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC;;;;OAIG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;;;OAIG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;;;;OAKG;IACH,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1G,kGAAkG;IAClG,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,WAAW;IAC1B,GAAG,EAAE,aAAa,CAAC;IACnB,gFAAgF;IAChF,MAAM,EAAE,OAAO,CAAC;IAChB,4FAA4F;IAC5F,UAAU,CAAC,EAAE,UAAU,EAAE,CAAC;CAC3B;AAED,sEAAsE;AACtE,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,OAAO,CAAC,UAAU,CAAC,GAAG,MAAM,EAAE,CAEtE;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,gBAAgB,EAAE,UAAU,EAAE,MAAM,EAAE,GAAG,MAAM,CAmCtF;AAgDD,wGAAwG;AACxG,wBAAgB,SAAS,CAAC,KAAK,EAAE,UAAU,GAAG,UAAU,CAKvD;AAED,qGAAqG;AACrG,wBAAsB,cAAc,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,WAAW,CAAC,CAwE5E;AA8CD,wBAAsB,iBAAiB,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CAsE5E"}
@@ -34,6 +34,7 @@ import { openGraphCache, splitCached, storeReads, withoutUnits } from "./graph-c
34
34
  import { readDeclaration, readerVersion, WORKSPACE_ERROR_CODES, WorkspaceReadError } from "./declaration.js";
35
35
  import { describePlan, emitDocument, executePlan, memberStatus, planJson, planMembers } from "./member-commands.js";
36
36
  import { loadKindRegistry } from "./kinds.js";
37
+ import { readKindLinks } from "./kind-link-readers.js";
37
38
  import { prepareKindReaders } from "./kind-readers.js";
38
39
  import { recordLinkRows } from "./record-assets.js";
39
40
  import { RecordReadError } from "./records.js";
@@ -198,7 +199,8 @@ export async function workspaceGraph(query) {
198
199
  member.stamp = split?.stamps.get(member.name) ?? null;
199
200
  }
200
201
  // Links (#2539) resolve against the declaration that was read, the revision's for --at, and the kinds installed now.
201
- const graph = composeWorkspaceGraph({ name: declaration.name, root: located.root }, inputs, { declaration, kinds });
202
+ // A kind's link reader (#3932) reads the member's files in the tree read, as check does.
203
+ const graph = composeWorkspaceGraph({ name: declaration.name, root: located.root }, inputs, { declaration, kinds, read: readKindLinks(declaration, located.tree, kinds) });
202
204
  // The box's factory and listing (#3146), from the declaration read, so --at gives them at that revision.
203
205
  for (const member of graph.members)
204
206
  member.box = graphBox(declaration.members.find((m) => m.name === member.name)?.box ?? null, located.tree, declaration.members);
@@ -93,6 +93,13 @@
93
93
  "$ref": "#/$defs/edge"
94
94
  }
95
95
  },
96
+ "dangling": {
97
+ "description": "Added in contract 1 by #3922. The members' references whose target is no node of their graph (#3910): a job `needs` a job no workflow declares, a `uses:` names another repository's workflow, a GitLab `include:` names a file chant did not generate. Each member's `ir.dangling` entry with `from` prefixed by its member and a `member` field, sorted by `from`, `path` and `value`. Absent when no member has one.",
98
+ "type": "array",
99
+ "items": {
100
+ "$ref": "#/$defs/dangling"
101
+ }
102
+ },
96
103
  "groups": {
97
104
  "type": "object",
98
105
  "required": [
@@ -158,6 +165,16 @@
158
165
  }
159
166
  },
160
167
  "description": "Keys prefixed with the member."
168
+ },
169
+ "byPipeline": {
170
+ "type": "object",
171
+ "additionalProperties": {
172
+ "type": "array",
173
+ "items": {
174
+ "type": "string"
175
+ }
176
+ },
177
+ "description": "Added in contract 1 by #3922. CI pipeline membership (#3910): a workflow or pipeline the member's CI lexicon reports, with the node ids it runs. Keys prefixed with the member, like byStack. Edges with `crossPipeline` run between these groups."
161
178
  }
162
179
  }
163
180
  },
@@ -181,6 +198,13 @@
181
198
  },
182
199
  "member": {
183
200
  "type": "string"
201
+ },
202
+ "previously": {
203
+ "type": "array",
204
+ "items": {
205
+ "type": "string"
206
+ },
207
+ "description": "Names this output had before, from a rename hint a lexicon read in the member's source (a sql `-- previously:` comment). A link to one of them is told the new name."
184
208
  }
185
209
  }
186
210
  }
@@ -516,7 +540,15 @@
516
540
  "type": "string"
517
541
  },
518
542
  "kind": {
519
- "type": "string"
543
+ "enum": [
544
+ "ref",
545
+ "needs",
546
+ "calls",
547
+ "triggers",
548
+ "includes",
549
+ "extends"
550
+ ],
551
+ "description": "`ref` for an edge from a typed reference. The CI kinds (#3910): `needs` (a job waits on another), `calls` (a reusable workflow or action is used), `triggers` (a pipeline starts another), `includes` (a file is pulled in), `extends` (a GitLab job inherits another). The list grows within contract 1; a reader ignores a kind it does not know."
520
552
  },
521
553
  "viaAttr": {
522
554
  "type": "string"
@@ -524,8 +556,50 @@
524
556
  "toAttr": {
525
557
  "type": "string"
526
558
  },
559
+ "crossPipeline": {
560
+ "type": "boolean",
561
+ "description": "Added in contract 1 by #3922. True when the edge leaves the pipeline `from` belongs to (#3910): a reusable-workflow call, a `workflow_run` trigger, a GitLab child or multi-project trigger, an include. Absent on every in-pipeline edge."
562
+ },
563
+ "member": {
564
+ "type": "string"
565
+ }
566
+ }
567
+ },
568
+ "dangling": {
569
+ "type": "object",
570
+ "required": [
571
+ "from",
572
+ "kind",
573
+ "path",
574
+ "value",
575
+ "member"
576
+ ],
577
+ "properties": {
578
+ "from": {
579
+ "type": "string",
580
+ "description": "The node holding the reference, prefixed with its member."
581
+ },
582
+ "kind": {
583
+ "$ref": "#/$defs/edge/properties/kind"
584
+ },
585
+ "path": {
586
+ "type": "string",
587
+ "description": "Where on `from` it was written, such as `needs`, `uses` or `trigger.include`."
588
+ },
589
+ "value": {
590
+ "type": "string",
591
+ "description": "The reference exactly as written."
592
+ },
593
+ "crossPipeline": {
594
+ "type": "boolean",
595
+ "description": "As on an edge."
596
+ },
527
597
  "member": {
528
598
  "type": "string"
599
+ },
600
+ "nested": {
601
+ "type": "string",
602
+ "description": "For a member of kind `workspace`: the nested workspace's member the reference was read through, as on a node."
529
603
  }
530
604
  }
531
605
  },
@@ -636,8 +710,23 @@
636
710
  "origin": {
637
711
  "enum": [
638
712
  "declared",
713
+ "kind-reader",
639
714
  "inferred:joinKey"
640
- ]
715
+ ],
716
+ "description": "How the row was found: declared in chant.workspace.json, read from the member's own files by its kind's link reader (#3932), or inferred by joinKey()."
717
+ },
718
+ "reader": {
719
+ "type": "string",
720
+ "description": "A kind-reader row (#3932): the link reader that found it, such as dbt-project."
721
+ },
722
+ "file": {
723
+ "type": "string",
724
+ "description": "A kind-reader row (#3932): the file that states the link, from the workspace root."
725
+ },
726
+ "line": {
727
+ "type": "integer",
728
+ "minimum": 1,
729
+ "description": "A kind-reader row (#3932): the line of file that states the link."
641
730
  },
642
731
  "resolves": {
643
732
  "const": "source"
@@ -728,8 +817,23 @@
728
817
  "origin": {
729
818
  "enum": [
730
819
  "declared",
820
+ "kind-reader",
731
821
  "inferred:joinKey"
732
- ]
822
+ ],
823
+ "description": "How the row was found: declared in chant.workspace.json, read from the member's own files by its kind's link reader (#3932), or inferred by joinKey()."
824
+ },
825
+ "reader": {
826
+ "type": "string",
827
+ "description": "A kind-reader row (#3932): the link reader that found it, such as dbt-project."
828
+ },
829
+ "file": {
830
+ "type": "string",
831
+ "description": "A kind-reader row (#3932): the file that states the link, from the workspace root."
832
+ },
833
+ "line": {
834
+ "type": "integer",
835
+ "minimum": 1,
836
+ "description": "A kind-reader row (#3932): the line of file that states the link."
733
837
  },
734
838
  "resolves": {
735
839
  "const": "source"
@@ -746,7 +850,8 @@
746
850
  "description": "The consumer's parameter node, when known."
747
851
  },
748
852
  "input": {
749
- "type": "string"
853
+ "type": "string",
854
+ "description": "The consumer's parameter the join would feed; for a kind-reader row (#3932), the output its file reads, which no single member was found to produce."
750
855
  },
751
856
  "status": {
752
857
  "const": "ambiguous"
@@ -75,7 +75,7 @@ import { followSquashes, squashLineOrigins } from "./squash.js";
75
75
  import { memberDirOnDisk, memberSymbolResolvers } from "./lexicon-symbols.js";
76
76
  import { resolveSymbol, symbolResolverFor } from "./symbols.js";
77
77
  import { readTrailerJoins } from "./trailer-joins.js";
78
- import { joinPath, skippedDir } from "./tree.js";
78
+ import { joinPath, skippedDir, workingTree } from "./tree.js";
79
79
  import { activeAttestors } from "./trust/attestor.js";
80
80
  import { commitProvenance, policyAtBase, resolveBase } from "./trust/provenance.js";
81
81
  import { locateWorkspace } from "./which-chant.js";
@@ -1295,7 +1295,12 @@ async function walk(query, head) {
1295
1295
  try {
1296
1296
  const groups = resolveGroups(declaration, located.tree);
1297
1297
  const kindsRegistry = loadKindRegistry(declaration.pins, located.rootOnDisk).registry;
1298
- rows = resolveLinks(declaration, sourceMemberHandles(declaration, located.tree, groups, kindsRegistry)).filter((r) => r.origin === "declared");
1298
+ const { gatherLexiconOutputs } = await import("./lexicon-outputs.js");
1299
+ const lexiconOutputs = await gatherLexiconOutputs(located.rootOnDisk, declaration, located.at ? workingTree(located.rootOnDisk) : located.tree, groups, kindsRegistry, {
1300
+ atRevision: located.at !== null,
1301
+ ...(query.loadLexicon ? { load: query.loadLexicon } : {}),
1302
+ });
1303
+ rows = resolveLinks(declaration, sourceMemberHandles(declaration, located.tree, groups, kindsRegistry, lexiconOutputs)).filter((r) => r.origin === "declared");
1299
1304
  }
1300
1305
  catch (err) {
1301
1306
  if (!(err instanceof WorkspaceReadError))
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Links a kind reads from its member's own files (#3932).
3
+ *
4
+ * A member's links usually come from its entry in `chant.workspace.json`, or
5
+ * are inferred from a chant member's parameters by `joinKey()`. Some members
6
+ * already say what they read in files of their own: a dbt project names the
7
+ * tables it reads in its `sources:` YAML. A kind can name a link reader in its
8
+ * kinds file (`reader`), and chant runs that reader over the member's
9
+ * directory to derive the member's links.
10
+ *
11
+ * The readers are chant's code, listed in {@link KIND_LINK_READERS}: a kinds
12
+ * file stays data, and chant never imports the package that supplies it. A
13
+ * reader runs offline and parses files as data. It never invokes the tool
14
+ * whose files it reads, so `chant workspace check` keeps its promise of no
15
+ * network and no member code.
16
+ *
17
+ * A reader returns links with the file and line that state them, and gaps:
18
+ * the places it could not read without guessing, such as a templated value.
19
+ * `resolveLinks` (`./links.ts`) resolves the links beside the declared ones,
20
+ * with origin `kind-reader`; WSP093 and WSP094 name the reader's file and line,
21
+ * and WSP099 reports the gaps.
22
+ */
23
+ import type { Declaration, Member } from "./declaration.js";
24
+ import { type FieldValue, type KindLinkReaderName, type KindRegistry } from "./kinds.js";
25
+ import type { WorkspaceTree } from "./tree.js";
26
+ /** One link a reader found in the member's files. */
27
+ export interface KindReadLink {
28
+ /**
29
+ * The producer the link names, or null when the member's files don't say
30
+ * which member produces it. A null producer is found among the members
31
+ * whose outputs include {@link anchor}; see `resolveLinks`.
32
+ */
33
+ member: string | null;
34
+ /** The producer's output the link reads. */
35
+ output: string;
36
+ /**
37
+ * The output that picks the producer when `member` is null; unset, the
38
+ * output itself. A dbt source column is anchored on its table, so a dropped
39
+ * column is missing from the member that still has the table.
40
+ */
41
+ anchor?: string;
42
+ /** The file that states the link, relative to the workspace root. */
43
+ file: string;
44
+ /** Its line, from 1. */
45
+ line: number;
46
+ }
47
+ /** A place the reader could not read without guessing. The links it would have stated are left out. */
48
+ export interface KindReadGap {
49
+ /** Relative to the workspace root. */
50
+ file: string;
51
+ line: number;
52
+ message: string;
53
+ }
54
+ export interface KindLinkRead {
55
+ /** The reader that ran. */
56
+ reader: KindLinkReaderName;
57
+ links: KindReadLink[];
58
+ gaps: KindReadGap[];
59
+ /**
60
+ * The member field that names the producer, for messages that ask the
61
+ * author to set it when the files don't say (`sqlMember` for dbt).
62
+ */
63
+ producerField?: string;
64
+ }
65
+ /** What a reader gets: the member, its directory and resolved fields, and the tree read. */
66
+ export interface KindReaderInput {
67
+ member: Member;
68
+ /** The member's directory, tree-relative; "" for the workspace root. */
69
+ dir: string;
70
+ /** The member's fields, defaults filled in (#3151); null when its kind declares none. */
71
+ fields: Record<string, FieldValue | null | Record<string, FieldValue | null>> | null;
72
+ tree: WorkspaceTree;
73
+ }
74
+ export type KindLinkReader = (input: KindReaderInput) => KindLinkRead;
75
+ /** Every reader chant ships, by the name a kinds file uses. */
76
+ export declare const LINK_READERS: Record<KindLinkReaderName, KindLinkReader>;
77
+ /**
78
+ * Run the link reader of every member whose kind has one. A member whose
79
+ * directory is missing is left out. A reader that throws is reported as a
80
+ * gap on the member's directory, so one unreadable project never stops
81
+ * the check.
82
+ */
83
+ export declare function readKindLinks(declaration: Declaration, tree: WorkspaceTree, kinds: KindRegistry): Map<string, KindLinkRead>;
84
+ //# sourceMappingURL=kind-link-readers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"kind-link-readers.d.ts","sourceRoot":"","sources":["../../src/workspace/kind-link-readers.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AACzD,OAAO,EAAuB,KAAK,UAAU,EAAE,KAAK,kBAAkB,EAAE,KAAK,YAAY,EAAE,MAAM,SAAS,CAAC;AAC3G,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,QAAQ,CAAC;AAG5C,qDAAqD;AACrD,MAAM,WAAW,YAAY;IAC3B;;;;OAIG;IACH,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,4CAA4C;IAC5C,MAAM,EAAE,MAAM,CAAC;IACf;;;;OAIG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,qEAAqE;IACrE,IAAI,EAAE,MAAM,CAAC;IACb,wBAAwB;IACxB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,uGAAuG;AACvG,MAAM,WAAW,WAAW;IAC1B,sCAAsC;IACtC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,YAAY;IAC3B,2BAA2B;IAC3B,MAAM,EAAE,kBAAkB,CAAC;IAC3B,KAAK,EAAE,YAAY,EAAE,CAAC;IACtB,IAAI,EAAE,WAAW,EAAE,CAAC;IACpB;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,4FAA4F;AAC5F,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,wEAAwE;IACxE,GAAG,EAAE,MAAM,CAAC;IACZ,yFAAyF;IACzF,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,GAAG,IAAI,GAAG,MAAM,CAAC,MAAM,EAAE,UAAU,GAAG,IAAI,CAAC,CAAC,GAAG,IAAI,CAAC;IACrF,IAAI,EAAE,aAAa,CAAC;CACrB;AAED,MAAM,MAAM,cAAc,GAAG,CAAC,KAAK,EAAE,eAAe,KAAK,YAAY,CAAC;AAEtE,+DAA+D;AAC/D,eAAO,MAAM,YAAY,EAAE,MAAM,CAAC,kBAAkB,EAAE,cAAc,CAEnE,CAAC;AAEF;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,WAAW,EAAE,WAAW,EAAE,IAAI,EAAE,aAAa,EAAE,KAAK,EAAE,YAAY,GAAG,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,CAe3H"}
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Links a kind reads from its member's own files (#3932).
3
+ *
4
+ * A member's links usually come from its entry in `chant.workspace.json`, or
5
+ * are inferred from a chant member's parameters by `joinKey()`. Some members
6
+ * already say what they read in files of their own: a dbt project names the
7
+ * tables it reads in its `sources:` YAML. A kind can name a link reader in its
8
+ * kinds file (`reader`), and chant runs that reader over the member's
9
+ * directory to derive the member's links.
10
+ *
11
+ * The readers are chant's code, listed in {@link KIND_LINK_READERS}: a kinds
12
+ * file stays data, and chant never imports the package that supplies it. A
13
+ * reader runs offline and parses files as data. It never invokes the tool
14
+ * whose files it reads, so `chant workspace check` keeps its promise of no
15
+ * network and no member code.
16
+ *
17
+ * A reader returns links with the file and line that state them, and gaps:
18
+ * the places it could not read without guessing, such as a templated value.
19
+ * `resolveLinks` (`./links.ts`) resolves the links beside the declared ones,
20
+ * with origin `kind-reader`; WSP093 and WSP094 name the reader's file and line,
21
+ * and WSP099 reports the gaps.
22
+ */
23
+ import { resolveMemberFields } from "./kinds.js";
24
+ import { readDbtProject } from "./dbt-project-reader.js";
25
+ /** Every reader chant ships, by the name a kinds file uses. */
26
+ export const LINK_READERS = {
27
+ "dbt-project": readDbtProject,
28
+ };
29
+ /**
30
+ * Run the link reader of every member whose kind has one. A member whose
31
+ * directory is missing is left out. A reader that throws is reported as a
32
+ * gap on the member's directory, so one unreadable project never stops
33
+ * the check.
34
+ */
35
+ export function readKindLinks(declaration, tree, kinds) {
36
+ const out = new Map();
37
+ for (const m of declaration.members) {
38
+ const kind = kinds.get(m.kind);
39
+ if (!kind?.reader)
40
+ continue;
41
+ const dir = m.dir === "." ? "" : m.dir;
42
+ if (tree.stat(dir) !== "dir")
43
+ continue;
44
+ const { fields } = resolveMemberFields(kind, m.fields);
45
+ try {
46
+ out.set(m.name, LINK_READERS[kind.reader]({ member: m, dir, fields, tree }));
47
+ }
48
+ catch (err) {
49
+ out.set(m.name, { reader: kind.reader, links: [], gaps: [{ file: dir || ".", line: 1, message: `the ${kind.reader} reader failed: ${err.message}` }] });
50
+ }
51
+ }
52
+ return out;
53
+ }