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.
- package/dist/index.mjs +112 -169
- 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
|
|
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
|
-
|
|
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 =
|
|
83
|
+
const directoryName = color.bold(basename(directoryPath));
|
|
122
84
|
const postMessage = ` Press enter to proceed or specify a different directory: `;
|
|
123
|
-
const userBaseDirectory = await
|
|
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
|
|
131
|
-
|
|
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
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
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
|
-
|
|
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
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
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(
|
|
160
|
-
|
|
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 ${
|
|
163
|
-
|
|
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 =
|
|
164
|
+
const directoryName = color.bold(basename(directoryPath));
|
|
169
165
|
if (!response.ok) {
|
|
170
|
-
throw new Error(`Failed to fetch ${
|
|
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
|
-
|
|
347
|
-
|
|
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
|
-
|
|
368
|
-
|
|
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
|
|
415
|
-
|
|
416
|
-
options: [
|
|
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 ${
|
|
425
|
-
|
|
426
|
-
)}?`
|
|
354
|
+
`Download the ${color.underline(
|
|
355
|
+
exampleLink
|
|
356
|
+
)} example to ${color.bold(join(process.cwd(), example))}?`
|
|
427
357
|
);
|
|
428
|
-
|
|
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
|
|
439
|
-
`The ${
|
|
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 ${
|
|
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
|
-
|
|
416
|
+
outro('renoun configured successfully!');
|
|
475
417
|
} else if (currentState === states.WARNING_STATE) {
|
|
476
|
-
|
|
418
|
+
log.warn(context.message);
|
|
477
419
|
} else {
|
|
478
|
-
|
|
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
|
|
501
|
-
|
|
442
|
+
const loader = spinner();
|
|
443
|
+
|
|
444
|
+
loader.start(`Installing ${color.bold('renoun')}...`);
|
|
502
445
|
|
|
503
446
|
try {
|
|
504
447
|
await installPackage(['renoun']);
|
|
505
|
-
|
|
448
|
+
loader.stop(`${color.bold('renoun')} installed successfully!`);
|
|
506
449
|
} catch (error) {
|
|
507
450
|
if (error instanceof Error) {
|
|
508
|
-
throw new Error(`
|
|
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.
|
|
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.
|
|
19
|
+
"bunchee": "^6.5.0"
|
|
20
20
|
},
|
|
21
21
|
"dependencies": {
|
|
22
22
|
"@antfu/install-pkg": "^1.0.0",
|
|
23
|
-
"
|
|
24
|
-
"
|
|
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",
|