@readme/cli 0.1.0 → 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 (53) hide show
  1. package/bin/readme.js +1 -1
  2. package/package.json +33 -24
  3. package/src/bootstrap.js +9 -13
  4. package/src/cli.js +17 -11
  5. package/src/commands/components.js +35 -18
  6. package/src/commands/dev.js +12 -8
  7. package/src/commands/eyes.js +12 -8
  8. package/src/commands/import.js +2190 -2048
  9. package/src/commands/lint.js +33 -29
  10. package/src/commands/oas-bundle.js +6 -2
  11. package/src/commands/oas-sync.js +96 -93
  12. package/src/commands/oas-validate.js +10 -7
  13. package/src/commands/pretty.js +177 -164
  14. package/src/commands/setup.js +24 -26
  15. package/src/commands/versions.js +6 -2
  16. package/src/dev/app/[...slug]/page.js +1 -0
  17. package/src/dev/app/layout.js +18 -11
  18. package/src/dev/app/page.js +1 -0
  19. package/src/dev/lib/docs.js +27 -19
  20. package/src/dev/next.config.mjs +2 -2
  21. package/src/gha.js +7 -7
  22. package/src/prompts/index.js +16 -6
  23. package/src/utils/eyes.js +143 -51
  24. package/src/utils/git.js +3 -3
  25. package/src/utils/lint.js +39 -38
  26. package/src/utils/llms.js +94 -91
  27. package/src/utils/markdown.js +256 -0
  28. package/src/utils/marketplace.js +12 -5
  29. package/src/utils/multi-spinner.js +5 -7
  30. package/src/utils/promise-pool.js +7 -5
  31. package/src/utils/reporter.js +88 -91
  32. package/src/utils/setup-templates.js +12 -6
  33. package/src/utils/styles.js +2 -1
  34. package/src/utils/tamagotchi.js +257 -91
  35. package/src/utils/tips.js +18 -9
  36. package/src/utils/url-segs.js +46 -41
  37. package/src/validators/components.js +55 -63
  38. package/src/validators/content.js +5 -10
  39. package/src/validators/duplicates.js +6 -2
  40. package/src/validators/frontmatter.js +19 -21
  41. package/src/validators/links.js +4 -8
  42. package/src/validators/oas-reference.js +7 -14
  43. package/src/validators/oas-schema.js +2 -0
  44. package/src/validators/ordering.js +5 -5
  45. package/src/validators/recipes.js +4 -12
  46. package/src/commands/import-oas.test.js +0 -277
  47. package/src/commands/import.classify.test.js +0 -172
  48. package/src/commands/import.landing.test.js +0 -217
  49. package/src/commands/import.slug-planner.test.js +0 -86
  50. package/src/commands/import.staging.test.js +0 -230
  51. package/src/commands/import.test.js +0 -382
  52. package/src/utils/llms.test.js +0 -302
  53. package/src/utils/url-segs.test.js +0 -239
package/bin/readme.js CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  import { main } from '../src/cli.js';
4
4
 
