create-textmode 1.0.6 → 1.0.7

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 (60) hide show
  1. package/LICENSE +661 -21
  2. package/README.md +120 -86
  3. package/bin/index.js +8 -8
  4. package/package.json +70 -54
  5. package/src/args.js +57 -30
  6. package/src/banner.js +57 -57
  7. package/src/cli.js +166 -154
  8. package/src/constants.js +59 -23
  9. package/src/fs-utils.js +136 -85
  10. package/src/packageManager.js +18 -18
  11. package/src/prompts.js +102 -90
  12. package/src/runCommand.js +46 -46
  13. package/src/summary.js +43 -33
  14. package/src/textmodeVersion.js +57 -45
  15. package/src/usage.js +21 -18
  16. package/src/versions.js +56 -52
  17. package/templates/vanilla-js/.prettierrc +8 -8
  18. package/templates/vanilla-js/_gitignore +2 -2
  19. package/templates/vanilla-js/eslint.config.js +22 -22
  20. package/templates/vanilla-js/index.html +25 -25
  21. package/templates/vanilla-js/package.json +26 -26
  22. package/templates/vanilla-js/src/sketch.js +28 -28
  23. package/templates/vanilla-js/vite.config.js +7 -7
  24. package/templates/vanilla-js-tweakpane/_gitignore +2 -2
  25. package/templates/vanilla-js-tweakpane/eslint.config.js +22 -22
  26. package/templates/vanilla-js-tweakpane/index.html +37 -37
  27. package/templates/vanilla-js-tweakpane/package.json +27 -27
  28. package/templates/vanilla-js-tweakpane/src/sketch.js +62 -62
  29. package/templates/vanilla-js-tweakpane/vite.config.js +7 -7
  30. package/templates/vanilla-ts/.prettierrc +8 -8
  31. package/templates/vanilla-ts/_gitignore +1 -1
  32. package/templates/vanilla-ts/eslint.config.js +23 -23
  33. package/templates/vanilla-ts/index.html +26 -26
  34. package/templates/vanilla-ts/package.json +30 -30
  35. package/templates/vanilla-ts/src/sketch.ts +28 -28
  36. package/templates/vanilla-ts/tsconfig.json +15 -15
  37. package/templates/vanilla-ts/vite.config.ts +7 -7
  38. package/templates/vanilla-ts-tweakpane/_gitignore +2 -2
  39. package/templates/vanilla-ts-tweakpane/eslint.config.js +23 -23
  40. package/templates/vanilla-ts-tweakpane/index.html +37 -37
  41. package/templates/vanilla-ts-tweakpane/package.json +32 -32
  42. package/templates/vanilla-ts-tweakpane/src/sketch.ts +72 -72
  43. package/templates/vanilla-ts-tweakpane/tsconfig.json +15 -15
  44. package/templates/vanilla-ts-tweakpane/vite.config.ts +7 -7
  45. package/templates/vanilla-js-fxhash/_gitignore +0 -2
  46. package/templates/vanilla-js-fxhash/eslint.config.js +0 -22
  47. package/templates/vanilla-js-fxhash/index.html +0 -27
  48. package/templates/vanilla-js-fxhash/package.json +0 -26
  49. package/templates/vanilla-js-fxhash/public/fxhash.min.js +0 -1
  50. package/templates/vanilla-js-fxhash/src/sketch.js +0 -101
  51. package/templates/vanilla-js-fxhash/vite.config.js +0 -7
  52. package/templates/vanilla-ts-fxhash/_gitignore +0 -2
  53. package/templates/vanilla-ts-fxhash/eslint.config.js +0 -23
  54. package/templates/vanilla-ts-fxhash/index.html +0 -27
  55. package/templates/vanilla-ts-fxhash/package.json +0 -30
  56. package/templates/vanilla-ts-fxhash/public/fxhash.min.js +0 -1
  57. package/templates/vanilla-ts-fxhash/src/fxhash.d.ts +0 -54
  58. package/templates/vanilla-ts-fxhash/src/sketch.ts +0 -108
  59. package/templates/vanilla-ts-fxhash/tsconfig.json +0 -15
  60. package/templates/vanilla-ts-fxhash/vite.config.ts +0 -7
