@readme/cli 0.1.0 → 0.3.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.
Files changed (57) hide show
  1. package/bin/git-format.js +80 -0
  2. package/bin/readme.js +1 -1
  3. package/package.json +54 -26
  4. package/src/bootstrap.js +9 -13
  5. package/src/cli.js +17 -11
  6. package/src/commands/components.js +35 -18
  7. package/src/commands/dev.js +12 -8
  8. package/src/commands/eyes.js +12 -8
  9. package/src/commands/import.js +2200 -2049
  10. package/src/commands/lint.js +33 -29
  11. package/src/commands/oas-bundle.js +6 -2
  12. package/src/commands/oas-sync.js +271 -161
  13. package/src/commands/oas-validate.js +10 -7
  14. package/src/commands/pretty.js +188 -165
  15. package/src/commands/setup.js +24 -26
  16. package/src/commands/versions.js +6 -2
  17. package/src/dev/app/[...slug]/page.js +1 -0
  18. package/src/dev/app/layout.js +18 -11
  19. package/src/dev/app/page.js +1 -0
  20. package/src/dev/lib/docs.js +27 -19
  21. package/src/dev/next.config.mjs +2 -2
  22. package/src/gha.js +7 -7
  23. package/src/prompts/index.js +16 -6
  24. package/src/utils/containment.js +87 -0
  25. package/src/utils/eyes.js +143 -51
  26. package/src/utils/git.js +3 -3
  27. package/src/utils/lint.js +39 -38
  28. package/src/utils/llms.js +94 -91
  29. package/src/utils/markdown.js +256 -0
  30. package/src/utils/marketplace.js +12 -5
  31. package/src/utils/multi-spinner.js +5 -7
  32. package/src/utils/promise-pool.js +7 -5
  33. package/src/utils/reporter.js +88 -94
  34. package/src/utils/setup-templates.js +12 -6
  35. package/src/utils/styles.js +2 -1
  36. package/src/utils/tamagotchi.js +257 -91
  37. package/src/utils/tips.js +18 -9
  38. package/src/utils/url-segs.js +46 -41
  39. package/src/validators/components.js +55 -63
  40. package/src/validators/content.js +16 -14
  41. package/src/validators/duplicates.js +6 -2
  42. package/src/validators/frontmatter.js +21 -29
  43. package/src/validators/links.js +4 -8
  44. package/src/validators/oas-reference.js +25 -18
  45. package/src/validators/oas-schema.js +2 -0
  46. package/src/validators/ordering.js +5 -5
  47. package/src/validators/recipes.js +4 -12
  48. package/vendor/git-format/CLAUDE.md +352 -0
  49. package/vendor/git-format/frontmatter.schema.json +199 -0
  50. package/src/commands/import-oas.test.js +0 -277
  51. package/src/commands/import.classify.test.js +0 -172
  52. package/src/commands/import.landing.test.js +0 -217
  53. package/src/commands/import.slug-planner.test.js +0 -86
  54. package/src/commands/import.staging.test.js +0 -230
  55. package/src/commands/import.test.js +0 -382
  56. package/src/utils/llms.test.js +0 -302
  57. package/src/utils/url-segs.test.js +0 -239
package/src/utils/eyes.js CHANGED
@@ -19,7 +19,15 @@ export const palettes = {
19
19
  red: { name: 'Red', body: '#EF4444', page: '#FFF5F5', pupil: '#7F1D1D', lid: '#FCA5A5' },
20
20
  teal: { name: 'Teal', body: '#14B8A6', page: '#F0FFFE', pupil: '#134E4A', lid: '#5EEAD4' },
21
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 },
22
+ owl: {
23
+ name: 'Owl',
24
+ body: '#8B5A2B',
25
+ page: '#FFF8EE',
26
+ pupil: '#2A1000',
27
+ lid: '#C4923A',
28
+ nose: '#FFD700',
29
+ hidden: true,
30
+ },
23
31
  };
24
32
 
25
33
  let currentPalette = palettes.blue;
