-
-
Notifications
You must be signed in to change notification settings - Fork 86
Expand file tree
/
Copy pathbuild-browser-scripts-doc.mjs
More file actions
137 lines (118 loc) · 4.38 KB
/
Copy pathbuild-browser-scripts-doc.mjs
File metadata and controls
137 lines (118 loc) · 4.38 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
#!/usr/bin/env node
// Copyright (c) 2024-2026 nich (@nichxbt). Licensed under the Apache License, Version 2.0.
/**
* Regenerate the browser-script catalog inside docs/browser-scripts.md.
*
* The catalog is derived from the scripts themselves rather than maintained by
* hand, because a hand-kept list of ~100 files is a list that is wrong within a
* month. Each browser script carries a three-line header:
*
* // scripts/keywordLiker.js
* // Like only posts containing specific keywords — with a prompt input box
* // Paste in DevTools console on x.com/home or any feed/search page
*
* Line 2 becomes the description, line 3 becomes the "Run it on" column.
*
* Only the region between the AUTOGEN markers is rewritten; the prose around it
* is hand-written and preserved.
*
* Usage:
* node scripts/build-browser-scripts-doc.mjs
* node scripts/build-browser-scripts-doc.mjs --check # fail if out of date
*
* @author nich (@nichxbt) - https://github.com/nirholas
* @see https://xactions.app
* @license Apache-2.0
*/
import { readFileSync, writeFileSync, readdirSync } from 'node:fs';
import { join, resolve, dirname } from 'node:path';
import { fileURLToPath } from 'node:url';
const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..');
const SCRIPTS_DIR = join(ROOT, 'scripts');
const DOC = join(ROOT, 'docs', 'browser-scripts.md');
const START = '<!-- AUTOGEN:SCRIPTS start -->';
const END = '<!-- AUTOGEN:SCRIPTS end -->';
const checkOnly = process.argv.includes('--check');
/**
* Read the header metadata out of one browser script.
*
* @param {string} file - File name inside scripts/
* @returns {{file: string, name: string, description: string, page: string}|null}
* Null when the file is not a browser console script.
*/
function parseScript(file) {
const source = readFileSync(join(SCRIPTS_DIR, file), 'utf8');
const head = source.split('\n').slice(0, 8);
const pasteLine = head.find((l) => /paste in devtools/i.test(l));
if (!pasteLine) return null;
const commentLines = head
.filter((l) => l.trim().startsWith('//'))
.map((l) => l.replace(/^\s*\/\/\s?/, '').trim())
.filter((l) => l.length > 0 && !/^copyright/i.test(l));
// First comment naming the file itself, then the description, then the
// "Paste in DevTools ..." line.
const description =
commentLines.find(
(l) => l !== `scripts/${file}` && !/paste in devtools/i.test(l) && !/^by /i.test(l),
) ?? '';
const page = pasteLine
.replace(/^\s*\/\/\s?/, '')
.replace(/^Paste in DevTools console on\s*/i, '')
.trim();
return {
file,
name: file.replace(/\.js$/, ''),
description: description.replace(/^Browser console script for /i, '') || file,
page,
};
}
const scripts = readdirSync(SCRIPTS_DIR)
.filter((f) => f.endsWith('.js'))
.map(parseScript)
.filter(Boolean)
.sort((a, b) => a.name.localeCompare(b.name));
const table = [
`_${scripts.length} scripts. Generated by \`npm run docs:scripts\` from the header of each file in [\`scripts/\`](../scripts/)._`,
'',
'| Script | What it does | Run it on |',
'|--------|--------------|-----------|',
...scripts.map(
(s) =>
`| [\`${s.name}\`](../scripts/${s.file}) | ${escapePipes(capitalize(s.description))} | \`${escapePipes(s.page)}\` |`,
),
].join('\n');
const existing = readFileSync(DOC, 'utf8');
const startIndex = existing.indexOf(START);
const endIndex = existing.indexOf(END);
if (startIndex === -1 || endIndex === -1) {
console.error(`Markers not found in ${DOC}. Expected ${START} and ${END}.`);
process.exit(1);
}
const updated =
existing.slice(0, startIndex + START.length) + '\n\n' + table + '\n\n' + existing.slice(endIndex);
if (checkOnly) {
if (updated === existing) {
console.log(`docs/browser-scripts.md is up to date (${scripts.length} scripts).`);
process.exit(0);
}
console.error('docs/browser-scripts.md is out of date. Run: npm run docs:scripts');
process.exit(1);
}
writeFileSync(DOC, updated);
console.log(`Wrote ${scripts.length} scripts into docs/browser-scripts.md`);
/**
* Escape pipes so a description cannot break the markdown table.
* @param {string} text
* @returns {string}
*/
function escapePipes(text) {
return text.replace(/\|/g, '\\|');
}
/**
* Capitalise the first letter, leaving the rest alone.
* @param {string} text
* @returns {string}
*/
function capitalize(text) {
return text.charAt(0).toUpperCase() + text.slice(1);
}