create-renoun 1.0.0 → 1.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 (2) hide show
  1. package/dist/index.mjs +112 -169
  2. package/package.json +5 -4
package/dist/index.mjs CHANGED
@@ -1,52 +1,14 @@
1
1
  #!/usr/bin/env node
2
+ import { installPackage } from '@antfu/install-pkg';
3
+ import { log, text, isCancel, confirm, spinner, intro, select, outro, cancel } from '@clack/prompts';
2
4
  import { readFileSync, existsSync, mkdirSync, writeFileSync, rmdirSync, createWriteStream } from 'node:fs';
3
5
  import __node_cjsPath, { resolve, basename, join } from 'node:path';
4
- import chalk from 'chalk';
6
+ import color from 'picocolors';
7
+ import terminalLink from 'terminal-link';
5
8
  import { Readable } from 'node:stream';
6
9
  import { pipeline } from 'node:stream/promises';
7
10
  import { homedir } from 'node:os';
8
- import process$1, { stdin, stdout } from 'node:process';
9
- import { createInterface } from 'node:readline/promises';
10
11
  import __node_cjsUrl from 'node:url';
11
- import { emitKeypressEvents, moveCursor, clearLine } from 'node:readline';
12
-
13
- class Log {
14
- static{
15
- this.base = 'renoun: ';
16
- }
17
- static{
18
- this.offset = Log.base.replace(/./g, ' ');
19
- }
20
- static info(message) {
21
- const finalMessage = chalk.rgb(205, 237, 255).bold(Log.base) + message;
22
- console.log(finalMessage.replace(/\n/g, `\n${Log.offset}`));
23
- }
24
- static error(message) {
25
- const finalMessage = chalk.rgb(237, 35, 0).bold(Log.base) + chalk.rgb(225, 205, 205)(message);
26
- console.error(finalMessage.replace(/\n/g, `\n${Log.offset}`));
27
- }
28
- static success(message) {
29
- const finalMessage = chalk.rgb(0, 204, 102).bold(Log.base) + message;
30
- console.log(finalMessage.replace(/\n/g, `\n${Log.offset}`));
31
- }
32
- static warning(message) {
33
- console.warn(chalk.rgb(255, 153, 51).bold(Log.base) + chalk.rgb(225, 200, 190)(message));
34
- }
35
- }
36
- async function askQuestion(question) {
37
- const readline = createInterface({
38
- input: stdin,
39
- output: stdout,
40
- terminal: true
41
- });
42
- const answer = await readline.question(`${chalk.rgb(205, 237, 255).bold(Log.base)}${question}`);
43
- readline.close();
44
- return answer;
45
- }
46
- async function askYesNo(question, { defaultYes = true, description } = {}) {
47
- const answer = await askQuestion(`${question} [${defaultYes ? 'Y/n' : 'y/N'}] ${description ? chalk.dim(description) : ''}`);
48
- return answer === '' ? defaultYes : answer.toLowerCase().startsWith('y');
49
- }
50
12
 
51
13
  const __filename$1 = __node_cjsUrl.fileURLToPath(import.meta.url);
52
14
  const __dirname$1 = __node_cjsPath.dirname(__filename$1);