@@ -124,7 +132,21 @@ function rebuildExpressions() {
124
132
 
125
133
  export const animations = {
126
134
  blink: {
127
- frames: ['right', 'right', 'right', 'right', 'right', 'right', 'half-blink', 'squint', 'closed', 'closed', 'squint', 'half-blink', 'right'],
135
+ frames: [
136
+ 'right',
137
+ 'right',
138
+ 'right',
139
+ 'right',
140
+ 'right',
141
+ 'right',
142
+ 'half-blink',
143
+ 'squint',
144
+ 'closed',
145
+ 'closed',
146
+ 'squint',
147
+ 'half-blink',
148
+ 'right',
149
+ ],
128
150
  durations: [1500, 100, 100, 100, 100, 100, 50, 50, 50, 80, 50, 50, 100],
129
151
  },
130
152
 
@@ -134,85 +156,154 @@ export const animations = {
134
156
  },
135
157
 
136
158
  lookaround: {
137
- frames: ['right', 'right', 'left', 'left', 'left', 'right', 'right', 'up-right', 'up-right', 'right', 'right', 'up-left', 'up-left', 'left', 'left', 'right'],
159
+ frames: [
160
+ 'right',
161
+ 'right',
162
+ 'left',
163
+ 'left',
164
+ 'left',
165
+ 'right',
166
+ 'right',
167
+ 'up-right',
168
+ 'up-right',
169
+ 'right',
170
+ 'right',
171
+ 'up-left',
172
+ 'up-left',
173
+ 'left',
174
+ 'left',
175
+ 'right',
176
+ ],
138
177
  durations: [600, 200, 120, 400, 200, 120, 400, 120, 400, 120, 200, 120, 400, 120, 200, 400],
139
178
  },
140
179
 
141
180
  'lookaround-blink': {
142
181
  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',
182
+ 'right',
183
+ 'right',
184
+ 'right',
185
+ 'left',
186
+ 'left',
187
+ 'left',
188
+ 'half-blink',
189
+ 'squint',
190
+ 'closed',
191
+ 'closed',
192
+ 'squint',
193
+ 'half-blink',
194
+ 'left',
195
+ 'right',
196
+ 'right',
197
+ 'up-right',
198
+ 'up-right',
199
+ 'right',
200
+ 'right',
201
+ 'right',
202
+ 'up-left',
203
+ 'up-left',
204
+ 'left',
205
+ 'left',
206
+ 'half-blink',
207
+ 'squint',
208
+ 'closed',
209
+ 'closed',
210
+ 'squint',
211
+ 'half-blink',
212
+ 'right',
213
+ 'right',
152
214
  ],
153
215
  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,
216
+ 800, 200, 200, 120, 400, 200, 50, 50, 50, 80, 50, 50, 200, 120, 400, 120, 500, 120, 200, 200, 120, 500, 120, 200,
217
+ 50, 50, 50, 80, 50, 50, 120, 600,
163
218
  ],
164
219
  },
165
220
 
