create-textmode 1.0.7 → 1.1.1
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/README.md +5 -5
- package/bin/index.js +2 -2
- package/package.json +74 -68
- package/src/args.js +43 -40
- package/src/banner.js +42 -42
- package/src/cli.js +137 -142
- package/src/constants.js +33 -33
- package/src/fs-utils.js +100 -103
- package/src/packageManager.js +13 -13
- package/src/prompts.js +66 -65
- package/src/runCommand.js +38 -38
- package/src/summary.js +28 -33
- package/src/textmodeVersion.js +46 -46
- package/src/usage.js +19 -17
- package/src/versions.js +36 -36
- package/templates/vanilla-js/package.json +7 -7
- package/templates/vanilla-js-tweakpane/package.json +7 -7
- package/templates/vanilla-ts/package.json +10 -10
- package/templates/vanilla-ts/tsconfig.json +1 -1
- package/templates/vanilla-ts-tweakpane/package.json +10 -10
- package/templates/vanilla-ts-tweakpane/tsconfig.json +1 -1
package/README.md
CHANGED
|
@@ -48,7 +48,7 @@ The CLI will prompt for project name, template, textmode.js version, official ad
|
|
|
48
48
|
|
|
49
49
|
```bash
|
|
50
50
|
# TypeScript template, pin textmode.js, auto-install, do not run dev server
|
|
51
|
-
npm create textmode@latest my-textmode-app -- --template vanilla-ts --textmode-version 0.
|
|
51
|
+
npm create textmode@latest my-textmode-app -- --template vanilla-ts --textmode-version 0.18.0 --install --no-run
|
|
52
52
|
|
|
53
53
|
# JavaScript template with pnpm, skip install and run
|
|
54
54
|
pnpm create textmode@latest demo -- --template vanilla-js --pm pnpm --no-install --no-run
|
|
@@ -62,7 +62,7 @@ npm create textmode@latest audio-art -- --template vanilla-js --addons synth,exp
|
|
|
62
62
|
- `--template <name>`: choose a template *(prompts if omitted)*
|
|
63
63
|
- `--addons <name1,name2,...>`: pre-install official textmode.js add-ons and wire their plugins into the starter sketch *(prompts via multi-select if omitted)*
|
|
64
64
|
- `--name <projectName>` or first positional arg: directory/package name *(default suggestion if omitted)*
|
|
65
|
-
- `--textmode-version <ver>`: pin textmode.js *(prompts from fetched stable versions >= 0.
|
|
65
|
+
- `--textmode-version <ver>`: pin textmode.js *(prompts from fetched stable versions >= 0.18.0; defaults to `latest`)*
|
|
66
66
|
- `--pm <npm|pnpm|yarn|bun>`: force a package manager *(auto-detected otherwise)*
|
|
67
67
|
- `--install` / `--no-install`: install dependencies after scaffold *(prompts if neither is provided)*
|
|
68
68
|
- `--run` / `--no-run`: start the dev server after install *(prompts if neither is provided)*
|
|
@@ -98,9 +98,9 @@ All official textmode.js add-on libraries are supported. When selected, the CLI
|
|
|
98
98
|
|
|
99
99
|
```bash
|
|
100
100
|
npm install # also installs git hooks (commitlint, lint-staged, pre-push protection)
|
|
101
|
-
npm run check # lint + test
|
|
102
|
-
npm run lint #
|
|
103
|
-
npm test #
|
|
101
|
+
npm run check # format check + lint + test
|
|
102
|
+
npm run lint # ESLint across codebase
|
|
103
|
+
npm test # Vitest (run once)
|
|
104
104
|
|
|
105
105
|
# Run CLI directly (no npm-create shim); no "--" needed
|
|
106
106
|
node bin/index.js demo --template vanilla-js --pm pnpm --no-install --no-run
|
package/bin/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,70 +1,76 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
2
|
+
"name": "create-textmode",
|
|
3
|
+
"version": "1.1.1",
|
|
4
|
+
"description": "Scaffold textmode.js projects with zero friction",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"create-textmode": "bin/index.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"bin",
|
|
11
|
+
"src",
|
|
12
|
+
"templates",
|
|
13
|
+
"README.md",
|
|
14
|
+
"LICENSE"
|
|
15
|
+
],
|
|
16
|
+
"engines": {
|
|
17
|
+
"node": ">=24"
|
|
18
|
+
},
|
|
19
|
+
"devEngines": {
|
|
20
|
+
"runtime": {
|
|
21
|
+
"name": "node",
|
|
22
|
+
"version": ">=24.0.0",
|
|
23
|
+
"onFail": "warn"
|
|
24
|
+
},
|
|
25
|
+
"packageManager": {
|
|
26
|
+
"name": "npm",
|
|
27
|
+
"version": ">=10.0.0",
|
|
28
|
+
"onFail": "warn"
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
"keywords": [
|
|
32
|
+
"textmode",
|
|
33
|
+
"textmode.js",
|
|
34
|
+
"create",
|
|
35
|
+
"scaffold",
|
|
36
|
+
"template"
|
|
37
|
+
],
|
|
38
|
+
"author": "humanbydefinition",
|
|
39
|
+
"license": "AGPL-3.0",
|
|
40
|
+
"homepage": "https://github.com/humanbydefinition/create-textmode#readme",
|
|
41
|
+
"repository": {
|
|
42
|
+
"type": "git",
|
|
43
|
+
"url": "git+https://github.com/humanbydefinition/create-textmode.git"
|
|
44
|
+
},
|
|
45
|
+
"bugs": {
|
|
46
|
+
"url": "https://github.com/humanbydefinition/create-textmode/issues"
|
|
47
|
+
},
|
|
48
|
+
"publishConfig": {
|
|
49
|
+
"access": "public"
|
|
50
|
+
},
|
|
51
|
+
"scripts": {
|
|
52
|
+
"test": "textmode-build test",
|
|
53
|
+
"test:watch": "textmode-build test:watch",
|
|
54
|
+
"format": "textmode-lint format",
|
|
55
|
+
"format:check": "textmode-lint format:check",
|
|
56
|
+
"lint": "textmode-lint lint .",
|
|
57
|
+
"lint:markdown": "textmode-lint lint:markdown",
|
|
58
|
+
"check": "npm run format:check && npm run lint && npm run test",
|
|
59
|
+
"release": "textmode-release release",
|
|
60
|
+
"prepare": "textmode-release hooks"
|
|
61
|
+
},
|
|
62
|
+
"dependencies": {
|
|
63
|
+
"@clack/prompts": "^1.7.0",
|
|
64
|
+
"boxen": "^8.0.1",
|
|
65
|
+
"kleur": "^4.1.5",
|
|
66
|
+
"minimist": "^1.2.8",
|
|
67
|
+
"unique-names-generator": "^4.7.1"
|
|
68
|
+
},
|
|
69
|
+
"prettier": "@textmode/lint/prettier",
|
|
70
|
+
"devDependencies": {
|
|
71
|
+
"@textmode/build": "^0.1.0",
|
|
72
|
+
"@textmode/lint": "^0.1.0",
|
|
73
|
+
"@textmode/release": "^0.2.1",
|
|
74
|
+
"textmode.js": "^0.18.0"
|
|
75
|
+
}
|
|
70
76
|
}
|
package/src/args.js
CHANGED
|
@@ -4,54 +4,57 @@ import { addons, templates } from './constants.js';
|
|
|
4
4
|
import { printUsage } from './usage.js';
|
|
5
5
|
|
|
6
6
|
export function parseArgv(rawArgs) {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
7
|
+
return minimist(rawArgs, {
|
|
8
|
+
alias: {
|
|
9
|
+
h: 'help',
|
|
10
|
+
t: 'template',
|
|
11
|
+
n: 'name',
|
|
12
|
+
f: 'force',
|
|
13
|
+
v: 'version',
|
|
14
|
+
tv: 'textmode-version',
|
|
15
|
+
},
|
|
16
|
+
string: ['template', 'name', 'pm', 'textmode-version', 'addons'],
|
|
17
|
+
boolean: ['help', 'force', 'version', 'install', 'run', 'no-install', 'no-run'],
|
|
18
|
+
default: { install: null, run: null },
|
|
19
|
+
});
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
export function ensureKnownTemplate(name) {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
23
|
+
if (!name) return;
|
|
24
|
+
const found = templates.find((t) => t.name === name);
|
|
25
|
+
if (found) return;
|
|
26
26
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
27
|
+
console.error(kleur.red(`Unknown template: ${name}`));
|
|
28
|
+
printUsage();
|
|
29
|
+
process.exit(1);
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
export function ensureKnownAddons(names) {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
33
|
+
if (!names) return;
|
|
34
|
+
const requested = names
|
|
35
|
+
.split(',')
|
|
36
|
+
.map((s) => s.trim())
|
|
37
|
+
.filter(Boolean);
|
|
38
|
+
const unknown = requested.filter((name) => !addons.find((a) => a.name === name));
|
|
39
|
+
if (unknown.length === 0) return;
|
|
40
|
+
|
|
41
|
+
console.error(kleur.red(`Unknown add-on(s): ${unknown.join(', ')}`));
|
|
42
|
+
console.error(kleur.gray(`Valid add-ons: ${addons.map((a) => a.name).join(', ')}`));
|
|
43
|
+
printUsage();
|
|
44
|
+
process.exit(1);
|
|
42
45
|
}
|
|
43
46
|
|
|
44
47
|
export function parseAddons(names) {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
48
|
+
if (!names) return [];
|
|
49
|
+
|
|
50
|
+
const uniqueNames = [
|
|
51
|
+
...new Set(
|
|
52
|
+
names
|
|
53
|
+
.split(',')
|
|
54
|
+
.map((s) => s.trim())
|
|
55
|
+
.filter(Boolean)
|
|
56
|
+
),
|
|
57
|
+
];
|
|
58
|
+
|
|
59
|
+
return uniqueNames.map((name) => addons.find((a) => a.name === name)).filter(Boolean);
|
|
57
60
|
}
|
package/src/banner.js
CHANGED
|
@@ -6,52 +6,52 @@ const SPLIT_FLAP_CHARS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789#@%&/\\<>[]=+*';
|
|
|
6
6
|
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
7
7
|
|
|
8
8
|
function randomFlapChar() {
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
const idx = Math.floor(Math.random() * SPLIT_FLAP_CHARS.length);
|
|
10
|
+
return SPLIT_FLAP_CHARS[idx];
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
async function animateSplitFlap(lines, { flips = 16, frameDelay = 32, columnStagger = 6 } = {}) {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
14
|
+
const canAnimate = process.stdout.isTTY && process.env.CI !== 'true';
|
|
15
|
+
if (!canAnimate) {
|
|
16
|
+
console.log(kleur.cyan(lines.join('\n')));
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const lineCount = lines.length;
|
|
21
|
+
const blankFrame = lines.map((line) => line.replace(/[^\s]/g, ' '));
|
|
22
|
+
let firstRender = true;
|
|
23
|
+
|
|
24
|
+
const render = (frame) => {
|
|
25
|
+
if (!firstRender) {
|
|
26
|
+
process.stdout.write(`\x1b[${lineCount}F`);
|
|
27
|
+
} else {
|
|
28
|
+
firstRender = false;
|
|
29
|
+
}
|
|
30
|
+
process.stdout.write(frame.join('\n'));
|
|
31
|
+
process.stdout.write('\n');
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
render(blankFrame);
|
|
35
|
+
|
|
36
|
+
for (let flip = 0; flip < flips; flip++) {
|
|
37
|
+
const frameLines = lines.map((line) => {
|
|
38
|
+
const chars = line.split('').map((ch, idx) => {
|
|
39
|
+
if (ch === ' ') return ' ';
|
|
40
|
+
const shouldSettle = flip >= flips - 2 || flip > Math.floor(idx / columnStagger);
|
|
41
|
+
return shouldSettle ? ch : randomFlapChar();
|
|
42
|
+
});
|
|
43
|
+
return kleur.cyan(chars.join(''));
|
|
44
|
+
});
|
|
45
|
+
render(frameLines);
|
|
46
|
+
await sleep(frameDelay + flip * 6);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
render(lines.map((line) => kleur.cyan(line)));
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
export async function printHeader() {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
53
|
+
const headerLines = HEADER.trim().split('\n');
|
|
54
|
+
console.log('');
|
|
55
|
+
await animateSplitFlap(headerLines);
|
|
56
|
+
console.log('');
|
|
57
57
|
}
|