@readme/cli 0.0.31 → 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.
Files changed (54) hide show
  1. package/README.md +10 -0
  2. package/bin/readme.js +1 -1
  3. package/package.json +34 -24
  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 +2263 -2066
  10. package/src/commands/lint.js +33 -29
  11. package/src/commands/oas-bundle.js +89 -0
  12. package/src/commands/oas-sync.js +96 -93
  13. package/src/commands/oas-validate.js +10 -7
  14. package/src/commands/pretty.js +177 -164
  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/eyes.js +143 -51
  25. package/src/utils/git.js +3 -3
  26. package/src/utils/lint.js +39 -38
  27. package/src/utils/llms.js +94 -91
  28. package/src/utils/markdown.js +256 -0
  29. package/src/utils/marketplace.js +12 -5
  30. package/src/utils/multi-spinner.js +5 -7
  31. package/src/utils/promise-pool.js +7 -5
  32. package/src/utils/reporter.js +88 -91
  33. package/src/utils/setup-templates.js +12 -6
  34. package/src/utils/styles.js +2 -1
  35. package/src/utils/tamagotchi.js +257 -91
  36. package/src/utils/tips.js +18 -9
  37. package/src/utils/url-segs.js +46 -41
  38. package/src/validators/components.js +55 -63
  39. package/src/validators/content.js +5 -10
  40. package/src/validators/duplicates.js +6 -2
  41. package/src/validators/frontmatter.js +19 -21
  42. package/src/validators/links.js +4 -8
  43. package/src/validators/oas-reference.js +7 -14
  44. package/src/validators/oas-schema.js +2 -0
  45. package/src/validators/ordering.js +5 -5
  46. package/src/validators/recipes.js +4 -12
  47. package/src/commands/import-oas.test.js +0 -277
  48. package/src/commands/import.classify.test.js +0 -172
  49. package/src/commands/import.landing.test.js +0 -217
  50. package/src/commands/import.slug-planner.test.js +0 -86
  51. package/src/commands/import.staging.test.js +0 -158
  52. package/src/commands/import.test.js +0 -382
  53. package/src/utils/llms.test.js +0 -302
  54. package/src/utils/url-segs.test.js +0 -239
@@ -1,7 +1,9 @@
1
- import { collectSidebar } from '../lib/docs';
2
- import { SidebarNav, SidebarPanel } from './Sidebar';
3
1
  import '@readme/markdown/dist/main.css';
2
+
3
+ import { collectSidebar } from '../lib/docs';
4
+
4
5
  import './globals.css';
6
+ import { SidebarNav, SidebarPanel } from './Sidebar';
5
7
 
