patram 0.1.1 → 0.2.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 (35) hide show
  1. package/lib/build-graph-identity.js +39 -7
  2. package/lib/build-graph.js +14 -1
  3. package/lib/cli-help-metadata.js +552 -0
  4. package/lib/derived-summary.js +278 -0
  5. package/lib/format-derived-summary-row.js +9 -0
  6. package/lib/format-node-header.js +19 -0
  7. package/lib/format-output-item-block.js +22 -0
  8. package/lib/format-output-metadata.js +62 -0
  9. package/lib/layout-stored-queries.js +150 -2
  10. package/lib/load-patram-config.js +401 -2
  11. package/lib/load-patram-config.types.ts +31 -0
  12. package/lib/output-view.types.ts +15 -0
  13. package/lib/parse-cli-arguments-helpers.js +263 -90
  14. package/lib/parse-cli-arguments.js +160 -8
  15. package/lib/parse-cli-arguments.types.ts +48 -3
  16. package/lib/parse-where-clause.js +604 -209
  17. package/lib/parse-where-clause.types.ts +70 -0
  18. package/lib/patram-cli.js +144 -17
  19. package/lib/patram.js +6 -0
  20. package/lib/query-graph.js +231 -119
  21. package/lib/query-inspection.js +523 -0
  22. package/lib/render-check-output.js +1 -1
  23. package/lib/render-cli-help.js +419 -0
  24. package/lib/render-json-output.js +57 -4
  25. package/lib/render-output-view.js +37 -8
  26. package/lib/render-plain-output.js +31 -86
  27. package/lib/render-rich-output.js +34 -87
  28. package/lib/resolve-where-clause.js +18 -3
  29. package/lib/tagged-fenced-block-error.js +17 -0
  30. package/lib/tagged-fenced-block-markdown.js +111 -0
  31. package/lib/tagged-fenced-block-metadata.js +97 -0
  32. package/lib/tagged-fenced-block-parser.js +292 -0
  33. package/lib/tagged-fenced-blocks.js +100 -0
  34. package/lib/tagged-fenced-blocks.types.ts +38 -0
  35. package/package.json +8 -3
@@ -0,0 +1,70 @@
1
+ import type { PatramDiagnostic } from './load-patram-config.types.ts';
2
+
3
+ export type ParsedFieldName = 'id' | 'kind' | 'path' | 'status' | 'title';
4
+
5
+ export interface ParsedFieldTerm {
6
+ field_name: ParsedFieldName;
7
+ kind: 'field';
8
+ operator: '=' | '^=' | '~';
9
+ value: string;
10
+ }
11
+
12
+ export interface ParsedFieldSetTerm {
13
+ field_name: ParsedFieldName;
14
+ kind: 'field_set';
15
+ operator: 'in' | 'not in';
16
+ values: string[];
17
+ }
18
+
19
+ export interface ParsedTraversalTerm {
20
+ column: number;
21
+ direction: 'in' | 'out';
22
+ relation_name: string;
23
+ }
24
+
25
+ export interface ParsedRelationTerm {
26
+ column: number;
27
+ kind: 'relation';
28
+ relation_name: string;
29
+ }
30
+
31
+ export interface ParsedRelationTargetTerm {
32
+ column: number;
33
+ kind: 'relation_target';
34
+ relation_name: string;
35
+ target_id: string;
36
+ }
37
+
38
+ export type ParsedAggregateComparison = '!=' | '<' | '<=' | '=' | '>' | '>=';
39
+ export type ParsedAggregateName = 'any' | 'count' | 'none';
40
+
41
+ export interface ParsedAggregateTerm {
42
+ aggregate_name: ParsedAggregateName;
43
+ clauses: ParsedClause[];
44
+ comparison?: ParsedAggregateComparison;
45
+ kind: 'aggregate';
46
+ traversal: ParsedTraversalTerm;
47
+ value?: number;
48
+ }
49
+
50
+ export type ParsedTerm =
51
+ | ParsedAggregateTerm
52
+ | ParsedFieldSetTerm
53
+ | ParsedFieldTerm
54
+ | ParsedRelationTargetTerm
55
+ | ParsedRelationTerm;
56
+
57
+ export interface ParsedClause {
58
+ is_negated: boolean;
59
+ term: ParsedTerm;
60
+ }
61
+
62
+ export type ParseWhereClauseResult =
63
+ | {
64
+ clauses: ParsedClause[];
65
+ success: true;
66
+ }
67
+ | {
68
+ diagnostic: PatramDiagnostic;
69
+ success: false;
70
+ };
package/lib/patram-cli.js CHANGED
@@ -1,6 +1,6 @@
1
1
  /* eslint-disable max-lines */