166
221
  all: {
167
222
  frames: [
168
223
  // idle, look around
169
- 'right', 'right', 'right',
170
- 'left', 'left', 'left',
171
- 'right', 'right',
224
+ 'right',
225
+ 'right',
226
+ 'right',
227
+ 'left',
228
+ 'left',
229
+ 'left',
230
+ 'right',
231
+ 'right',
172
232
  // blink
173
- 'half-blink', 'squint', 'closed', 'closed', 'squint', 'half-blink',
233
+ 'half-blink',
234
+ 'squint',
235
+ 'closed',
236
+ 'closed',
237
+ 'squint',
238
+ 'half-blink',
174
239
  // look up + bounce
175
- 'right', 'right',
176
- 'up-right', 'up-right', 'right',
177
- 'right:up', 'right:up', 'right', 'right:up', 'right:up', 'right',
240
+ 'right',
241
+ 'right',
242
+ 'up-right',
243
+ 'up-right',
244
+ 'right',
245
+ 'right:up',
246
+ 'right:up',
247
+ 'right',
248
+ 'right:up',
249
+ 'right:up',
250
+ 'right',
178
251
  // look the other way
179
- 'right', 'left', 'left',
180
- 'up-left', 'up-left', 'left',
252
+ 'right',
253
+ 'left',
254
+ 'left',
255
+ 'up-left',
256
+ 'up-left',
257
+ 'left',
181
258
  // blink
182
- 'half-blink', 'squint', 'closed', 'closed', 'squint', 'half-blink',
259
+ 'half-blink',
260
+ 'squint',
261
+ 'closed',
262
+ 'closed',
263
+ 'squint',
264
+ 'half-blink',
183
265
  // bounce
184
- 'left', 'left:up', 'left:up', 'left', 'left:up', 'left:up', 'left',
266
+ 'left',
267
+ 'left:up',
268
+ 'left:up',
269
+ 'left',
270
+ 'left:up',
271
+ 'left:up',
272
+ 'left',
185
273
  // settle back + bounce
186
- 'right', 'right', 'right',
187
- 'right:up', 'right:up', 'right',
274
+ 'right',
275
+ 'right',
276
+ 'right',
277
+ 'right:up',
278
+ 'right:up',
279
+ 'right',
188
280
  // blink
189
- 'half-blink', 'squint', 'closed', 'closed', 'squint', 'half-blink',
190
- 'right', 'right',
281
+ 'half-blink',
282
+ 'squint',
283
+ 'closed',
284
+ 'closed',
285
+ 'squint',
286
+ 'half-blink',
287
+ 'right',
288
+ 'right',
191
289
  ],
192
290
  durations: [
193
291
  // idle, look around
194
- 800, 200, 200,
195
- 120, 400, 200,
196
- 120, 400,
292
+ 800, 200, 200, 120, 400, 200, 120, 400,
197
293
  // blink
198
294
  50, 50, 50, 80, 50, 50,
199
295
  // look up + bounce
200
- 200, 200,
201
- 120, 500, 120,
202
- 150, 150, 150, 150, 150, 200,
296
+ 200, 200, 120, 500, 120, 150, 150, 150, 150, 150, 200,
203
297
  // look the other way
204
- 300, 120, 400,
205
- 120, 500, 120,
298
+ 300, 120, 400, 120, 500, 120,
206
299
  // blink
207
300
  50, 50, 50, 80, 50, 50,
208
301
  // bounce
209
302
  200, 150, 150, 150, 150, 150, 200,
210
303
  // settle back + bounce
211
- 120, 200, 300,
212
- 150, 150, 200,
304
+ 120, 200, 300, 150, 150, 200,
213
305
  // blink
214
- 50, 50, 50, 80, 50, 50,
215
- 200, 800,
306
+ 50, 50, 50, 80, 50, 50, 200, 800,
216
307
  ],
217
308
  },
218
309
  };
