create-textmode 1.0.7 → 1.1.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/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.17.1 --install --no-run
51
+ npm create textmode@latest my-textmode-app -- --template vanilla-ts --textmode-version 0.17.2 --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.17.1; defaults to `latest`)*
65
+ - `--textmode-version <ver>`: pin textmode.js *(prompts from fetched stable versions >= 0.17.2; 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 # node --check syntax gate
103
- npm test # vitest (run once)
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
@@ -3,6 +3,6 @@ import kleur from 'kleur';
3
3
  import { run } from '../src/cli.js';
4
4
 
5
5
  run().catch((err) => {
6
- console.error(kleur.red(err.stack || err.message || err));
7
- process.exit(1);
6
+ console.error(kleur.red(err.stack || err.message || err));
7
+ process.exit(1);
8
8
  });
package/package.json CHANGED
@@ -1,70 +1,76 @@
1
1
  {
2
- "name": "create-textmode",
3
- "version": "1.0.7",
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": ">=18.0.0"
18
- },
19
- "keywords": [
20
- "textmode",
21
- "textmode.js",
22
- "create",
23
- "scaffold",
24
- "template"
25
- ],
26
- "author": "humanbydefinition",
27
- "license": "AGPL-3.0",
28
- "homepage": "https://github.com/humanbydefinition/create-textmode#readme",
29
- "repository": {
30
- "type": "git",
31
- "url": "git+https://github.com/humanbydefinition/create-textmode.git"
32
- },
33
- "bugs": {
34
- "url": "https://github.com/humanbydefinition/create-textmode/issues"
35
- },
36
- "publishConfig": {
37
- "access": "public"
38
- },
39
- "scripts": {
40
- "test": "vitest run",
41
- "test:watch": "vitest",
42
- "test:verbose": "vitest run --reporter=verbose",
43
- "lint": "node --check bin/index.js && node --check src/*.js && node --check tests/*.js",
44
- "check": "npm run lint && npm run test",
45
- "prepare": "install-git-hooks"
46
- },
47
- "dependencies": {
48
- "@clack/prompts": "^0.11.0",
49
- "boxen": "^7.1.1",
50
- "kleur": "^4.1.5",
51
- "minimist": "^1.2.8",
52
- "unique-names-generator": "^4.7.1"
53
- },
54
- "devDependencies": {
55
- "@commitlint/cli": "^21.2.1",
56
- "@textmode/commitlint-config": "^0.1.1",
57
- "@textmode/tooling-scripts": "^0.5.0",
58
- "lint-staged": "^17.0.8",
59
- "textmode.js": "^0.8.1",
60
- "vitest": "^4.1.10"
61
- },
62
- "commitlint": {
63
- "extends": [
64
- "@textmode/commitlint-config"
65
- ]
66
- },
67
- "lint-staged": {
68
- "*.{js,mjs,cjs}": "node --check"
69
- }
2
+ "name": "create-textmode",
3
+ "version": "1.1.0",
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.17.2"
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
- 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
- });
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
- if (!name) return;
24
- const found = templates.find((t) => t.name === name);
25
- if (found) return;
23
+ if (!name) return;
24
+ const found = templates.find((t) => t.name === name);
25
+ if (found) return;
26
26
 
27
- console.error(kleur.red(`Unknown template: ${name}`));
28
- printUsage();
29
- process.exit(1);
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
- if (!names) return;
34
- const requested = names.split(',').map((s) => s.trim()).filter(Boolean);
35
- const unknown = requested.filter((name) => !addons.find((a) => a.name === name));
36
- if (unknown.length === 0) return;
37
-
38
- console.error(kleur.red(`Unknown add-on(s): ${unknown.join(', ')}`));
39
- console.error(kleur.gray(`Valid add-ons: ${addons.map((a) => a.name).join(', ')}`));
40
- printUsage();
41
- process.exit(1);
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
- if (!names) return [];
46
-
47
- const uniqueNames = [...new Set(
48
- names
49
- .split(',')
50
- .map((s) => s.trim())
51
- .filter(Boolean)
52
- )];
53
-
54
- return uniqueNames
55
- .map((name) => addons.find((a) => a.name === name))
56
- .filter(Boolean);
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
- const idx = Math.floor(Math.random() * SPLIT_FLAP_CHARS.length);
10
- return SPLIT_FLAP_CHARS[idx];
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
- 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)));
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
- const headerLines = HEADER.trim().split('\n');
54
- console.log('');
55
- await animateSplitFlap(headerLines);
56
- console.log('');
53
+ const headerLines = HEADER.trim().split('\n');
54
+ console.log('');
55
+ await animateSplitFlap(headerLines);
56
+ console.log('');
57
57
  }