@@ -1,18 +1,18 @@
1
- export function detectPackageManager() {
2
- const ua = process.env.npm_config_user_agent || '';
3
- if (ua.includes('pnpm')) return 'pnpm';
4
- if (ua.includes('yarn')) return 'yarn';
5
- if (ua.includes('bun')) return 'bun';
6
- return 'npm';
7
- }
8
-
9
- export function pmCommands(pm) {
10
- switch (pm) {
11
- case 'pnpm':
12
- case 'yarn':
13
- case 'bun':
14
- return { install: ['install'], runDev: ['run', 'dev'] };
15
- default:
16
- return { install: ['install'], runDev: ['run', 'dev'] };
17
- }
18
- }
1
+ export function detectPackageManager() {
2
+ const ua = process.env.npm_config_user_agent || '';
3
+ if (ua.includes('pnpm')) return 'pnpm';
4
+ if (ua.includes('yarn')) return 'yarn';
5
+ if (ua.includes('bun')) return 'bun';
6
+ return 'npm';
7
+ }
8
+
9
+ export function pmCommands(pm) {
10
+ switch (pm) {
11
+ case 'pnpm':
12
+ case 'yarn':
13
+ case 'bun':
14
+ return { install: ['install'], runDev: ['run', 'dev'] };
15
+ default:
16
+ return { install: ['install'], runDev: ['run', 'dev'] };
17
+ }
18
+ }
package/src/prompts.js CHANGED
@@ -1,90 +1,102 @@
1
- import path from 'path';
2
- import { confirm, isCancel, cancel, select, text } from '@clack/prompts';
3
- import kleur from 'kleur';
4
- import { uniqueNamesGenerator, adjectives, colors, animals } from 'unique-names-generator';
5
- import { templates } from './constants.js';
6
-
7
- /**
8
- * Handle user cancellation uniformly.
9
- * @param {unknown} value - The value returned from a prompt.
10
- * @returns {boolean} True if the user cancelled.
11
- */
12
- export function handleCancel(value) {
13
- if (isCancel(value)) {
14
- cancel('Operation cancelled.');
15
- process.exit(0);
16
- }
17
- return false;
18
- }
19
-
20
- export async function promptTemplate() {
21
- const choice = await select({
22
- message: `${kleur.cyan('Select a template')} ${kleur.gray('(↑↓ move, ↵ confirm)')}`,
23
- options: templates.map((t) => ({ value: t.name, label: t.label })),
24
- initialValue: templates[0].name
25
- });
26
-
27
- handleCancel(choice);
28
- return choice;
29
- }
30
-
31
- export function suggestProjectName() {
32
- return uniqueNamesGenerator({
33
- dictionaries: [adjectives, colors, animals],
34
- separator: '-',
35
- length: 3
36
- });
37
- }
38
-
39
- export async function promptProjectName(defaultName) {
40
- const name = await text({
41
- message: `${kleur.cyan('Project name')} ${kleur.gray('(enter to accept default)')}`,
42
- initialValue: defaultName,
43
- validate: (value) => (value && value.trim().length > 0 ? undefined : 'Name cannot be empty')
44
- });
45
-
46
- handleCancel(name);
47
- return name.trim();
48
- }
49
-
50
- export async function promptOverwrite(targetDir) {
51
- const response = await confirm({
52
- message: `Directory ${path.basename(targetDir)} is not empty. Continue?`,
53
- initialValue: false
54
- });
55
-
56
- handleCancel(response);
57
- return response;
58
- }
59
-
60
- export async function promptInstall(pm) {
61
- const decision = await confirm({
62
- message: `Install dependencies with ${pm}?`,
63
- initialValue: true
64
- });
65
-
66
- handleCancel(decision);
67
- return decision;
68
- }
69
-
70
- export async function promptRun(pm) {
71
- const decision = await confirm({
72
- message: `Run dev server now with ${pm}?`,
73
- initialValue: false
74
- });
75
-
76
- handleCancel(decision);
77
- return decision;
78
- }
79
-
80
- export async function promptTextmodeVersion(options) {
81
- const choice = await select({
82
- message: `${kleur.cyan('Select textmode.js version')} ${kleur.gray('(latest recommended)')}`,
83
- options,
84
- initialValue: options[0]?.value,
85
- maxItems: 5
86
- });
87
-
88
- handleCancel(choice);
89
- return choice;
90
- }
1
+ import path from 'path';
2
+ import { confirm, isCancel, cancel, multiselect, select, text } from '@clack/prompts';
3
+ import kleur from 'kleur';
4
+ import { uniqueNamesGenerator, adjectives, colors, animals } from 'unique-names-generator';
5
+ import { addons, templates } from './constants.js';
6
+
7
+ /**
8
+ * Handle user cancellation uniformly.
9
+ * @param {unknown} value - The value returned from a prompt.
10
+ * @returns {boolean} True if the user cancelled.
11
+ */
12
+ export function handleCancel(value) {
13
+ if (isCancel(value)) {
14
+ cancel('Operation cancelled.');
15
+ process.exit(0);
16
+ }
17
+ return false;
18
+ }
19
+
20
+ export async function promptTemplate() {
21
+ const choice = await select({
22
+ message: `${kleur.cyan('Select a template')} ${kleur.gray('(↑↓ move, ↵ confirm)')}`,
23
+ options: templates.map((t) => ({ value: t.name, label: t.label })),
24
+ initialValue: templates[0].name
25
+ });
26
+
27
+ handleCancel(choice);
28
+ return choice;
29
+ }
30
+
31
+ export async function promptAddons() {
32
+ const selection = await multiselect({
33
+ message: `${kleur.cyan('Select add-on libraries to pre-install')} ${kleur.gray('(space to toggle, ↵ confirm, none = skip)')}`,
34
+ options: addons.map((a) => ({ value: a.name, label: a.label, hint: a.description })),
35
+ required: false,
36
+ initialValue: []
37
+ });
38
+
39
+ handleCancel(selection);
40
+ return Array.isArray(selection) ? selection : [];
41
+ }
42
+
43
+ export function suggestProjectName() {
44
+ return uniqueNamesGenerator({
45
+ dictionaries: [adjectives, colors, animals],
46
+ separator: '-',
47
+ length: 3
48
+ });
49
+ }
50
+
51
+ export async function promptProjectName(defaultName) {
52
+ const name = await text({
53
+ message: `${kleur.cyan('Project name')} ${kleur.gray('(enter to accept default)')}`,
54
+ initialValue: defaultName,
55
+ validate: (value) => (value && value.trim().length > 0 ? undefined : 'Name cannot be empty')
56
+ });
57
+
58
+ handleCancel(name);
59
+ return name.trim();
60
+ }
61
+
62
+ export async function promptOverwrite(targetDir) {
63
+ const response = await confirm({
64
+ message: `Directory ${path.basename(targetDir)} is not empty. Continue?`,
65
+ initialValue: false
66
+ });
67
+
68
+ handleCancel(response);
69
+ return response;
70
+ }
71
+
72
+ export async function promptInstall(pm) {
73
+ const decision = await confirm({
74
+ message: `Install dependencies with ${pm}?`,
75
+ initialValue: true
76
+ });
77
+
78
+ handleCancel(decision);
79
+ return decision;
80
+ }
81
+
82
+ export async function promptRun(pm) {
83
+ const decision = await confirm({
84
+ message: `Run dev server now with ${pm}?`,
85
+ initialValue: false
86
+ });
87
+
88
+ handleCancel(decision);
89
+ return decision;
90
+ }
91
+
92
+ export async function promptTextmodeVersion(options) {
93
+ const choice = await select({
94
+ message: `${kleur.cyan('Select textmode.js version')} ${kleur.gray('(latest recommended, >= 0.17.1)')}`,
95
+ options,
96
+ initialValue: options[0]?.value,
97
+ maxItems: 5
98
+ });
99
+
100
+ handleCancel(choice);
101
+ return choice;
102
+ }
package/src/runCommand.js CHANGED
@@ -1,46 +1,46 @@
1
- import { spawn } from 'child_process';
2
- import readline from 'readline';
3
-
4
- export function runCommand(cmd, args, cwd) {
5
- return new Promise((resolve, reject) => {
6
- const child = spawn(cmd, args, {
7
- cwd,
8
- stdio: 'inherit',
9
- shell: process.platform === 'win32'
10
- });
11
-
12
- child.on('exit', (code) => {
13
- if (code === 0) resolve();
14
- else reject(new Error(`${cmd} ${args.join(' ')} exited with code ${code}`));
15
- });
16
-
17
- child.on('error', reject);
18
- });
19
- }
20
-
21
- export function runCommandLogged(cmd, args, cwd, onLine) {
22
- return new Promise((resolve, reject) => {
23
- const child = spawn(cmd, args, {
24
- cwd,
25
- stdio: ['inherit', 'pipe', 'pipe'],
26
- shell: process.platform === 'win32'
27
- });
28
-
29
- const forward = (stream) => {
30
- if (!stream) return;
31
- const rl = readline.createInterface({ input: stream });
32
- rl.on('line', (line) => onLine(line));
33
- rl.on('error', () => {});
34
- };
35
-
36
- forward(child.stdout);
37
- forward(child.stderr);
38
-
39
- child.on('exit', (code) => {
40
- if (code === 0) resolve();
41
- else reject(new Error(`${cmd} ${args.join(' ')} exited with code ${code}`));
42
- });
43
-
44
- child.on('error', reject);
45
- });
46
- }
1
+ import { spawn } from 'child_process';
2
+ import readline from 'readline';
3
+
4
+ export function runCommand(cmd, args, cwd) {
5
+ return new Promise((resolve, reject) => {
6
+ const child = spawn(cmd, args, {
7
+ cwd,
8
+ stdio: 'inherit',
9
+ shell: process.platform === 'win32'
10
+ });
11
+
12
+ child.on('exit', (code) => {
13
+ if (code === 0) resolve();
14
+ else reject(new Error(`${cmd} ${args.join(' ')} exited with code ${code}`));
15
+ });
16
+
17
+ child.on('error', reject);
18
+ });
19
+ }
20
+
21
+ export function runCommandLogged(cmd, args, cwd, onLine) {
22
+ return new Promise((resolve, reject) => {
23
+ const child = spawn(cmd, args, {
24
+ cwd,
25
+ stdio: ['inherit', 'pipe', 'pipe'],
26
+ shell: process.platform === 'win32'
27
+ });
28
+
29
+ const forward = (stream) => {
30
+ if (!stream) return;
31
+ const rl = readline.createInterface({ input: stream });
32
+ rl.on('line', (line) => onLine(line));
33
+ rl.on('error', () => {});
34
+ };
35
+
36
+ forward(child.stdout);
37
+ forward(child.stderr);
38
+
39
+ child.on('exit', (code) => {
40
+ if (code === 0) resolve();
41
+ else reject(new Error(`${cmd} ${args.join(' ')} exited with code ${code}`));
42
+ });
43
+
44
+ child.on('error', reject);
45
+ });
46
+ }
package/src/summary.js CHANGED
@@ -1,33 +1,43 @@
1
- import kleur from 'kleur';
2
- import boxen from 'boxen';
3
-
4
- export function printSummary({ projectName, pm, pmCmds, installDone, runDone }) {
5
- const installCmd = `${pm} ${pmCmds.install.join(' ')}`;
6
- const runCmd = `${pm} ${pmCmds.runDev.join(' ')}`;
7
-
8
- const steps = [
9
- `cd ${projectName}`,
10
- installDone ? `✓ already ran ${installCmd}` : installCmd,
11
- runDone ? `✓ dev server is running (${runCmd})` : runCmd
12
- ]
13
- .filter(Boolean)
14
- .join('\n');
15
-
16
- const infoLines = [
17
- '',
18
- '',
19
- kleur.bold().cyan('Helpful links:'),
20
- `${kleur.cyan(' Documentation:')} https://code.textmode.art`,
21
- `${kleur.cyan(' Community:')} https://discord.gg/sjrw8QXNks`,
22
- `${kleur.cyan(' CLI issues:')} https://github.com/humanbydefinition/create-textmode/issues`
23
- ].join('\n');
24
-
25
- const boxed = boxen(`${kleur.bold().cyan('Next steps:')}\n${steps}${infoLines}`, {
26
- padding: { top: 0, bottom: 0, left: 2, right: 2 },
27
- margin: { top: 0, bottom: 0 },
28
- borderStyle: 'round',
29
- borderColor: 'cyan'
30
- });
31
-
32
- console.log(boxed);
33
- }
1
+ import kleur from 'kleur';
2
+ import boxen from 'boxen';
3
+
4
+ export function printSummary({ projectName, pm, pmCmds, installDone, runDone, addons = [] }) {
5
+ const installCmd = `${pm} ${pmCmds.install.join(' ')}`;
6
+ const runCmd = `${pm} ${pmCmds.runDev.join(' ')}`;
7
+
8
+ const steps = [
9
+ `cd ${projectName}`,
10
+ installDone ? `✓ already ran ${installCmd}` : installCmd,
11
+ runDone ? `✓ dev server is running (${runCmd})` : runCmd
12
+ ]
13
+ .filter(Boolean)
14
+ .join('\n');
15
+
16
+ const addonLines =
17
+ addons.length > 0
18
+ ? [
19
+ '',
20
+ '',
21
+ kleur.bold().cyan('Add-ons installed:'),
22
+ addons.map((a) => ` ${a.label}`).join('\n')
23
+ ].join('\n')
24
+ : '';
25
+
26
+ const infoLines = [
27
+ addonLines,
28
+ '',
29
+ kleur.bold().cyan('Helpful links:'),
30
+ `${kleur.cyan(' Documentation:')} https://code.textmode.art`,
31
+ `${kleur.cyan(' Community:')} https://discord.gg/sjrw8QXNks`,
32
+ `${kleur.cyan(' CLI issues:')} https://github.com/humanbydefinition/create-textmode/issues`
33
+ ].join('\n');
34
+
35
+ const boxed = boxen(`${kleur.bold().cyan('Next steps:')}\n${steps}${infoLines}`, {
36
+ padding: { top: 0, bottom: 0, left: 2, right: 2 },
37
+ margin: { top: 0, bottom: 0 },
38
+ borderStyle: 'round',
39
+ borderColor: 'cyan'
40
+ });
41
+
42
+ console.log(boxed);
43
+ }
@@ -1,45 +1,57 @@
1
- import { spinner, log } from '@clack/prompts';
2
- import { getTextmodeVersions } from './versions.js';
3
-
4
- export async function resolveTextmodeVersion(requestedTextmodeVersion, promptTextmodeVersion) {
5
- let textmodeVersion = 'latest';
6
- let stableVersions = [];
7
-
8
- const versionSpinner = spinner();
9
- versionSpinner.start('Fetching textmode.js versions...');
10
- try {
11
- stableVersions = await getTextmodeVersions();
12
- if (stableVersions.length === 0) throw new Error('No versions found');
13
- versionSpinner.stop('Fetched textmode.js versions.');
14
- } catch (err) {
15
- versionSpinner.stop('Could not fetch versions.');
16
- log.warn('Using latest version as fallback.');
17
- stableVersions = [];
18
- textmodeVersion = 'latest';
19
- }
20
-
21
- const latestVersion = stableVersions[0];
22
- const availableOptions = [
23
- {
24
- value: 'latest',
25
- label: latestVersion ? `latest (${latestVersion})` : 'latest (recommended)'
26
- },
27
- ...stableVersions.slice(1).map((v) => ({ value: v, label: v }))
28
- ];
29
-
30
- if (requestedTextmodeVersion) {
31
- const found = availableOptions.find((opt) => opt.value === requestedTextmodeVersion);
32
- if (found) {
33
- textmodeVersion = requestedTextmodeVersion;
34
- } else if (stableVersions.includes(requestedTextmodeVersion)) {
35
- textmodeVersion = requestedTextmodeVersion;
36
- } else {
37
- log.warn(`Requested textmode.js@${requestedTextmodeVersion} not found; using latest instead.`);
38
- textmodeVersion = 'latest';
39
- }
40
- } else if (stableVersions.length > 0) {
41
- textmodeVersion = await promptTextmodeVersion(availableOptions);
42
- }
43
-
44
- return { textmodeVersion, stableVersions, availableOptions };
45
- }
1
+ import { spinner, log } from '@clack/prompts';
2
+ import { compareSemverDesc, getTextmodeVersions, filterAtLeast } from './versions.js';
3
+ import { MIN_TEXTMODE_VERSION } from './constants.js';
4
+
5
+ export async function resolveTextmodeVersion(requestedTextmodeVersion, promptTextmodeVersion) {
6
+ let textmodeVersion = 'latest';
7
+ let stableVersions = [];
8
+
9
+ const versionSpinner = spinner();
10
+ versionSpinner.start('Fetching textmode.js versions...');
11
+ try {
12
+ stableVersions = await getTextmodeVersions();
13
+ if (stableVersions.length === 0) throw new Error('No versions found');
14
+ versionSpinner.stop('Fetched textmode.js versions.');
15
+ } catch (err) {
16
+ versionSpinner.stop('Could not fetch versions.');
17
+ log.warn('Using latest version as fallback.');
18
+ stableVersions = [];
19
+ textmodeVersion = 'latest';
20
+ }
21
+
22
+ // Only offer versions at or above the global minimum (e.g. >= 0.17.1).
23
+ // Every official add-on peer-depends on a lower floor, so this also covers
24
+ // add-on compatibility.
25
+ const eligibleVersions = filterAtLeast(stableVersions, MIN_TEXTMODE_VERSION);
26
+
27
+ const latestVersion = eligibleVersions[0] || stableVersions[0];
28
+ const availableOptions = [
29
+ {
30
+ value: 'latest',
31
+ label: latestVersion ? `latest (${latestVersion})` : 'latest (recommended)'
32
+ },
33
+ ...eligibleVersions.slice(1).map((v) => ({ value: v, label: v }))
34
+ ];
35
+
36
+ if (requestedTextmodeVersion) {
37
+ const found = availableOptions.find((opt) => opt.value === requestedTextmodeVersion);
38
+ if (found) {
39
+ textmodeVersion = requestedTextmodeVersion;
40
+ } else if (stableVersions.includes(requestedTextmodeVersion)) {
41
+ textmodeVersion = requestedTextmodeVersion;
42
+ if (compareSemverDesc(requestedTextmodeVersion, MIN_TEXTMODE_VERSION) > 0) {
43
+ log.warn(
44
+ `textmode.js must be >= ${MIN_TEXTMODE_VERSION}, but ${requestedTextmodeVersion} is older. Upgrading to latest.`
45
+ );
46
+ textmodeVersion = 'latest';
47
+ }
48
+ } else {
49
+ log.warn(`Requested textmode.js@${requestedTextmodeVersion} not found; using latest instead.`);
50
+ textmodeVersion = 'latest';
51
+ }
52
+ } else if (eligibleVersions.length > 0) {
53
+ textmodeVersion = await promptTextmodeVersion(availableOptions);
54
+ }
55
+
56
+ return { textmodeVersion, stableVersions, availableOptions };
57
+ }
package/src/usage.js CHANGED
@@ -1,18 +1,21 @@
1
- import { templates } from './constants.js';
2
-
3
- export function printUsage() {
4
- const list = templates.map((t) => ` - ${t.name}`).join('\n');
5
-
6
- console.log(`Usage: npm create textmode@latest [project-name] -- [options]\n`);
7
- console.log('Options:');
8
- console.log(' --template <name> Choose a template');
9
- console.log(' --name <name> Project directory name (alias: positional arg)');
10
- console.log(' --pm <npm|pnpm|yarn|bun> Force package manager (auto-detected if omitted)');
11
- console.log(' --textmode-version <ver> Pin textmode.js version (default: latest, prompts if omitted)');
12
- console.log(' --install / --no-install Install dependencies after scaffold');
13
- console.log(' --run / --no-run Run dev server after install');
14
- console.log(' --force Allow using a non-empty directory');
15
- console.log(' --help Show this help');
16
- console.log(' --version Show CLI version');
17
- console.log('\nTemplates:\n' + list);
18
- }
1
+ import { addons, templates } from './constants.js';
2
+
3
+ export function printUsage() {
4
+ const list = templates.map((t) => ` - ${t.name}`).join('\n');
5
+ const addonList = addons.map((a) => ` - ${a.name} (${a.package})`).join('\n');
6
+
7
+ console.log(`Usage: npm create textmode@latest [project-name] -- [options]\n`);
8
+ console.log('Options:');
9
+ console.log(' --template <name> Choose a template');
10
+ console.log(' --addons <name1,name2,...> Pre-install official textmode.js add-ons');
11
+ console.log(' --name <name> Project directory name (alias: positional arg)');
12
+ console.log(' --pm <npm|pnpm|yarn|bun> Force package manager (auto-detected if omitted)');
13
+ console.log(' --textmode-version <ver> Pin textmode.js version (default: latest; only >= 0.17.1 offered)');
14
+ console.log(' --install / --no-install Install dependencies after scaffold');
15
+ console.log(' --run / --no-run Run dev server after install');
16
+ console.log(' --force Allow using a non-empty directory');
17
+ console.log(' --help Show this help');
18
+ console.log(' --version Show CLI version');
19
+ console.log('\nTemplates:\n' + list);
20
+ console.log('\nAdd-ons:\n' + addonList);
21
+ }
package/src/versions.js CHANGED
@@ -1,52 +1,56 @@
1
- import https from 'https';
2
-
3
- let cachedVersions = null;
4
-
5
- function fetchJson(url) {
6
- return new Promise((resolve, reject) => {
7
- https
8
- .get(url, (res) => {
9
- let data = '';
10
- res.on('data', (chunk) => {
11
- data += chunk;
12
- });
13
- res.on('end', () => {
14
- try {
15
- resolve(JSON.parse(data));
16
- } catch (err) {
17
- reject(err);
18
- }
19
- });
20
- })
21
- .on('error', reject);
22
- });
23
- }
24
-
25
- export function isStable(version) {
26
- // Exclude prerelease tags like -beta, -rc, etc.
27
- return !version.includes('-');
28
- }
29
-
30
- export function compareSemverDesc(a, b) {
31
- const pa = a.split('.').map(Number);
32
- const pb = b.split('.').map(Number);
33
- for (let i = 0; i < Math.max(pa.length, pb.length); i += 1) {
34
- const va = pa[i] || 0;
35
- const vb = pb[i] || 0;
36
- if (va > vb) return -1;
37
- if (va < vb) return 1;
38
- }
39
- return 0;
40
- }
41
-
42
- export async function getTextmodeVersions(limit = 20) {
43
- if (cachedVersions) return cachedVersions.slice(0, limit);
44
-
45
- const data = await fetchJson('https://registry.npmjs.org/textmode.js');
46
- const versions = Object.keys(data.versions || {})
47
- .filter(isStable)
48
- .sort(compareSemverDesc);
49
-
50
- cachedVersions = versions;
51
- return versions.slice(0, limit);
52
- }
1
+ import https from 'https';
2
+
3
+ let cachedVersions = null;
4
+
5
+ function fetchJson(url) {
6
+ return new Promise((resolve, reject) => {
7
+ https
8
+ .get(url, (res) => {
9
+ let data = '';
10
+ res.on('data', (chunk) => {
11
+ data += chunk;
12
+ });
13
+ res.on('end', () => {
14
+ try {
15
+ resolve(JSON.parse(data));
16
+ } catch (err) {
17
+ reject(err);
18
+ }
19
+ });
20
+ })
21
+ .on('error', reject);
22
+ });
23
+ }
24
+
25
+ export function isStable(version) {
26
+ // Exclude prerelease tags like -beta, -rc, etc.
27
+ return !version.includes('-');
28
+ }
29
+
30
+ export function compareSemverDesc(a, b) {
31
+ const pa = a.split('.').map(Number);
32
+ const pb = b.split('.').map(Number);
33
+ for (let i = 0; i < Math.max(pa.length, pb.length); i += 1) {
34
+ const va = pa[i] || 0;
35
+ const vb = pb[i] || 0;
36
+ if (va > vb) return -1;
37
+ if (va < vb) return 1;
38
+ }
39
+ return 0;
40
+ }
41
+
42
+ export function filterAtLeast(versions, min) {
43
+ return versions.filter((v) => compareSemverDesc(v, min) <= 0);
44
+ }
45
+
46
+ export async function getTextmodeVersions(limit = 20) {
47
+ if (cachedVersions) return cachedVersions.slice(0, limit);
48
+
49
+ const data = await fetchJson('https://registry.npmjs.org/textmode.js');
50
+ const versions = Object.keys(data.versions || {})
51
+ .filter(isStable)
52
+ .sort(compareSemverDesc);
53
+
54
+ cachedVersions = versions;
55
+ return versions.slice(0, limit);
56
+ }