5
- main().catch((err) => {
5
+ main().catch(err => {
6
6
  console.error(err);
7
7
  process.exit(1);
8
8
  });
package/package.json CHANGED
@@ -1,12 +1,24 @@
1
1
  {
2
2
  "name": "@readme/cli",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "The ReadMe CLI",
5
- "type": "module",
5
+ "keywords": [
6
+ "cli",
7
+ "docs",
8
+ "readme"
9
+ ],
10
+ "license": "ISC",
6
11
  "bin": {
7
12
  "readme": "./bin/readme.js",
8
13
  "readme_": "./bin/readme.js"
9
14
  },
15
+ "files": [
16
+ "bin",
17
+ "src",
18
+ "vendor",
19
+ "README.md"
20
+ ],
21
+ "type": "module",
10
22
  "exports": {
11
23
  ".": "./src/index.js",
12
24
  "./commands/lint": "./src/commands/lint.js",
@@ -17,30 +29,18 @@
17
29
  "./prompts": "./src/prompts/index.js",
18
30
  "./package.json": "./package.json"
19
31
  },
20
- "files": [
21
- "bin",
22
- "src",
23
- "vendor",
24
- "README.md"
25
- ],
26
- "scripts": {
27
- "start": "node bin/readme.js",
28
- "test": "node --test src/**/*.test.js test/*.test.js",
29
- "build:gha": "ncc build src/gha.js -o dist-gha --external @readme/markdown"
30
- },
31
- "engines": {
32
- "node": ">=22"
33
- },
34
- "keywords": [
35
- "readme",
36
- "cli",
37
- "docs"
38
- ],
39
- "license": "ISC",
40
32
  "publishConfig": {
41
33
  "access": "public",
42
34
  "registry": "https://registry.npmjs.org"
43
35
  },
36
+ "scripts": {
37
+ "build:gha": "ncc build src/gha.js -o dist-gha --external @readme/markdown",
38
+ "format": "npx oxlint --fix && npx oxfmt --write",
39
+ "lint": "npx oxlint && npx oxfmt --check .",
40
+ "pretest": "npm run lint",
41
+ "start": "node bin/readme.js",
42
+ "test": "vitest run"
43
+ },
44
44
  "dependencies": {
45
45
  "@anthropic-ai/claude-agent-sdk": "^0.3.207",
46
46
  "@readme/markdown": "^13.3.0",
@@ -50,16 +50,25 @@
50
50
  "chalk": "^5.4.1",
51
51
  "cli-spinners": "^3.4.0",
52
52
  "commander": "^13.1.0",
53
- "gray-matter": "^4.0.3",
53
+ "js-yaml": "^5.5.0",
54
54
  "log-update": "^8.0.0",
55
55
  "next": "^15.0.0",
56
56
  "oas-normalize": "^15.7.1",
57
57
  "ora": "^9.3.0",
58
58
  "react": "^18.3.1",
59
59
  "react-dom": "^18.3.1",
60
+ "remove-undefined-objects": "^9.0.0",
60
61
  "update-notifier": "^7.3.1"
61
62
  },
62
63
  "devDependencies": {
63
- "@vercel/ncc": "^0.45.0"
64
+ "@readme/oxlint-config": "^1.9.0",
65
+ "@vercel/ncc": "^0.45.0",
66
+ "@vitest/coverage-v8": "^5.0.5",
67
+ "oxfmt": "^0.74.0",
68
+ "oxlint": "^1.89.0",
69
+ "vitest": "^5.0.5"
70
+ },
71
+ "engines": {
72
+ "node": ">=22"
64
73
  }
65
74
  }
package/src/bootstrap.js CHANGED
@@ -1,8 +1,10 @@
1
1
  import { execSync } from 'node:child_process';
2
2
  import fs from 'node:fs';
3
- import path from 'node:path';
4
3
  import { createRequire } from 'node:module';
4
+ import path from 'node:path';
5
+
5
6
  import updateNotifier from 'update-notifier';
7
+
6
8
  import * as styles from './utils/styles.js';
7
9
 
8
10
  const require = createRequire(import.meta.url);
@@ -22,7 +24,7 @@ export default async function bootstrap({ skipValidation = false } = {}) {
22
24
  // 3. Make sure this is a ReadMe project repo
23
25
  if (!skipValidation) {
24
26
  if (!findGitRoot()) {
25
- styles.error('This doesn\'t appear to be a git repository.');
27
+ styles.error("This doesn't appear to be a git repository.");
26
28
  styles.info('Run this command from inside a git repo to get started.');
27
29
  styles.info(`You can skip this check with ${styles.bold(`${styles.binName()} --no-check`)}.`);
28
30
  process.exit(1);
@@ -67,30 +69,24 @@ function validateReadMeRepo(gitRoot) {
67
69
  branches = '';
68
70
  }
69
71
 
70
- const hasVersionBranch = branches
71
- .split('\n')
72
- .some((b) => /^(origin\/)?v[0-9]/.test(b.trim()));
72
+ const hasVersionBranch = branches.split('\n').some(b => /^(origin\/)?v[0-9]/.test(b.trim()));
73
73
 
74
74
  // Check for /docs or /reference at the repo root
75
75
  const hasDocs = fs.existsSync(path.join(gitRoot, 'docs'));
76
76
  const hasReference = fs.existsSync(path.join(gitRoot, 'reference'));
77
77
 
78
78
  if (!hasVersionBranch || (!hasDocs && !hasReference)) {
79
- styles.error('This doesn\'t look like a ReadMe docs repo.');
79
+ styles.error("This doesn't look like a ReadMe docs repo.");
80
80
 
81
81
  if (!hasVersionBranch) {
82
- styles.info(
83
- `We couldn't find a version branch (e.g. ${styles.bold('v1')}, ${styles.bold('v2')}).`
84
- );
82
+ styles.info(`We couldn't find a version branch (e.g. ${styles.bold('v1')}, ${styles.bold('v2')}).`);
85
83
  }
86
84
  if (!hasDocs && !hasReference) {
87
- styles.info(
88
- `We couldn't find a ${styles.bold('/docs')} or ${styles.bold('/reference')} folder.`
89
- );
85
+ styles.info(`We couldn't find a ${styles.bold('/docs')} or ${styles.bold('/reference')} folder.`);
90
86
  }
91
87
 
92
88
  console.log();
93
- styles.info('Make sure you\'re running this inside a ReadMe docs project.');
89
+ styles.info("Make sure you're running this inside a ReadMe docs project.");
94
90
  styles.info(`You can skip this check with ${styles.bold(`${styles.binName()} --no-check`)}.`);
95
91
  process.exit(1);
96
92
  }
package/src/cli.js CHANGED
@@ -1,12 +1,14 @@
1
1
  import fs from 'node:fs';
2
+ import { createRequire } from 'node:module';
2
3
  import path from 'node:path';
3
4
  import { pathToFileURL, fileURLToPath } from 'node:url';
5
+
4
6
  import { Command } from 'commander';
5
- import { createRequire } from 'node:module';
6
7
  import updateNotifier from 'update-notifier';
8
+
7
9
  import bootstrap from './bootstrap.js';
8
- import * as styles from './utils/styles.js';
9
10
  import { header, setPalette, isAgenticCli } from './utils/eyes.js';
11
+ import * as styles from './utils/styles.js';
10
12
  import { loadPet, applyDecay, getPetHeader } from './utils/tamagotchi.js';
11
13
  import { getRandomTip } from './utils/tips.js';
12
14
 
@@ -46,18 +48,20 @@ export async function main() {
46
48
 
47
49
  if (!isRunningInClaude) {
48
50
  program.addHelpText('beforeAll', () => {
49
- return '\n' + header({
51
+ return `\n${header({
50
52
  version: pkg.version,
51
53
  binName: styles.binName(),
52
54
  greeting: petGreeting,
53
55
  expression: petExpression,
54
- }).map(l => ' ' + l).join('\n') + '\n';
56
+ })
57
+ .map(l => ` ${l}`)
58
+ .join('\n')}\n`;
55
59
  });
56
60
  }
57
61
 
58
62
  // Auto-discover and register every command in src/commands/
59
63
  const commandsDir = path.join(path.dirname(fileURLToPath(import.meta.url)), 'commands');
60
- const files = fs.readdirSync(commandsDir).filter((f) => f.endsWith('.js') && !f.endsWith('.test.js'));
64
+ const files = fs.readdirSync(commandsDir).filter(f => f.endsWith('.js') && !f.endsWith('.test.js'));
61
65
 
62
66
  // Load all modules, then sort by order (default 0)
63
67
  const mods = [];
@@ -111,7 +115,7 @@ export async function main() {
111
115
  program.addHelpText('after', () => {
112
116
  // Group registered commands by category. The built-in `help` command
113
117
  // doesn't go through the loop above, so we add it manually.
114
- const groups = { 'Linting': [], 'OAS Tooling': [], 'Other': [] };
118
+ const groups = { Linting: [], 'OAS Tooling': [], Other: [] };
115
119
  for (const [cmd, category] of commandCategories.entries()) {
116
120
  if (cmd._hidden) continue;
117
121
  if (!groups[category]) groups[category] = [];
@@ -123,12 +127,12 @@ export async function main() {
123
127
 
124
128
  const allCmds = Object.values(groups).flat();
125
129
  if (allCmds.length === 0) return '';
126
- const nameWidth = Math.max(...allCmds.map((c) => c.name().length));
130
+ const nameWidth = Math.max(...allCmds.map(c => c.name().length));
127
131
 
128
132
  // Map cmd → its source module so we can pull optional helpHint.
129
133
  const cmdToMod = new Map();
130
134
  for (const mod of mods) {
131
- const cmd = program.commands.find((c) => c.name() === mod.command);
135
+ const cmd = program.commands.find(c => c.name() === mod.command);
132
136
  if (cmd) cmdToMod.set(cmd, mod);
133
137
  }
134
138
 
@@ -158,10 +162,12 @@ export async function main() {
158
162
  if (tip) {
159
163
  let output = '';
160
164
  const origLog = console.log;
161
- console.log = (...args) => { output += args.join(' ') + '\n'; };
165
+ console.log = (...args) => {
166
+ output += `${args.join(' ')}\n`;
167
+ };
162
168
  tip.render();
163
169
  console.log = origLog;
164
- return '\n' + output;
170
+ return `\n${output}`;
165
171
  }
166
172
  return '';
167
173
  });
@@ -185,7 +191,7 @@ export async function main() {
185
191
  let cmdIdx = 2;
186
192
  while (cmdIdx < argv.length && argv[cmdIdx].startsWith('-')) cmdIdx++;
187
193
  const cmdArg = argv[cmdIdx];
188
- if (cmdArg && cmdArg.includes(':') && !program.commands.some((c) => c.name() === cmdArg)) {
194
+ if (cmdArg && cmdArg.includes(':') && !program.commands.some(c => c.name() === cmdArg)) {
189
195
  const [base, ...rest] = cmdArg.split(':');
190
196
  argv.splice(cmdIdx, 1, base, rest.join(':'));
191
197
  }
@@ -13,9 +13,11 @@
13
13
  import fs from 'node:fs';
14
14
  import path from 'node:path';
15
15
  import readline from 'node:readline';
16
- import ora from 'ora';
16
+
17
17
  import { query } from '@anthropic-ai/claude-agent-sdk';
18
- import * as styles from '../utils/styles.js';
18
+ import ora from 'ora';
19
+
20
+ import { stripCodeFences } from '../prompts/index.js';
19
21
  import {
20
22
  listMarketplaceComponents,
21
23
  fetchMarketplaceComponent,
@@ -23,7 +25,7 @@ import {
23
25
  localComponentsDir,
24
26
  listLocalComponents,
25
27
  } from '../utils/marketplace.js';
26
- import { stripCodeFences } from '../prompts/index.js';
28
+ import * as styles from '../utils/styles.js';
27
29
 
28
30
  export const command = 'components [action] [name...]';
29
31
  export const order = 3;
@@ -33,7 +35,7 @@ export const beta = true;
33
35
  export const helpHint = [
34
36
  'readme components # list marketplace + installed',
35
37
  'readme components add <Name> # copy a marketplace component into ./components/',
36
- 'readme components show <Name> # print the component\'s source',
38
+ "readme components show <Name> # print the component's source",
37
39
  'readme components new "<idea>" # ask Claude to generate a new component',
38
40
  ].join('\n');
39
41
 
@@ -46,7 +48,7 @@ export function args(cmd) {
46
48
  export async function run(action, names, options, _cmd, ctx) {
47
49
  const { gitRoot } = ctx;
48
50
  const sub = (action || 'list').toLowerCase();
49
- const arg = (names && names.length) ? names.join(' ') : null;
51
+ const arg = names && names.length ? names.join(' ') : null;
50
52
 
51
53
  switch (sub) {
52
54
  case 'list':
@@ -107,9 +109,9 @@ async function runList(gitRoot, options) {
107
109
  }
108
110
  spinner.stop();
109
111
 
110
- const local = new Set(listLocalComponents(gitRoot).map((c) => c.name.toLowerCase()));
112
+ const local = new Set(listLocalComponents(gitRoot).map(c => c.name.toLowerCase()));
111
113
 
112
- const nameWidth = Math.max(...marketplace.map((c) => c.name.length));
114
+ const nameWidth = Math.max(...marketplace.map(c => c.name.length));
113
115
  for (const c of marketplace) {
114
116
  const installed = local.has(c.name.toLowerCase());
115
117
  const marker = installed ? styles.success('●') : styles.dim('○');
@@ -118,7 +120,7 @@ async function runList(gitRoot, options) {
118
120
  }
119
121
 
120
122
  console.log();
121
- const installedCount = [...local].filter((n) => marketplace.some((c) => c.name.toLowerCase() === n)).length;
123
+ const installedCount = [...local].filter(n => marketplace.some(c => c.name.toLowerCase() === n)).length;
122
124
  styles.info(`${marketplace.length} components in the marketplace · ${installedCount} already installed.`);
123
125
  styles.info(`Run ${styles.bold('readme components add <Name>')} to copy one into ./components/.`);
124
126
  }
@@ -173,12 +175,22 @@ async function runShow(name, options) {
173
175
  console.log(` ${styles.heading(comp.name)} ${styles.dim(comp.htmlUrl)}`);
174
176
  if (comp.readme) {
175
177
  console.log();
176
- console.log(comp.readme.split('\n').map((l) => ' ' + l).join('\n'));
178
+ console.log(
179
+ comp.readme
180
+ .split('\n')
181
+ .map(l => ` ${l}`)
182
+ .join('\n'),
183
+ );
177
184
  }
178
185
  console.log();
179
186
  console.log(` ${styles.bold('Source')} ${styles.dim(`(${comp.name}.mdx)`)}`);
180
187
  console.log();
181
- console.log(comp.mdx.split('\n').map((l) => ' ' + l).join('\n'));
188
+ console.log(
189
+ comp.mdx
190
+ .split('\n')
191
+ .map(l => ` ${l}`)
192
+ .join('\n'),
193
+ );
182
194
  console.log();
183
195
  }
184
196
 
@@ -198,7 +210,9 @@ async function runNew(gitRoot, description, options) {
198
210
  try {
199
211
  const c = await fetchMarketplaceComponent(n);
200
212
  exemplars.push({ name: c.name, mdx: c.mdx });
201
- } catch { /* skip */ }
213
+ } catch {
214
+ /* skip */
215
+ }
202
216
  }
203
217
 
204
218
  const systemPrompt = [
@@ -221,7 +235,7 @@ async function runNew(gitRoot, description, options) {
221
235
  '',
222
236
  'For style and conventions, follow these existing components (do NOT copy them — match their structure):',
223
237
  '',
224
- ...exemplars.map((e) => `### ${e.name}\n\`\`\`mdx\n${e.mdx}\n\`\`\``),
238
+ ...exemplars.map(e => `### ${e.name}\n\`\`\`mdx\n${e.mdx}\n\`\`\``),
225
239
  '',
226
240
  'Now write the new component .mdx. Pick a good PascalCase name that matches the brief.',
227
241
  ].join('\n');
@@ -243,7 +257,7 @@ async function runNew(gitRoot, description, options) {
243
257
  }
244
258
  } else if (message.type === 'result') {
245
259
  if (message.subtype && message.subtype !== 'success') {
246
- throw new Error(`${message.subtype}${message.error?.message ? ' — ' + message.error.message : ''}`);
260
+ throw new Error(`${message.subtype}${message.error?.message ? ` — ${message.error.message}` : ''}`);
247
261
  }
248
262
  break;
249
263
  }
@@ -258,7 +272,7 @@ async function runNew(gitRoot, description, options) {
258
272
  const mdx = stripCodeFences(text).trim();
259
273
  const nameMatch = mdx.match(/export\s+const\s+([A-Z][A-Za-z0-9]+)\s*=/);
260
274
  if (!nameMatch) {
261
- styles.error('Claude\'s response did not include `export const <Name> = ...`. Try again.');
275
+ styles.error("Claude's response did not include `export const <Name> = ...`. Try again.");
262
276
  process.exit(1);
263
277
  }
264
278
 
@@ -266,10 +280,13 @@ async function runNew(gitRoot, description, options) {
266
280
  const file = path.join(dest, `${name}.mdx`);
267
281
  if (fs.existsSync(file) && !options.yes) {
268
282
  const ok = await confirm(`${styles.bold(name)}.mdx already exists. Overwrite?`);
269
- if (!ok) { styles.info('Aborted.'); return; }
283
+ if (!ok) {
284
+ styles.info('Aborted.');
285
+ return;
286
+ }
270
287
  }
271
288
 
272
- fs.writeFileSync(file, mdx + '\n', 'utf-8');
289
+ fs.writeFileSync(file, `${mdx}\n`, 'utf-8');
273
290
  console.log();
274
291
  styles.ok(`Created ${styles.bold(name)} → ${styles.dim(path.relative(gitRoot, file))}`);
275
292
  styles.info(`Use it in any docs page as ${styles.bold(`<${name} ... />`)}.`);
@@ -278,9 +295,9 @@ async function runNew(gitRoot, description, options) {
278
295
  // ─── tiny readline confirm ───────────────────────────────────────────────────
279
296
 
280
297
  function confirm(message) {
281
- return new Promise((resolve) => {
298
+ return new Promise(resolve => {
282
299
  const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
283
- rl.question(`${styles.warn('?')} ${message} ${styles.dim('[y/N]')} `, (ans) => {
300
+ rl.question(`${styles.warn('?')} ${message} ${styles.dim('[y/N]')} `, ans => {
284
301
  rl.close();
285
302
  resolve(/^y(es)?$/i.test(ans.trim()));
286
303
  });
@@ -1,11 +1,13 @@
1
1
  import fs from 'fs';
2
- import path from 'path';
3
2
  import { createServer } from 'http';
4
- import { fileURLToPath } from 'url';
5
3
  import { createRequire } from 'node:module';
4
+ import path from 'path';
5
+ import { fileURLToPath } from 'url';
6
+
6
7
  import chalk from 'chalk';
7
- import { binName } from '../utils/styles.js';
8
+
8
9
  import { header, setPalette, isAgenticCli } from '../utils/eyes.js';
10
+ import { binName } from '../utils/styles.js';
9
11
  import { loadPet } from '../utils/tamagotchi.js';
10
12
 
11
13
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
@@ -35,15 +37,17 @@ export async function run(options, _cmd, ctx) {
35
37
  try {
36
38
  const pet = loadPet();
37
39
  if (pet?.color) setPalette(pet.color);
38
- } catch {}
40
+ } catch {
41
+ // no-op
42
+ }
39
43
 
40
44
  // Suppress Next.js noisy compile/request logging
41
45
  for (const stream of [process.stdout, process.stderr]) {
42
46
  const original = stream.write.bind(stream);
43
- stream.write = (chunk, ...args) => {
47
+ stream.write = (chunk, ...streamAgs) => {
44
48
  const str = typeof chunk === 'string' ? chunk : chunk.toString();
45
49
  if (/^ [○✓⨯▲ ]*(Compiled|Compiling|GET |POST |⚠|Warning:)/.test(str)) return true;
46
- return original(chunk, ...args);
50
+ return original(chunk, ...streamAgs);
47
51
  };
48
52
  }
49
53
 
@@ -84,7 +88,7 @@ export async function run(options, _cmd, ctx) {
84
88
 
85
89
  function listen(port) {
86
90
  return new Promise((resolve, reject) => {
87
- const onError = (err) => {
91
+ const onError = err => {
88
92
  if (err.code === 'EADDRINUSE') {
89
93
  resolve(listen(port + 1));
90
94
  } else {
@@ -108,7 +112,7 @@ export async function run(options, _cmd, ctx) {
108
112
  binName: `${binName()} dev`,
109
113
  });
110
114
  for (const line of headerLines) {
111
- console.log(' ' + line);
115
+ console.log(` ${line}`);
112
116
  }
113
117
  console.log();
114
118
  }
@@ -1,4 +1,5 @@
1
1
  import { createRequire } from 'node:module';
2
+
2
3
  import { printEyes, animate, expressions, animations } from '../utils/eyes.js';
3
4
  import { binName } from '../utils/styles.js';
4
5
 
@@ -11,14 +12,12 @@ export const skipBootstrap = true;
11
12
 
12
13
  export async function run(variant) {
13
14
  if (!variant) {
14
- // Show all expressions side by side
15
- const chalk = (await import('chalk')).default;
16
- const names = Object.keys(expressions);
17
- const rendered = Object.fromEntries(names.map(n => [n, expressions[n] ? require : null]));
18
-
19
15
  console.log('');
20
16
  const ctrl = animate('all', { indent: ' ', version: pkg.version, binName: binName() });
21
- process.on('SIGINT', () => { ctrl.stop(); process.exit(); });
17
+ process.on('SIGINT', () => {
18
+ ctrl.stop();
19
+ process.exit();
20
+ });
22
21
  return;
23
22
  }
24
23
 
@@ -29,9 +28,14 @@ export async function run(variant) {
29
28
  } else if (animations[variant]) {
30
29
  console.log('');
31
30
  const ctrl = animate(variant, { indent: ' ', version: pkg.version, binName: binName() });
32
- process.on('SIGINT', () => { ctrl.stop(); process.exit(); });
31
+ process.on('SIGINT', () => {
32
+ ctrl.stop();
33
+ process.exit();
34
+ });
33
35
  } else {
34
- console.error(`Unknown: "${variant}". Try: ${[...Object.keys(expressions), ...Object.keys(animations)].join(', ')}`);
36
+ console.error(
37
+ `Unknown: "${variant}". Try: ${[...Object.keys(expressions), ...Object.keys(animations)].join(', ')}`,
38
+ );
35
39
  process.exit(1);
36
40
  }
37
41
  }