patram 0.0.2 → 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.
- package/bin/patram.js +25 -147
- package/lib/build-graph-identity.js +270 -0
- package/lib/build-graph.js +156 -77
- package/lib/check-graph.js +23 -7
- package/lib/claim-helpers.js +55 -0
- package/lib/cli-help-metadata.js +552 -0
- package/lib/command-output.js +83 -0
- package/lib/derived-summary.js +278 -0
- package/lib/format-derived-summary-row.js +9 -0
- package/lib/format-node-header.js +19 -0
- package/lib/format-output-item-block.js +22 -0
- package/lib/format-output-metadata.js +62 -0
- package/lib/layout-stored-queries.js +361 -0
- package/lib/list-queries.js +18 -0
- package/lib/list-source-files.js +50 -15
- package/lib/load-patram-config.js +505 -18
- package/lib/load-patram-config.types.ts +40 -0
- package/lib/load-project-graph.js +124 -0
- package/lib/output-view.types.ts +88 -0
- package/lib/parse-claims.js +38 -158
- package/lib/parse-claims.types.ts +7 -0
- package/lib/parse-cli-arguments-helpers.js +446 -0
- package/lib/parse-cli-arguments.js +266 -0
- package/lib/parse-cli-arguments.types.ts +69 -0
- package/lib/parse-cli-color-options.js +44 -0
- package/lib/parse-cli-query-pagination.js +49 -0
- package/lib/parse-jsdoc-blocks.js +184 -0
- package/lib/parse-jsdoc-claims.js +280 -0
- package/lib/parse-jsdoc-prose.js +111 -0
- package/lib/parse-markdown-claims.js +242 -0
- package/lib/parse-markdown-directives.js +136 -0
- package/lib/parse-where-clause.js +707 -0
- package/lib/parse-where-clause.types.ts +70 -0
- package/lib/patram-cli.js +464 -0
- package/lib/patram-config.js +3 -1
- package/lib/patram-config.types.ts +2 -1
- package/lib/patram.js +6 -0
- package/lib/query-graph.js +368 -0
- package/lib/query-inspection.js +523 -0
- package/lib/render-check-output.js +315 -0
- package/lib/render-cli-help.js +419 -0
- package/lib/render-json-output.js +161 -0
- package/lib/render-output-view.js +222 -0
- package/lib/render-plain-output.js +182 -0
- package/lib/render-rich-output.js +240 -0
- package/lib/render-rich-source.js +1333 -0
- package/lib/resolve-check-target.js +190 -0
- package/lib/resolve-output-mode.js +60 -0
- package/lib/resolve-patram-graph-config.js +88 -0
- package/lib/resolve-where-clause.js +66 -0
- package/lib/show-document.js +311 -0
- package/lib/source-file-defaults.js +28 -0
- package/lib/tagged-fenced-block-error.js +17 -0
- package/lib/tagged-fenced-block-markdown.js +111 -0
- package/lib/tagged-fenced-block-metadata.js +97 -0
- package/lib/tagged-fenced-block-parser.js +292 -0
- package/lib/tagged-fenced-blocks.js +100 -0
- package/lib/tagged-fenced-blocks.types.ts +38 -0
- package/lib/write-paged-output.js +87 -0
- package/package.json +28 -12
- package/bin/patram.test.js +0 -184
- package/lib/build-graph.test.js +0 -141
- package/lib/check-graph.test.js +0 -103
- package/lib/list-source-files.test.js +0 -101
- package/lib/load-patram-config.test.js +0 -211
- package/lib/parse-claims.test.js +0 -113
- package/lib/patram-config.test.js +0 -147
|
@@ -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
|
+
};
|
|
@@ -0,0 +1,464 @@
|
|
|
1
|
+
/* eslint-disable max-lines */
|
|
2
|
+
/**
|
|
3
|
+
* @import { ParsedCliCommandRequest } from './parse-cli-arguments.types.ts';
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import process from 'node:process';
|
|
7
|
+
|
|
8
|
+
import { checkGraph } from './check-graph.js';
|
|
9
|
+
import {
|
|
10
|
+
shouldPageCommandOutput,
|
|
11
|
+
writeCommandOutput,
|
|
12
|
+
} from './command-output.js';
|
|
13
|
+
import { listRepoFiles } from './list-source-files.js';
|
|
14
|
+
import { listQueries } from './list-queries.js';
|
|
15
|
+
import { loadPatramConfig } from './load-patram-config.js';
|
|
16
|
+
import { loadProjectGraph } from './load-project-graph.js';
|
|
17
|
+
import { parseCliArguments } from './parse-cli-arguments.js';
|
|
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';
|
|
21
|
+
import {
|
|
22
|
+
renderCheckDiagnostics,
|
|
23
|
+
renderCheckSuccess,
|
|
24
|
+
} from './render-check-output.js';
|
|
25
|
+
import {
|
|
26
|
+
renderCliParseError,
|
|
27
|
+
renderHelpRequest,
|
|
28
|
+
renderInvalidWhereDiagnostic,
|
|
29
|
+
} from './render-cli-help.js';
|
|
30
|
+
import {
|
|
31
|
+
resolveCheckTarget,
|
|
32
|
+
selectCheckTargetDiagnostics,
|
|
33
|
+
selectCheckTargetSourceFiles,
|
|
34
|
+
} from './resolve-check-target.js';
|
|
35
|
+
import {
|
|
36
|
+
createOutputView,
|
|
37
|
+
createShowOutputView,
|
|
38
|
+
} from './render-output-view.js';
|
|
39
|
+
import { resolveWhereClause } from './resolve-where-clause.js';
|
|
40
|
+
import { resolveOutputMode } from './resolve-output-mode.js';
|
|
41
|
+
import { loadShowOutput } from './show-document.js';
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Patram command execution flow.
|
|
45
|
+
*
|
|
46
|
+
* Loads repo state and routes `check`, `query`, `queries`, and `show` through
|
|
47
|
+
* the shared output pipeline.
|
|
48
|
+
*
|
|
49
|
+
* Kind: cli
|
|
50
|
+
* Status: active
|
|
51
|
+
* Implements Command: ../docs/reference/commands/check.md
|
|
52
|
+
* Implements Command: ../docs/reference/commands/query.md
|
|
53
|
+
* Implements Command: ../docs/reference/commands/queries.md
|
|
54
|
+
* Implements Command: ../docs/reference/commands/show.md
|
|
55
|
+
* Tracked in: ../docs/plans/v0/source-anchor-dogfooding.md
|
|
56
|
+
* Decided by: ../docs/decisions/cli-output-architecture.md
|
|
57
|
+
* Decided by: ../docs/decisions/cli-argument-parser.md
|
|
58
|
+
* @patram
|
|
59
|
+
* @see {@link ./parse-cli-arguments.js}
|
|
60
|
+
* @see {@link ./render-output-view.js}
|
|
61
|
+
*/
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Run the Patram CLI.
|
|
65
|
+
*
|
|
66
|
+
* @param {string[]} cli_arguments
|
|
67
|
+
* @param {{ stderr: { write(chunk: string): boolean }, stdout: { isTTY?: boolean, write(chunk: string): boolean }, write_paged_output?: (output_text: string) => Promise<void> }} io_context
|
|
68
|
+
* @returns {Promise<number>}
|
|
69
|
+
*/
|
|
70
|
+
export async function main(cli_arguments, io_context) {
|
|
71
|
+
const parsed_arguments = parseCliArguments(cli_arguments);
|
|
72
|
+
|
|
73
|
+
if (!parsed_arguments.success) {
|
|
74
|
+
io_context.stderr.write(renderCliParseError(parsed_arguments.error));
|
|
75
|
+
|
|
76
|
+
return 1;
|
|
77
|
+
}
|
|
78
|
+
|
|
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
|
+
);
|
|
88
|
+
|
|
89
|
+
if (parsed_command.command_name === 'check') {
|
|
90
|
+
return runCheckCommand(parsed_command, io_context);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (parsed_command.command_name === 'query') {
|
|
94
|
+
return runQueryCommand(parsed_command, io_context);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (parsed_command.command_name === 'queries') {
|
|
98
|
+
return runQueriesCommand(parsed_command, io_context);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (parsed_command.command_name === 'show') {
|
|
102
|
+
return runShowCommand(parsed_command, io_context);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
io_context.stderr.write('Unknown command.\n');
|
|
106
|
+
|
|
107
|
+
return 1;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* @param {ParsedCliCommandRequest} parsed_command
|
|
112
|
+
* @param {{ stderr: { write(chunk: string): boolean }, stdout: { isTTY?: boolean, write(chunk: string): boolean }, write_paged_output?: (output_text: string) => Promise<void> }} io_context
|
|
113
|
+
* @returns {Promise<number>}
|
|
114
|
+
*/
|
|
115
|
+
async function runCheckCommand(parsed_command, io_context) {
|
|
116
|
+
const output_mode = resolveOutputMode(parsed_command, {
|
|
117
|
+
is_tty: io_context.stdout.isTTY === true,
|
|
118
|
+
no_color: process.env.NO_COLOR !== undefined,
|
|
119
|
+
term: process.env.TERM,
|
|
120
|
+
});
|
|
121
|
+
const resolved_target = await resolveCheckTarget(
|
|
122
|
+
parsed_command.command_arguments[0],
|
|
123
|
+
);
|
|
124
|
+
const project_graph_result = await loadProjectGraph(
|
|
125
|
+
resolved_target.project_directory,
|
|
126
|
+
);
|
|
127
|
+
const repo_file_paths = await listRepoFiles(
|
|
128
|
+
resolved_target.project_directory,
|
|
129
|
+
);
|
|
130
|
+
const selected_source_file_paths = selectCheckTargetSourceFiles(
|
|
131
|
+
project_graph_result.source_file_paths,
|
|
132
|
+
resolved_target,
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
if (project_graph_result.diagnostics.length > 0) {
|
|
136
|
+
io_context.stderr.write(
|
|
137
|
+
renderCheckDiagnostics(project_graph_result.diagnostics, output_mode),
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
return 1;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const diagnostics = checkGraph(project_graph_result.graph, repo_file_paths);
|
|
144
|
+
const selected_diagnostics = selectCheckTargetDiagnostics(
|
|
145
|
+
diagnostics,
|
|
146
|
+
resolved_target,
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
if (selected_diagnostics.length > 0) {
|
|
150
|
+
io_context.stderr.write(
|
|
151
|
+
renderCheckDiagnostics(selected_diagnostics, output_mode),
|
|
152
|
+
);
|
|
153
|
+
|
|
154
|
+
return 1;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
io_context.stdout.write(
|
|
158
|
+
renderCheckSuccess(selected_source_file_paths.length, output_mode),
|
|
159
|
+
);
|
|
160
|
+
|
|
161
|
+
return 0;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* @param {ParsedCliCommandRequest} parsed_command
|
|
166
|
+
* @param {{ stderr: { write(chunk: string): boolean }, stdout: { isTTY?: boolean, write(chunk: string): boolean }, write_paged_output?: (output_text: string) => Promise<void> }} io_context
|
|
167
|
+
* @returns {Promise<number>}
|
|
168
|
+
*/
|
|
169
|
+
async function runQueryCommand(parsed_command, io_context) {
|
|
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
|
+
|
|
186
|
+
const project_graph_result = await loadProjectGraph(process.cwd());
|
|
187
|
+
|
|
188
|
+
if (project_graph_result.diagnostics.length > 0) {
|
|
189
|
+
writeDiagnostics(io_context.stderr, project_graph_result.diagnostics);
|
|
190
|
+
|
|
191
|
+
return 1;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const where_clause = resolveWhereClause(
|
|
195
|
+
project_graph_result.config,
|
|
196
|
+
parsed_command.command_arguments,
|
|
197
|
+
);
|
|
198
|
+
|
|
199
|
+
if (!where_clause.success) {
|
|
200
|
+
io_context.stderr.write(`${where_clause.message}\n`);
|
|
201
|
+
|
|
202
|
+
return 1;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const query_result = queryGraph(
|
|
206
|
+
project_graph_result.graph,
|
|
207
|
+
where_clause.value.where_clause,
|
|
208
|
+
createQueryPaginationOptions(parsed_command, use_pager),
|
|
209
|
+
);
|
|
210
|
+
|
|
211
|
+
if (query_result.diagnostics.length > 0) {
|
|
212
|
+
io_context.stderr.write(
|
|
213
|
+
renderInvalidWhereDiagnostic(query_result.diagnostics[0]),
|
|
214
|
+
);
|
|
215
|
+
|
|
216
|
+
return 1;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const derived_summary_evaluator = createDerivedSummaryEvaluator(
|
|
220
|
+
project_graph_result.config,
|
|
221
|
+
project_graph_result.graph,
|
|
222
|
+
);
|
|
223
|
+
|
|
224
|
+
await writeCommandOutput(
|
|
225
|
+
io_context,
|
|
226
|
+
parsed_command,
|
|
227
|
+
createOutputView('query', query_result.nodes, {
|
|
228
|
+
derived_summary_evaluator,
|
|
229
|
+
...createQueryOutputOptions(parsed_command, query_result, use_pager),
|
|
230
|
+
}),
|
|
231
|
+
);
|
|
232
|
+
|
|
233
|
+
return 0;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
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
|
|
304
|
+
* @param {{ stderr: { write(chunk: string): boolean }, stdout: { isTTY?: boolean, write(chunk: string): boolean }, write_paged_output?: (output_text: string) => Promise<void> }} io_context
|
|
305
|
+
* @returns {Promise<number>}
|
|
306
|
+
*/
|
|
307
|
+
async function runQueriesCommand(parsed_command, io_context) {
|
|
308
|
+
const load_result = await loadPatramConfig(process.cwd());
|
|
309
|
+
|
|
310
|
+
if (load_result.diagnostics.length > 0) {
|
|
311
|
+
writeDiagnostics(io_context.stderr, load_result.diagnostics);
|
|
312
|
+
|
|
313
|
+
return 1;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
const repo_config = load_result.config;
|
|
317
|
+
|
|
318
|
+
if (!repo_config) {
|
|
319
|
+
throw new Error('Expected a valid Patram repo config.');
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
await writeCommandOutput(
|
|
323
|
+
io_context,
|
|
324
|
+
parsed_command,
|
|
325
|
+
createOutputView('queries', listQueries(repo_config.queries)),
|
|
326
|
+
);
|
|
327
|
+
|
|
328
|
+
return 0;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* @param {ParsedCliCommandRequest} parsed_command
|
|
333
|
+
* @param {{ stderr: { write(chunk: string): boolean }, stdout: { isTTY?: boolean, write(chunk: string): boolean }, write_paged_output?: (output_text: string) => Promise<void> }} io_context
|
|
334
|
+
* @returns {Promise<number>}
|
|
335
|
+
*/
|
|
336
|
+
async function runShowCommand(parsed_command, io_context) {
|
|
337
|
+
const project_graph_result = await loadProjectGraph(process.cwd());
|
|
338
|
+
|
|
339
|
+
if (project_graph_result.diagnostics.length > 0) {
|
|
340
|
+
writeDiagnostics(io_context.stderr, project_graph_result.diagnostics);
|
|
341
|
+
|
|
342
|
+
return 1;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
const show_output = await loadShowOutput(
|
|
346
|
+
parsed_command.command_arguments[0],
|
|
347
|
+
process.cwd(),
|
|
348
|
+
project_graph_result.graph,
|
|
349
|
+
);
|
|
350
|
+
|
|
351
|
+
if (!show_output.success) {
|
|
352
|
+
writeDiagnostics(io_context.stderr, [show_output.diagnostic]);
|
|
353
|
+
|
|
354
|
+
return 1;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
const derived_summary_evaluator = createDerivedSummaryEvaluator(
|
|
358
|
+
project_graph_result.config,
|
|
359
|
+
project_graph_result.graph,
|
|
360
|
+
);
|
|
361
|
+
|
|
362
|
+
await writeCommandOutput(
|
|
363
|
+
io_context,
|
|
364
|
+
parsed_command,
|
|
365
|
+
createShowOutputView(show_output.value, {
|
|
366
|
+
derived_summary_evaluator,
|
|
367
|
+
graph_nodes: project_graph_result.graph.nodes,
|
|
368
|
+
}),
|
|
369
|
+
);
|
|
370
|
+
|
|
371
|
+
return 0;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
/**
|
|
375
|
+
* @param {{ write(chunk: string): boolean }} output_stream
|
|
376
|
+
* @param {import('./load-patram-config.types.ts').PatramDiagnostic[]} diagnostics
|
|
377
|
+
*/
|
|
378
|
+
function writeDiagnostics(output_stream, diagnostics) {
|
|
379
|
+
for (const diagnostic of diagnostics) {
|
|
380
|
+
output_stream.write(formatDiagnostic(diagnostic));
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* @param {ParsedCliCommandRequest} parsed_command
|
|
386
|
+
* @param {{ total_count: number, nodes: import('./build-graph.types.ts').GraphNode[] }} query_result
|
|
387
|
+
* @param {boolean} use_pager
|
|
388
|
+
* @returns {{ hints: string[], limit: number, offset: number, total_count: number }}
|
|
389
|
+
*/
|
|
390
|
+
function createQueryOutputOptions(parsed_command, query_result, use_pager) {
|
|
391
|
+
/** @type {string[]} */
|
|
392
|
+
const hints = [];
|
|
393
|
+
const limit =
|
|
394
|
+
parsed_command.query_limit ??
|
|
395
|
+
(use_pager ? query_result.nodes.length : DEFAULT_QUERY_LIMIT);
|
|
396
|
+
const offset = parsed_command.query_offset ?? 0;
|
|
397
|
+
|
|
398
|
+
if (query_result.total_count === 0) {
|
|
399
|
+
hints.push('Try: patram query --where "kind=task"');
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
if (
|
|
403
|
+
!use_pager &&
|
|
404
|
+
parsed_command.query_limit === undefined &&
|
|
405
|
+
parsed_command.query_offset === undefined &&
|
|
406
|
+
query_result.total_count > DEFAULT_QUERY_LIMIT
|
|
407
|
+
) {
|
|
408
|
+
hints.push(
|
|
409
|
+
'Hint: use --offset <n> or --limit <n> to page through more matches.',
|
|
410
|
+
);
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
return {
|
|
414
|
+
hints,
|
|
415
|
+
limit,
|
|
416
|
+
offset,
|
|
417
|
+
total_count: query_result.total_count,
|
|
418
|
+
};
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
/**
|
|
422
|
+
* @param {ParsedCliCommandRequest} parsed_command
|
|
423
|
+
* @param {boolean} use_pager
|
|
424
|
+
* @returns {{ limit?: number, offset: number }}
|
|
425
|
+
*/
|
|
426
|
+
function createQueryPaginationOptions(parsed_command, use_pager) {
|
|
427
|
+
/** @type {{ limit?: number, offset: number }} */
|
|
428
|
+
const pagination_options = {
|
|
429
|
+
offset: parsed_command.query_offset ?? 0,
|
|
430
|
+
};
|
|
431
|
+
|
|
432
|
+
if (parsed_command.query_limit !== undefined) {
|
|
433
|
+
pagination_options.limit = parsed_command.query_limit;
|
|
434
|
+
} else if (!use_pager) {
|
|
435
|
+
pagination_options.limit = DEFAULT_QUERY_LIMIT;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
return pagination_options;
|
|
439
|
+
}
|
|
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
|
+
|
|
458
|
+
/**
|
|
459
|
+
* @param {import('./load-patram-config.types.ts').PatramDiagnostic} diagnostic
|
|
460
|
+
* @returns {string}
|
|
461
|
+
*/
|
|
462
|
+
function formatDiagnostic(diagnostic) {
|
|
463
|
+
return `${diagnostic.path}:${diagnostic.line}:${diagnostic.column} ${diagnostic.level} ${diagnostic.code} ${diagnostic.message}\n`;
|
|
464
|
+
}
|
package/lib/patram-config.js
CHANGED
|
@@ -8,7 +8,8 @@ import { z } from 'zod';
|
|
|
8
8
|
const KIND_NAME_SCHEMA = z.string().min(1);
|
|
9
9
|
const RELATION_NAME_SCHEMA = z.string().min(1);
|
|
10
10
|
const CLAIM_TYPE_SCHEMA = z.string().min(1);
|
|
11
|
-
const
|
|
11
|
+
const KEY_SOURCE_SCHEMA = z.enum(['path', 'value']);
|
|
12
|
+
const TARGET_SCHEMA = z.enum(['path', 'value']);
|
|
12
13
|
|
|
13
14
|
const kind_definition_schema = z
|
|
14
15
|
.object({
|
|
@@ -28,6 +29,7 @@ const relation_definition_schema = z
|
|
|
28
29
|
const mapping_node_schema = z
|
|
29
30
|
.object({
|
|
30
31
|
field: z.string().min(1),
|
|
32
|
+
key: KEY_SOURCE_SCHEMA.optional(),
|
|
31
33
|
kind: KIND_NAME_SCHEMA,
|
|
32
34
|
})
|
|
33
35
|
.strict();
|
|
@@ -11,12 +11,13 @@ export interface RelationDefinition {
|
|
|
11
11
|
|
|
12
12
|
export interface MappingNodeDefinition {
|
|
13
13
|
field: string;
|
|
14
|
+
key?: 'path' | 'value';
|
|
14
15
|
kind: string;
|
|
15
16
|
}
|
|
16
17
|
|
|
17
18
|
export interface MappingEmitDefinition {
|
|
18
19
|
relation: string;
|
|
19
|
-
target: 'path';
|
|
20
|
+
target: 'path' | 'value';
|
|
20
21
|
target_kind: string;
|
|
21
22
|
}
|
|
22
23
|
|