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
|
@@ -1,211 +0,0 @@
|
|
|
1
|
-
import { mkdtemp, rm, writeFile } from 'node:fs/promises';
|
|
2
|
-
import { tmpdir } from 'node:os';
|
|
3
|
-
import { join } from 'node:path';
|
|
4
|
-
|
|
5
|
-
import { afterEach, expect, it } from 'vitest';
|
|
6
|
-
|
|
7
|
-
import { loadPatramConfig } from './load-patram-config.js';
|
|
8
|
-
|
|
9
|
-
const test_context = createTestContext();
|
|
10
|
-
|
|
11
|
-
afterEach(async () => {
|
|
12
|
-
await cleanupTestContext(test_context);
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
it('loads and validates .patram.json from a project directory', async () => {
|
|
16
|
-
test_context.project_directory = await createTempProjectDirectory();
|
|
17
|
-
await writeProjectConfig(
|
|
18
|
-
test_context.project_directory,
|
|
19
|
-
createValidConfigSource(),
|
|
20
|
-
);
|
|
21
|
-
|
|
22
|
-
const load_result = await loadPatramConfig(test_context.project_directory);
|
|
23
|
-
|
|
24
|
-
expect(load_result).toEqual({
|
|
25
|
-
config: {
|
|
26
|
-
include: ['docs/**/*.md'],
|
|
27
|
-
queries: {
|
|
28
|
-
pending: {
|
|
29
|
-
where: 'kind=task and status=pending',
|
|
30
|
-
},
|
|
31
|
-
},
|
|
32
|
-
},
|
|
33
|
-
config_path: '.patram.json',
|
|
34
|
-
diagnostics: [],
|
|
35
|
-
});
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
it('defaults to the current working directory', async () => {
|
|
39
|
-
test_context.project_directory = await createTempProjectDirectory();
|
|
40
|
-
await writeProjectConfig(
|
|
41
|
-
test_context.project_directory,
|
|
42
|
-
createValidConfigSource(),
|
|
43
|
-
);
|
|
44
|
-
process.chdir(test_context.project_directory);
|
|
45
|
-
|
|
46
|
-
const load_result = await loadPatramConfig();
|
|
47
|
-
|
|
48
|
-
expect(load_result.config?.queries.pending.where).toBe(
|
|
49
|
-
'kind=task and status=pending',
|
|
50
|
-
);
|
|
51
|
-
expect(load_result.diagnostics).toEqual([]);
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
it('reports a missing config file', async () => {
|
|
55
|
-
test_context.project_directory = await createTempProjectDirectory();
|
|
56
|
-
const load_result = await loadPatramConfig(test_context.project_directory);
|
|
57
|
-
|
|
58
|
-
expect(load_result.config).toBeNull();
|
|
59
|
-
expect(load_result.diagnostics).toEqual([
|
|
60
|
-
{
|
|
61
|
-
code: 'config.not_found',
|
|
62
|
-
column: 1,
|
|
63
|
-
level: 'error',
|
|
64
|
-
line: 1,
|
|
65
|
-
message: 'Config file ".patram.json" was not found.',
|
|
66
|
-
path: '.patram.json',
|
|
67
|
-
},
|
|
68
|
-
]);
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
it('reports invalid JSON syntax', async () => {
|
|
72
|
-
test_context.project_directory = await createTempProjectDirectory();
|
|
73
|
-
await writeProjectConfig(test_context.project_directory, createBrokenJson());
|
|
74
|
-
|
|
75
|
-
const load_result = await loadPatramConfig(test_context.project_directory);
|
|
76
|
-
|
|
77
|
-
expect(load_result.config).toBeNull();
|
|
78
|
-
expect(load_result.diagnostics).toEqual([
|
|
79
|
-
{
|
|
80
|
-
code: 'config.invalid_json',
|
|
81
|
-
column: 1,
|
|
82
|
-
level: 'error',
|
|
83
|
-
line: 3,
|
|
84
|
-
message: 'Invalid JSON syntax.',
|
|
85
|
-
path: '.patram.json',
|
|
86
|
-
},
|
|
87
|
-
]);
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
it('reports config validation diagnostics', async () => {
|
|
91
|
-
test_context.project_directory = await createTempProjectDirectory();
|
|
92
|
-
await writeProjectConfig(
|
|
93
|
-
test_context.project_directory,
|
|
94
|
-
createInvalidConfigSource(),
|
|
95
|
-
);
|
|
96
|
-
|
|
97
|
-
const load_result = await loadPatramConfig(test_context.project_directory);
|
|
98
|
-
|
|
99
|
-
expect(load_result.config).toBeNull();
|
|
100
|
-
expect(load_result.diagnostics).toEqual([
|
|
101
|
-
{
|
|
102
|
-
code: 'config.invalid',
|
|
103
|
-
column: 1,
|
|
104
|
-
level: 'error',
|
|
105
|
-
line: 1,
|
|
106
|
-
message:
|
|
107
|
-
'Invalid config at "include": Include must contain at least one glob.',
|
|
108
|
-
path: '.patram.json',
|
|
109
|
-
},
|
|
110
|
-
{
|
|
111
|
-
code: 'config.invalid',
|
|
112
|
-
column: 1,
|
|
113
|
-
level: 'error',
|
|
114
|
-
line: 1,
|
|
115
|
-
message:
|
|
116
|
-
'Invalid config at "queries.pending.where": Stored query "where" must not be empty.',
|
|
117
|
-
path: '.patram.json',
|
|
118
|
-
},
|
|
119
|
-
]);
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
/**
|
|
123
|
-
* Create a temporary project directory.
|
|
124
|
-
*
|
|
125
|
-
* @returns {Promise<string>}
|
|
126
|
-
*/
|
|
127
|
-
async function createTempProjectDirectory() {
|
|
128
|
-
return mkdtemp(join(tmpdir(), 'patram-load-config-'));
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
/**
|
|
132
|
-
* Write a project config file.
|
|
133
|
-
*
|
|
134
|
-
* @param {string} project_directory
|
|
135
|
-
* @param {string} config_source
|
|
136
|
-
*/
|
|
137
|
-
async function writeProjectConfig(project_directory, config_source) {
|
|
138
|
-
await writeFile(join(project_directory, '.patram.json'), config_source);
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
/**
|
|
142
|
-
* Remove a temporary directory tree.
|
|
143
|
-
*
|
|
144
|
-
* @param {string} project_directory
|
|
145
|
-
*/
|
|
146
|
-
async function removeDirectory(project_directory) {
|
|
147
|
-
await rm(project_directory, { force: true, recursive: true });
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
/**
|
|
151
|
-
* Create invalid JSON for parser tests.
|
|
152
|
-
*
|
|
153
|
-
* @returns {string}
|
|
154
|
-
*/
|
|
155
|
-
function createBrokenJson() {
|
|
156
|
-
return ['{', ' "include": [', '}'].join('\n');
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
/**
|
|
160
|
-
* Create invalid config JSON.
|
|
161
|
-
*
|
|
162
|
-
* @returns {string}
|
|
163
|
-
*/
|
|
164
|
-
function createInvalidConfigSource() {
|
|
165
|
-
return JSON.stringify({
|
|
166
|
-
include: [],
|
|
167
|
-
queries: {
|
|
168
|
-
pending: {
|
|
169
|
-
where: '',
|
|
170
|
-
},
|
|
171
|
-
},
|
|
172
|
-
});
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
/**
|
|
176
|
-
* Create valid config JSON.
|
|
177
|
-
*
|
|
178
|
-
* @returns {string}
|
|
179
|
-
*/
|
|
180
|
-
function createValidConfigSource() {
|
|
181
|
-
return JSON.stringify({
|
|
182
|
-
include: ['docs/**/*.md'],
|
|
183
|
-
queries: {
|
|
184
|
-
pending: {
|
|
185
|
-
where: 'kind=task and status=pending',
|
|
186
|
-
},
|
|
187
|
-
},
|
|
188
|
-
});
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
/**
|
|
192
|
-
* @returns {{ original_working_directory: string, project_directory: string | null }}
|
|
193
|
-
*/
|
|
194
|
-
function createTestContext() {
|
|
195
|
-
return {
|
|
196
|
-
original_working_directory: process.cwd(),
|
|
197
|
-
project_directory: null,
|
|
198
|
-
};
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
/**
|
|
202
|
-
* @param {{ original_working_directory: string, project_directory: string | null }} test_context
|
|
203
|
-
*/
|
|
204
|
-
async function cleanupTestContext(test_context) {
|
|
205
|
-
process.chdir(test_context.original_working_directory);
|
|
206
|
-
|
|
207
|
-
if (test_context.project_directory) {
|
|
208
|
-
await removeDirectory(test_context.project_directory);
|
|
209
|
-
test_context.project_directory = null;
|
|
210
|
-
}
|
|
211
|
-
}
|
package/lib/parse-claims.test.js
DELETED
|
@@ -1,113 +0,0 @@
|
|
|
1
|
-
import { expect, it } from 'vitest';
|
|
2
|
-
|
|
3
|
-
import { parseClaims } from './parse-claims.js';
|
|
4
|
-
|
|
5
|
-
it('extracts markdown title, links and directives as neutral claims', () => {
|
|
6
|
-
const claims = parseClaims({
|
|
7
|
-
path: 'docs/patram.md',
|
|
8
|
-
source: createMarkdownSource(),
|
|
9
|
-
});
|
|
10
|
-
|
|
11
|
-
expect(claims).toEqual(createExpectedMarkdownClaims());
|
|
12
|
-
});
|
|
13
|
-
|
|
14
|
-
it('uses the first line as a plain markdown title', () => {
|
|
15
|
-
const claims = parseClaims({
|
|
16
|
-
path: 'docs/plain-title.md',
|
|
17
|
-
source: ['Patram Plain Title', '', 'Body text.'].join('\n'),
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
expect(claims).toEqual([
|
|
21
|
-
{
|
|
22
|
-
document_id: 'doc:docs/plain-title.md',
|
|
23
|
-
id: 'claim:doc:docs/plain-title.md:1',
|
|
24
|
-
origin: {
|
|
25
|
-
column: 1,
|
|
26
|
-
line: 1,
|
|
27
|
-
path: 'docs/plain-title.md',
|
|
28
|
-
},
|
|
29
|
-
type: 'document.title',
|
|
30
|
-
value: 'Patram Plain Title',
|
|
31
|
-
},
|
|
32
|
-
]);
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
it('returns no claims for unsupported file types', () => {
|
|
36
|
-
const claims = parseClaims({
|
|
37
|
-
path: 'bin/patram.js',
|
|
38
|
-
source: 'console.log("Patram");',
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
expect(claims).toEqual([]);
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
it('returns no claims for extensionless files', () => {
|
|
45
|
-
const claims = parseClaims({
|
|
46
|
-
path: 'README',
|
|
47
|
-
source: '# Patram',
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
expect(claims).toEqual([]);
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Create markdown input for parser tests.
|
|
55
|
-
*
|
|
56
|
-
* @returns {string}
|
|
57
|
-
*/
|
|
58
|
-
function createMarkdownSource() {
|
|
59
|
-
return [
|
|
60
|
-
'# Patram',
|
|
61
|
-
'',
|
|
62
|
-
'Read the [graph design](./graph-v0.md).',
|
|
63
|
-
'Defined by: terms/patram.md',
|
|
64
|
-
].join('\n');
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* Create the expected markdown claims.
|
|
69
|
-
*
|
|
70
|
-
* @returns {object[]}
|
|
71
|
-
*/
|
|
72
|
-
function createExpectedMarkdownClaims() {
|
|
73
|
-
return [
|
|
74
|
-
{
|
|
75
|
-
document_id: 'doc:docs/patram.md',
|
|
76
|
-
id: 'claim:doc:docs/patram.md:1',
|
|
77
|
-
origin: {
|
|
78
|
-
column: 1,
|
|
79
|
-
line: 1,
|
|
80
|
-
path: 'docs/patram.md',
|
|
81
|
-
},
|
|
82
|
-
type: 'document.title',
|
|
83
|
-
value: 'Patram',
|
|
84
|
-
},
|
|
85
|
-
{
|
|
86
|
-
document_id: 'doc:docs/patram.md',
|
|
87
|
-
id: 'claim:doc:docs/patram.md:2',
|
|
88
|
-
origin: {
|
|
89
|
-
column: 10,
|
|
90
|
-
line: 3,
|
|
91
|
-
path: 'docs/patram.md',
|
|
92
|
-
},
|
|
93
|
-
type: 'markdown.link',
|
|
94
|
-
value: {
|
|
95
|
-
target: './graph-v0.md',
|
|
96
|
-
text: 'graph design',
|
|
97
|
-
},
|
|
98
|
-
},
|
|
99
|
-
{
|
|
100
|
-
document_id: 'doc:docs/patram.md',
|
|
101
|
-
id: 'claim:doc:docs/patram.md:3',
|
|
102
|
-
name: 'defined_by',
|
|
103
|
-
origin: {
|
|
104
|
-
column: 1,
|
|
105
|
-
line: 4,
|
|
106
|
-
path: 'docs/patram.md',
|
|
107
|
-
},
|
|
108
|
-
parser: 'markdown',
|
|
109
|
-
type: 'directive',
|
|
110
|
-
value: 'terms/patram.md',
|
|
111
|
-
},
|
|
112
|
-
];
|
|
113
|
-
}
|
|
@@ -1,147 +0,0 @@
|
|
|
1
|
-
import { expect, it } from 'vitest';
|
|
2
|
-
|
|
3
|
-
import graph_v0_config from '../docs/graph-v0.config.json' with { type: 'json' };
|
|
4
|
-
import { parsePatramConfig } from './patram-config.js';
|
|
5
|
-
|
|
6
|
-
it('parses the documented v0 config example', () => {
|
|
7
|
-
const config_json = parsePatramConfig(graph_v0_config);
|
|
8
|
-
|
|
9
|
-
expect(config_json.kinds.term.label).toBe('Term');
|
|
10
|
-
expect(config_json.mappings['markdown.directive.defined_by'].emit).toEqual({
|
|
11
|
-
relation: 'defines',
|
|
12
|
-
target: 'path',
|
|
13
|
-
target_kind: 'term',
|
|
14
|
-
});
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
it('rejects mappings that reference unknown relations', () => {
|
|
18
|
-
const issues = getIssues(createMissingRelationConfig());
|
|
19
|
-
|
|
20
|
-
expect(issues).toContainEqual(
|
|
21
|
-
expect.objectContaining({
|
|
22
|
-
message: 'Unknown relation "missing_relation".',
|
|
23
|
-
path: ['mappings', 'markdown.link', 'emit', 'relation'],
|
|
24
|
-
}),
|
|
25
|
-
);
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
it('rejects mappings that reference unknown kinds', () => {
|
|
29
|
-
const issues = getIssues(createUnknownKindConfig());
|
|
30
|
-
|
|
31
|
-
expect(issues).toContainEqual(
|
|
32
|
-
expect.objectContaining({
|
|
33
|
-
message: 'Unknown kind "term".',
|
|
34
|
-
path: [
|
|
35
|
-
'mappings',
|
|
36
|
-
'markdown.directive.defined_by',
|
|
37
|
-
'emit',
|
|
38
|
-
'target_kind',
|
|
39
|
-
],
|
|
40
|
-
}),
|
|
41
|
-
);
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
it('rejects mappings without semantic output', () => {
|
|
45
|
-
const issues = getIssues(createEmptyMappingConfig());
|
|
46
|
-
|
|
47
|
-
expect(issues).toContainEqual(
|
|
48
|
-
expect.objectContaining({
|
|
49
|
-
message: 'Mapping must define at least one of "emit" or "node".',
|
|
50
|
-
path: ['mappings', 'document.title'],
|
|
51
|
-
}),
|
|
52
|
-
);
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* Create a config with an unknown relation reference.
|
|
57
|
-
*
|
|
58
|
-
* @returns {object}
|
|
59
|
-
*/
|
|
60
|
-
function createMissingRelationConfig() {
|
|
61
|
-
return {
|
|
62
|
-
kinds: {
|
|
63
|
-
document: {
|
|
64
|
-
builtin: true,
|
|
65
|
-
},
|
|
66
|
-
},
|
|
67
|
-
mappings: {
|
|
68
|
-
'markdown.link': {
|
|
69
|
-
emit: {
|
|
70
|
-
relation: 'missing_relation',
|
|
71
|
-
target: 'path',
|
|
72
|
-
target_kind: 'document',
|
|
73
|
-
},
|
|
74
|
-
},
|
|
75
|
-
},
|
|
76
|
-
relations: {},
|
|
77
|
-
};
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* Create a config with an unknown kind reference.
|
|
82
|
-
*
|
|
83
|
-
* @returns {object}
|
|
84
|
-
*/
|
|
85
|
-
function createUnknownKindConfig() {
|
|
86
|
-
return {
|
|
87
|
-
kinds: {
|
|
88
|
-
document: {
|
|
89
|
-
builtin: true,
|
|
90
|
-
},
|
|
91
|
-
},
|
|
92
|
-
mappings: {
|
|
93
|
-
'markdown.directive.defined_by': {
|
|
94
|
-
emit: {
|
|
95
|
-
relation: 'links_to',
|
|
96
|
-
target: 'path',
|
|
97
|
-
target_kind: 'term',
|
|
98
|
-
},
|
|
99
|
-
},
|
|
100
|
-
},
|
|
101
|
-
relations: {
|
|
102
|
-
links_to: {
|
|
103
|
-
from: ['document'],
|
|
104
|
-
to: ['document'],
|
|
105
|
-
},
|
|
106
|
-
},
|
|
107
|
-
};
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
/**
|
|
111
|
-
* Create a config with an empty mapping definition.
|
|
112
|
-
*
|
|
113
|
-
* @returns {object}
|
|
114
|
-
*/
|
|
115
|
-
function createEmptyMappingConfig() {
|
|
116
|
-
return {
|
|
117
|
-
kinds: {
|
|
118
|
-
document: {
|
|
119
|
-
builtin: true,
|
|
120
|
-
},
|
|
121
|
-
},
|
|
122
|
-
mappings: {
|
|
123
|
-
'document.title': {},
|
|
124
|
-
},
|
|
125
|
-
relations: {},
|
|
126
|
-
};
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
/**
|
|
130
|
-
* Parse config and return validation issues.
|
|
131
|
-
*
|
|
132
|
-
* @param {object} config_json
|
|
133
|
-
* @returns {unknown[]}
|
|
134
|
-
*/
|
|
135
|
-
function getIssues(config_json) {
|
|
136
|
-
try {
|
|
137
|
-
parsePatramConfig(config_json);
|
|
138
|
-
} catch (error) {
|
|
139
|
-
if (error instanceof Error && 'issues' in error) {
|
|
140
|
-
return /** @type {{ issues: unknown[] }} */ (error).issues;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
throw error;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
throw new Error('Expected configuration parsing to fail.');
|
|
147
|
-
}
|