@@ -73,7 +35,7 @@ if (!existsSync(cacheDirectory)) {
73
35
  return data.latest;
74
36
  } catch (error) {
75
37
  if (error instanceof Error) {
76
- Log.error(`Error fetching package version: ${error}`);
38
+ log.error(`Error fetching package version: ${error}`);
77
39
  }
78
40
  clearTimeout(timeoutId);
79
41
  return packageJson$1.version;
@@ -118,56 +80,90 @@ function shouldRefreshCache() {
118
80
  */ async function fetchExample(exampleSlug, message = '') {
119
81
  let workingDirectory = process.cwd();
120
82
  const directoryPath = `examples/${exampleSlug}`;
121
- const directoryName = chalk.bold(basename(directoryPath));
83
+ const directoryName = color.bold(basename(directoryPath));
122
84
  const postMessage = ` Press enter to proceed or specify a different directory: `;
123
- const userBaseDirectory = await askQuestion(message ? `${message}${postMessage}` : `Download the ${chalk.bold(directoryName)} example to ${chalk.bold(join(workingDirectory, exampleSlug))}}?${postMessage}`);
85
+ const userBaseDirectory = await text({
86
+ message: message ? `${message}${postMessage}` : `Download the ${directoryName} example to ${color.bold(join(workingDirectory, exampleSlug))}?${postMessage}`,
87
+ placeholder: exampleSlug
88
+ });
89
+ if (isCancel(userBaseDirectory)) {
90
+ throw new Error('Example download cancelled.');
91
+ }
124
92
  if (userBaseDirectory) {
125
93
  workingDirectory = join(workingDirectory, userBaseDirectory);
126
94
  } else {
127
95
  workingDirectory = join(workingDirectory, exampleSlug);
128
96
  }
129
97
  if (existsSync(workingDirectory)) {
130
- const isYes = await askYesNo(`The directory ${chalk.bold(workingDirectory)} already exists. Do you want to overwrite it?`, {
131
- defaultYes: false
98
+ const isYes = await confirm({
99
+ message: `The directory ${color.bold(workingDirectory)} already exists. Do you want to overwrite it?`,
100
+ initialValue: false
132
101
  });
102
+ if (isCancel(isYes)) {
103
+ log.warn('Overwrite confirmation cancelled.');
104
+ throw new Error('User cancelled the overwrite.');
105
+ }
133
106
  if (isYes) {
134
- rmdirSync(workingDirectory, {
135
- recursive: true
136
- });
137
- mkdirSync(workingDirectory, {
138
- recursive: true
139
- });
107
+ try {
108
+ rmdirSync(workingDirectory, {
109
+ recursive: true
110
+ });
111
+ mkdirSync(workingDirectory, {
112
+ recursive: true
113
+ });
114
+ log.info(`Overwritten the directory ${color.bold(workingDirectory)}.`);
115
+ } catch (error) {
116
+ if (error instanceof Error) {
117
+ throw new Error(`Failed to overwrite directory ${workingDirectory}: ${error.message}`);
118
+ }
119
+ }
140
120
  } else {
141
- Log.info(`Skipping download of ${directoryName} example to ${chalk.bold(workingDirectory)}.`);
142
- return;
121
+ log.info(`Skipping download of ${directoryName} example to ${color.bold(workingDirectory)}.`);
122
+ return false;
143
123
  }
144
124
  } else {
145
125
  mkdirSync(workingDirectory, {
146
126
  recursive: true
147
127
  });
148
128
  }
149
- Log.info(`Downloading ${directoryName} example to ${chalk.bold(workingDirectory)}.`);
150
- await fetchGitHubDirectory({
151
- owner: 'souporserious',
152
- repo: 'renoun',
153
- branch: 'main',
154
- basePath: '.',
155
- directoryPath,
156
- workingDirectory
157
- });
129
+ log.info(`Downloading ${directoryName} example to ${color.bold(workingDirectory)}.`);
130
+ const loader = spinner();
131
+ loader.start('Fetching example files...');
132
+ try {
133
+ await fetchGitHubDirectory({
134
+ owner: 'souporserious',
135
+ repo: 'renoun',
136
+ branch: 'main',
137
+ basePath: '.',
138
+ directoryPath,
139
+ workingDirectory
140
+ });
141
+ loader.stop('Example files fetched successfully.');
142
+ } catch (error) {
143
+ log.error('Failed to fetch example files.');
144
+ throw error;
145
+ }
158
146
  const { detectPackageManager } = await import('@antfu/install-pkg');
159
- const packageManager = await detectPackageManager(process.cwd());
160
- await reformatPackageJson(workingDirectory);
147
+ const packageManager = await detectPackageManager(workingDirectory);
148
+ try {
149
+ await reformatPackageJson(workingDirectory);
150
+ loader.start('Reformatting package.json...');
151
+ loader.stop('package.json reformatted successfully.');
152
+ } catch (error) {
153
+ log.error('Failed to reformat package.json.');
154
+ throw error;
155
+ }
161
156
  writeFileSync(join(workingDirectory, '.gitignore'), '.next\nnode_modules\nout', 'utf-8');
162
- const introInstallInstructions = workingDirectory === process.cwd() ? `Run` : `Change to the ${chalk.bold(userBaseDirectory ?? directoryName)} directory and run`;
163
- Log.success(`Example ${chalk.bold(directoryName)} fetched and configured successfully! ${introInstallInstructions} ${chalk.bold(`${packageManager ?? 'npm'} install`)} to install the dependencies and get started.`);
157
+ const introInstallInstructions = workingDirectory === process.cwd() ? `Run ${color.bold(`${packageManager ?? 'npm'} install`)} to install the dependencies and get started.` : `Change to the ${color.bold(directoryName)} directory and run ${color.bold(`${packageManager ?? 'npm'} install`)} to install the dependencies and get started.`;
158
+ log.success(`Example ${color.bold(directoryName)} fetched and configured successfully! ${introInstallInstructions}`);
159
+ return true;
164
160
  }
165
161
  /** Fetches the contents of a directory in a GitHub repository and downloads them to the local file system. */ async function fetchGitHubDirectory({ owner, repo, branch, directoryPath, workingDirectory, basePath }) {
166
162
  const apiUrl = `https://api.github.com/repos/${owner}/${repo}/contents/${directoryPath}?ref=${branch}`;
167
163
  const response = await fetch(apiUrl);
168
- const directoryName = chalk.bold(basename(directoryPath));
164
+ const directoryName = color.bold(basename(directoryPath));
169
165
  if (!response.ok) {
170
- throw new Error(`Failed to fetch ${chalk.red(directoryName)} at ${apiUrl}: ${response.statusText}`);
166
+ throw new Error(`Failed to fetch ${directoryName} from ${apiUrl}: ${response.statusText}`);
171
167
  }
172
168
  const items = await response.json();
173
169
  for (let item of items){
@@ -253,79 +249,6 @@ const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
253
249
  return isVersionLessThan(packageJson.version, currentVersion);
254
250
  }
255
251
 
256
- async function pickExample({ options }) {
257
- const readline = createInterface({
258
- input: process$1.stdin,
259
- output: process$1.stdout,
260
- terminal: true
261
- });
262
- emitKeypressEvents(process$1.stdin);
263
- if (process$1.stdin.isTTY) {
264
- process$1.stdin.setRawMode(true);
265
- }
266
- let selectedIndex = 0;
267
- let lastRenderLineCount = 0;
268
- function render() {
269
- // Move the cursor back up to where we last printed, then clear those lines
270
- for(let index = 0; index < lastRenderLineCount; index++){
271
- moveCursor(process$1.stdout, 0, -1);
272
- clearLine(process$1.stdout, 0);
273
- }
274
- const lines = [];
275
- lines.push(chalk.bold('\nSelect an example below to clone and get started:\n'));
276
- for(let index = 0; index < options.length; index++){
277
- if (index === selectedIndex) {
278
- lines.push(chalk.cyan(`> ${options[index]}`));
279
- } else {
280
- lines.push(` ${options[index]}`);
281
- }
282
- }
283
- lines.push(chalk.dim('\nUse ↑ / ↓ to move, Enter to select, Ctrl+C to exit.'));
284
- const output = lines.join('\n');
285
- process$1.stdout.write(output + '\n');
286
- const newlineCount = (output.match(/\n/g) || []).length;
287
- lastRenderLineCount = newlineCount + 1;
288
- }
289
- render();
290
- return new Promise((resolve, reject)=>{
291
- function onKeyPress(_string, key) {
292
- if (!key) return;
293
- switch(key.name){
294
- case 'up':
295
- selectedIndex = (selectedIndex - 1 + options.length) % options.length;
296
- render();
297
- break;
298
- case 'down':
299
- selectedIndex = (selectedIndex + 1) % options.length;
300
- render();
301
- break;
302
- case 'return':
303
- cleanup();
304
- resolve(options[selectedIndex]);
305
- break;
306
- default:
307
- if (key.ctrl && key.name === 'c') {
308
- cleanup();
309
- reject(new Error('User aborted'));
310
- }
311
- }
312
- }
313
- function cleanup() {
314
- if (process$1.stdin.isTTY) {
315
- process$1.stdin.setRawMode(false);
316
- }
317
- process$1.stdin.off('keypress', onKeyPress);
318
- readline.close();
319
- // Clear the picker on exit
320
- for(let index = 0; index < lastRenderLineCount; index++){
321
- moveCursor(process$1.stdout, 0, -1);
322
- clearLine(process$1.stdout, 0);
323
- }
324
- }
325
- process$1.stdin.on('keypress', onKeyPress);
326
- });
327
- }
328
-
329
252
  const states = {
330
253
  INITIAL_STATE: 'initialState',
331
254
  CHECK_OUTDATED: 'checkOutdated',
@@ -343,8 +266,8 @@ async function start() {
343
266
  const context = { message: '' };
344
267
  let currentState = states.INITIAL_STATE;
345
268
 
346
- console.log(
347
- `Welcome to ${chalk.bold(chalk.yellow('renoun'))}!\nThe Documentation Toolkit for React`
269
+ intro(
270
+ `${color.bgYellowBright(color.bold(color.black('renoun')))} The Documentation Toolkit for React`
348
271
  );
349
272
 
350
273
  while (
@@ -364,11 +287,8 @@ async function start() {
364
287
  */
365
288
  case states.CHECK_OUTDATED: {
366
289
  if (await isPackageOutdated('create-renoun')) {
367
- const installCommand = chalk.bold('npm create renoun@latest');
368
- Log.warning(
369
- `A new version of ${chalk.bold(
370
- 'create-renoun'
371
- )} is available. Please use the latest version by running ${installCommand}.`
290
+ log.warn(
291
+ `A new version of ${color.bold('create-renoun')} is available. Please use the latest version by running ${color.bold('npm create renoun@latest')}.`
372
292
  );
373
293
  }
374
294
  currentState = states.CHECK_EXAMPLE;
@@ -411,21 +331,37 @@ async function start() {
411
331
  * - After fetching, go directly to SUCCESS_STATE.
412
332
  */
413
333
  case states.PICK_EXAMPLE: {
414
- const terminalLink = (await import('terminal-link')).default;
415
- const example = await pickExample({
416
- options: ['blog', 'design-system'],
334
+ const example = await select({
335
+ message: 'Choose an example below to get started:',
336
+ options: [
337
+ { value: 'blog', label: 'Blog' },
338
+ { value: 'design-system', label: 'Design System' },
339
+ ],
417
340
  });
341
+
342
+ if (isCancel(example)) {
343
+ context.message = 'Example selection cancelled.';
344
+ currentState = states.WARNING_STATE;
345
+ break
346
+ }
347
+
418
348
  const exampleLink = terminalLink(
419
349
  example,
420
350
  `https://github.com/souporserious/renoun/tree/main/examples/${example}`
421
351
  );
422
- await fetchExample(
352
+ const fetched = await fetchExample(
423
353
  example,
424
- `Download the ${exampleLink} example to ${chalk.bold(
425
- join(process.cwd(), example)
426
- )}?`
354
+ `Download the ${color.underline(
355
+ exampleLink
356
+ )} example to ${color.bold(join(process.cwd(), example))}?`
427
357
  );
428
- currentState = states.SUCCESS_STATE;
358
+
359
+ if (fetched) {
360
+ currentState = states.SUCCESS_STATE;
361
+ } else {
362
+ context.message = `Example download exited. Please download an example or install renoun manually to continue.`;
363
+ currentState = states.WARNING_STATE;
364
+ }
429
365
  break
430
366
  }
431
367
 
@@ -435,14 +371,20 @@ async function start() {
435
371
  */
436
372
  case states.CHECK_RENOUN_INSTALLED: {
437
373
  if (!checkRenounInstalled()) {
438
- const isYes = await askYesNo(
439
- `The ${chalk.bold('renoun')} package is not installed. Would you like to install it now?`
440
- );
374
+ const isYes = await confirm({
375
+ message: `The ${color.bold('renoun')} package is not installed. Would you like to install it now?`,
376
+ });
377
+
378
+ if (isCancel(isYes)) {
379
+ context.message = `Installation cancelled. Please install renoun manually.`;
380
+ currentState = states.WARNING_STATE;
381
+ break
382
+ }
441
383
 
442
384
  if (isYes) {
443
385
  currentState = states.INSTALL_RENOUN;
444
386
  } else {
445
- context.message = `Please install the ${chalk.bold('renoun')} package before continuing.`;
387
+ context.message = `Please install the ${color.bold('renoun')} package before continuing.`;
446
388
  currentState = states.WARNING_STATE;
447
389
  }
448
390
  } else {
@@ -471,11 +413,11 @@ async function start() {
471
413
  }
472
414
 
473
415
  if (currentState === states.SUCCESS_STATE) {
474
- Log.success('renoun configured successfully!');
416
+ outro('renoun configured successfully!');
475
417
  } else if (currentState === states.WARNING_STATE) {
476
- Log.warning(context.message);
418
+ log.warn(context.message);
477
419
  } else {
478
- Log.error(context.message);
420
+ cancel(context.message);
479
421
  process.exit(1);
480
422
  }
481
423
  }
@@ -497,15 +439,16 @@ function checkRenounInstalled() {
497
439
  * Install renoun using @antfu/install-pkg
498
440
  */
499
441
  async function installRenoun() {
500
- const { installPackage } = await import('@antfu/install-pkg');
501
- Log.info(`Installing ${chalk.bold('renoun')}...`);
442
+ const loader = spinner();
443
+
444
+ loader.start(`Installing ${color.bold('renoun')}...`);
502
445
 
503
446
  try {
504
447
  await installPackage(['renoun']);
505
- Log.success('renoun package installed successfully!');
448
+ loader.stop(`${color.bold('renoun')} installed successfully!`);
506
449
  } catch (error) {
507
450
  if (error instanceof Error) {
508
- throw new Error(`Installing renoun failed: ${error.message}`)
451
+ throw new Error(`Failed to install renoun: ${error.message}`)
509
452
  }
510
453
  }
511
454
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "create-renoun",
3
3
  "type": "module",
4
- "version": "1.0.0",
4
+ "version": "1.2.0",
5
5
  "author": "Travis Arnold",
6
6
  "license": "AGPL-3.0-or-later",
7
7
  "repository": {
@@ -16,12 +16,13 @@
16
16
  ],
17
17
  "bin": "./dist/index.mjs",
18
18
  "devDependencies": {
19
- "bunchee": "^6.3.2"
19
+ "bunchee": "^6.5.0"
20
20
  },
21
21
  "dependencies": {
22
22
  "@antfu/install-pkg": "^1.0.0",
23
- "chalk": "^5.4.1",
24
- "terminal-link": "^3.0.0"
23
+ "@clack/prompts": "^0.10.0",
24
+ "picocolors": "^1.1.1",
25
+ "terminal-link": "^4.0.0"
25
26
  },
26
27
  "scripts": {
27
28
  "build": "bunchee",