@@ -253,7 +344,7 @@ export function header(opts = {}) {
253
344
  const icon = eyes(expression);
254
345
  const gap = ' ';
255
346
 
256
- const title = chalk.bold.hex(currentPalette.body)('The ReadMe CLI') + (version ? ' ' + chalk.dim(`v${version}`) : '');
347
+ const title = chalk.bold.hex(currentPalette.body)('The ReadMe CLI') + (version ? ` ${chalk.dim(`v${version}`)}` : '');
257
348
  const bin = binName ? chalk.white(binName) : '';
258
349
  const greetLine = greeting ? chalk.dim(greeting) : '';
259
350
 
@@ -296,7 +387,8 @@ export function animate(name, opts = {}) {
296
387
  const headerLines = [];
297
388
  if (version || bin) {
298
389
  const gap = ' ';
299
- headerLines[0] = gap + chalk.bold.hex(currentPalette.body)('The ReadMe CLI') + (version ? ' ' + chalk.dim(`v${version}`) : '');
390
+ headerLines[0] =
391
+ gap + chalk.bold.hex(currentPalette.body)('The ReadMe CLI') + (version ? ` ${chalk.dim(`v${version}`)}` : '');
300
392
  headerLines[1] = gap + (bin ? chalk.white(bin) : '');
301
393
  if (subtitle) headerLines[2] = gap + subtitle;
302
394
  }
@@ -340,7 +432,7 @@ export function animate(name, opts = {}) {
340
432
  firstDraw = false;
341
433
 
342
434
  for (const line of lines) {
343
- process.stdout.write(indent + line + '\n');
435
+ process.stdout.write(`${indent + line}\n`);
344
436
  }
345
437
 
346
438
  const duration = anim.durations[i % anim.durations.length];
package/src/utils/git.js CHANGED
@@ -63,10 +63,10 @@ export function detectRemotePlatform() {
63
63
  const remotes = readRemotes();
64
64
  if (!remotes) return null;
65
65
  for (const [key, def] of Object.entries(PLATFORMS)) {
66
- if (def.remoteHosts && def.remoteHosts.some((h) => remotes.toLowerCase().includes(h))) {
66
+ if (def.remoteHosts && def.remoteHosts.some(h => remotes.toLowerCase().includes(h))) {
67
67
  return key;
68
68
  }
69
- if (def.remotePathHints && def.remotePathHints.some((p) => remotes.toLowerCase().includes(p))) {
69
+ if (def.remotePathHints && def.remotePathHints.some(p => remotes.toLowerCase().includes(p))) {
70
70
  return key;
71
71
  }
72
72
  }
@@ -81,7 +81,7 @@ export function detectExistingCi(gitRoot) {
81
81
  if (!gitRoot) return [];
82
82
  const found = [];
83
83
  for (const [key, def] of Object.entries(PLATFORMS)) {
84
- const hit = def.fsMarkers.some((marker) => fs.existsSync(path.join(gitRoot, marker)));
84
+ const hit = def.fsMarkers.some(marker => fs.existsSync(path.join(gitRoot, marker)));
85
85
  if (hit) found.push(key);
86
86
  }
87
87
  return found;
package/src/utils/lint.js CHANGED
@@ -1,15 +1,16 @@
1
- import fs from 'node:fs'
2
- import path from 'node:path'
3
- import * as componentsValidator from '../validators/components.js'
4
- import * as contentValidator from '../validators/content.js'
5
- import * as duplicatesValidator from '../validators/duplicates.js'
6
- import * as frontmatterValidator from '../validators/frontmatter.js'
7
- import * as linksValidator from '../validators/links.js'
8
- import * as nestingValidator from '../validators/nesting.js'
9
- import * as oasReferenceValidator from '../validators/oas-reference.js'
10
- import * as oasSchemaValidator from '../validators/oas-schema.js'
11
- import * as orderingValidator from '../validators/ordering.js'
12
- import * as recipesValidator from '../validators/recipes.js'
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+
4
+ import * as componentsValidator from '../validators/components.js';
5
+ import * as contentValidator from '../validators/content.js';
6
+ import * as duplicatesValidator from '../validators/duplicates.js';
7
+ import * as frontmatterValidator from '../validators/frontmatter.js';
8
+ import * as linksValidator from '../validators/links.js';
9
+ import * as nestingValidator from '../validators/nesting.js';
10
+ import * as oasReferenceValidator from '../validators/oas-reference.js';
11
+ import * as oasSchemaValidator from '../validators/oas-schema.js';
12
+ import * as orderingValidator from '../validators/ordering.js';
13
+ import * as recipesValidator from '../validators/recipes.js';
13
14
 
14
15
  const TARGET_PATTERNS = [
15
16
  { dir: 'custom_blocks', ext: ['.mdx', '.md'] },
@@ -17,28 +18,28 @@ const TARGET_PATTERNS = [
17
18
  { dir: 'reference', ext: '.md' },
18
19
  { dir: 'custom_pages', ext: '.md' },
19
20
  { dir: 'recipes', ext: '.md' },
20
- ]
21
+ ];
21
22
 
22
23
  /**
23
24
  * Collect all target files from the repo root.
24
25
  */
25
26
  export function collectFiles(gitRoot) {
26
- const files = []
27
+ const files = [];
27
28
 
28
29
  for (const { dir, ext } of TARGET_PATTERNS) {
29
- const dirPath = path.join(gitRoot, dir)
30
- if (!fs.existsSync(dirPath)) continue
30
+ const dirPath = path.join(gitRoot, dir);
31
+ if (!fs.existsSync(dirPath)) continue;
31
32
 
32
- const exts = Array.isArray(ext) ? ext : [ext]
33
- const entries = fs.readdirSync(dirPath, { recursive: true })
33
+ const exts = Array.isArray(ext) ? ext : [ext];
34
+ const entries = fs.readdirSync(dirPath, { recursive: true });
34
35
  for (const entry of entries) {
35
- if (exts.some((e) => entry.endsWith(e))) {
36
- files.push(path.join(dir, entry))
36
+ if (exts.some(e => entry.endsWith(e))) {
37
+ files.push(path.join(dir, entry));
37
38
  }
38
39
  }
39
40
  }
40
41
 
41
- return files.sort()
42
+ return files.sort();
42
43
  }
43
44
 
44
45
  // Every validator in src/validators/, imported statically (rather than
@@ -57,14 +58,14 @@ const ALL_VALIDATORS = [
57
58
  oasSchemaValidator,
58
59
  orderingValidator,
59
60
  recipesValidator,
60
- ]
61
+ ];
61
62
 
62
63
  /**
63
64
  * Validators can export `validate()` for per-file checks and/or
64
65
  * `validateAll()` for cross-file checks (like ordering).
65
66
  */
66
67
  function loadValidators() {
67
- return ALL_VALIDATORS.filter((mod) => mod.name && (mod.validate || mod.validateAll))
68
+ return ALL_VALIDATORS.filter(mod => mod.name && (mod.validate || mod.validateAll));
68
69
  }
69
70
 
70
71
  /**
@@ -73,42 +74,42 @@ function loadValidators() {
73
74
  * Calls `onFile(relativePath)` before processing each file (for progress reporting).
74
75
  */
75
76
  export async function runValidators(files, gitRoot, { onFile, onBeforeCrossFile, fix, nonInteractive } = {}) {
76
- const validators = await loadValidators()
77
- const results = []
77
+ const validators = await loadValidators();
78
+ const results = [];
78
79
 
79
80
  // Per-file validators.
80
81
  for (const relativePath of files) {
81
- if (onFile) onFile(relativePath)
82
+ if (onFile) onFile(relativePath);
82
83
 
83
- const filePath = path.join(gitRoot, relativePath)
84
- const content = fs.readFileSync(filePath, 'utf-8')
84
+ const filePath = path.join(gitRoot, relativePath);
85
+ const content = fs.readFileSync(filePath, 'utf-8');
85
86
 
86
87
  for (const validator of validators) {
87
- if (!validator.validate) continue
88
- const result = validator.validate({ filePath, content, relativePath, fix })
88
+ if (!validator.validate) continue;
89
+ const result = validator.validate({ filePath, content, relativePath, fix });
89
90
  if (result) {
90
91
  if (Array.isArray(result)) {
91
- results.push(...result)
92
+ results.push(...result);
92
93
  } else {
93
- results.push(result)
94
+ results.push(result);
94
95
  }
95
96
  }
96
97
  }
97
98
  }
98
99
 
99
100
  // Cross-file validators — stop the spinner first so interactive prompts work.
100
- if (onBeforeCrossFile) onBeforeCrossFile()
101
+ if (onBeforeCrossFile) onBeforeCrossFile();
101
102
  for (const validator of validators) {
102
- if (!validator.validateAll) continue
103
- const result = await validator.validateAll(files, gitRoot, { fix, nonInteractive })
103
+ if (!validator.validateAll) continue;
104
+ const result = await validator.validateAll(files, gitRoot, { fix, nonInteractive });
104
105
  if (result) {
105
106
  if (Array.isArray(result)) {
106
- results.push(...result)
107
+ results.push(...result);
107
108
  } else {
108
- results.push(result)
109
+ results.push(result);
109
110
  }
110
111
  }
111
112
  }
112
113
 
113
- return results
114
+ return results;
114
115
  }