@rtorcato/js-tooling 2.14.0 โ 2.16.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/cli/commands/doctor.js +61 -2
- package/dist/cli/commands/fix-targets.js +2 -0
- package/dist/cli/commands/fix.js +32 -2
- package/dist/cli/generators/security.js +22 -0
- package/dist/cli/index.js +6 -0
- package/package.json +9 -2
- package/tooling/typescript/v1/tsconfig.base.json +77 -0
- package/tooling/typescript/v1/tsconfig.express.json +9 -0
- package/tooling/typescript/v1/tsconfig.next.json +19 -0
- package/tooling/typescript/v1/tsconfig.node.json +9 -0
- package/tooling/typescript/v1/tsconfig.react.json +14 -0
- package/tooling/typescript/v1/tsconfig.test.json +8 -0
|
@@ -529,11 +529,27 @@ async function checkDependabot(dir) {
|
|
|
529
529
|
};
|
|
530
530
|
}
|
|
531
531
|
}
|
|
532
|
+
for (const candidate of [
|
|
533
|
+
'renovate.json',
|
|
534
|
+
'renovate.json5',
|
|
535
|
+
'.github/renovate.json',
|
|
536
|
+
'.github/renovate.json5',
|
|
537
|
+
'.renovaterc',
|
|
538
|
+
'.renovaterc.json',
|
|
539
|
+
]) {
|
|
540
|
+
if (await fs.pathExists(path.join(dir, candidate))) {
|
|
541
|
+
return {
|
|
542
|
+
check: 'Dependabot',
|
|
543
|
+
status: 'ok',
|
|
544
|
+
detail: `${candidate} found (Renovate)`,
|
|
545
|
+
};
|
|
546
|
+
}
|
|
547
|
+
}
|
|
532
548
|
return {
|
|
533
549
|
check: 'Dependabot',
|
|
534
550
|
status: 'optional-missing',
|
|
535
|
-
detail: 'no
|
|
536
|
-
hint: 'Run `npx @rtorcato/js-tooling fix dependabot` to scaffold weekly dep updates',
|
|
551
|
+
detail: 'no Dependabot or Renovate config',
|
|
552
|
+
hint: 'Run `npx @rtorcato/js-tooling fix dependabot` (or `fix renovate`) to scaffold weekly dep updates',
|
|
537
553
|
};
|
|
538
554
|
}
|
|
539
555
|
async function checkCodeQL(dir) {
|
|
@@ -580,6 +596,48 @@ async function checkCodeQL(dir) {
|
|
|
580
596
|
hint: 'Run `npx @rtorcato/js-tooling fix codeql` to scaffold CodeQL security scanning',
|
|
581
597
|
};
|
|
582
598
|
}
|
|
599
|
+
function isPublishableLibrary(pkg) {
|
|
600
|
+
if (!pkg || pkg.private === true)
|
|
601
|
+
return false;
|
|
602
|
+
return !!(pkg.exports || pkg.main || pkg.module || pkg.files);
|
|
603
|
+
}
|
|
604
|
+
async function checkAreTheTypesWrong(_dir, pkg) {
|
|
605
|
+
if (!isPublishableLibrary(pkg)) {
|
|
606
|
+
return {
|
|
607
|
+
check: 'are-the-types-wrong',
|
|
608
|
+
status: 'ok',
|
|
609
|
+
detail: 'not applicable (private or no published exports)',
|
|
610
|
+
};
|
|
611
|
+
}
|
|
612
|
+
const deps = {
|
|
613
|
+
...(pkg?.dependencies ?? {}),
|
|
614
|
+
...(pkg?.devDependencies ?? {}),
|
|
615
|
+
};
|
|
616
|
+
const scripts = pkg?.scripts ?? {};
|
|
617
|
+
const hasDep = !!deps['are-the-types-wrong'];
|
|
618
|
+
const hasScript = Object.values(scripts).some((s) => /\battw\b|are-the-types-wrong/.test(s));
|
|
619
|
+
if (hasDep && hasScript) {
|
|
620
|
+
return {
|
|
621
|
+
check: 'are-the-types-wrong',
|
|
622
|
+
status: 'ok',
|
|
623
|
+
detail: 'are-the-types-wrong installed and wired into a script',
|
|
624
|
+
};
|
|
625
|
+
}
|
|
626
|
+
if (hasDep) {
|
|
627
|
+
return {
|
|
628
|
+
check: 'are-the-types-wrong',
|
|
629
|
+
status: 'drift',
|
|
630
|
+
detail: 'are-the-types-wrong installed but no script runs it',
|
|
631
|
+
hint: 'Add `"attw": "attw --pack"` to package.json scripts and call it from your verify/CI chain',
|
|
632
|
+
};
|
|
633
|
+
}
|
|
634
|
+
return {
|
|
635
|
+
check: 'are-the-types-wrong',
|
|
636
|
+
status: 'optional-missing',
|
|
637
|
+
detail: 'are-the-types-wrong not configured',
|
|
638
|
+
hint: 'Run `pnpm add -D are-the-types-wrong && attw --pack` to validate TypeScript exports before publishing',
|
|
639
|
+
};
|
|
640
|
+
}
|
|
583
641
|
async function checkTreeshakeSetup(dir, pkg) {
|
|
584
642
|
const appCheckPath = path.join(dir, 'apps', 'treeshake-check', 'check.mjs');
|
|
585
643
|
if (await fs.pathExists(appCheckPath)) {
|
|
@@ -690,6 +748,7 @@ export async function runDoctor(dir) {
|
|
|
690
748
|
results.push(await checkCodeQL(targetDir));
|
|
691
749
|
results.push(await checkGitLabCI(targetDir));
|
|
692
750
|
results.push(await checkCodeowners(targetDir));
|
|
751
|
+
results.push(await checkAreTheTypesWrong(targetDir, pkg));
|
|
693
752
|
results.push(await checkTreeshakeSetup(targetDir, pkg));
|
|
694
753
|
// Lockfile-driven demotion: if the lock records an intentional opt-out for a
|
|
695
754
|
// check that's currently optional-missing, demote it to ok with a clear detail.
|
|
@@ -24,6 +24,7 @@ export const FIX_TARGETS = {
|
|
|
24
24
|
'GitLab CI': 'gitlab-ci',
|
|
25
25
|
lockfile: 'lockfile',
|
|
26
26
|
'.js-tooling.json': 'lockfile',
|
|
27
|
+
'are-the-types-wrong': 'attw',
|
|
27
28
|
};
|
|
28
29
|
export function getFixTargetForCheck(checkName) {
|
|
29
30
|
return FIX_TARGETS[checkName] ?? null;
|
|
@@ -110,6 +111,7 @@ export function lockfilePatchForTarget(target, lock) {
|
|
|
110
111
|
case 'semantic-release':
|
|
111
112
|
return c.semanticRelease ? null : { semanticRelease: true };
|
|
112
113
|
case 'dependabot':
|
|
114
|
+
case 'renovate':
|
|
113
115
|
case 'codeql':
|
|
114
116
|
return c.securityAutomation ? null : { securityAutomation: true };
|
|
115
117
|
case 'tsconfig':
|
package/dist/cli/commands/fix.js
CHANGED
|
@@ -10,7 +10,7 @@ import { generateESLintConfig, generatePrettierConfig } from '../generators/lint
|
|
|
10
10
|
import { ensureEnginesNode, generateCodeowners, generateEditorConfig, generateKnipConfig, generateNvmrc, generateSizeLimitConfig, } from '../generators/misc.js';
|
|
11
11
|
import { generateConfigs } from '../generators/index.js';
|
|
12
12
|
import { composeVerifyScriptFromPkg } from '../generators/package-json.js';
|
|
13
|
-
import { generateCodeQLWorkflow, generateDependabotConfig } from '../generators/security.js';
|
|
13
|
+
import { generateCodeQLWorkflow, generateDependabotConfig, generateRenovateConfig, } from '../generators/security.js';
|
|
14
14
|
import { generateVitestConfig } from '../generators/testing.js';
|
|
15
15
|
import { generateTreeshakeCheck, inferSubpathsFromExports } from '../generators/treeshake.js';
|
|
16
16
|
import { copyPreset } from '../utils/copy-preset.js';
|
|
@@ -222,6 +222,17 @@ const FIXERS = [
|
|
|
222
222
|
return { filesWritten: ['.github/dependabot.yml'] };
|
|
223
223
|
},
|
|
224
224
|
},
|
|
225
|
+
{
|
|
226
|
+
target: 'renovate',
|
|
227
|
+
description: 'Scaffold renovate.json (weekly schedule; alternative to Dependabot)',
|
|
228
|
+
appliesTo: ['Dependabot'],
|
|
229
|
+
outputs: ['renovate.json'],
|
|
230
|
+
riskLevel: 'safe-add',
|
|
231
|
+
async run({ targetDir }) {
|
|
232
|
+
await generateRenovateConfig(targetDir);
|
|
233
|
+
return { filesWritten: ['renovate.json'] };
|
|
234
|
+
},
|
|
235
|
+
},
|
|
225
236
|
{
|
|
226
237
|
target: 'codeql',
|
|
227
238
|
description: 'Scaffold .github/workflows/codeql.yml (security scanning)',
|
|
@@ -393,6 +404,18 @@ const FIXERS = [
|
|
|
393
404
|
export function getFixers() {
|
|
394
405
|
return FIXERS;
|
|
395
406
|
}
|
|
407
|
+
async function ownOutputsPresent(targetDir, fixer) {
|
|
408
|
+
for (const out of fixer.outputs) {
|
|
409
|
+
// Outputs that reference a package.json field (e.g. "package.json (scripts.verify)")
|
|
410
|
+
// can't be cheaply file-checked here; treat as present so we don't accidentally
|
|
411
|
+
// re-run safe-merge fixers on every targeted invocation.
|
|
412
|
+
if (out.includes('('))
|
|
413
|
+
return true;
|
|
414
|
+
if (await fs.pathExists(path.join(targetDir, out)))
|
|
415
|
+
return true;
|
|
416
|
+
}
|
|
417
|
+
return false;
|
|
418
|
+
}
|
|
396
419
|
function findFixer(target) {
|
|
397
420
|
const normalized = target.toLowerCase();
|
|
398
421
|
return FIXERS.find((f) => f.target.toLowerCase() === normalized);
|
|
@@ -587,7 +610,14 @@ export async function fixCommand(target, options = {}) {
|
|
|
587
610
|
// fixable when the user explicitly targets it โ treat it as optional-missing
|
|
588
611
|
// so the override + lockfile resync paths run.
|
|
589
612
|
const lockfileDemoted = lock !== null && declinedInLock(lock, result.check);
|
|
590
|
-
|
|
613
|
+
// When multiple fixers share a check (e.g. dependabot + renovate both apply to
|
|
614
|
+
// "Dependabot" deps-update coverage), the check can be `ok` from a sibling tool
|
|
615
|
+
// while this fixer's own outputs are still absent. In that case, treat as missing
|
|
616
|
+
// so the targeted scaffold runs.
|
|
617
|
+
const fixerOutputsPresent = await ownOutputsPresent(targetDir, fixer);
|
|
618
|
+
const effectiveResult = result.status === 'ok' && (lockfileDemoted || !fixerOutputsPresent)
|
|
619
|
+
? { ...result, status: 'optional-missing' }
|
|
620
|
+
: result;
|
|
591
621
|
if (effectiveResult.status === 'ok') {
|
|
592
622
|
actions.push(recordFor(fixer.target, result.check, 'ok', 'already-ok', []));
|
|
593
623
|
if (json)
|
|
@@ -24,6 +24,28 @@ updates:
|
|
|
24
24
|
`;
|
|
25
25
|
await fs.writeFile(filepath, content);
|
|
26
26
|
}
|
|
27
|
+
export async function generateRenovateConfig(targetDir) {
|
|
28
|
+
const filepath = path.join(targetDir, 'renovate.json');
|
|
29
|
+
const content = `${JSON.stringify({
|
|
30
|
+
$schema: 'https://docs.renovatebot.com/renovate-schema.json',
|
|
31
|
+
extends: ['config:recommended', ':semanticCommits', ':semanticCommitTypeAll(chore)'],
|
|
32
|
+
schedule: ['before 4am on Monday'],
|
|
33
|
+
prConcurrentLimit: 10,
|
|
34
|
+
prHourlyLimit: 0,
|
|
35
|
+
rangeStrategy: 'bump',
|
|
36
|
+
packageRules: [
|
|
37
|
+
{
|
|
38
|
+
matchManagers: ['github-actions'],
|
|
39
|
+
commitMessagePrefix: 'chore(ci):',
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
matchManagers: ['npm'],
|
|
43
|
+
commitMessagePrefix: 'chore(deps):',
|
|
44
|
+
},
|
|
45
|
+
],
|
|
46
|
+
}, null, 2)}\n`;
|
|
47
|
+
await fs.writeFile(filepath, content);
|
|
48
|
+
}
|
|
27
49
|
export async function generateCodeQLWorkflow(targetDir) {
|
|
28
50
|
await fs.ensureDir(path.join(targetDir, '.github', 'workflows'));
|
|
29
51
|
const filepath = path.join(targetDir, '.github', 'workflows', 'codeql.yml');
|
package/dist/cli/index.js
CHANGED
|
@@ -184,6 +184,12 @@ const TOOL_CATALOG = [
|
|
|
184
184
|
exports: [],
|
|
185
185
|
fixTarget: 'dependabot',
|
|
186
186
|
},
|
|
187
|
+
{
|
|
188
|
+
name: 'Renovate',
|
|
189
|
+
description: 'Weekly automated dependency updates (alternative to Dependabot)',
|
|
190
|
+
exports: [],
|
|
191
|
+
fixTarget: 'renovate',
|
|
192
|
+
},
|
|
187
193
|
{
|
|
188
194
|
name: 'CodeQL',
|
|
189
195
|
description: 'GitHub security scanning workflow',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rtorcato/js-tooling",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.16.0",
|
|
4
4
|
"description": "JavaScript and TypeScript tooling for Node.js, React, Next.js, and Vitest.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"keywords": [
|
|
@@ -52,6 +52,7 @@
|
|
|
52
52
|
"tooling/prettier/index.mjs",
|
|
53
53
|
"tooling/prettier/index.d.mts",
|
|
54
54
|
"tooling/typescript/*.json",
|
|
55
|
+
"tooling/typescript/v1/*.json",
|
|
55
56
|
"tooling/typescript/reset.d.ts",
|
|
56
57
|
"tooling/vite/vite.config.mjs",
|
|
57
58
|
"tooling/vite/vite.config.d.mts",
|
|
@@ -113,6 +114,12 @@
|
|
|
113
114
|
"./typescript/react": "./tooling/typescript/tsconfig.react.json",
|
|
114
115
|
"./typescript/test": "./tooling/typescript/tsconfig.test.json",
|
|
115
116
|
"./typescript/reset": "./tooling/typescript/reset.d.ts",
|
|
117
|
+
"./typescript/base@1": "./tooling/typescript/v1/tsconfig.base.json",
|
|
118
|
+
"./typescript/next@1": "./tooling/typescript/v1/tsconfig.next.json",
|
|
119
|
+
"./typescript/express@1": "./tooling/typescript/v1/tsconfig.express.json",
|
|
120
|
+
"./typescript/node@1": "./tooling/typescript/v1/tsconfig.node.json",
|
|
121
|
+
"./typescript/react@1": "./tooling/typescript/v1/tsconfig.react.json",
|
|
122
|
+
"./typescript/test@1": "./tooling/typescript/v1/tsconfig.test.json",
|
|
116
123
|
"./vite": {
|
|
117
124
|
"types": "./tooling/vite/vite.config.d.mts",
|
|
118
125
|
"import": "./tooling/vite/vite.config.mjs"
|
|
@@ -240,7 +247,7 @@
|
|
|
240
247
|
"ts-jest": "^29.0.0",
|
|
241
248
|
"tsup": "^8.0.0",
|
|
242
249
|
"typescript": ">=5.0.0",
|
|
243
|
-
"typescript-eslint": "
|
|
250
|
+
"typescript-eslint": "^8.0.0",
|
|
244
251
|
"vitest": ">=3.0.0"
|
|
245
252
|
},
|
|
246
253
|
"peerDependenciesMeta": {
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json.schemastore.org/tsconfig",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
// ๐ง Output & Module Resolution
|
|
5
|
+
"moduleResolution": "bundler",
|
|
6
|
+
"module": "esnext",
|
|
7
|
+
"target": "esnext",
|
|
8
|
+
// "module": "esnext", //
|
|
9
|
+
// "moduleResolution": "node", // Prefer "bundler" if you're using Vite, esbuild, etc.
|
|
10
|
+
// "target": "esnext", //
|
|
11
|
+
"lib": ["ES2022"], // Specify library files to be included in the compilation
|
|
12
|
+
"rootDir": "src",
|
|
13
|
+
"outDir": "dist",
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
// ๐ JavaScript Support
|
|
17
|
+
"allowJs": true, // Allow JavaScript files to be compiled
|
|
18
|
+
"checkJs": true, // Enable type checking on JavaScript files
|
|
19
|
+
"resolveJsonModule": true, // Allow importing JSON files
|
|
20
|
+
"isolatedModules": true, // Ensure that each file can be safely transpiled independently
|
|
21
|
+
"esModuleInterop": true, // Enables emit interoperability between CommonJS and ES Modules
|
|
22
|
+
"allowSyntheticDefaultImports": true, // recommend enabling this for smoother interop
|
|
23
|
+
|
|
24
|
+
// โ๏ธ Module Detection
|
|
25
|
+
"moduleDetection": "force", // Force module detection
|
|
26
|
+
|
|
27
|
+
// ๐ Performance
|
|
28
|
+
"skipLibCheck": true, // Skip type checking of declaration files
|
|
29
|
+
"incremental": true, // Enable incremental compilation
|
|
30
|
+
"disableSourceOfProjectReferenceRedirect": true, // Disable source of project reference redirect
|
|
31
|
+
|
|
32
|
+
// ๐จ Strict Type Checking
|
|
33
|
+
"strict": true, // Enable all strict type checking options
|
|
34
|
+
"noUncheckedIndexedAccess": true, // Disallow accessing an index signature with an arbitrary key
|
|
35
|
+
"noImplicitAny": true, // Disallow implicit any type
|
|
36
|
+
"noImplicitReturns": true, // Disallow functions that do not return a value
|
|
37
|
+
"noImplicitOverride": true, // Ensure override keyword is used
|
|
38
|
+
"noImplicitThis": true, // Disallow 'this' with an implicit any type
|
|
39
|
+
"noPropertyAccessFromIndexSignature": true, // Disallow property access from index signature
|
|
40
|
+
|
|
41
|
+
// ๐งน Lint-Like Settings
|
|
42
|
+
"noUnusedLocals": true, // Disallow unused locals
|
|
43
|
+
"noUnusedParameters": true, // Disallow unused parameters
|
|
44
|
+
"noFallthroughCasesInSwitch": true, // Disallow fallthrough cases in switch statements
|
|
45
|
+
|
|
46
|
+
// ๐ฆ Types & Declarations
|
|
47
|
+
"types": ["node", "vitest"], // Specify type declaration files to be included in the compilation
|
|
48
|
+
"declaration": false, // Do not generate .d.ts files
|
|
49
|
+
"declarationMap": false, // Do not generate sourcemaps for d.ts files
|
|
50
|
+
|
|
51
|
+
// ๐ Emit Settings
|
|
52
|
+
"noEmit": true, // Do not emit output files
|
|
53
|
+
"sourceMap": true, // Generate sourcemaps for .ts files
|
|
54
|
+
|
|
55
|
+
"paths": { // Path mapping for module resolution
|
|
56
|
+
"~/*": ["./src/*"],
|
|
57
|
+
"@/*": ["./src/*"]
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
"include": ["src/**/*", "index.d.ts"], // Include files in the src directory and index.d.ts
|
|
61
|
+
"exclude": [
|
|
62
|
+
"node_modules",
|
|
63
|
+
"build",
|
|
64
|
+
"dist",
|
|
65
|
+
".next",
|
|
66
|
+
".expo",
|
|
67
|
+
"OLD",
|
|
68
|
+
"**/*.test.ts",
|
|
69
|
+
"**/*.test.tsx",
|
|
70
|
+
"**/*.spec.ts",
|
|
71
|
+
"**/*.spec.tsx",
|
|
72
|
+
".turbo",
|
|
73
|
+
"out",
|
|
74
|
+
"not-used",
|
|
75
|
+
"vitest.config.ts"
|
|
76
|
+
] // Exclude directories from compilation
|
|
77
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "./tsconfig.base.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"lib": ["ES2022"], // Only include ES libs, no DOM
|
|
5
|
+
"types": ["node", "express", "vitest"], // Add express types for API development
|
|
6
|
+
},
|
|
7
|
+
"include": ["src", "index.d.ts"],
|
|
8
|
+
"exclude": ["node_modules", "dist", "build", "test", "**/*.test.ts", "**/*.spec.ts"]
|
|
9
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "./tsconfig.base.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"jsx": "preserve", // Let Next.js handle JSX transformation
|
|
5
|
+
"lib": ["DOM", "DOM.Iterable", "ESNext"], // Include DOM types for browser APIs
|
|
6
|
+
"module": "esnext", // Next.js expects ESNext modules
|
|
7
|
+
"moduleResolution": "bundler", // Modern module resolution for Next.js
|
|
8
|
+
"allowJs": true, // Allow JavaScript files
|
|
9
|
+
"esModuleInterop": true, // Interop for CommonJS/ESM
|
|
10
|
+
"types": ["react", "tailwindcss", "vitest"],
|
|
11
|
+
"plugins": [ { "name": "next" } ], // Next.js plugin for TypeScript
|
|
12
|
+
"paths": {
|
|
13
|
+
"~/*": ["./src/*"],
|
|
14
|
+
"@/*": ["./src/*"]
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"include": ["next-env.d.ts", "src", "pages", "app", "components", "types", "index.d.ts"],
|
|
18
|
+
"exclude": ["node_modules", "dist", ".next", "build", "out"]
|
|
19
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "./tsconfig.base.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"jsx": "react-jsx", // Use the new JSX transform for React 17+
|
|
5
|
+
"lib": ["DOM", "DOM.Iterable", "ESNext"],// Include DOM types for browser APIs
|
|
6
|
+
"types": ["react", "react-dom", "tailwindcss", "vitest"],
|
|
7
|
+
"paths": {
|
|
8
|
+
"~/*": ["./src/*"],
|
|
9
|
+
"@/*": ["./src/*"]
|
|
10
|
+
}
|
|
11
|
+
},
|
|
12
|
+
"include": ["src", "index.d.ts", "types"],
|
|
13
|
+
"exclude": ["node_modules", "dist", "build", "out", "**/*.test.ts", "**/*.spec.ts"]
|
|
14
|
+
}
|