@readme/cli 0.0.26
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/README.md +55 -0
- package/bin/readme.js +8 -0
- package/package.json +58 -0
- package/src/bootstrap.js +97 -0
- package/src/cli.js +189 -0
- package/src/commands/dev.js +119 -0
- package/src/commands/eyes.js +37 -0
- package/src/commands/import.js +2565 -0
- package/src/commands/lint.js +70 -0
- package/src/commands/oas-sync.js +364 -0
- package/src/commands/oas-validate.js +208 -0
- package/src/commands/play.js +17 -0
- package/src/commands/pretty.js +133 -0
- package/src/commands/setup.js +256 -0
- package/src/commands/versions.js +81 -0
- package/src/dev/.next/app-build-manifest.json +20 -0
- package/src/dev/.next/build-manifest.json +31 -0
- package/src/dev/.next/cache/.rscinfo +1 -0
- package/src/dev/.next/cache/next-devtools-config.json +1 -0
- package/src/dev/.next/cache/webpack/client-development/0.pack.gz +0 -0
- package/src/dev/.next/cache/webpack/client-development/1.pack.gz +0 -0
- package/src/dev/.next/cache/webpack/client-development/10.pack.gz +0 -0
- package/src/dev/.next/cache/webpack/client-development/11.pack.gz +0 -0
- package/src/dev/.next/cache/webpack/client-development/2.pack.gz +0 -0
- package/src/dev/.next/cache/webpack/client-development/3.pack.gz +0 -0
- package/src/dev/.next/cache/webpack/client-development/3.pack.gz_ +0 -0
- package/src/dev/.next/cache/webpack/client-development/4.pack.gz +0 -0
- package/src/dev/.next/cache/webpack/client-development/5.pack.gz +0 -0
- package/src/dev/.next/cache/webpack/client-development/5.pack.gz_ +0 -0
- package/src/dev/.next/cache/webpack/client-development/6.pack.gz +0 -0
- package/src/dev/.next/cache/webpack/client-development/7.pack.gz +0 -0
- package/src/dev/.next/cache/webpack/client-development/7.pack.gz_ +0 -0
- package/src/dev/.next/cache/webpack/client-development/8.pack.gz +0 -0
- package/src/dev/.next/cache/webpack/client-development/9.pack.gz +0 -0
- package/src/dev/.next/cache/webpack/client-development/index.pack.gz.old +0 -0
- package/src/dev/.next/cache/webpack/client-development-fallback/0.pack.gz +0 -0
- package/src/dev/.next/cache/webpack/client-development-fallback/1.pack.gz +0 -0
- package/src/dev/.next/cache/webpack/client-development-fallback/index.pack.gz +0 -0
- package/src/dev/.next/cache/webpack/client-development-fallback/index.pack.gz.old +0 -0
- package/src/dev/.next/cache/webpack/edge-server-development/0.pack.gz +0 -0
- package/src/dev/.next/cache/webpack/edge-server-development/1.pack.gz +0 -0
- package/src/dev/.next/cache/webpack/edge-server-development/index.pack.gz +0 -0
- package/src/dev/.next/cache/webpack/edge-server-development/index.pack.gz.old +0 -0
- package/src/dev/.next/cache/webpack/server-development/0.pack.gz +0 -0
- package/src/dev/.next/cache/webpack/server-development/1.pack.gz +0 -0
- package/src/dev/.next/cache/webpack/server-development/10.pack.gz +0 -0
- package/src/dev/.next/cache/webpack/server-development/11.pack.gz +0 -0
- package/src/dev/.next/cache/webpack/server-development/12.pack.gz +0 -0
- package/src/dev/.next/cache/webpack/server-development/13.pack.gz +0 -0
- package/src/dev/.next/cache/webpack/server-development/14.pack.gz +0 -0
- package/src/dev/.next/cache/webpack/server-development/15.pack.gz +0 -0
- package/src/dev/.next/cache/webpack/server-development/2.pack.gz +0 -0
- package/src/dev/.next/cache/webpack/server-development/2.pack.gz_ +0 -0
- package/src/dev/.next/cache/webpack/server-development/3.pack.gz +0 -0
- package/src/dev/.next/cache/webpack/server-development/3.pack.gz_ +0 -0
- package/src/dev/.next/cache/webpack/server-development/4.pack.gz +0 -0
- package/src/dev/.next/cache/webpack/server-development/5.pack.gz +0 -0
- package/src/dev/.next/cache/webpack/server-development/6.pack.gz +0 -0
- package/src/dev/.next/cache/webpack/server-development/6.pack.gz_ +0 -0
- package/src/dev/.next/cache/webpack/server-development/7.pack.gz +0 -0
- package/src/dev/.next/cache/webpack/server-development/7.pack.gz_ +0 -0
- package/src/dev/.next/cache/webpack/server-development/8.pack.gz +0 -0
- package/src/dev/.next/cache/webpack/server-development/9.pack.gz +0 -0
- package/src/dev/.next/cache/webpack/server-development/9.pack.gz_ +0 -0
- package/src/dev/.next/cache/webpack/server-development/index.pack.gz +0 -0
- package/src/dev/.next/cache/webpack/server-development/index.pack.gz.old +0 -0
- package/src/dev/.next/package.json +1 -0
- package/src/dev/.next/prerender-manifest.json +11 -0
- package/src/dev/.next/react-loadable-manifest.json +1 -0
- package/src/dev/.next/routes-manifest.json +1 -0
- package/src/dev/.next/server/app/[...slug]/page.js +360 -0
- package/src/dev/.next/server/app/[...slug]/page_client-reference-manifest.js +1 -0
- package/src/dev/.next/server/app/page.js +349 -0
- package/src/dev/.next/server/app/page_client-reference-manifest.js +1 -0
- package/src/dev/.next/server/app-paths-manifest.json +3 -0
- package/src/dev/.next/server/edge-runtime-webpack.js +1151 -0
- package/src/dev/.next/server/interception-route-rewrite-manifest.js +1 -0
- package/src/dev/.next/server/middleware-build-manifest.js +33 -0
- package/src/dev/.next/server/middleware-manifest.json +32 -0
- package/src/dev/.next/server/middleware-react-loadable-manifest.js +1 -0
- package/src/dev/.next/server/middleware.js +1113 -0
- package/src/dev/.next/server/next-font-manifest.js +1 -0
- package/src/dev/.next/server/next-font-manifest.json +1 -0
- package/src/dev/.next/server/pages-manifest.json +5 -0
- package/src/dev/.next/server/server-reference-manifest.js +1 -0
- package/src/dev/.next/server/server-reference-manifest.json +5 -0
- package/src/dev/.next/server/static/webpack/633457081244afec._.hot-update.json +1 -0
- package/src/dev/.next/server/vendor-chunks/@readme.js +25 -0
- package/src/dev/.next/server/vendor-chunks/@swc.js +55 -0
- package/src/dev/.next/server/vendor-chunks/next.js +3659 -0
- package/src/dev/.next/server/webpack-runtime.js +209 -0
- package/src/dev/.next/static/chunks/app/[...slug]/loading.js +28 -0
- package/src/dev/.next/static/chunks/app/[...slug]/page.js +28 -0
- package/src/dev/.next/static/chunks/app/layout.js +171 -0
- package/src/dev/.next/static/chunks/app/page.js +28 -0
- package/src/dev/.next/static/chunks/app-pages-internals.js +182 -0
- package/src/dev/.next/static/chunks/main-app.js +1882 -0
- package/src/dev/.next/static/chunks/polyfills.js +1 -0
- package/src/dev/.next/static/chunks/webpack.js +1393 -0
- package/src/dev/.next/static/css/app/layout.css +559 -0
- package/src/dev/.next/static/development/_buildManifest.js +1 -0
- package/src/dev/.next/static/development/_ssgManifest.js +1 -0
- package/src/dev/.next/static/webpack/633457081244afec._.hot-update.json +1 -0
- package/src/dev/.next/static/webpack/ec52a3fce0f78db0.webpack.hot-update.json +1 -0
- package/src/dev/.next/static/webpack/webpack.ec52a3fce0f78db0.hot-update.js +12 -0
- package/src/dev/.next/trace +21 -0
- package/src/dev/.next/types/app/[...slug]/page.ts +84 -0
- package/src/dev/.next/types/app/layout.ts +84 -0
- package/src/dev/.next/types/app/page.ts +84 -0
- package/src/dev/.next/types/cache-life.d.ts +141 -0
- package/src/dev/.next/types/package.json +1 -0
- package/src/dev/.next/types/routes.d.ts +55 -0
- package/src/dev/app/Sidebar.js +149 -0
- package/src/dev/app/[...slug]/loading.js +16 -0
- package/src/dev/app/[...slug]/page.js +43 -0
- package/src/dev/app/globals.css +167 -0
- package/src/dev/app/layout.js +73 -0
- package/src/dev/app/page.js +19 -0
- package/src/dev/lib/docs.js +337 -0
- package/src/dev/middleware.js +7 -0
- package/src/dev/next.config.mjs +22 -0
- package/src/index.js +12 -0
- package/src/prompts/index.js +352 -0
- package/src/utils/claude.js +15 -0
- package/src/utils/eyes.js +365 -0
- package/src/utils/git.js +143 -0
- package/src/utils/lint.js +99 -0
- package/src/utils/reporter.js +319 -0
- package/src/utils/setup-templates.js +323 -0
- package/src/utils/styles.js +50 -0
- package/src/utils/tamagotchi.js +1139 -0
- package/src/utils/tips.js +90 -0
- package/src/validators/components.js +230 -0
- package/src/validators/content.js +53 -0
- package/src/validators/duplicates.js +45 -0
- package/src/validators/frontmatter.js +247 -0
- package/src/validators/links.js +68 -0
- package/src/validators/nesting.js +50 -0
- package/src/validators/numbering.js +136 -0
- package/src/validators/oas-reference.js +126 -0
- package/src/validators/oas-schema.js +106 -0
- package/src/validators/ordering.js +121 -0
- package/src/validators/recipes.js +143 -0
- package/vendor/TOOLS.md +19 -0
|
@@ -0,0 +1,365 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
|
|
3
|
+
const _ = null;
|
|
4
|
+
|
|
5
|
+
// True when invoked by an agentic coding CLI (Claude Code, OpenAI Codex).
|
|
6
|
+
// Used to skip the "fun" ASCII-eye header and other decorative output.
|
|
7
|
+
export function isAgenticCli() {
|
|
8
|
+
return !!(process.env.CLAUDECODE || process.env.CODEX_HOME || process.env.CODEX_SANDBOX);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// ── Palettes ────────────────────────────────────────────
|
|
12
|
+
|
|
13
|
+
export const palettes = {
|
|
14
|
+
blue: { name: 'Blue', body: '#018EF5', page: '#FFF5E6', pupil: '#003580', lid: '#63D2FF' },
|
|
15
|
+
green: { name: 'Green', body: '#2D9F3F', page: '#F0FFE6', pupil: '#0A4D1A', lid: '#7FE08A' },
|
|
16
|
+
purple: { name: 'Purple', body: '#8B5CF6', page: '#F3EEFF', pupil: '#3B1A8B', lid: '#C4A8FF' },
|
|
17
|
+
orange: { name: 'Orange', body: '#F97316', page: '#FFF5EB', pupil: '#7C2D12', lid: '#FDBA74' },
|
|
18
|
+
pink: { name: 'Pink', body: '#EC4899', page: '#FFF0F6', pupil: '#831843', lid: '#F9A8D4' },
|
|
19
|
+
red: { name: 'Red', body: '#EF4444', page: '#FFF5F5', pupil: '#7F1D1D', lid: '#FCA5A5' },
|
|
20
|
+
teal: { name: 'Teal', body: '#14B8A6', page: '#F0FFFE', pupil: '#134E4A', lid: '#5EEAD4' },
|
|
21
|
+
yellow: { name: 'Yellow', body: '#EAB308', page: '#FEFCE8', pupil: '#713F12', lid: '#FDE047' },
|
|
22
|
+
owl: { name: 'Owl', body: '#8B5A2B', page: '#FFF8EE', pupil: '#2A1000', lid: '#C4923A', nose: '#FFD700', hidden: true },
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
let currentPalette = palettes.blue;
|
|
26
|
+
|
|
27
|
+
export function setPalette(name) {
|
|
28
|
+
if (palettes[name]) {
|
|
29
|
+
currentPalette = palettes[name];
|
|
30
|
+
rebuildExpressions();
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function cell(top, bot) {
|
|
35
|
+
if (top === bot) return top === null ? ' ' : chalk.hex(top)('██');
|
|
36
|
+
if (top === null) return chalk.hex(bot)('▄▄');
|
|
37
|
+
if (bot === null) return chalk.hex(top)('▀▀');
|
|
38
|
+
return chalk.hex(top).bgHex(bot)('▀▀');
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function render(px) {
|
|
42
|
+
const lines = [];
|
|
43
|
+
for (let r = 0; r < px.length; r += 2) {
|
|
44
|
+
let line = '';
|
|
45
|
+
for (let c = 0; c < px[0].length; c++) {
|
|
46
|
+
line += cell(px[r][c], px[r + 1]?.[c] ?? null);
|
|
47
|
+
}
|
|
48
|
+
lines.push(line);
|
|
49
|
+
}
|
|
50
|
+
return lines;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// ── Pixel grids ─────────────────────────────────────────
|
|
54
|
+
|
|
55
|
+
function applyLid(eye, lid, L) {
|
|
56
|
+
// lid: 0 = open, 1 = half, 2 = squint, 3 = closed
|
|
57
|
+
const count = lid === 3 ? 2 : lid;
|
|
58
|
+
for (let i = 0; i < count && i < 2; i++) {
|
|
59
|
+
eye[i][0] = L;
|
|
60
|
+
eye[i][1] = L;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function makeFrame(eyeL, eyeR, lidL = 0, lidR = lidL) {
|
|
65
|
+
const { body: B, page: W, pupil: D, lid: L, nose: N = B } = currentPalette;
|
|
66
|
+
// eyeL/eyeR: [row, col] of pupil within the 2x2 page area
|
|
67
|
+
// row 0 = top, 1 = bottom; col 0 = left, 1 = right
|
|
68
|
+
// lidL/lidR: 0 = open, 1 = half, 2 = squint (most), 3 = closed
|
|
69
|
+
const page = [
|
|
70
|
+
[W, W],
|
|
71
|
+
[W, W],
|
|
72
|
+
];
|
|
73
|
+
|
|
74
|
+
// Place pupils
|
|
75
|
+
const left = page.map(r => [...r]);
|
|
76
|
+
const right = page.map(r => [...r]);
|
|
77
|
+
left[eyeL[0]][eyeL[1]] = D;
|
|
78
|
+
right[eyeR[0]][eyeR[1]] = D;
|
|
79
|
+
|
|
80
|
+
// Apply eyelids per eye
|
|
81
|
+
applyLid(left, lidL, L);
|
|
82
|
+
applyLid(right, lidR, L);
|
|
83
|
+
|
|
84
|
+
return [
|
|
85
|
+
[B, B, B, _, B, B, B],
|
|
86
|
+
[B, left[0][0], left[0][1], B, right[0][0], right[0][1], B],
|
|
87
|
+
[B, left[1][0], left[1][1], B, right[1][0], right[1][1], B],
|
|
88
|
+
[B, left[1][0], left[1][1], B, right[1][0], right[1][1], B],
|
|
89
|
+
[B, B, B, N, B, B, B],
|
|
90
|
+
[_, _, _, N, _, _, _],
|
|
91
|
+
[_, _, _, _, _, _, _],
|
|
92
|
+
];
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Bounce-up: prepend empty row to shift pixel pairing
|
|
96
|
+
function makeBounceUp(px) {
|
|
97
|
+
const empty = new Array(px[0].length).fill(_);
|
|
98
|
+
return [empty, ...px];
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// ── Static expressions ──────────────────────────────────
|
|
102
|
+
|
|
103
|
+
function buildExpressions() {
|
|
104
|
+
return {
|
|
105
|
+
right: makeFrame([1, 1], [1, 1]),
|
|
106
|
+
left: makeFrame([1, 0], [1, 0]),
|
|
107
|
+
'up-right': makeFrame([0, 1], [0, 1]),
|
|
108
|
+
'up-left': makeFrame([0, 0], [0, 0]),
|
|
109
|
+
'half-blink': makeFrame([1, 1], [1, 1], 1),
|
|
110
|
+
'half-blink-left': makeFrame([1, 0], [1, 0], 1),
|
|
111
|
+
'half-blink-right': makeFrame([1, 1], [1, 1], 1),
|
|
112
|
+
squint: makeFrame([1, 1], [1, 1], 2),
|
|
113
|
+
closed: makeFrame([1, 1], [1, 1], 3),
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export let expressions = buildExpressions();
|
|
118
|
+
|
|
119
|
+
function rebuildExpressions() {
|
|
120
|
+
expressions = buildExpressions();
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// ── Animations ──────────────────────────────────────────
|
|
124
|
+
|
|
125
|
+
export const animations = {
|
|
126
|
+
blink: {
|
|
127
|
+
frames: ['right', 'right', 'right', 'right', 'right', 'right', 'half-blink', 'squint', 'closed', 'closed', 'squint', 'half-blink', 'right'],
|
|
128
|
+
durations: [1500, 100, 100, 100, 100, 100, 50, 50, 50, 80, 50, 50, 100],
|
|
129
|
+
},
|
|
130
|
+
|
|
131
|
+
bounce: {
|
|
132
|
+
frames: ['right', 'right', 'right:up', 'right:up', 'right'],
|
|
133
|
+
durations: [400, 200, 200, 200, 200],
|
|
134
|
+
},
|
|
135
|
+
|
|
136
|
+
lookaround: {
|
|
137
|
+
frames: ['right', 'right', 'left', 'left', 'left', 'right', 'right', 'up-right', 'up-right', 'right', 'right', 'up-left', 'up-left', 'left', 'left', 'right'],
|
|
138
|
+
durations: [600, 200, 120, 400, 200, 120, 400, 120, 400, 120, 200, 120, 400, 120, 200, 400],
|
|
139
|
+
},
|
|
140
|
+
|
|
141
|
+
'lookaround-blink': {
|
|
142
|
+
frames: [
|
|
143
|
+
'right', 'right', 'right',
|
|
144
|
+
'left', 'left', 'left',
|
|
145
|
+
'half-blink', 'squint', 'closed', 'closed', 'squint', 'half-blink',
|
|
146
|
+
'left', 'right', 'right',
|
|
147
|
+
'up-right', 'up-right', 'right',
|
|
148
|
+
'right', 'right',
|
|
149
|
+
'up-left', 'up-left', 'left', 'left',
|
|
150
|
+
'half-blink', 'squint', 'closed', 'closed', 'squint', 'half-blink',
|
|
151
|
+
'right', 'right',
|
|
152
|
+
],
|
|
153
|
+
durations: [
|
|
154
|
+
800, 200, 200,
|
|
155
|
+
120, 400, 200,
|
|
156
|
+
50, 50, 50, 80, 50, 50,
|
|
157
|
+
200, 120, 400,
|
|
158
|
+
120, 500, 120,
|
|
159
|
+
200, 200,
|
|
160
|
+
120, 500, 120, 200,
|
|
161
|
+
50, 50, 50, 80, 50, 50,
|
|
162
|
+
120, 600,
|
|
163
|
+
],
|
|
164
|
+
},
|
|
165
|
+
|
|
166
|
+
all: {
|
|
167
|
+
frames: [
|
|
168
|
+
// idle, look around
|
|
169
|
+
'right', 'right', 'right',
|
|
170
|
+
'left', 'left', 'left',
|
|
171
|
+
'right', 'right',
|
|
172
|
+
// blink
|
|
173
|
+
'half-blink', 'squint', 'closed', 'closed', 'squint', 'half-blink',
|
|
174
|
+
// look up + bounce
|
|
175
|
+
'right', 'right',
|
|
176
|
+
'up-right', 'up-right', 'right',
|
|
177
|
+
'right:up', 'right:up', 'right', 'right:up', 'right:up', 'right',
|
|
178
|
+
// look the other way
|
|
179
|
+
'right', 'left', 'left',
|
|
180
|
+
'up-left', 'up-left', 'left',
|
|
181
|
+
// blink
|
|
182
|
+
'half-blink', 'squint', 'closed', 'closed', 'squint', 'half-blink',
|
|
183
|
+
// bounce
|
|
184
|
+
'left', 'left:up', 'left:up', 'left', 'left:up', 'left:up', 'left',
|
|
185
|
+
// settle back + bounce
|
|
186
|
+
'right', 'right', 'right',
|
|
187
|
+
'right:up', 'right:up', 'right',
|
|
188
|
+
// blink
|
|
189
|
+
'half-blink', 'squint', 'closed', 'closed', 'squint', 'half-blink',
|
|
190
|
+
'right', 'right',
|
|
191
|
+
],
|
|
192
|
+
durations: [
|
|
193
|
+
// idle, look around
|
|
194
|
+
800, 200, 200,
|
|
195
|
+
120, 400, 200,
|
|
196
|
+
120, 400,
|
|
197
|
+
// blink
|
|
198
|
+
50, 50, 50, 80, 50, 50,
|
|
199
|
+
// look up + bounce
|
|
200
|
+
200, 200,
|
|
201
|
+
120, 500, 120,
|
|
202
|
+
150, 150, 150, 150, 150, 200,
|
|
203
|
+
// look the other way
|
|
204
|
+
300, 120, 400,
|
|
205
|
+
120, 500, 120,
|
|
206
|
+
// blink
|
|
207
|
+
50, 50, 50, 80, 50, 50,
|
|
208
|
+
// bounce
|
|
209
|
+
200, 150, 150, 150, 150, 150, 200,
|
|
210
|
+
// settle back + bounce
|
|
211
|
+
120, 200, 300,
|
|
212
|
+
150, 150, 200,
|
|
213
|
+
// blink
|
|
214
|
+
50, 50, 50, 80, 50, 50,
|
|
215
|
+
200, 800,
|
|
216
|
+
],
|
|
217
|
+
},
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
// ── Public API ──────────────────────────────────────────
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Get rendered lines for a static expression.
|
|
224
|
+
* @param {'right'|'left'|'up-right'|'up-left'|'half-blink'|'squint'|'closed'} name
|
|
225
|
+
* @returns {string[]}
|
|
226
|
+
*/
|
|
227
|
+
export function eyes(name = 'right') {
|
|
228
|
+
const px = expressions[name];
|
|
229
|
+
if (!px) throw new Error(`Unknown expression: ${name}. Valid: ${Object.keys(expressions).join(', ')}`);
|
|
230
|
+
return render(px);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Print a static expression to stdout.
|
|
235
|
+
* @param {'right'|'left'|'up-right'|'up-left'|'half-blink'|'squint'|'closed'} name
|
|
236
|
+
* @param {string} [indent='']
|
|
237
|
+
*/
|
|
238
|
+
export function printEyes(name = 'right', indent = '') {
|
|
239
|
+
for (const line of eyes(name)) {
|
|
240
|
+
console.log(indent + line);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Get rendered lines for the header: eyes + "The ReadMe CLI" / version.
|
|
246
|
+
* @param {object} [opts]
|
|
247
|
+
* @param {string} [opts.expression='right']
|
|
248
|
+
* @param {string} [opts.version]
|
|
249
|
+
* @returns {string[]}
|
|
250
|
+
*/
|
|
251
|
+
export function header(opts = {}) {
|
|
252
|
+
const { expression = 'right', version, binName, greeting } = opts;
|
|
253
|
+
const icon = eyes(expression);
|
|
254
|
+
const gap = ' ';
|
|
255
|
+
|
|
256
|
+
const title = chalk.bold.hex(currentPalette.body)('The ReadMe CLI') + (version ? ' ' + chalk.dim(`v${version}`) : '');
|
|
257
|
+
const bin = binName ? chalk.white(binName) : '';
|
|
258
|
+
const greetLine = greeting ? chalk.dim(greeting) : '';
|
|
259
|
+
|
|
260
|
+
return icon.map((line, i) => {
|
|
261
|
+
if (i === 0) return line + gap + title;
|
|
262
|
+
if (i === 1) return line + gap + bin;
|
|
263
|
+
if (i === 2 && greetLine) return line + gap + greetLine;
|
|
264
|
+
return line;
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Print the header to stdout.
|
|
270
|
+
* @param {object} [opts]
|
|
271
|
+
* @param {string} [opts.expression='right']
|
|
272
|
+
* @param {string} [opts.version]
|
|
273
|
+
* @param {string} [opts.indent='']
|
|
274
|
+
*/
|
|
275
|
+
export function printHeader(opts = {}) {
|
|
276
|
+
const { indent = '', ...rest } = opts;
|
|
277
|
+
for (const line of header(rest)) {
|
|
278
|
+
console.log(indent + line);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Run an animation loop. Returns an abort function.
|
|
284
|
+
* @param {'blink'|'bounce'|'lookaround'|'lookaround-blink'} name
|
|
285
|
+
* @param {object} [opts]
|
|
286
|
+
* @param {string} [opts.indent='']
|
|
287
|
+
* @param {boolean} [opts.loop=true]
|
|
288
|
+
* @returns {{ stop: () => void }}
|
|
289
|
+
*/
|
|
290
|
+
export function animate(name, opts = {}) {
|
|
291
|
+
const { indent = '', loop = true, version, binName: bin, subtitle } = opts;
|
|
292
|
+
const anim = animations[name];
|
|
293
|
+
if (!anim) throw new Error(`Unknown animation: ${name}. Valid: ${Object.keys(animations).join(', ')}`);
|
|
294
|
+
|
|
295
|
+
// Build static header text lines (appended to the right of each frame row)
|
|
296
|
+
const headerLines = [];
|
|
297
|
+
if (version || bin) {
|
|
298
|
+
const gap = ' ';
|
|
299
|
+
headerLines[0] = gap + chalk.bold.hex(currentPalette.body)('The ReadMe CLI') + (version ? ' ' + chalk.dim(`v${version}`) : '');
|
|
300
|
+
headerLines[1] = gap + (bin ? chalk.white(bin) : '');
|
|
301
|
+
if (subtitle) headerLines[2] = gap + subtitle;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
let stopped = false;
|
|
305
|
+
let firstDraw = true;
|
|
306
|
+
|
|
307
|
+
// Resolve a frame name (possibly with :up suffix) to rendered lines
|
|
308
|
+
function resolveFrame(frameName) {
|
|
309
|
+
const isUp = frameName.endsWith(':up');
|
|
310
|
+
const expr = isUp ? frameName.slice(0, -3) : frameName;
|
|
311
|
+
let px = expressions[expr];
|
|
312
|
+
if (isUp) px = makeBounceUp(px);
|
|
313
|
+
return render(px);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// Pre-calculate the max height across all frames
|
|
317
|
+
const maxRows = Math.max(...anim.frames.map(f => resolveFrame(f).length));
|
|
318
|
+
|
|
319
|
+
async function run() {
|
|
320
|
+
// Hide cursor
|
|
321
|
+
process.stdout.write('\x1b[?25l');
|
|
322
|
+
|
|
323
|
+
let i = 0;
|
|
324
|
+
while (!stopped) {
|
|
325
|
+
const frameName = anim.frames[i % anim.frames.length];
|
|
326
|
+
const lines = resolveFrame(frameName);
|
|
327
|
+
|
|
328
|
+
// Pad to max height
|
|
329
|
+
while (lines.length < maxRows) lines.push(' '.repeat(7));
|
|
330
|
+
|
|
331
|
+
// Append header text if provided
|
|
332
|
+
if (headerLines.length) {
|
|
333
|
+
for (let j = 0; j < lines.length; j++) {
|
|
334
|
+
if (headerLines[j]) lines[j] += headerLines[j];
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// Move cursor up to overwrite previous frame
|
|
339
|
+
if (!firstDraw) process.stdout.write(`\x1b[${maxRows}A`);
|
|
340
|
+
firstDraw = false;
|
|
341
|
+
|
|
342
|
+
for (const line of lines) {
|
|
343
|
+
process.stdout.write(indent + line + '\n');
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
const duration = anim.durations[i % anim.durations.length];
|
|
347
|
+
await new Promise(r => setTimeout(r, duration));
|
|
348
|
+
|
|
349
|
+
i++;
|
|
350
|
+
if (!loop && i >= anim.frames.length) break;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// Show cursor
|
|
354
|
+
process.stdout.write('\x1b[?25h');
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
run();
|
|
358
|
+
|
|
359
|
+
return {
|
|
360
|
+
stop() {
|
|
361
|
+
stopped = true;
|
|
362
|
+
process.stdout.write('\x1b[?25h');
|
|
363
|
+
},
|
|
364
|
+
};
|
|
365
|
+
}
|
package/src/utils/git.js
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { execSync } from 'node:child_process';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
|
|
5
|
+
export const WORKFLOW_VERSION = 2;
|
|
6
|
+
|
|
7
|
+
// Platform definitions: keep ordering — first match wins for filesystem detection.
|
|
8
|
+
export const PLATFORMS = {
|
|
9
|
+
github: {
|
|
10
|
+
label: 'GitHub Actions',
|
|
11
|
+
remoteHosts: ['github.com'],
|
|
12
|
+
workflowFile: '.github/workflows/readme-lint.yml',
|
|
13
|
+
fsMarkers: ['.github/workflows'],
|
|
14
|
+
},
|
|
15
|
+
gitlab: {
|
|
16
|
+
label: 'GitLab CI',
|
|
17
|
+
remoteHosts: ['gitlab.com'],
|
|
18
|
+
remotePathHints: ['/gitlab/'],
|
|
19
|
+
workflowFile: '.gitlab-ci.yml',
|
|
20
|
+
fsMarkers: ['.gitlab-ci.yml'],
|
|
21
|
+
},
|
|
22
|
+
bitbucket: {
|
|
23
|
+
label: 'Bitbucket Pipelines',
|
|
24
|
+
remoteHosts: ['bitbucket.org'],
|
|
25
|
+
workflowFile: 'bitbucket-pipelines.yml',
|
|
26
|
+
fsMarkers: ['bitbucket-pipelines.yml'],
|
|
27
|
+
},
|
|
28
|
+
circleci: {
|
|
29
|
+
label: 'CircleCI',
|
|
30
|
+
workflowFile: '.circleci/config.yml',
|
|
31
|
+
fsMarkers: ['.circleci/config.yml'],
|
|
32
|
+
},
|
|
33
|
+
rwx: {
|
|
34
|
+
label: 'RWX Mint',
|
|
35
|
+
workflowFile: '.mint/readme-lint.yml',
|
|
36
|
+
fsMarkers: ['.mint'],
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
let _remotesCache;
|
|
41
|
+
|
|
42
|
+
function readRemotes() {
|
|
43
|
+
if (_remotesCache !== undefined) return _remotesCache;
|
|
44
|
+
try {
|
|
45
|
+
_remotesCache = execSync('git remote -v', {
|
|
46
|
+
encoding: 'utf-8',
|
|
47
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
48
|
+
});
|
|
49
|
+
} catch {
|
|
50
|
+
_remotesCache = '';
|
|
51
|
+
}
|
|
52
|
+
return _remotesCache;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function hasGithubRemote() {
|
|
56
|
+
return /github\.com/i.test(readRemotes());
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Inspect the git remote and return the matching platform key, or null.
|
|
61
|
+
*/
|
|
62
|
+
export function detectRemotePlatform() {
|
|
63
|
+
const remotes = readRemotes();
|
|
64
|
+
if (!remotes) return null;
|
|
65
|
+
for (const [key, def] of Object.entries(PLATFORMS)) {
|
|
66
|
+
if (def.remoteHosts && def.remoteHosts.some((h) => remotes.toLowerCase().includes(h))) {
|
|
67
|
+
return key;
|
|
68
|
+
}
|
|
69
|
+
if (def.remotePathHints && def.remotePathHints.some((p) => remotes.toLowerCase().includes(p))) {
|
|
70
|
+
return key;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Scan the filesystem for existing CI configuration files. Returns an array
|
|
78
|
+
* of platform keys (in PLATFORMS declaration order) that have markers present.
|
|
79
|
+
*/
|
|
80
|
+
export function detectExistingCi(gitRoot) {
|
|
81
|
+
if (!gitRoot) return [];
|
|
82
|
+
const found = [];
|
|
83
|
+
for (const [key, def] of Object.entries(PLATFORMS)) {
|
|
84
|
+
const hit = def.fsMarkers.some((marker) => fs.existsSync(path.join(gitRoot, marker)));
|
|
85
|
+
if (hit) found.push(key);
|
|
86
|
+
}
|
|
87
|
+
return found;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Detect whether an existing GitHub workflow uses the Blacksmith runner.
|
|
92
|
+
*/
|
|
93
|
+
export function usesBlacksmith(gitRoot) {
|
|
94
|
+
if (!gitRoot) return false;
|
|
95
|
+
const dir = path.join(gitRoot, '.github/workflows');
|
|
96
|
+
if (!fs.existsSync(dir)) return false;
|
|
97
|
+
try {
|
|
98
|
+
for (const f of fs.readdirSync(dir)) {
|
|
99
|
+
if (!/\.ya?ml$/.test(f)) continue;
|
|
100
|
+
const body = fs.readFileSync(path.join(dir, f), 'utf-8');
|
|
101
|
+
if (/blacksmith/i.test(body)) return true;
|
|
102
|
+
}
|
|
103
|
+
} catch {
|
|
104
|
+
// ignore
|
|
105
|
+
}
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Combined detection for the bare `setup` command. Filesystem markers win
|
|
111
|
+
* over remote host (existing CI is the strongest signal of intent).
|
|
112
|
+
*/
|
|
113
|
+
export function detectPlatform(gitRoot) {
|
|
114
|
+
const existing = detectExistingCi(gitRoot);
|
|
115
|
+
const remote = detectRemotePlatform();
|
|
116
|
+
return {
|
|
117
|
+
existing,
|
|
118
|
+
remote,
|
|
119
|
+
recommended: existing[0] || remote || null,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export function hasGithubWorkflow(gitRoot) {
|
|
124
|
+
return fs.existsSync(path.join(gitRoot, PLATFORMS.github.workflowFile));
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export function hasReadmeWorkflow(gitRoot, platformKey) {
|
|
128
|
+
const def = PLATFORMS[platformKey];
|
|
129
|
+
if (!def) return false;
|
|
130
|
+
return fs.existsSync(path.join(gitRoot, def.workflowFile));
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export function getWorkflowVersion(gitRoot) {
|
|
134
|
+
const file = path.join(gitRoot, PLATFORMS.github.workflowFile);
|
|
135
|
+
if (!fs.existsSync(file)) return null;
|
|
136
|
+
try {
|
|
137
|
+
const first = fs.readFileSync(file, 'utf-8').split('\n')[0];
|
|
138
|
+
const match = first.match(/^# readme-lint v(\d+)/);
|
|
139
|
+
return match ? Number(match[1]) : 0;
|
|
140
|
+
} catch {
|
|
141
|
+
return 0;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { pathToFileURL } from 'node:url';
|
|
4
|
+
|
|
5
|
+
const TARGET_PATTERNS = [
|
|
6
|
+
{ dir: 'custom_blocks', ext: ['.mdx', '.md'] },
|
|
7
|
+
{ dir: 'docs', ext: '.md' },
|
|
8
|
+
{ dir: 'reference', ext: '.md' },
|
|
9
|
+
{ dir: 'custom_pages', ext: '.md' },
|
|
10
|
+
{ dir: 'recipes', ext: '.md' },
|
|
11
|
+
];
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Collect all target files from the repo root.
|
|
15
|
+
*/
|
|
16
|
+
export function collectFiles(gitRoot) {
|
|
17
|
+
const files = [];
|
|
18
|
+
|
|
19
|
+
for (const { dir, ext } of TARGET_PATTERNS) {
|
|
20
|
+
const dirPath = path.join(gitRoot, dir);
|
|
21
|
+
if (!fs.existsSync(dirPath)) continue;
|
|
22
|
+
|
|
23
|
+
const exts = Array.isArray(ext) ? ext : [ext];
|
|
24
|
+
const entries = fs.readdirSync(dirPath, { recursive: true });
|
|
25
|
+
for (const entry of entries) {
|
|
26
|
+
if (exts.some((e) => entry.endsWith(e))) {
|
|
27
|
+
files.push(path.join(dir, entry));
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return files.sort();
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Auto-discover all validators from src/validators/.
|
|
37
|
+
* Validators can export `validate()` for per-file checks and/or
|
|
38
|
+
* `validateAll()` for cross-file checks (like ordering).
|
|
39
|
+
*/
|
|
40
|
+
async function loadValidators() {
|
|
41
|
+
const validatorsDir = path.join(path.dirname(new URL(import.meta.url).pathname), '..', 'validators');
|
|
42
|
+
const files = fs.readdirSync(validatorsDir).filter((f) => f.endsWith('.js'));
|
|
43
|
+
const validators = [];
|
|
44
|
+
|
|
45
|
+
for (const file of files) {
|
|
46
|
+
const mod = await import(pathToFileURL(path.join(validatorsDir, file)).href);
|
|
47
|
+
if (mod.name && (mod.validate || mod.validateAll)) {
|
|
48
|
+
validators.push(mod);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return validators;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Run all validators against every file. Returns array of result objects.
|
|
57
|
+
* Each result has { file, rule, message, severity? } where severity defaults to 'error'.
|
|
58
|
+
* Calls `onFile(relativePath)` before processing each file (for progress reporting).
|
|
59
|
+
*/
|
|
60
|
+
export async function runValidators(files, gitRoot, { onFile, onBeforeCrossFile, fix } = {}) {
|
|
61
|
+
const validators = await loadValidators();
|
|
62
|
+
const results = [];
|
|
63
|
+
|
|
64
|
+
// Per-file validators.
|
|
65
|
+
for (const relativePath of files) {
|
|
66
|
+
if (onFile) onFile(relativePath);
|
|
67
|
+
|
|
68
|
+
const filePath = path.join(gitRoot, relativePath);
|
|
69
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
70
|
+
|
|
71
|
+
for (const validator of validators) {
|
|
72
|
+
if (!validator.validate) continue;
|
|
73
|
+
const result = validator.validate({ filePath, content, relativePath, fix });
|
|
74
|
+
if (result) {
|
|
75
|
+
if (Array.isArray(result)) {
|
|
76
|
+
results.push(...result);
|
|
77
|
+
} else {
|
|
78
|
+
results.push(result);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Cross-file validators — stop the spinner first so interactive prompts work.
|
|
85
|
+
if (onBeforeCrossFile) onBeforeCrossFile();
|
|
86
|
+
for (const validator of validators) {
|
|
87
|
+
if (!validator.validateAll) continue;
|
|
88
|
+
const result = await validator.validateAll(files, gitRoot, { fix });
|
|
89
|
+
if (result) {
|
|
90
|
+
if (Array.isArray(result)) {
|
|
91
|
+
results.push(...result);
|
|
92
|
+
} else {
|
|
93
|
+
results.push(result);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return results;
|
|
99
|
+
}
|