@rtorcato/repo-tooling 3.13.2 → 3.15.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/AGENTS.md +2 -0
- package/dist/base/checks.js +24 -5
- package/dist/base/fixers.js +44 -1
- package/dist/base/github-settings.js +269 -0
- package/dist/base/labels.js +205 -0
- package/dist/cli/commands/doctor.js +7 -0
- package/dist/cli/commands/fix-targets.js +2 -0
- package/dist/cli/commands/fix.js +35 -9
- package/dist/cli/generators/security.js +90 -9
- package/dist/cli/utils/copied-assets.js +79 -0
- package/dist/cli/utils/copy-preset.js +23 -0
- package/dist/cli/utils/lockfile.js +26 -3
- package/package.json +2 -1
- package/skills/ai-issue-loop/SKILL.md +71 -28
- package/tooling/docusaurus/docs-helpers.mjs +109 -0
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
// Canonical docs-generator helpers for @rtorcato/* repos (shipped by
|
|
2
|
+
// @rtorcato/repo-tooling — copy via `repo-tooling copy docusaurus-docs-helpers`).
|
|
3
|
+
//
|
|
4
|
+
// The pure pieces every subpath-exports package's doc generator needs, so the
|
|
5
|
+
// generator script above them stays small and project-specific:
|
|
6
|
+
//
|
|
7
|
+
// escapeForMarkdownTable(text) — make a JSDoc summary safe for a table cell
|
|
8
|
+
// collectExportNames(file) — recursive `export` parser over a module graph
|
|
9
|
+
// spliceGeneratedBlock(existing, block) — rewrite only the fenced generated region
|
|
10
|
+
//
|
|
11
|
+
// Zero-config and side-effect free: no paths, no package names, no I/O beyond
|
|
12
|
+
// reading the files you hand it, so this file is copied unmodified.
|
|
13
|
+
//
|
|
14
|
+
// import {
|
|
15
|
+
// collectExportNames,
|
|
16
|
+
// escapeForMarkdownTable,
|
|
17
|
+
// MARKER_END,
|
|
18
|
+
// MARKER_START,
|
|
19
|
+
// spliceGeneratedBlock,
|
|
20
|
+
// } from './docs-helpers.mjs'
|
|
21
|
+
|
|
22
|
+
import { existsSync, readFileSync } from 'node:fs'
|
|
23
|
+
import { dirname, join, resolve } from 'node:path'
|
|
24
|
+
|
|
25
|
+
const EXPORT_NAMED =
|
|
26
|
+
/export\s+(?:async\s+)?(?:function|const|let|class|type|interface|enum)\s+([A-Za-z_$][\w$]*)/g
|
|
27
|
+
const EXPORT_BRACE = /export\s*\{\s*([^}]+)\}/g
|
|
28
|
+
// Both re-export forms, any specifier — `export * from …` and
|
|
29
|
+
// `export { … } from …`. Non-relative specifiers are filtered below rather than
|
|
30
|
+
// in the pattern, so a bare-package re-export is recognised and then skipped
|
|
31
|
+
// instead of silently parsed as nothing.
|
|
32
|
+
const REEXPORT_FROM = /export\s+(?:\*|\{[^}]*\})\s+from\s+['"]([^'"]+)['"]/g
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Escape `text` so it survives a markdown table cell.
|
|
36
|
+
*
|
|
37
|
+
* Neutralises raw HTML-ish tags outside code spans, which would otherwise
|
|
38
|
+
* confuse Docusaurus's MDX parser — but keeps them inside backticks, where
|
|
39
|
+
* `Success<T>` and `<br>` are the point. Then escapes pipes everywhere, since
|
|
40
|
+
* one anywhere in the cell breaks the row.
|
|
41
|
+
*
|
|
42
|
+
* Escaping the `<` beats stripping `/<[^>]*>/`: a one-pass tag strip can leave
|
|
43
|
+
* a tag behind on nested input (`<<b>>` -> `<b>`) and silently eats text like
|
|
44
|
+
* `Success<T>` when the JSDoc forgot the backticks.
|
|
45
|
+
*
|
|
46
|
+
* Escape the backslash in the same pass as the pipe, not after it: escaping
|
|
47
|
+
* only `|` turns the input `a\|b` into `a\\|b`, which markdown reads as an
|
|
48
|
+
* escaped backslash followed by a live pipe, and the row breaks anyway.
|
|
49
|
+
*/
|
|
50
|
+
export function escapeForMarkdownTable(text) {
|
|
51
|
+
return text
|
|
52
|
+
.split(/(`[^`]*`)/)
|
|
53
|
+
.map((part, i) => (i % 2 === 1 ? part : part.replace(/</g, '<')))
|
|
54
|
+
.join('')
|
|
55
|
+
.replace(/[\\|]/g, '\\$&')
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Collect every name `file` exports, following relative re-exports into the
|
|
60
|
+
* files they name. Returns the accumulating `names` set; `seen` guards against
|
|
61
|
+
* an import cycle re-entering a file.
|
|
62
|
+
*
|
|
63
|
+
* For an aliased export the *alias* is the exported name — `export { foo as
|
|
64
|
+
* bar }` exports `bar`, which is what a consumer imports — so this takes the
|
|
65
|
+
* last segment, not the first.
|
|
66
|
+
*/
|
|
67
|
+
export function collectExportNames(file, names = new Set(), seen = new Set()) {
|
|
68
|
+
if (seen.has(file) || !existsSync(file)) return names
|
|
69
|
+
seen.add(file)
|
|
70
|
+
const src = readFileSync(file, 'utf8')
|
|
71
|
+
|
|
72
|
+
for (const m of src.matchAll(EXPORT_NAMED)) names.add(m[1])
|
|
73
|
+
for (const m of src.matchAll(EXPORT_BRACE)) {
|
|
74
|
+
for (const part of m[1].split(',')) {
|
|
75
|
+
const name = part
|
|
76
|
+
.trim()
|
|
77
|
+
.split(/\s+as\s+/)
|
|
78
|
+
.pop()
|
|
79
|
+
?.trim()
|
|
80
|
+
if (name) names.add(name)
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
for (const m of src.matchAll(REEXPORT_FROM)) {
|
|
84
|
+
if (!m[1].startsWith('.')) continue
|
|
85
|
+
const base = resolve(dirname(file), m[1].replace(/\.js$/, ''))
|
|
86
|
+
for (const candidate of [`${base}.ts`, `${base}.tsx`, join(base, 'index.ts')]) {
|
|
87
|
+
if (existsSync(candidate)) {
|
|
88
|
+
collectExportNames(candidate, names, seen)
|
|
89
|
+
break
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return names
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export const MARKER_START =
|
|
97
|
+
'<!-- generated:exports — do not edit; `pnpm docs:generate` rewrites this block -->'
|
|
98
|
+
export const MARKER_END = '<!-- /generated:exports -->'
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Splice `block` into `existing` between the markers. Returns null when the
|
|
102
|
+
* page has no markers, which means "hand-written, leave it alone".
|
|
103
|
+
*/
|
|
104
|
+
export function spliceGeneratedBlock(existing, block) {
|
|
105
|
+
const start = existing.indexOf(MARKER_START)
|
|
106
|
+
const end = existing.indexOf(MARKER_END)
|
|
107
|
+
if (start === -1 || end === -1 || end < start) return null
|
|
108
|
+
return existing.slice(0, start) + block + existing.slice(end + MARKER_END.length)
|
|
109
|
+
}
|