2
2
  /**
3
- * @import { ParsedCliArguments } from './parse-cli-arguments.types.ts';
3
+ * @import { ParsedCliCommandRequest } from './parse-cli-arguments.types.ts';
4
4
  */
5
5
 
6
6
  import process from 'node:process';
@@ -16,10 +16,17 @@ import { loadPatramConfig } from './load-patram-config.js';
16
16
  import { loadProjectGraph } from './load-project-graph.js';
17
17
  import { parseCliArguments } from './parse-cli-arguments.js';
18
18
  import { DEFAULT_QUERY_LIMIT, queryGraph } from './query-graph.js';
19
+ import { inspectQuery, renderQueryInspection } from './query-inspection.js';
20
+ import { createDerivedSummaryEvaluator } from './derived-summary.js';
19
21
  import {
20
22
  renderCheckDiagnostics,
21
23
  renderCheckSuccess,
22
24
  } from './render-check-output.js';
25
+ import {
26
+ renderCliParseError,
27
+ renderHelpRequest,
28
+ renderInvalidWhereDiagnostic,
29
+ } from './render-cli-help.js';
23
30
  import {
24
31
  resolveCheckTarget,
25
32
  selectCheckTargetDiagnostics,
@@ -64,12 +71,20 @@ export async function main(cli_arguments, io_context) {
64
71
  const parsed_arguments = parseCliArguments(cli_arguments);
65
72
 
66
73
  if (!parsed_arguments.success) {
67
- io_context.stderr.write(`${parsed_arguments.message}\n`);
74
+ io_context.stderr.write(renderCliParseError(parsed_arguments.error));
68
75
 
69
76
  return 1;
70
77
  }
71
78
 
72
- const parsed_command = parsed_arguments.value;
79
+ if (parsed_arguments.value.kind === 'help') {
80
+ io_context.stdout.write(renderHelpRequest(parsed_arguments.value));
81
+
82
+ return 0;
83
+ }
84
+
85
+ const parsed_command = /** @type {ParsedCliCommandRequest} */ (
86
+ parsed_arguments.value
87
+ );
73
88
 
74
89
  if (parsed_command.command_name === 'check') {
75
90
  return runCheckCommand(parsed_command, io_context);
@@ -93,7 +108,7 @@ export async function main(cli_arguments, io_context) {
93
108
  }
94
109
 
95
110
  /**
96
- * @param {ParsedCliArguments} parsed_command
111
+ * @param {ParsedCliCommandRequest} parsed_command
97
112
  * @param {{ stderr: { write(chunk: string): boolean }, stdout: { isTTY?: boolean, write(chunk: string): boolean }, write_paged_output?: (output_text: string) => Promise<void> }} io_context
98
113
  * @returns {Promise<number>}
99
114
  */
@@ -147,12 +162,27 @@ async function runCheckCommand(parsed_command, io_context) {
147
162
  }
148
163
 
149
164
  /**
150
- * @param {ParsedCliArguments} parsed_command
165
+ * @param {ParsedCliCommandRequest} parsed_command
151
166
  * @param {{ stderr: { write(chunk: string): boolean }, stdout: { isTTY?: boolean, write(chunk: string): boolean }, write_paged_output?: (output_text: string) => Promise<void> }} io_context
152
167
  * @returns {Promise<number>}
153
168
  */
154
169
  async function runQueryCommand(parsed_command, io_context) {
155
170
  const use_pager = shouldPageCommandOutput(parsed_command, io_context.stdout);
171
+ const output_mode = resolveOutputMode(parsed_command, {
172
+ is_tty: io_context.stdout.isTTY === true,
173
+ no_color: process.env.NO_COLOR !== undefined,
174
+ term: process.env.TERM,
175
+ });
176
+
177
+ if (parsed_command.query_inspection_mode) {
178
+ return runQueryInspectionCommand(
179
+ parsed_command,
180
+ io_context,
181
+ output_mode,
182
+ use_pager,
183
+ );
184
+ }
185
+
156
186
  const project_graph_result = await loadProjectGraph(process.cwd());
157
187
 
158
188
  if (project_graph_result.diagnostics.length > 0) {
@@ -174,31 +204,103 @@ async function runQueryCommand(parsed_command, io_context) {
174
204
 
175
205
  const query_result = queryGraph(
176
206
  project_graph_result.graph,
177
- where_clause.value,
207
+ where_clause.value.where_clause,
178
208
  createQueryPaginationOptions(parsed_command, use_pager),
179
209
  );
180
210
 
181
211
  if (query_result.diagnostics.length > 0) {
182
- writeDiagnostics(io_context.stderr, query_result.diagnostics);
212
+ io_context.stderr.write(
213
+ renderInvalidWhereDiagnostic(query_result.diagnostics[0]),
214
+ );
183
215
 
184
216
  return 1;
185
217
  }
186
218
 
219
+ const derived_summary_evaluator = createDerivedSummaryEvaluator(
220
+ project_graph_result.config,
221
+ project_graph_result.graph,
222
+ );
223
+
187
224
  await writeCommandOutput(
188
225
  io_context,
189
226
  parsed_command,
190
- createOutputView(
191
- 'query',
192
- query_result.nodes,
193
- createQueryOutputOptions(parsed_command, query_result, use_pager),
194
- ),
227
+ createOutputView('query', query_result.nodes, {
228
+ derived_summary_evaluator,
229
+ ...createQueryOutputOptions(parsed_command, query_result, use_pager),
230
+ }),
195
231
  );
196
232
 
197
233
  return 0;
198
234
  }
199
235
 
200
236
  /**
201
- * @param {ParsedCliArguments} parsed_command
237
+ * @param {ParsedCliCommandRequest} parsed_command
238
+ * @param {{ stderr: { write(chunk: string): boolean }, stdout: { isTTY?: boolean, write(chunk: string): boolean } }} io_context
239
+ * @param {import('./output-view.types.ts').ResolvedOutputMode} output_mode
240
+ * @param {boolean} use_pager
241
+ * @returns {Promise<number>}
242
+ */
243
+ async function runQueryInspectionCommand(
244
+ parsed_command,
245
+ io_context,
246
+ output_mode,
247
+ use_pager,
248
+ ) {
249
+ const query_inspection_mode = parsed_command.query_inspection_mode;
250
+
251
+ if (!query_inspection_mode) {
252
+ throw new Error('Expected a query inspection mode.');
253
+ }
254
+
255
+ const load_result = await loadPatramConfig(process.cwd());
256
+
257
+ if (load_result.diagnostics.length > 0) {
258
+ io_context.stderr.write(
259
+ renderCheckDiagnostics(load_result.diagnostics, output_mode),
260
+ );
261
+
262
+ return 1;
263
+ }
264
+
265
+ const repo_config = load_result.config;
266
+
267
+ if (!repo_config) {
268
+ throw new Error('Expected a valid Patram repo config.');
269
+ }
270
+
271
+ const where_clause = resolveWhereClause(
272
+ repo_config,
273
+ parsed_command.command_arguments,
274
+ );
275
+
276
+ if (!where_clause.success) {
277
+ io_context.stderr.write(`${where_clause.message}\n`);
278
+
279
+ return 1;
280
+ }
281
+
282
+ const query_inspection = inspectQuery(repo_config, where_clause.value, {
283
+ inspection_mode: query_inspection_mode,
284
+ ...createQueryExecutionOptions(parsed_command, use_pager),
285
+ });
286
+
287
+ if (!query_inspection.success) {
288
+ io_context.stderr.write(
289
+ renderCheckDiagnostics(query_inspection.diagnostics, output_mode),
290
+ );
291
+
292
+ return 1;
293
+ }
294
+
295
+ io_context.stdout.write(
296
+ renderQueryInspection(query_inspection.value, output_mode),
297
+ );
298
+
299
+ return 0;
300
+ }
301
+
302
+ /**
303
+ * @param {ParsedCliCommandRequest} parsed_command
202
304
  * @param {{ stderr: { write(chunk: string): boolean }, stdout: { isTTY?: boolean, write(chunk: string): boolean }, write_paged_output?: (output_text: string) => Promise<void> }} io_context
203
305
  * @returns {Promise<number>}
204
306
  */
@@ -227,7 +329,7 @@ async function runQueriesCommand(parsed_command, io_context) {
227
329
  }
228
330
 
229
331
  /**
230
- * @param {ParsedCliArguments} parsed_command
332
+ * @param {ParsedCliCommandRequest} parsed_command
231
333
  * @param {{ stderr: { write(chunk: string): boolean }, stdout: { isTTY?: boolean, write(chunk: string): boolean }, write_paged_output?: (output_text: string) => Promise<void> }} io_context
232
334
  * @returns {Promise<number>}
233
335
  */
@@ -252,10 +354,18 @@ async function runShowCommand(parsed_command, io_context) {
252
354
  return 1;
253
355
  }
254
356
 
357
+ const derived_summary_evaluator = createDerivedSummaryEvaluator(
358
+ project_graph_result.config,
359
+ project_graph_result.graph,
360
+ );
361
+
255
362
  await writeCommandOutput(
256
363
  io_context,
257
364
  parsed_command,
258
- createShowOutputView(show_output.value),
365
+ createShowOutputView(show_output.value, {
366
+ derived_summary_evaluator,
367
+ graph_nodes: project_graph_result.graph.nodes,
368
+ }),
259
369
  );
260
370
 
261
371
  return 0;
@@ -272,7 +382,7 @@ function writeDiagnostics(output_stream, diagnostics) {
272
382
  }
273
383
 
274
384
  /**
275
- * @param {ParsedCliArguments} parsed_command
385
+ * @param {ParsedCliCommandRequest} parsed_command
276
386
  * @param {{ total_count: number, nodes: import('./build-graph.types.ts').GraphNode[] }} query_result
277
387
  * @param {boolean} use_pager
278
388
  * @returns {{ hints: string[], limit: number, offset: number, total_count: number }}
@@ -309,7 +419,7 @@ function createQueryOutputOptions(parsed_command, query_result, use_pager) {
309
419
  }
310
420
 
311
421
  /**
312
- * @param {ParsedCliArguments} parsed_command
422
+ * @param {ParsedCliCommandRequest} parsed_command
313
423
  * @param {boolean} use_pager
314
424
  * @returns {{ limit?: number, offset: number }}
315
425
  */
@@ -328,6 +438,23 @@ function createQueryPaginationOptions(parsed_command, use_pager) {
328
438
  return pagination_options;
329
439
  }
330
440
 
441
+ /**
442
+ * @param {ParsedCliCommandRequest} parsed_command
443
+ * @param {boolean} use_pager
444
+ * @returns {{ limit: number | null, offset: number }}
445
+ */
446
+ function createQueryExecutionOptions(parsed_command, use_pager) {
447
+ const pagination_options = createQueryPaginationOptions(
448
+ parsed_command,
449
+ use_pager,
450
+ );
451
+
452
+ return {
453
+ limit: pagination_options.limit ?? null,
454
+ offset: pagination_options.offset,
455
+ };
456
+ }
457
+
331
458
  /**
332
459
  * @param {import('./load-patram-config.types.ts').PatramDiagnostic} diagnostic
333
460
  * @returns {string}
package/lib/patram.js ADDED
@@ -0,0 +1,6 @@
1
+ export {
2
+ extractTaggedFencedBlocks,
3
+ loadTaggedFencedBlocks,
4
+ selectTaggedBlock,
5
+ selectTaggedBlocks,
6
+ } from './tagged-fenced-blocks.js';