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,361 @@
|
|
|
1
|
+
/* eslint-disable max-lines */
|
|
2
|
+
/**
|
|
3
|
+
* @import { OutputStoredQueryItem } from './output-view.types.ts';
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { parseWhereClause } from './parse-where-clause.js';
|
|
7
|
+
|
|
8
|
+
const MAX_STORED_QUERY_WIDTH = 100;
|
|
9
|
+
const MIN_TERM_COLUMN_WIDTH = 20;
|
|
10
|
+
const STORED_QUERY_COLUMN_GAP = 2;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* @typedef {'field_name' | 'keyword' | 'literal' | 'name' | 'operator' | 'plain'} StoredQuerySegmentKind
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @typedef {{ kind: StoredQuerySegmentKind, text: string }} StoredQuerySegment
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Layout stored queries into styled lines shared by plain and rich renderers.
|
|
22
|
+
*
|
|
23
|
+
* @param {OutputStoredQueryItem[]} output_items
|
|
24
|
+
* @returns {StoredQuerySegment[][]}
|
|
25
|
+
*/
|
|
26
|
+
export function layoutStoredQueries(output_items) {
|
|
27
|
+
if (output_items.length === 0) {
|
|
28
|
+
return [];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const name_column_width = Math.max(
|
|
32
|
+
...output_items.map((output_item) => output_item.name.length),
|
|
33
|
+
);
|
|
34
|
+
const term_column_width = Math.max(
|
|
35
|
+
MIN_TERM_COLUMN_WIDTH,
|
|
36
|
+
MAX_STORED_QUERY_WIDTH - name_column_width - STORED_QUERY_COLUMN_GAP,
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
return output_items.flatMap((output_item) =>
|
|
40
|
+
layoutStoredQuery(output_item, name_column_width, term_column_width),
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* @param {OutputStoredQueryItem} output_item
|
|
46
|
+
* @param {number} name_column_width
|
|
47
|
+
* @param {number} term_column_width
|
|
48
|
+
* @returns {StoredQuerySegment[][]}
|
|
49
|
+
*/
|
|
50
|
+
function layoutStoredQuery(output_item, name_column_width, term_column_width) {
|
|
51
|
+
const term_lines = wrapPhrases(
|
|
52
|
+
createStoredQueryPhrases(output_item.where),
|
|
53
|
+
term_column_width,
|
|
54
|
+
);
|
|
55
|
+
const continuation_prefix = ' '.repeat(
|
|
56
|
+
name_column_width + STORED_QUERY_COLUMN_GAP,
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
return term_lines.map((line_segments, line_index) => {
|
|
60
|
+
if (line_index === 0) {
|
|
61
|
+
return [
|
|
62
|
+
{
|
|
63
|
+
kind: 'name',
|
|
64
|
+
text: output_item.name.padEnd(name_column_width, ' '),
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
kind: 'plain',
|
|
68
|
+
text: ' '.repeat(STORED_QUERY_COLUMN_GAP),
|
|
69
|
+
},
|
|
70
|
+
...line_segments,
|
|
71
|
+
];
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return [
|
|
75
|
+
{
|
|
76
|
+
kind: 'plain',
|
|
77
|
+
text: continuation_prefix,
|
|
78
|
+
},
|
|
79
|
+
...line_segments,
|
|
80
|
+
];
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* @param {string} where_clause
|
|
86
|
+
* @returns {StoredQuerySegment[][]}
|
|
87
|
+
*/
|
|
88
|
+
function createStoredQueryPhrases(where_clause) {
|
|
89
|
+
const parse_result = parseWhereClause(where_clause);
|
|
90
|
+
|
|
91
|
+
if (!parse_result.success) {
|
|
92
|
+
return createFallbackPhrases(where_clause);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return parse_result.clauses.map((clause, clause_index) =>
|
|
96
|
+
createClausePhrase(clause, clause_index > 0),
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* @param {{
|
|
102
|
+
* is_negated: boolean,
|
|
103
|
+
* term:
|
|
104
|
+
* | { field_name: 'id' | 'kind' | 'path' | 'status' | 'title', kind: 'field', operator: '=' | '^=' | '~', value: string }
|
|
105
|
+
* | { field_name: 'id' | 'kind' | 'path' | 'status' | 'title', kind: 'field_set', operator: 'in' | 'not in', values: string[] }
|
|
106
|
+
* | { kind: 'relation', relation_name: string }
|
|
107
|
+
* | { kind: 'relation_target', relation_name: string, target_id: string }
|
|
108
|
+
* | {
|
|
109
|
+
* aggregate_name: 'any' | 'count' | 'none',
|
|
110
|
+
* clauses: unknown[],
|
|
111
|
+
* comparison?: '!=' | '<' | '<=' | '=' | '>' | '>=',
|
|
112
|
+
* kind: 'aggregate',
|
|
113
|
+
* traversal: { direction: 'in' | 'out', relation_name: string },
|
|
114
|
+
* value?: number,
|
|
115
|
+
* },
|
|
116
|
+
* }} clause
|
|
117
|
+
* @param {boolean} should_prefix_and
|
|
118
|
+
* @returns {StoredQuerySegment[]}
|
|
119
|
+
*/
|
|
120
|
+
function createClausePhrase(clause, should_prefix_and) {
|
|
121
|
+
/** @type {StoredQuerySegment[]} */
|
|
122
|
+
const phrase = [];
|
|
123
|
+
|
|
124
|
+
if (should_prefix_and) {
|
|
125
|
+
phrase.push({ kind: 'keyword', text: 'and' });
|
|
126
|
+
phrase.push({ kind: 'plain', text: ' ' });
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (clause.is_negated) {
|
|
130
|
+
phrase.push({ kind: 'keyword', text: 'not' });
|
|
131
|
+
phrase.push({ kind: 'plain', text: ' ' });
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
phrase.push(...createTermSegments(clause.term));
|
|
135
|
+
|
|
136
|
+
return phrase;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* @param {
|
|
141
|
+
* | { field_name: 'id' | 'kind' | 'path' | 'status' | 'title', kind: 'field', operator: '=' | '^=' | '~', value: string }
|
|
142
|
+
* | { field_name: 'id' | 'kind' | 'path' | 'status' | 'title', kind: 'field_set', operator: 'in' | 'not in', values: string[] }
|
|
143
|
+
* | { kind: 'relation', relation_name: string }
|
|
144
|
+
* | { kind: 'relation_target', relation_name: string, target_id: string }
|
|
145
|
+
* | {
|
|
146
|
+
* aggregate_name: 'any' | 'count' | 'none',
|
|
147
|
+
* clauses: { is_negated: boolean, term: unknown }[],
|
|
148
|
+
* comparison?: '!=' | '<' | '<=' | '=' | '>' | '>=',
|
|
149
|
+
* kind: 'aggregate',
|
|
150
|
+
* traversal: { direction: 'in' | 'out', relation_name: string },
|
|
151
|
+
* value?: number,
|
|
152
|
+
* }
|
|
153
|
+
* } term
|
|
154
|
+
* @returns {StoredQuerySegment[]}
|
|
155
|
+
*/
|
|
156
|
+
function createTermSegments(term) {
|
|
157
|
+
if (term.kind === 'aggregate') {
|
|
158
|
+
return createAggregateSegments(term);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (term.kind === 'field') {
|
|
162
|
+
return [
|
|
163
|
+
{ kind: 'field_name', text: term.field_name },
|
|
164
|
+
{ kind: 'operator', text: term.operator },
|
|
165
|
+
{ kind: 'literal', text: term.value },
|
|
166
|
+
];
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (term.kind === 'field_set') {
|
|
170
|
+
return createFieldSetSegments(term);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (term.kind === 'relation_target') {
|
|
174
|
+
return [
|
|
175
|
+
{ kind: 'field_name', text: term.relation_name },
|
|
176
|
+
{ kind: 'operator', text: '=' },
|
|
177
|
+
{ kind: 'literal', text: term.target_id },
|
|
178
|
+
];
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return [
|
|
182
|
+
{ kind: 'field_name', text: term.relation_name },
|
|
183
|
+
{ kind: 'operator', text: ':*' },
|
|
184
|
+
];
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* @param {{ field_name: 'id' | 'kind' | 'path' | 'status' | 'title', kind: 'field_set', operator: 'in' | 'not in', values: string[] }} term
|
|
189
|
+
* @returns {StoredQuerySegment[]}
|
|
190
|
+
*/
|
|
191
|
+
function createFieldSetSegments(term) {
|
|
192
|
+
return [
|
|
193
|
+
{ kind: 'field_name', text: term.field_name },
|
|
194
|
+
{ kind: 'plain', text: ' ' },
|
|
195
|
+
{ kind: 'operator', text: term.operator },
|
|
196
|
+
{ kind: 'plain', text: ' ' },
|
|
197
|
+
{ kind: 'operator', text: '[' },
|
|
198
|
+
...createListSegments(term.values),
|
|
199
|
+
{ kind: 'operator', text: ']' },
|
|
200
|
+
];
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* @param {{
|
|
205
|
+
* aggregate_name: 'any' | 'count' | 'none',
|
|
206
|
+
* clauses: { is_negated: boolean, term: unknown }[],
|
|
207
|
+
* comparison?: '!=' | '<' | '<=' | '=' | '>' | '>=',
|
|
208
|
+
* kind: 'aggregate',
|
|
209
|
+
* traversal: { direction: 'in' | 'out', relation_name: string },
|
|
210
|
+
* value?: number,
|
|
211
|
+
* }} term
|
|
212
|
+
* @returns {StoredQuerySegment[]}
|
|
213
|
+
*/
|
|
214
|
+
function createAggregateSegments(term) {
|
|
215
|
+
/** @type {StoredQuerySegment[]} */
|
|
216
|
+
const segments = [
|
|
217
|
+
{ kind: 'field_name', text: term.aggregate_name },
|
|
218
|
+
{ kind: 'operator', text: '(' },
|
|
219
|
+
...createTraversalSegments(term.traversal),
|
|
220
|
+
{ kind: 'operator', text: ', ' },
|
|
221
|
+
...createNestedClauseSegments(term.clauses),
|
|
222
|
+
{ kind: 'operator', text: ')' },
|
|
223
|
+
];
|
|
224
|
+
|
|
225
|
+
if (term.aggregate_name === 'count') {
|
|
226
|
+
segments.push({ kind: 'plain', text: ' ' });
|
|
227
|
+
segments.push({ kind: 'operator', text: term.comparison ?? '=' });
|
|
228
|
+
segments.push({ kind: 'plain', text: ' ' });
|
|
229
|
+
segments.push({ kind: 'literal', text: String(term.value ?? 0) });
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
return segments;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* @param {{ direction: 'in' | 'out', relation_name: string }} traversal
|
|
237
|
+
* @returns {StoredQuerySegment[]}
|
|
238
|
+
*/
|
|
239
|
+
function createTraversalSegments(traversal) {
|
|
240
|
+
return [
|
|
241
|
+
{ kind: 'field_name', text: traversal.direction },
|
|
242
|
+
{ kind: 'operator', text: ':' },
|
|
243
|
+
{ kind: 'field_name', text: traversal.relation_name },
|
|
244
|
+
];
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* @param {{ is_negated: boolean, term: unknown }[]} clauses
|
|
249
|
+
* @returns {StoredQuerySegment[]}
|
|
250
|
+
*/
|
|
251
|
+
function createNestedClauseSegments(clauses) {
|
|
252
|
+
return clauses.flatMap((clause, clause_index) => {
|
|
253
|
+
const clause_phrase = createClausePhrase(
|
|
254
|
+
/** @type {{
|
|
255
|
+
* is_negated: boolean,
|
|
256
|
+
* term:
|
|
257
|
+
* | { field_name: 'id' | 'kind' | 'path' | 'status' | 'title', kind: 'field', operator: '=' | '^=' | '~', value: string }
|
|
258
|
+
* | { field_name: 'id' | 'kind' | 'path' | 'status' | 'title', kind: 'field_set', operator: 'in' | 'not in', values: string[] }
|
|
259
|
+
* | { kind: 'relation', relation_name: string }
|
|
260
|
+
* | { kind: 'relation_target', relation_name: string, target_id: string }
|
|
261
|
+
* | {
|
|
262
|
+
* aggregate_name: 'any' | 'count' | 'none',
|
|
263
|
+
* clauses: { is_negated: boolean, term: unknown }[],
|
|
264
|
+
* comparison?: '!=' | '<' | '<=' | '=' | '>' | '>=',
|
|
265
|
+
* kind: 'aggregate',
|
|
266
|
+
* traversal: { direction: 'in' | 'out', relation_name: string },
|
|
267
|
+
* value?: number,
|
|
268
|
+
* },
|
|
269
|
+
* }} */ (clause),
|
|
270
|
+
clause_index > 0,
|
|
271
|
+
);
|
|
272
|
+
|
|
273
|
+
if (clause_index === 0) {
|
|
274
|
+
return clause_phrase;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
return [{ kind: 'plain', text: ' ' }, ...clause_phrase];
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* @param {string[]} values
|
|
283
|
+
* @returns {StoredQuerySegment[]}
|
|
284
|
+
*/
|
|
285
|
+
function createListSegments(values) {
|
|
286
|
+
return values.flatMap((value, value_index) => {
|
|
287
|
+
if (value_index === 0) {
|
|
288
|
+
return [{ kind: 'literal', text: value }];
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
return [
|
|
292
|
+
{ kind: 'operator', text: ', ' },
|
|
293
|
+
{ kind: 'literal', text: value },
|
|
294
|
+
];
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* @param {string} where_clause
|
|
300
|
+
* @returns {StoredQuerySegment[][]}
|
|
301
|
+
*/
|
|
302
|
+
function createFallbackPhrases(where_clause) {
|
|
303
|
+
const tokens = where_clause.match(/\S+/gu) ?? [];
|
|
304
|
+
|
|
305
|
+
if (tokens.length === 0) {
|
|
306
|
+
return [[{ kind: 'literal', text: where_clause }]];
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
return tokens.map((token) => [{ kind: 'literal', text: token }]);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* @param {StoredQuerySegment[][]} phrases
|
|
314
|
+
* @param {number} term_column_width
|
|
315
|
+
* @returns {StoredQuerySegment[][]}
|
|
316
|
+
*/
|
|
317
|
+
function wrapPhrases(phrases, term_column_width) {
|
|
318
|
+
/** @type {StoredQuerySegment[][]} */
|
|
319
|
+
const lines = [];
|
|
320
|
+
/** @type {StoredQuerySegment[]} */
|
|
321
|
+
let current_line = [];
|
|
322
|
+
let current_width = 0;
|
|
323
|
+
|
|
324
|
+
for (const phrase of phrases) {
|
|
325
|
+
const phrase_width = measureSegments(phrase);
|
|
326
|
+
|
|
327
|
+
if (current_line.length === 0) {
|
|
328
|
+
current_line = [...phrase];
|
|
329
|
+
current_width = phrase_width;
|
|
330
|
+
continue;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
if (current_width + 1 + phrase_width > term_column_width) {
|
|
334
|
+
lines.push(current_line);
|
|
335
|
+
current_line = [...phrase];
|
|
336
|
+
current_width = phrase_width;
|
|
337
|
+
continue;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
current_line.push({ kind: 'plain', text: ' ' });
|
|
341
|
+
current_line.push(...phrase);
|
|
342
|
+
current_width += 1 + phrase_width;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
if (current_line.length > 0) {
|
|
346
|
+
lines.push(current_line);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
return lines;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* @param {StoredQuerySegment[]} segments
|
|
354
|
+
* @returns {number}
|
|
355
|
+
*/
|
|
356
|
+
function measureSegments(segments) {
|
|
357
|
+
return segments.reduce(
|
|
358
|
+
(total_width, segment) => total_width + segment.text.length,
|
|
359
|
+
0,
|
|
360
|
+
);
|
|
361
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @import { StoredQueryConfig } from './load-patram-config.types.ts';
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* List stored queries in stable name order.
|
|
7
|
+
*
|
|
8
|
+
* @param {Record<string, StoredQueryConfig>} stored_queries
|
|
9
|
+
* @returns {{ name: string, where: string }[]}
|
|
10
|
+
*/
|
|
11
|
+
export function listQueries(stored_queries) {
|
|
12
|
+
return Object.entries(stored_queries)
|
|
13
|
+
.sort(([left_name], [right_name]) => left_name.localeCompare(right_name))
|
|
14
|
+
.map(([name, stored_query]) => ({
|
|
15
|
+
name,
|
|
16
|
+
where: stored_query.where,
|
|
17
|
+
}));
|
|
18
|
+
}
|
package/lib/list-source-files.js
CHANGED
|
@@ -1,6 +1,21 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { globby } from 'globby';
|
|
2
2
|
import process from 'node:process';
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* Source file scanning.
|
|
6
|
+
*
|
|
7
|
+
* Expands include globs into stable repo-relative file lists for indexing and
|
|
8
|
+
* broken-link validation.
|
|
9
|
+
*
|
|
10
|
+
* Kind: scan
|
|
11
|
+
* Status: active
|
|
12
|
+
* Tracked in: ../docs/plans/v0/source-anchor-dogfooding.md
|
|
13
|
+
* Decided by: ../docs/decisions/source-scan.md
|
|
14
|
+
* @patram
|
|
15
|
+
* @see {@link ./load-project-graph.js}
|
|
16
|
+
* @see {@link ../docs/decisions/source-scan.md}
|
|
17
|
+
*/
|
|
18
|
+
|
|
4
19
|
/**
|
|
5
20
|
* List source files matched by Patram include globs.
|
|
6
21
|
*
|
|
@@ -12,26 +27,46 @@ export async function listSourceFiles(
|
|
|
12
27
|
include_patterns,
|
|
13
28
|
project_directory = process.cwd(),
|
|
14
29
|
) {
|
|
15
|
-
|
|
16
|
-
|
|
30
|
+
const source_file_paths = await listMatchingFiles(
|
|
31
|
+
include_patterns,
|
|
32
|
+
project_directory,
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
return [...new Set(source_file_paths)].sort(comparePaths);
|
|
36
|
+
}
|
|
17
37
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
38
|
+
/**
|
|
39
|
+
* List repo files available for broken-link validation.
|
|
40
|
+
*
|
|
41
|
+
* @param {string} [project_directory]
|
|
42
|
+
* @returns {Promise<string[]>}
|
|
43
|
+
*/
|
|
44
|
+
export async function listRepoFiles(project_directory = process.cwd()) {
|
|
45
|
+
const repo_file_paths = await listMatchingFiles(['**/*'], project_directory, {
|
|
46
|
+
dot: true,
|
|
47
|
+
});
|
|
25
48
|
|
|
26
|
-
return [...
|
|
49
|
+
return [...new Set(repo_file_paths)].sort(comparePaths);
|
|
27
50
|
}
|
|
28
51
|
|
|
29
52
|
/**
|
|
30
|
-
* @param {string}
|
|
31
|
-
* @
|
|
53
|
+
* @param {string[]} include_patterns
|
|
54
|
+
* @param {string} project_directory
|
|
55
|
+
* @param {{ dot?: boolean }} [options]
|
|
56
|
+
* @returns {Promise<string[]>}
|
|
32
57
|
*/
|
|
33
|
-
function
|
|
34
|
-
|
|
58
|
+
async function listMatchingFiles(
|
|
59
|
+
include_patterns,
|
|
60
|
+
project_directory,
|
|
61
|
+
options = {},
|
|
62
|
+
) {
|
|
63
|
+
return globby(include_patterns, {
|
|
64
|
+
cwd: project_directory,
|
|
65
|
+
dot: options.dot ?? false,
|
|
66
|
+
expandDirectories: false,
|
|
67
|
+
gitignore: true,
|
|
68
|
+
onlyFiles: true,
|
|
69
|
+
});
|
|
35
70
|
}
|
|
36
71
|
|
|
37
72
|
/**
|