6
8
  export const metadata = {
7
9
  title: {
@@ -21,10 +23,13 @@ export default async function RootLayout({ children }) {
21
23
  </head>
22
24
  <body className="bg-[#000343] text-gray-900 antialiased">
23
25
  {/* Top Navigation */}
24
- <header className="fixed top-0 inset-x-0 h-[48px] bg-[#000343] flex items-center px-5 z-50" style={{
25
- backgroundImage: `linear-gradient(rgba(255,255,255,0.08) 1px, transparent 1px), linear-gradient(90deg, rgba(255,255,255,0.08) 1px, transparent 1px)`,
26
- backgroundSize: '20px 20px',
27
- }}>
26
+ <header
27
+ className="fixed top-0 inset-x-0 h-[48px] bg-[#000343] flex items-center px-5 z-50"
28
+ style={{
29
+ backgroundImage: `linear-gradient(rgba(255,255,255,0.08) 1px, transparent 1px), linear-gradient(90deg, rgba(255,255,255,0.08) 1px, transparent 1px)`,
30
+ backgroundSize: '20px 20px',
31
+ }}
32
+ >
28
33
  <div className="flex items-center gap-3 mr-8 shrink-0">
29
34
  <div className="flex items-center gap-2">
30
35
  <i className="fa-brands fa-readme text-[#018ef5] text-[18px] relative top-[1px]" />
@@ -50,17 +55,19 @@ export default async function RootLayout({ children }) {
50
55
 
51
56
  {/* Content */}
52
57
  <main className="ml-[300px] flex-1 min-w-0 bg-white rounded-tr-xl">
53
- <div className="max-w-[820px] py-10 px-16">
54
- {children}
55
- </div>
58
+ <div className="max-w-[820px] py-10 px-16">{children}</div>
56
59
  </main>
57
60
  </div>
58
61
 
59
- <style dangerouslySetInnerHTML={{ __html: `
62
+ <style
63
+ dangerouslySetInnerHTML={{
64
+ __html: `
60
65
  [data-sidebar-group][data-expanded="false"] .sidebar-children { display: none; }
61
66
  [data-sidebar-group][data-expanded="true"] .sidebar-children { display: block; }
62
67
  [data-sidebar-group][data-expanded="true"] .sidebar-chevron { transform: rotate(90deg); }
63
- `}} />
68
+ `,
69
+ }}
70
+ />
64
71
  <script
65
72
  dangerouslySetInnerHTML={{
66
73
  __html: `(function(){var es=new EventSource('/__reload');es.onmessage=function(e){if(e.data==='reload')location.reload()};})();
@@ -1,4 +1,5 @@
1
1
  import { redirect } from 'next/navigation';
2
+
2
3
  import { getFirstPageHref } from '../lib/docs';
3
4
 
4
5
  export const dynamic = 'force-dynamic';
@@ -1,9 +1,11 @@
1
- import { createRequire } from 'node:module';
2
1
  import fs from 'fs';
2
+ import { createRequire } from 'node:module';
3
3
  import path from 'path';
4
- import matter from 'gray-matter';
4
+
5
5
  import { compile, run } from '@readme/markdown';
6
- import yaml from 'js-yaml';
6
+ import { load as loadYAML } from 'js-yaml';
7
+
8
+ import { parseFrontmatter } from '../../utils/markdown.js';
7
9
 
8
10
  // Use createRequire to load react-dom/server at runtime,
9
11
  // bypassing Next.js App Router's static import restriction.
@@ -36,7 +38,7 @@ function walkDir(dirPath, sectionDir, depth = 0) {
36
38
  let order = [];
37
39
  const orderPath = path.join(dirPath, '_order.yaml');
38
40
  if (fs.existsSync(orderPath)) {
39
- order = yaml.load(fs.readFileSync(orderPath, 'utf-8')) || [];
41
+ order = loadYAML(fs.readFileSync(orderPath, 'utf-8')) || [];
40
42
  }
41
43
 
42
44
  // Get all items (exclude dotfiles and _-prefixed files)
@@ -90,16 +92,14 @@ function walkDir(dirPath, sectionDir, depth = 0) {
90
92
  // If either exists, treat this as a clickable parent page.
91
93
  const indexPath = path.join(dirPath, name, 'index.md');
92
94
  const siblingMdPath = path.join(dirPath, `${name}.md`);
93
- const parentPagePath = fs.existsSync(indexPath)
94
- ? indexPath
95
- : fs.existsSync(siblingMdPath) ? siblingMdPath : null;
95
+ const parentPagePath = fs.existsSync(indexPath) ? indexPath : fs.existsSync(siblingMdPath) ? siblingMdPath : null;
96
96
 
97
97
  let title = name;
98
98
  let href = null;
99
99
  let hidden = false;
100
100
  let icon = null;
101
101
  if (parentPagePath) {
102
- const { data } = matter(fs.readFileSync(parentPagePath, 'utf-8'));
102
+ const { data } = parseFrontmatter(fs.readFileSync(parentPagePath, 'utf-8'));
103
103
  title = data.title || name;
104
104
  href = `/${sectionDir}/${encodeURIComponent(name)}`;
105
105
  hidden = !!data.hidden;
@@ -112,7 +112,7 @@ function walkDir(dirPath, sectionDir, depth = 0) {
112
112
  mdFiles.delete(name);
113
113
  } else if (isMd) {
114
114
  const filePath = path.join(dirPath, `${name}.md`);
115
- const { data } = matter(fs.readFileSync(filePath, 'utf-8'));
115
+ const { data } = parseFrontmatter(fs.readFileSync(filePath, 'utf-8'));
116
116
  const linkUrl = data.link?.url;
117
117
  entries.push({
118
118
  title: data.title || name,
@@ -152,7 +152,7 @@ function loadCustomBlocks(docsRoot) {
152
152
 
153
153
  for (const file of files) {
154
154
  const raw = fs.readFileSync(path.join(blocksDir, file), 'utf-8');
155
- const { data, content } = matter(raw);
155
+ const { data, content } = parseFrontmatter(raw);
156
156
  const name = data.name || path.basename(file, path.extname(file));
157
157
  sources[name] = content;
158
158
  modules[name] = run(compile(content));
@@ -202,7 +202,7 @@ export function getPage(slugArray) {
202
202
  if (!filePath) return null;
203
203
 
204
204
  const raw = fs.readFileSync(filePath, 'utf-8');
205
- const { data, content } = matter(raw);
205
+ const { data, content } = parseFrontmatter(raw);
206
206
 
207
207
  let html;
208
208
 
@@ -254,7 +254,7 @@ function renderOasOperationPreview(specFile, operationId) {
254
254
  let spec;
255
255
  try {
256
256
  const rawSpec = fs.readFileSync(specPath, 'utf-8');
257
- spec = specFile.toLowerCase().endsWith('.json') ? JSON.parse(rawSpec) : yaml.load(rawSpec);
257
+ spec = specFile.toLowerCase().endsWith('.json') ? JSON.parse(rawSpec) : loadYAML(rawSpec);
258
258
  } catch (e) {
259
259
  return `<p><em>Couldn't parse <code>reference/${specFile}</code>: ${escapeHtml(e.message)}</em></p>`;
260
260
  }
@@ -272,16 +272,21 @@ function renderOasOperationPreview(specFile, operationId) {
272
272
  }
273
273
 
274
274
  const { method, path: opPath, op } = found;
275
- const methodColor = {
276
- GET: '#22c55e', POST: '#3b82f6', PUT: '#f59e0b', PATCH: '#a855f7', DELETE: '#ef4444',
277
- }[method] || '#6b7280';
275
+ const methodColor =
276
+ {
277
+ GET: '#22c55e',
278
+ POST: '#3b82f6',
279
+ PUT: '#f59e0b',
280
+ PATCH: '#a855f7',
281
+ DELETE: '#ef4444',
282
+ }[method] || '#6b7280';
278
283
 
279
284
  const parts = [];
280
285
  parts.push(
281
286
  `<div style="display: flex; gap: 0.5rem; align-items: center; margin-bottom: 1rem;">` +
282
- `<span style="background: ${methodColor}; color: white; padding: 0.15rem 0.5rem; border-radius: 0.25rem; font-family: monospace; font-weight: 600; font-size: 0.85rem;">${method}</span>` +
283
- `<code style="font-size: 1rem;">${escapeHtml(opPath)}</code>` +
284
- `</div>`,
287
+ `<span style="background: ${methodColor}; color: white; padding: 0.15rem 0.5rem; border-radius: 0.25rem; font-family: monospace; font-weight: 600; font-size: 0.85rem;">${method}</span>` +
288
+ `<code style="font-size: 1rem;">${escapeHtml(opPath)}</code>` +
289
+ `</div>`,
285
290
  );
286
291
  if (op.description) parts.push(`<p>${escapeHtml(op.description)}</p>`);
287
292
 
@@ -317,7 +322,10 @@ function renderOasOperationPreview(specFile, operationId) {
317
322
  }
318
323
 
319
324
  function escapeHtml(s) {
320
- return String(s).replace(/[&<>"']/g, (c) => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[c]));
325
+ return String(s).replace(
326
+ /[&<>"']/g,
327
+ c => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' })[c],
328
+ );
321
329
  }
322
330
 
323
331
  function findFirstHref(items) {
@@ -10,8 +10,8 @@ export default {
10
10
  // Next's SWC loader excludes `node_modules` from JSX transformation by
11
11
  // default. Marking our own package as a transpile target bypasses that.
12
12
  transpilePackages: ['@readme/cli'],
13
- serverExternalPackages: ['gray-matter', 'js-yaml', '@readme/markdown'],
14
- webpack: (config) => {
13
+ serverExternalPackages: ['js-yaml', '@readme/markdown'],
14
+ webpack: config => {
15
15
  // Suppress noisy cache serialization warnings from large @readme/markdown bundle
16
16
  config.infrastructureLogging = {
17
17
  ...config.infrastructureLogging,
package/src/gha.js CHANGED
@@ -17,8 +17,8 @@
17
17
  // let it do what it already does, exit code included.
18
18
 
19
19
  import * as lint from './commands/lint.js';
20
- import * as oasValidate from './commands/oas-validate.js';
21
20
  import * as oasSync from './commands/oas-sync.js';
21
+ import * as oasValidate from './commands/oas-validate.js';
22
22
  import { writeGithubActionsOutputs } from './utils/gha-output.js';
23
23
 
24
24
  // Each command's run(options, cmd, ctx) only ever reads specific boolean
@@ -26,8 +26,8 @@ import { writeGithubActionsOutputs } from './utils/gha-output.js';
26
26
  // (the commander Command instance normal CLI invocation would pass) — so a
27
27
  // plain options object and a null cmd are enough here.
28
28
  const COMMANDS = {
29
- 'lint': { run: lint.run, flags: ['json', 'github', 'fix'] },
30
- 'validate': { run: lint.run, flags: ['json', 'github', 'fix'] }, // alias, matches src/commands/lint.js
29
+ lint: { run: lint.run, flags: ['json', 'github', 'fix'] },
30
+ validate: { run: lint.run, flags: ['json', 'github', 'fix'] }, // alias, matches src/commands/lint.js
31
31
  'oas:validate': { run: oasValidate.run, flags: ['dereference'] },
32
32
  'oas:sync': { run: oasSync.run, flags: [] },
33
33
  };
@@ -46,7 +46,7 @@ export async function run() {
46
46
  if (!entry) {
47
47
  console.error(
48
48
  `::error::Unknown or unsupported command "${command || '(empty)'}" for the \`readme\` input. ` +
49
- `Supported: ${[...new Set(Object.keys(COMMANDS).filter((k) => k !== 'validate'))].join(', ')}.`,
49
+ `Supported: ${[...new Set(Object.keys(COMMANDS).filter(k => k !== 'validate'))].join(', ')}.`,
50
50
  );
51
51
  writeGithubActionsOutputs({ 'has-errors': 'true' });
52
52
  process.exit(1);
@@ -72,8 +72,8 @@ export async function run() {
72
72
 
73
73
  if (badTokens.length > 0) {
74
74
  console.error(
75
- `::error::Unknown option${badTokens.length > 1 ? 's' : ''} ${badTokens.map((t) => `"${t}"`).join(', ')} for "${command}". ` +
76
- `Supported: ${entry.flags.length > 0 ? entry.flags.map((f) => `--${f}`).join(', ') : '(none)'}.`,
75
+ `::error::Unknown option${badTokens.length > 1 ? 's' : ''} ${badTokens.map(t => `"${t}"`).join(', ')} for "${command}". ` +
76
+ `Supported: ${entry.flags.length > 0 ? entry.flags.map(f => `--${f}`).join(', ') : '(none)'}.`,
77
77
  );
78
78
  writeGithubActionsOutputs({ 'has-errors': 'true' });
79
79
  process.exit(1);
@@ -89,7 +89,7 @@ export async function run() {
89
89
  await entry.run(options, null, { gitRoot });
90
90
  }
91
91
 
92
- run().catch((err) => {
92
+ run().catch(err => {
93
93
  console.error(err);
94
94
  // A thrown error (as opposed to a command's own controlled process.exit)
95
95
  // means whatever run() was doing never reached its own
@@ -25,7 +25,7 @@ export const slotOrphansSystemPrompt = [
25
25
  'Example output: {"assignments": [0, 2, 0, 1, -1, 3]}',
26
26
  '',
27
27
  'Guidance:',
28
- '- Choose the best semantic fit based on the orphan\'s title and URL path.',
28
+ "- Choose the best semantic fit based on the orphan's title and URL path.",
29
29
  '- Only use -1 when no existing category is plausible.',
30
30
  ].join('\n');
31
31
 
@@ -54,13 +54,18 @@ export const slotOrphansOutputSchema = {
54
54
  */
55
55
  export function buildSlotOrphansUserPrompt({ categories, orphans }) {
56
56
  const catList = categories.map((c, i) => {
57
- const sample = (c.pages || []).slice(0, 3).map((p) => p.title).join(', ');
57
+ const sample = (c.pages || [])
58
+ .slice(0, 3)
59
+ .map(p => p.title)
60
+ .join(', ');
58
61
  return `${i}. ${c.title}${sample ? ` (e.g. ${sample})` : ''}`;
59
62
  });
60
63
 
61
64
  const orphanList = orphans.map((p, i) => {
62
65
  let relPath = p.url;
63
- try { relPath = new URL(p.url).pathname; } catch {}
66
+ try {
67
+ relPath = new URL(p.url).pathname;
68
+ } catch {}
64
69
  return `${i}. ${p.title} — ${relPath}`;
65
70
  });
66
71
 
@@ -264,13 +269,17 @@ export const organizeFromScratchOutputSchema = {
264
269
  export function buildOrganizeFromScratchUserPrompt({ siteTitle, items }) {
265
270
  const compactLines = items.map((it, idx) => {
266
271
  let relPath = it.url;
267
- try { relPath = new URL(it.url).pathname + new URL(it.url).search; } catch {}
272
+ try {
273
+ relPath = new URL(it.url).pathname + new URL(it.url).search;
274
+ } catch {}
268
275
  return `${idx}\t${it.title}\t${relPath}`;
269
276
  });
270
277
 
271
278
  let origin = '(unknown)';
272
279
  if (items.length > 0) {
273
- try { origin = new URL(items[0].url).origin; } catch {}
280
+ try {
281
+ origin = new URL(items[0].url).origin;
282
+ } catch {}
274
283
  }
275
284
 
276
285
  return [
@@ -507,7 +516,8 @@ export function prettifyPagePrompt(input) {
507
516
  * @returns {string}
508
517
  */
509
518
  export function stripCodeFences(text) {
510
- return String(text).trim()
519
+ return String(text)
520
+ .trim()
511
521
  .replace(/^```json\s*/i, '')
512
522
  .replace(/^```\s*/i, '')
513
523
  .replace(/\s*```$/, '')
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;