@jeportie/create-checks 1.3.0 → 1.4.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
@@ -1,6 +1,6 @@
1
1
  # create-checks
2
2
 
3
- A zero-config scaffolding CLI that wires **ESLint + Prettier** into any Node.js project in one command.
3
+ A zero-config scaffolding CLI that wires **ESLint + Prettier + TypeScript** into any Node.js project — and optionally sets up **Vitest** for testing — in one interactive command.
4
4
 
5
5
  ```sh
6
6
  npm create @jeportie/checks
@@ -13,13 +13,17 @@ npm create @jeportie/checks
13
13
  Running `npm create @jeportie/checks` inside an existing project will:
14
14
 
15
15
  1. **Install** ESLint, Prettier, and their plugins as dev dependencies
16
- 2. **Copy** `eslint.config.js` and `prettier.config.js` into your project root
17
- 3. **Inject** three scripts into your `package.json`:
16
+ 2. **Copy** config files into your project root:
17
+ - `eslint.config.js` and `prettier.config.js`
18
+ - `.editorconfig`, `tsconfig.base.json`, `tsconfig.json`
19
+ - `.eslintignore`, `.prettierignore`, `.gitignore`
20
+ 3. **Inject** scripts into your `package.json`:
18
21
  - `lint` → `eslint .`
19
22
  - `format` → `prettier . --write`
20
23
  - `typecheck` → `tsc --noEmit`
24
+ 4. **Prompt** you to optionally set up Vitest with a choice of presets
21
25
 
22
- Everything runs in your project nothing is installed globally.
26
+ All existing files are left untouched (the CLI skips them with a notice).
23
27
 
24
28
  ---
25
29
 
@@ -29,10 +33,18 @@ Everything runs in your project — nothing is installed globally.
29
33
  # Inside your existing project
30
34
  npm create @jeportie/checks
31
35
 
32
- # Then use the injected scripts
36
+ # ESLint / Prettier / TypeScript scripts
33
37
  npm run lint
34
38
  npm run format
35
39
  npm run typecheck
40
+
41
+ # If you chose the Native or Coverage Vitest preset
42
+ npm test
43
+ npm run test:unit
44
+ npm run test:integration
45
+
46
+ # If you chose the Coverage preset
47
+ npm run test:coverage
36
48
  ```
37
49
 
38
50
  ---
@@ -46,9 +58,9 @@ npm create @jeportie/checks
46
58
  └─▶ node runs ./src/index.js (registered via "bin" in package.json)
47
59
 
