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,87 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import { once } from 'node:events';
|
|
3
|
+
import process from 'node:process';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @param {string} output_text
|
|
7
|
+
* @returns {Promise<void>}
|
|
8
|
+
*/
|
|
9
|
+
export async function writePagedOutput(output_text) {
|
|
10
|
+
const pager_command = process.env.PAGER?.trim();
|
|
11
|
+
|
|
12
|
+
if (pager_command) {
|
|
13
|
+
await writeOutputThroughPager(output_text, pager_command, [], true);
|
|
14
|
+
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
await writeOutputThroughPager(output_text, 'less', ['-FIRXS'], false);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* @param {string} output_text
|
|
23
|
+
* @param {string} pager_command
|
|
24
|
+
* @param {string[]} pager_arguments
|
|
25
|
+
* @param {boolean} use_shell
|
|
26
|
+
* @returns {Promise<void>}
|
|
27
|
+
*/
|
|
28
|
+
async function writeOutputThroughPager(
|
|
29
|
+
output_text,
|
|
30
|
+
pager_command,
|
|
31
|
+
pager_arguments,
|
|
32
|
+
use_shell,
|
|
33
|
+
) {
|
|
34
|
+
const pager_process = spawn(pager_command, pager_arguments, {
|
|
35
|
+
shell: use_shell,
|
|
36
|
+
stdio: ['pipe', 'inherit', 'inherit'],
|
|
37
|
+
});
|
|
38
|
+
const close_promise = once(pager_process, 'close');
|
|
39
|
+
const error_promise = once(pager_process, 'error').then(([error]) => {
|
|
40
|
+
throw error;
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
await writeToPagerInput(pager_process.stdin, output_text);
|
|
44
|
+
|
|
45
|
+
const [exit_code, signal_code] = await Promise.race([
|
|
46
|
+
close_promise,
|
|
47
|
+
error_promise,
|
|
48
|
+
]);
|
|
49
|
+
|
|
50
|
+
if (exit_code === 0) {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (signal_code) {
|
|
55
|
+
throw new Error(`Pager exited with signal "${signal_code}".`);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
throw new Error(`Pager exited with code ${exit_code}.`);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* @param {NodeJS.WritableStream} pager_input
|
|
63
|
+
* @param {string} output_text
|
|
64
|
+
* @returns {Promise<void>}
|
|
65
|
+
*/
|
|
66
|
+
function writeToPagerInput(pager_input, output_text) {
|
|
67
|
+
return new Promise((resolve, reject) => {
|
|
68
|
+
pager_input.once('error', (error) => {
|
|
69
|
+
if (isBrokenPipe(error)) {
|
|
70
|
+
resolve();
|
|
71
|
+
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
reject(error);
|
|
76
|
+
});
|
|
77
|
+
pager_input.end(output_text, 'utf8', resolve);
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* @param {unknown} error
|
|
83
|
+
* @returns {boolean}
|
|
84
|
+
*/
|
|
85
|
+
function isBrokenPipe(error) {
|
|
86
|
+
return error instanceof Error && 'code' in error && error.code === 'EPIPE';
|
|
87
|
+
}
|
package/package.json
CHANGED
|
@@ -1,13 +1,23 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "patram",
|
|
3
|
-
"version": "0.0
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"type": "module",
|
|
5
|
+
"main": "./lib/patram.js",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": "./lib/patram.js",
|
|
8
|
+
"./bin/patram.js": "./bin/patram.js"
|
|
9
|
+
},
|
|
5
10
|
"files": [
|
|
6
|
-
"bin/",
|
|
7
|
-
"lib
|
|
11
|
+
"bin/patram.js",
|
|
12
|
+
"lib/**/*.js",
|
|
13
|
+
"lib/**/*.ts",
|
|
14
|
+
"!bin/**/*.test.js",
|
|
15
|
+
"!bin/**/*.test-helpers.js",
|
|
16
|
+
"!lib/**/*.test.js",
|
|
17
|
+
"!lib/**/*.test-helpers.js"
|
|
8
18
|
],
|
|
9
19
|
"bin": {
|
|
10
|
-
"patram": "
|
|
20
|
+
"patram": "bin/patram.js"
|
|
11
21
|
},
|
|
12
22
|
"homepage": "https://github.com/mantoni/patram",
|
|
13
23
|
"repository": {
|
|
@@ -19,18 +29,18 @@
|
|
|
19
29
|
},
|
|
20
30
|
"license": "MIT",
|
|
21
31
|
"scripts": {
|
|
22
|
-
"all": "npm run check:lint && npm run check:format && npm run check:types && npm run
|
|
32
|
+
"all": "npm run check:lint && npm run check:format && npm run check:types && npm run check:patram && npm run test:coverage && npm run check:dupes",
|
|
23
33
|
"check:dupes": "jscpd --min-tokens 100 --min-lines 6 --mode mild --threshold 0 --reporters console --gitignore .",
|
|
24
34
|
"check:format": "prettier --check .",
|
|
25
35
|
"check:lint": "eslint .",
|
|
26
|
-
"check:
|
|
36
|
+
"check:patram": "./bin/patram.js check",
|
|
37
|
+
"check:staged": "lint-staged",
|
|
27
38
|
"check:types": "tsc",
|
|
28
39
|
"postversion": "git push && git push --tags",
|
|
29
40
|
"preversion": "npm run all",
|
|
30
41
|
"prepare": "husky",
|
|
31
|
-
"test": "
|
|
42
|
+
"test": "vitest run",
|
|
32
43
|
"test:coverage": "vitest run --coverage",
|
|
33
|
-
"test:unit": "vitest run",
|
|
34
44
|
"version": "node scripts/update-changelog.js && git add CHANGELOG.md package.json package-lock.json"
|
|
35
45
|
},
|
|
36
46
|
"lint-staged": {
|
|
@@ -41,13 +51,21 @@
|
|
|
41
51
|
]
|
|
42
52
|
},
|
|
43
53
|
"dependencies": {
|
|
54
|
+
"@shikijs/cli": "^4.0.2",
|
|
55
|
+
"@shikijs/vscode-textmate": "^10.0.2",
|
|
56
|
+
"ansis": "^4.2.0",
|
|
57
|
+
"beautiful-mermaid": "^1.1.3",
|
|
58
|
+
"globby": "^16.1.1",
|
|
59
|
+
"md4x": "^0.0.25",
|
|
60
|
+
"shiki": "^4.0.2",
|
|
61
|
+
"string-width": "^8.2.0",
|
|
62
|
+
"wrap-ansi": "^10.0.0",
|
|
44
63
|
"zod": "^4.3.6"
|
|
45
64
|
},
|
|
46
65
|
"devDependencies": {
|
|
47
66
|
"@eslint/js": "^10.0.1",
|
|
48
67
|
"@types/node": "^24.12.0",
|
|
49
68
|
"@vitest/coverage-v8": "^4.1.0",
|
|
50
|
-
"ansis": "^4.2.0",
|
|
51
69
|
"eslint": "^10.0.3",
|
|
52
70
|
"eslint-plugin-jsdoc": "^62.8.0",
|
|
53
71
|
"globals": "^17.4.0",
|
|
@@ -56,9 +74,7 @@
|
|
|
56
74
|
"lint-staged": "^16.2.6",
|
|
57
75
|
"prettier": "^3.5.3",
|
|
58
76
|
"slice-ansi": "^8.0.0",
|
|
59
|
-
"string-width": "^8.2.0",
|
|
60
77
|
"typescript": "^5.8.2",
|
|
61
|
-
"vitest": "^4.1.0"
|
|
62
|
-
"wrap-ansi": "^10.0.0"
|
|
78
|
+
"vitest": "^4.1.0"
|
|
63
79
|
}
|
|
64
80
|
}
|
package/bin/patram.test.js
DELETED
|
@@ -1,184 +0,0 @@
|
|
|
1
|
-
import { mkdtemp, mkdir, 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 { main } from './patram.js';
|
|
8
|
-
|
|
9
|
-
const test_context = createTestContext();
|
|
10
|
-
|
|
11
|
-
afterEach(async () => {
|
|
12
|
-
await cleanupTestContext(test_context);
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
it('prints config diagnostics for check failures', async () => {
|
|
16
|
-
test_context.project_directory = await createTempProjectDirectory();
|
|
17
|
-
const io_context = createIoContext();
|
|
18
|
-
|
|
19
|
-
const exit_code = await main(['check', test_context.project_directory], {
|
|
20
|
-
stderr: io_context.stderr,
|
|
21
|
-
stdout: io_context.stdout,
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
expect(exit_code).toBe(1);
|
|
25
|
-
expect(io_context.stderr_chunks).toEqual([
|
|
26
|
-
'.patram.json:1:1 error config.not_found Config file ".patram.json" was not found.\n',
|
|
27
|
-
]);
|
|
28
|
-
expect(io_context.stdout_chunks).toEqual([]);
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
it('prints broken link diagnostics for check failures', async () => {
|
|
32
|
-
test_context.project_directory = await createTempProjectDirectory();
|
|
33
|
-
const io_context = createIoContext();
|
|
34
|
-
|
|
35
|
-
await writeProjectConfig(test_context.project_directory);
|
|
36
|
-
await writeProjectFile(
|
|
37
|
-
test_context.project_directory,
|
|
38
|
-
'docs/patram.md',
|
|
39
|
-
createBrokenLinkSource(),
|
|
40
|
-
);
|
|
41
|
-
|
|
42
|
-
const exit_code = await main(['check', test_context.project_directory], {
|
|
43
|
-
stderr: io_context.stderr,
|
|
44
|
-
stdout: io_context.stdout,
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
expect(exit_code).toBe(1);
|
|
48
|
-
expect(io_context.stderr_chunks).toEqual([
|
|
49
|
-
'docs/patram.md:3:5 error graph.link_broken Document link target "docs/missing.md" was not found.\n',
|
|
50
|
-
]);
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
it('defaults check to the current working directory and exits 0 on valid input', async () => {
|
|
54
|
-
test_context.project_directory = await createTempProjectDirectory();
|
|
55
|
-
const io_context = createIoContext();
|
|
56
|
-
|
|
57
|
-
await writeProjectConfig(test_context.project_directory);
|
|
58
|
-
await writeProjectFile(
|
|
59
|
-
test_context.project_directory,
|
|
60
|
-
'docs/patram.md',
|
|
61
|
-
createValidLinkSource(),
|
|
62
|
-
);
|
|
63
|
-
await writeProjectFile(
|
|
64
|
-
test_context.project_directory,
|
|
65
|
-
'docs/guide.md',
|
|
66
|
-
'# Guide\n',
|
|
67
|
-
);
|
|
68
|
-
process.chdir(test_context.project_directory);
|
|
69
|
-
|
|
70
|
-
const exit_code = await main(['check'], {
|
|
71
|
-
stderr: io_context.stderr,
|
|
72
|
-
stdout: io_context.stdout,
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
expect(exit_code).toBe(0);
|
|
76
|
-
expect(io_context.stderr_chunks).toEqual([]);
|
|
77
|
-
expect(io_context.stdout_chunks).toEqual([]);
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* @returns {string}
|
|
82
|
-
*/
|
|
83
|
-
function createBrokenLinkSource() {
|
|
84
|
-
return ['# Patram', '', 'See [missing](./missing.md).'].join('\n');
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* @returns {string}
|
|
89
|
-
*/
|
|
90
|
-
function createValidLinkSource() {
|
|
91
|
-
return ['# Patram', '', 'See [guide](./guide.md).'].join('\n');
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
/**
|
|
95
|
-
* @returns {{ original_working_directory: string, project_directory: string | null }}
|
|
96
|
-
*/
|
|
97
|
-
function createTestContext() {
|
|
98
|
-
return {
|
|
99
|
-
original_working_directory: process.cwd(),
|
|
100
|
-
project_directory: null,
|
|
101
|
-
};
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
/**
|
|
105
|
-
* @returns {{ stderr: { write(chunk: string): boolean }, stderr_chunks: string[], stdout: { write(chunk: string): boolean }, stdout_chunks: string[] }}
|
|
106
|
-
*/
|
|
107
|
-
function createIoContext() {
|
|
108
|
-
/** @type {string[]} */
|
|
109
|
-
const stderr_chunks = [];
|
|
110
|
-
/** @type {string[]} */
|
|
111
|
-
const stdout_chunks = [];
|
|
112
|
-
|
|
113
|
-
return {
|
|
114
|
-
stderr: {
|
|
115
|
-
/**
|
|
116
|
-
* @param {string} chunk
|
|
117
|
-
* @returns {boolean}
|
|
118
|
-
*/
|
|
119
|
-
write(chunk) {
|
|
120
|
-
stderr_chunks.push(chunk);
|
|
121
|
-
|
|
122
|
-
return true;
|
|
123
|
-
},
|
|
124
|
-
},
|
|
125
|
-
stderr_chunks,
|
|
126
|
-
stdout: {
|
|
127
|
-
/**
|
|
128
|
-
* @param {string} chunk
|
|
129
|
-
* @returns {boolean}
|
|
130
|
-
*/
|
|
131
|
-
write(chunk) {
|
|
132
|
-
stdout_chunks.push(chunk);
|
|
133
|
-
|
|
134
|
-
return true;
|
|
135
|
-
},
|
|
136
|
-
},
|
|
137
|
-
stdout_chunks,
|
|
138
|
-
};
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
/**
|
|
142
|
-
* @returns {Promise<string>}
|
|
143
|
-
*/
|
|
144
|
-
async function createTempProjectDirectory() {
|
|
145
|
-
return mkdtemp(join(tmpdir(), 'patram-check-command-'));
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
/**
|
|
149
|
-
* @param {{ original_working_directory: string, project_directory: string | null }} test_context
|
|
150
|
-
*/
|
|
151
|
-
async function cleanupTestContext(test_context) {
|
|
152
|
-
process.chdir(test_context.original_working_directory);
|
|
153
|
-
|
|
154
|
-
if (test_context.project_directory) {
|
|
155
|
-
await rm(test_context.project_directory, { force: true, recursive: true });
|
|
156
|
-
test_context.project_directory = null;
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
/**
|
|
161
|
-
* @param {string} project_directory
|
|
162
|
-
*/
|
|
163
|
-
async function writeProjectConfig(project_directory) {
|
|
164
|
-
await writeFile(
|
|
165
|
-
join(project_directory, '.patram.json'),
|
|
166
|
-
JSON.stringify({
|
|
167
|
-
include: ['docs/**/*.md'],
|
|
168
|
-
queries: {},
|
|
169
|
-
}),
|
|
170
|
-
);
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
/**
|
|
174
|
-
* @param {string} project_directory
|
|
175
|
-
* @param {string} relative_path
|
|
176
|
-
* @param {string} source_text
|
|
177
|
-
*/
|
|
178
|
-
async function writeProjectFile(project_directory, relative_path, source_text) {
|
|
179
|
-
const file_path = join(project_directory, relative_path);
|
|
180
|
-
const directory_path = file_path.slice(0, file_path.lastIndexOf('/'));
|
|
181
|
-
|
|
182
|
-
await mkdir(directory_path, { recursive: true });
|
|
183
|
-
await writeFile(file_path, source_text);
|
|
184
|
-
}
|
package/lib/build-graph.test.js
DELETED
|
@@ -1,141 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @import { PatramClaim } from './parse-claims.types.ts';
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { expect, it } from 'vitest';
|
|
6
|
-
|
|
7
|
-
import graph_v0_config from '../docs/graph-v0.config.json' with { type: 'json' };
|
|
8
|
-
import { buildGraph } from './build-graph.js';
|
|
9
|
-
import { parseClaims } from './parse-claims.js';
|
|
10
|
-
import { parsePatramConfig } from './patram-config.js';
|
|
11
|
-
|
|
12
|
-
const patram_config = parsePatramConfig(graph_v0_config);
|
|
13
|
-
|
|
14
|
-
it('builds document nodes, configured target nodes and edges from claims', () => {
|
|
15
|
-
const graph = buildGraph(patram_config, createMarkdownClaims());
|
|
16
|
-
|
|
17
|
-
expect(graph).toEqual(createExpectedGraph());
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
it('keeps source document nodes even when a claim has no mapping', () => {
|
|
21
|
-
const graph = buildGraph(patram_config, [
|
|
22
|
-
{
|
|
23
|
-
document_id: 'doc:docs/orphan.md',
|
|
24
|
-
id: 'claim:doc:docs/orphan.md:1',
|
|
25
|
-
origin: {
|
|
26
|
-
column: 1,
|
|
27
|
-
line: 2,
|
|
28
|
-
path: 'docs/orphan.md',
|
|
29
|
-
},
|
|
30
|
-
type: 'directive',
|
|
31
|
-
value: 'ignored',
|
|
32
|
-
},
|
|
33
|
-
]);
|
|
34
|
-
|
|
35
|
-
expect(graph).toEqual({
|
|
36
|
-
edges: [],
|
|
37
|
-
nodes: {
|
|
38
|
-
'doc:docs/orphan.md': {
|
|
39
|
-
id: 'doc:docs/orphan.md',
|
|
40
|
-
kind: 'document',
|
|
41
|
-
path: 'docs/orphan.md',
|
|
42
|
-
},
|
|
43
|
-
},
|
|
44
|
-
});
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Create markdown claims for graph materialization tests.
|
|
49
|
-
*
|
|
50
|
-
* @returns {PatramClaim[]}
|
|
51
|
-
*/
|
|
52
|
-
function createMarkdownClaims() {
|
|
53
|
-
return parseClaims({
|
|
54
|
-
path: 'docs/patram.md',
|
|
55
|
-
source: createMarkdownSource(),
|
|
56
|
-
});
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* Create markdown input for graph materialization tests.
|
|
61
|
-
*
|
|
62
|
-
* @returns {string}
|
|
63
|
-
*/
|
|
64
|
-
function createMarkdownSource() {
|
|
65
|
-
return [
|
|
66
|
-
'# Patram',
|
|
67
|
-
'',
|
|
68
|
-
'Read the [graph design](./graph-v0.md).',
|
|
69
|
-
'Defined by: terms/patram.md',
|
|
70
|
-
].join('\n');
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* Create the expected graph for the materialization test.
|
|
75
|
-
*
|
|
76
|
-
* @returns {object}
|
|
77
|
-
*/
|
|
78
|
-
function createExpectedGraph() {
|
|
79
|
-
return {
|
|
80
|
-
edges: createExpectedEdges(),
|
|
81
|
-
nodes: createExpectedNodes(),
|
|
82
|
-
};
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
/**
|
|
86
|
-
* Create the expected edges for the materialization test.
|
|
87
|
-
*
|
|
88
|
-
* @returns {object[]}
|
|
89
|
-
*/
|
|
90
|
-
function createExpectedEdges() {
|
|
91
|
-
return [
|
|
92
|
-
{
|
|
93
|
-
from: 'doc:docs/patram.md',
|
|
94
|
-
id: 'edge:1',
|
|
95
|
-
origin: {
|
|
96
|
-
column: 10,
|
|
97
|
-
line: 3,
|
|
98
|
-
path: 'docs/patram.md',
|
|
99
|
-
},
|
|
100
|
-
relation: 'links_to',
|
|
101
|
-
to: 'doc:docs/graph-v0.md',
|
|
102
|
-
},
|
|
103
|
-
{
|
|
104
|
-
from: 'doc:docs/patram.md',
|
|
105
|
-
id: 'edge:2',
|
|
106
|
-
origin: {
|
|
107
|
-
column: 1,
|
|
108
|
-
line: 4,
|
|
109
|
-
path: 'docs/patram.md',
|
|
110
|
-
},
|
|
111
|
-
relation: 'defines',
|
|
112
|
-
to: 'term:docs/terms/patram.md',
|
|
113
|
-
},
|
|
114
|
-
];
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
/**
|
|
118
|
-
* Create the expected nodes for the materialization test.
|
|
119
|
-
*
|
|
120
|
-
* @returns {object}
|
|
121
|
-
*/
|
|
122
|
-
function createExpectedNodes() {
|
|
123
|
-
return {
|
|
124
|
-
'doc:docs/graph-v0.md': {
|
|
125
|
-
id: 'doc:docs/graph-v0.md',
|
|
126
|
-
kind: 'document',
|
|
127
|
-
path: 'docs/graph-v0.md',
|
|
128
|
-
},
|
|
129
|
-
'doc:docs/patram.md': {
|
|
130
|
-
id: 'doc:docs/patram.md',
|
|
131
|
-
kind: 'document',
|
|
132
|
-
path: 'docs/patram.md',
|
|
133
|
-
title: 'Patram',
|
|
134
|
-
},
|
|
135
|
-
'term:docs/terms/patram.md': {
|
|
136
|
-
id: 'term:docs/terms/patram.md',
|
|
137
|
-
key: 'docs/terms/patram.md',
|
|
138
|
-
kind: 'term',
|
|
139
|
-
},
|
|
140
|
-
};
|
|
141
|
-
}
|
package/lib/check-graph.test.js
DELETED
|
@@ -1,103 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @import { BuildGraphResult } from './build-graph.types.ts';
|
|
3
|
-
*/
|
|
4
|
-
import { expect, it } from 'vitest';
|
|
5
|
-
|
|
6
|
-
import { checkGraph } from './check-graph.js';
|
|
7
|
-
|
|
8
|
-
it('reports broken document links', () => {
|
|
9
|
-
const diagnostics = checkGraph(createGraphWithBrokenLink(), [
|
|
10
|
-
'docs/patram.md',
|
|
11
|
-
]);
|
|
12
|
-
|
|
13
|
-
expect(diagnostics).toEqual([
|
|
14
|
-
{
|
|
15
|
-
code: 'graph.link_broken',
|
|
16
|
-
column: 10,
|
|
17
|
-
level: 'error',
|
|
18
|
-
line: 3,
|
|
19
|
-
message: 'Document link target "docs/missing.md" was not found.',
|
|
20
|
-
path: 'docs/patram.md',
|
|
21
|
-
},
|
|
22
|
-
]);
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
it('reports graph edges that reference missing nodes', () => {
|
|
26
|
-
const diagnostics = checkGraph(createGraphWithMissingTargetNode(), [
|
|
27
|
-
'docs/patram.md',
|
|
28
|
-
]);
|
|
29
|
-
|
|
30
|
-
expect(diagnostics).toEqual([
|
|
31
|
-
{
|
|
32
|
-
code: 'graph.edge_missing_to',
|
|
33
|
-
column: 8,
|
|
34
|
-
level: 'error',
|
|
35
|
-
line: 6,
|
|
36
|
-
message:
|
|
37
|
-
'Graph edge "edge:1" references missing target node "doc:docs/missing.md".',
|
|
38
|
-
path: 'docs/patram.md',
|
|
39
|
-
},
|
|
40
|
-
]);
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* @returns {BuildGraphResult}
|
|
45
|
-
*/
|
|
46
|
-
function createGraphWithBrokenLink() {
|
|
47
|
-
return {
|
|
48
|
-
edges: [
|
|
49
|
-
{
|
|
50
|
-
from: 'doc:docs/patram.md',
|
|
51
|
-
id: 'edge:1',
|
|
52
|
-
origin: {
|
|
53
|
-
column: 10,
|
|
54
|
-
line: 3,
|
|
55
|
-
path: 'docs/patram.md',
|
|
56
|
-
},
|
|
57
|
-
relation: 'links_to',
|
|
58
|
-
to: 'doc:docs/missing.md',
|
|
59
|
-
},
|
|
60
|
-
],
|
|
61
|
-
nodes: {
|
|
62
|
-
'doc:docs/missing.md': {
|
|
63
|
-
id: 'doc:docs/missing.md',
|
|
64
|
-
kind: 'document',
|
|
65
|
-
path: 'docs/missing.md',
|
|
66
|
-
},
|
|
67
|
-
'doc:docs/patram.md': {
|
|
68
|
-
id: 'doc:docs/patram.md',
|
|
69
|
-
kind: 'document',
|
|
70
|
-
path: 'docs/patram.md',
|
|
71
|
-
title: 'Patram',
|
|
72
|
-
},
|
|
73
|
-
},
|
|
74
|
-
};
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* @returns {BuildGraphResult}
|
|
79
|
-
*/
|
|
80
|
-
function createGraphWithMissingTargetNode() {
|
|
81
|
-
return {
|
|
82
|
-
edges: [
|
|
83
|
-
{
|
|
84
|
-
from: 'doc:docs/patram.md',
|
|
85
|
-
id: 'edge:1',
|
|
86
|
-
origin: {
|
|
87
|
-
column: 8,
|
|
88
|
-
line: 6,
|
|
89
|
-
path: 'docs/patram.md',
|
|
90
|
-
},
|
|
91
|
-
relation: 'links_to',
|
|
92
|
-
to: 'doc:docs/missing.md',
|
|
93
|
-
},
|
|
94
|
-
],
|
|
95
|
-
nodes: {
|
|
96
|
-
'doc:docs/patram.md': {
|
|
97
|
-
id: 'doc:docs/patram.md',
|
|
98
|
-
kind: 'document',
|
|
99
|
-
path: 'docs/patram.md',
|
|
100
|
-
},
|
|
101
|
-
},
|
|
102
|
-
};
|
|
103
|
-
}
|
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
import { mkdtemp, mkdir, 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 { listSourceFiles } from './list-source-files.js';
|
|
8
|
-
|
|
9
|
-
const test_context = createTestContext();
|
|
10
|
-
|
|
11
|
-
afterEach(async () => {
|
|
12
|
-
await cleanupTestContext(test_context);
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
it('lists matching source files as sorted repo-relative paths', async () => {
|
|
16
|
-
test_context.project_directory = await createTempProjectDirectory();
|
|
17
|
-
|
|
18
|
-
await writeProjectFile(test_context.project_directory, 'docs/zeta.md');
|
|
19
|
-
await writeProjectFile(test_context.project_directory, 'docs/alpha.md');
|
|
20
|
-
await writeProjectFile(
|
|
21
|
-
test_context.project_directory,
|
|
22
|
-
'docs/nested/overview.md',
|
|
23
|
-
);
|
|
24
|
-
await writeProjectFile(test_context.project_directory, 'docs/notes.txt');
|
|
25
|
-
await writeProjectFile(test_context.project_directory, 'notes/todo.md');
|
|
26
|
-
|
|
27
|
-
const source_files = await listSourceFiles(
|
|
28
|
-
['docs/**/*.md'],
|
|
29
|
-
test_context.project_directory,
|
|
30
|
-
);
|
|
31
|
-
|
|
32
|
-
expect(source_files).toEqual([
|
|
33
|
-
'docs/alpha.md',
|
|
34
|
-
'docs/nested/overview.md',
|
|
35
|
-
'docs/zeta.md',
|
|
36
|
-
]);
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
it('deduplicates paths matched by multiple include globs', async () => {
|
|
40
|
-
test_context.project_directory = await createTempProjectDirectory();
|
|
41
|
-
|
|
42
|
-
await writeProjectFile(test_context.project_directory, 'docs/patram.md');
|
|
43
|
-
|
|
44
|
-
const source_files = await listSourceFiles(
|
|
45
|
-
['docs/**/*.md', 'docs/patram.md'],
|
|
46
|
-
test_context.project_directory,
|
|
47
|
-
);
|
|
48
|
-
|
|
49
|
-
expect(source_files).toEqual(['docs/patram.md']);
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* Create a temporary project directory.
|
|
54
|
-
*
|
|
55
|
-
* @returns {Promise<string>}
|
|
56
|
-
*/
|
|
57
|
-
async function createTempProjectDirectory() {
|
|
58
|
-
return mkdtemp(join(tmpdir(), 'patram-list-source-files-'));
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Write a file under the temporary project directory.
|
|
63
|
-
*
|
|
64
|
-
* @param {string} project_directory
|
|
65
|
-
* @param {string} relative_path
|
|
66
|
-
*/
|
|
67
|
-
async function writeProjectFile(project_directory, relative_path) {
|
|
68
|
-
const file_path = join(project_directory, relative_path);
|
|
69
|
-
const parent_directory = file_path.slice(0, file_path.lastIndexOf('/'));
|
|
70
|
-
|
|
71
|
-
await mkdir(parent_directory, { recursive: true });
|
|
72
|
-
await writeFile(file_path, `# ${relative_path}\n`);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* Remove a temporary directory tree.
|
|
77
|
-
*
|
|
78
|
-
* @param {string} project_directory
|
|
79
|
-
*/
|
|
80
|
-
async function removeDirectory(project_directory) {
|
|
81
|
-
await rm(project_directory, { force: true, recursive: true });
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
* @returns {{ project_directory: string | null }}
|
|
86
|
-
*/
|
|
87
|
-
function createTestContext() {
|
|
88
|
-
return {
|
|
89
|
-
project_directory: null,
|
|
90
|
-
};
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
* @param {{ project_directory: string | null }} test_context
|
|
95
|
-
*/
|
|
96
|
-
async function cleanupTestContext(test_context) {
|
|
97
|
-
if (test_context.project_directory) {
|
|
98
|
-
await removeDirectory(test_context.project_directory);
|
|
99
|
-
test_context.project_directory = null;
|
|
100
|
-
}
|
|
101
|
-
}
|