48
60
  ├─ 1. npm install -D eslint prettier ... (in YOUR project's CWD)
49
- ├─ 2. copy src/templates/eslint.config.js → YOUR project/eslint.config.js
50
- ├─ 3. copy src/templates/prettier.config.jsYOUR project/prettier.config.js
51
- └─ 4. read YOUR project/package.json inject scripts write it back
61
+ ├─ 2. copy template config files → YOUR project root
62
+ ├─ 3. read YOUR project/package.jsoninject scripts → write it back
63
+ └─ 4. ask interactively whether to set up Vitest (and which preset)
52
64
  ```
53
65
 
54
66
  ### Template path resolution
@@ -88,6 +100,21 @@ When npm installs a package that has a `bin` field, it creates a symlink in `nod
88
100
  | `@stylistic/eslint-plugin` | Code style rules (quotes, spacing, etc.) |
89
101
  | `eslint-plugin-import` | Import order and resolution rules |
90
102
  | `eslint-import-resolver-typescript` | Resolves TypeScript paths in import plugin |
103
+ | `typescript` | TypeScript compiler |
104
+ | `@types/node` | Node.js type definitions |
105
+
106
+ **Vitest preset — Native** (when selected):
107
+
108
+ | Package | Purpose |
109
+ | --------- | ---------------------------- |
110
+ | `vitest` | Fast, Vite-native test runner |
111
+
112
+ **Vitest preset — Coverage** (when selected):
113
+
114
+ | Package | Purpose |
115
+ | ----------------------- | ------------------------------------------ |
116
+ | `vitest` | Fast, Vite-native test runner |
117
+ | `@vitest/coverage-v8` | Code coverage powered by V8 |
91
118
 
92
119
  ---
93
120
 
@@ -111,6 +138,119 @@ Standard Prettier settings:
111
138
  - Single quotes, trailing commas, 80-char print width
112
139
  - No semicolons in prose, arrow parens always
113
140
 
141
+ ### `.eslintignore` / `.prettierignore`
142
+
143
+ Both ignore files are pre-populated with:
144
+
145
+ ```
146
+ dist
147
+ node_modules
148
+ package-lock.json
149
+ coverage
150
+ ```
151
+
152
+ ### `.gitignore`
153
+
154
+ Includes `node_modules`, `dist`, `coverage`, `.env*`, and `*.log`.
155
+
156
+ ### `tsconfig.base.json` / `tsconfig.json`
157
+
158
+ Strict TypeScript configuration. `tsconfig.json` includes `src`, `test`, `__tests__`, and root-level `*.ts` / `*.js` files.
159
+
160
+ ### `vitest.config.ts` (Vitest presets only)
161
+
162
+ Both presets create a `vitest.config.ts` in your project root with:
163
+
164
+ - A `resolve.alias` mapping `@` → `src/` so your tests can import using the same path alias as your source code:
165
+
166
+ ```ts
167
+ import { myUtil } from '@/utils/myUtil';
168
+ ```
169
+
170
+ - A `test.include` that covers both `__tests__/` and `test/` directory conventions:
171
+
172
+ ```ts
173
+ include: [
174
+ '**/__tests__/**/*.{test,spec}.{ts,tsx,js}',
175
+ '**/test/**/*.{test,spec}.{ts,tsx,js}',
176
+ ]
177
+ ```
178
+
179
+ **Coverage preset** additionally adds:
180
+
181
+ ```ts
182
+ coverage: {
183
+ enabled: true,
184
+ reporter: ['json-summary', 'json', 'html'],
185
+ include: ['src/**/*'],
186
+ reportOnFailure: true,
187
+ },
188
+ ```
189
+
190
+ The `coverage/` output folder is automatically excluded from ESLint, Prettier, and Git via the installed ignore files.
191
+
192
+ ---
193
+
194
+ ## Vitest presets in detail
195
+
196
+ When the CLI runs interactively it asks:
197
+
198
+ ```
199
+ ? Do you want to set up Vitest for testing? (Y/n)
200
+ ? Which Vitest configuration would you like?
201
+ ❯ Native — vitest + path alias (@→src), test/test:unit/test:integration scripts
202
+ Coverage — adds @vitest/coverage-v8, HTML/JSON reports, test:coverage script
203
+ ```
204
+
205
+ ### Native preset
206
+
207
+ Installs `vitest` and adds these scripts:
208
+
209
+ ```json
210
+ "test": "vitest --run",
211
+ "test:unit": "vitest unit --run",
212
+ "test:integration": "vitest int --run"
213
+ ```
214
+
215
+ `test:unit` matches any file whose path contains `unit`.
216
+ `test:integration` matches any file whose path contains `int`.
217
+
218
+ ### Coverage preset
219
+
220
+ Installs `vitest` + `@vitest/coverage-v8` and adds:
221
+
222
+ ```json
223
+ "test": "vitest --run",
224
+ "test:unit": "vitest unit --run",
225
+ "test:integration": "vitest int --run",
226
+ "test:coverage": "vitest --coverage --run"
227
+ ```
228
+
229
+ Coverage reports are written to `coverage/` and include HTML, JSON, and a JSON summary (compatible with GitHub Actions PR annotations).
230
+
231
+ ---
232
+
233
+ ## Non-interactive / CI usage
234
+
235
+ Set the `VITEST_PRESET` environment variable to bypass the interactive prompt:
236
+
237
+ ```sh
238
+ # Skip Vitest setup
239
+ VITEST_PRESET=none node ./src/index.js
240
+
241
+ # Install vitest with path alias only
242
+ VITEST_PRESET=native node ./src/index.js
243
+
244
+ # Install vitest with coverage reporting
245
+ VITEST_PRESET=coverage node ./src/index.js
246
+ ```
247
+
248
+ Set `NO_INSTALL=1` to skip all `npm install` calls (useful for testing):
249
+
250
+ ```sh
251
+ NO_INSTALL=1 node ./src/index.js
252
+ ```
253
+
114
254
  ---
115
255
 
116
256
  ## Next steps — optional tooling
@@ -267,7 +407,7 @@ npm install
267
407
  ### Run tests
268
408
 
269
409
  ```sh
270
- npm test # all tests
410
+ npm test # all tests (30 integration tests)
271
411
  npm run test:integration # integration tests only
272
412
  npm run test:coverage # with coverage report
273
413
  ```
@@ -281,22 +421,36 @@ npm init -y
281
421
  node /path/to/create-checks/src/index.js
282
422
  ```
283
423
 
424
+ To test non-interactively:
425
+
426
+ ```sh
427
+ VITEST_PRESET=coverage NO_INSTALL=1 node /path/to/create-checks/src/index.js
428
+ ```
429
+
284
430
  ### Project structure
285
431
 
286
432
  ```
287
433
  create-checks/
288
434
  ├── src/
289
- │ ├── index.js # CLI entrypoint (#!/usr/bin/env node)
435
+ │ ├── index.js # CLI entrypoint (#!/usr/bin/env node)
290
436
  │ └── templates/
291
- │ ├── eslint.config.js # copied into the user's project
292
- └── prettier.config.js
437
+ │ ├── eslint.config.js # copied into the user's project
438
+ ├── prettier.config.js
439
+ │ ├── .editorconfig
440
+ │ ├── .eslintignore # dist, node_modules, package-lock.json, coverage
441
+ │ ├── .prettierignore # dist, node_modules, package-lock.json, coverage
442
+ │ ├── .gitignore # node_modules, dist, coverage, .env*, *.log
443
+ │ ├── tsconfig.base.json
444
+ │ ├── tsconfig.json # includes src, test, __tests__
445
+ │ ├── vitest.config.native.ts # resolve alias + test:unit/integration
446
+ │ └── vitest.config.coverage.ts # + coverage block and test:coverage
293
447
  ├── __tests__/
294
448
  │ └── integration/
295
- │ └── index.int.test.js # spawns the CLI in a temp dir
449
+ │ └── index.int.test.js # 30 integration tests
296
450
  ├── .github/workflows/
297
451
  │ ├── pull-request-checks.yml
298
452
  │ └── semantic-release.yml
299
- ├── release.config.mjs # semantic-release configuration
453
+ ├── release.config.mjs # semantic-release configuration
300
454
  └── package.json
301
455
  ```
302
456
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jeportie/create-checks",
3
- "version": "1.3.0",
3
+ "version": "1.4.0",
4
4
  "description": "Scaffolding CLI that installs ESLint + Prettier and injects config into any project",
5
5
  "author": "jeportie",
6
6
  "repository": {
@@ -39,6 +39,7 @@
39
39
  "dependencies": {
40
40
  "execa": "^8.0.1",
41
41
  "fs-extra": "^11.2.0",
42
+ "inquirer": "~10.2.2",
42
43
  "picocolors": "^1.0.0"
43
44
  },
44
45
  "devDependencies": {
package/src/index.js CHANGED
@@ -2,15 +2,28 @@
2
2
 
3
3
  import fs from 'fs-extra';
4
4
  import { execa } from 'execa';
5
+ import inquirer from 'inquirer';
5
6
  import pc from 'picocolors';
6
7
  import path from 'node:path';
7
8
  import { fileURLToPath } from 'node:url';
8
9
 
9
10
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
10
11
  const cwd = process.cwd();
12
+ const pkgPath = path.join(cwd, 'package.json');
11
13
 
12
14
  console.log(pc.cyan('\n🔧 create-checks — setting up ESLint + Prettier\n'));
13
15
 
16
+ /* ---------------- ENSURE package.json EXISTS ---------------- */
17
+
18
+ if (!(await fs.pathExists(pkgPath))) {
19
+ console.log(pc.yellow(' No package.json found — running npm init -y...'));
20
+ await execa('npm', ['init', '-y'], { stdio: 'inherit' });
21
+ const pkg = await fs.readJson(pkgPath);
22
+ pkg.type = 'module';
23
+ await fs.writeJson(pkgPath, pkg, { spaces: 2 });
24
+ console.log(pc.green(' ✔') + ' package.json created with "type": "module"');
25
+ }
26
+
14
27
  /* ---------------- INSTALL DEPENDENCIES ---------------- */
15
28
 
16
29
  if (!process.env.NO_INSTALL) {
@@ -20,7 +33,8 @@ if (!process.env.NO_INSTALL) {
20
33
  [
21
34
  'install',
22
35
  '-D',
23
- 'eslint',
36
+
37
+ 'eslint@^9',
24
38
  'prettier',
25
39
  'eslint-config-prettier@^9.1.0',
26
40
  '@eslint/js',
@@ -50,6 +64,27 @@ if (!(await fs.pathExists(path.join(cwd, '.editorconfig')))) {
50
64
  console.log(pc.dim(' –') + ' .editorconfig (already exists, skipped)');
51
65
  }
52
66
 
67
+ if (!(await fs.pathExists(path.join(cwd, '.eslintignore')))) {
68
+ await fs.copyFile(path.join(__dirname, 'templates/.eslintignore'), path.join(cwd, '.eslintignore'));
69
+ console.log(pc.green(' ✔') + ' .eslintignore');
70
+ } else {
71
+ console.log(pc.dim(' –') + ' .eslintignore (already exists, skipped)');
72
+ }
73
+
74
+ if (!(await fs.pathExists(path.join(cwd, '.prettierignore')))) {
75
+ await fs.copyFile(path.join(__dirname, 'templates/.prettierignore'), path.join(cwd, '.prettierignore'));
76
+ console.log(pc.green(' ✔') + ' .prettierignore');
77
+ } else {
78
+ console.log(pc.dim(' –') + ' .prettierignore (already exists, skipped)');
79
+ }
80
+
81
+ if (!(await fs.pathExists(path.join(cwd, '.gitignore')))) {
82
+ await fs.copyFile(path.join(__dirname, 'templates/.gitignore'), path.join(cwd, '.gitignore'));
83
+ console.log(pc.green(' ✔') + ' .gitignore');
84
+ } else {
85
+ console.log(pc.dim(' –') + ' .gitignore (already exists, skipped)');
86
+ }
87
+
53
88
  if (!(await fs.pathExists(path.join(cwd, 'tsconfig.base.json')))) {
54
89
  await fs.copyFile(path.join(__dirname, 'templates/tsconfig.base.json'), path.join(cwd, 'tsconfig.base.json'));
55
90
  console.log(pc.green(' ✔') + ' tsconfig.base.json');
@@ -66,7 +101,6 @@ if (!(await fs.pathExists(path.join(cwd, 'tsconfig.json')))) {
66
101
 
67
102
  /* ---------------- UPDATE package.json ---------------- */
68
103
 
69
- const pkgPath = path.join(cwd, 'package.json');
70
104
  const pkg = await fs.readJson(pkgPath);
71
105
 
72
106
  pkg.scripts = {
@@ -79,4 +113,77 @@ pkg.scripts = {
79
113
  await fs.writeJson(pkgPath, pkg, { spaces: 2 });
80
114
  console.log(pc.green(' ✔') + ' package.json (scripts: lint, format, typecheck)');
81
115
 
116
+ /* ---------------- VITEST SETUP ---------------- */
117
+
118
+ // VITEST_PRESET can be set to 'native', 'coverage', or 'none' to bypass the prompt
119
+ // (used by tests and CI environments where stdin is not a TTY)
120
+ let vitestPreset = process.env.VITEST_PRESET;
121
+
122
+ if (!vitestPreset && process.stdin.isTTY) {
123
+ const { setupVitest } = await inquirer.prompt([
124
+ {
125
+ type: 'confirm',
126
+ name: 'setupVitest',
127
+ message: 'Do you want to set up Vitest for testing?',
128
+ default: true,
129
+ },
130
+ ]);
131
+
132
+ if (setupVitest) {
133
+ const { preset } = await inquirer.prompt([
134
+ {
135
+ type: 'list',
136
+ name: 'preset',
137
+ message: 'Which Vitest configuration would you like?',
138
+ choices: [
139
+ {
140
+ name: 'Native — vitest + path alias (@→src), test/test:unit/test:integration scripts',
141
+ value: 'native',
142
+ },
143
+ {
144
+ name: 'Coverage — adds @vitest/coverage-v8, HTML/JSON reports, test:coverage script',
145
+ value: 'coverage',
146
+ },
147
+ ],
148
+ },
149
+ ]);
150
+ vitestPreset = preset;
151
+ }
152
+ }
153
+
154
+ if (vitestPreset === 'native' || vitestPreset === 'coverage') {
155
+ console.log(pc.cyan('\n🧪 Setting up Vitest...\n'));
156
+
157
+ if (!process.env.NO_INSTALL) {
158
+ const vitestDeps = ['vitest'];
159
+ if (vitestPreset === 'coverage') {
160
+ vitestDeps.push('@vitest/coverage-v8');
161
+ }
162
+ console.log(pc.dim(' Installing vitest dependencies...'));
163
+ await execa('npm', ['install', '-D', ...vitestDeps], { stdio: 'inherit' });
164
+ }
165
+
166
+ await fs.copyFile(
167
+ path.join(__dirname, `templates/vitest.config.${vitestPreset}.ts`),
168
+ path.join(cwd, 'vitest.config.ts'),
169
+ );
170
+ console.log(pc.green(' ✔') + ' vitest.config.ts');
171
+
172
+ const updatedPkg = await fs.readJson(pkgPath);
173
+ updatedPkg.scripts = {
174
+ ...updatedPkg.scripts,
175
+ test: 'vitest --run',
176
+ 'test:unit': 'vitest unit --run',
177
+ 'test:integration': 'vitest int --run',
178
+ };
179
+ if (vitestPreset === 'coverage') {
180
+ updatedPkg.scripts['test:coverage'] = 'vitest --coverage --run';
181
+ }
182
+ await fs.writeJson(pkgPath, updatedPkg, { spaces: 2 });
183
+
184
+ const addedScripts = ['test', 'test:unit', 'test:integration'];
185
+ if (vitestPreset === 'coverage') addedScripts.push('test:coverage');
186
+ console.log(pc.green(' ✔') + ` package.json (scripts: ${addedScripts.join(', ')})`);
187
+ }
188
+
82
189
  console.log(pc.green('\n✅ Done!\n'));
@@ -0,0 +1,4 @@
1
+ dist
2
+ node_modules
3
+ package-lock.json
4
+ coverage
@@ -0,0 +1,4 @@
1
+ dist
2
+ node_modules
3
+ package-lock.json
4
+ coverage
@@ -1,13 +1,29 @@
1
1
  {
2
2
  "extends": "./tsconfig.base.json",
3
3
  "compilerOptions": {
4
+ "module": "ESNExt",
5
+ "moduleResolution": "Bundler",
4
6
  "noEmit": true,
5
7
  "baseUrl": ".",
6
8
  "paths": {
7
- "@/*": ["src/*"]
9
+ "@/*": [
10
+ "src/*"
11
+ ]
8
12
  },
9
- "types": ["node"]
13
+ "types": [
14
+ "node"
15
+ ]
10
16
  },
11
- "include": ["src", "./*.ts", "./*.js", "./*.mjs", "./*.cjs"],
12
- "exclude": ["node_modules"]
17
+ "include": [
18
+ "src",
19
+ "test",
20
+ "__tests__",
21
+ "./*.ts",
22
+ "./*.js",
23
+ "./*.mjs",
24
+ "./*.cjs"
25
+ ],
26
+ "exclude": [
27
+ "node_modules"
28
+ ]
13
29
  }
@@ -0,0 +1,23 @@
1
+ import { resolve } from 'path';
2
+
3
+ import { defineConfig } from 'vitest/config';
4
+
5
+ export default defineConfig({
6
+ resolve: {
7
+ alias: {
8
+ '@': resolve(__dirname, 'src'),
9
+ },
10
+ },
11
+ test: {
12
+ include: [
13
+ '**/__tests__/**/*.{test,spec}.{ts,tsx,js}',
14
+ '**/test/**/*.{test,spec}.{ts,tsx,js}',
15
+ ],
16
+ coverage: {
17
+ enabled: true,
18
+ reporter: ['json-summary', 'json', 'html'],
19
+ include: ['src/**/*'],
20
+ reportOnFailure: true,
21
+ },
22
+ },
23
+ });
@@ -0,0 +1,17 @@
1
+ import { resolve } from 'path';
2
+
3
+ import { defineConfig } from 'vitest/config';
4
+
5
+ export default defineConfig({
6
+ resolve: {
7
+ alias: {
8
+ '@': resolve(__dirname, 'src'),
9
+ },
10
+ },
11
+ test: {
12
+ include: [
13
+ '**/__tests__/**/*.{test,spec}.{ts,tsx,js}',
14
+ '**/test/**/*.{test,spec}.{ts,tsx,js}',
15
+ ],
16
+ },
17
+ });