@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.
@@ -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 .github/dependabot.yml',
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':
@@ -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
- const effectiveResult = result.status === 'ok' && lockfileDemoted ? { ...result, status: 'optional-missing' } : result;
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.14.0",
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,9 @@
1
+ {
2
+ "extends": "./tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "lib": ["ES2022"],
5
+ "types": ["node", "vitest"]
6
+ },
7
+ "include": ["src", "index.d.ts"],
8
+ "exclude": ["node_modules", "dist", "build", "test", "**/*.test.ts", "**/*.spec.ts"]
9
+ }
@@ -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
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "extends": "./tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "noEmit": true,
5
+ "types": ["node", "vitest"]
6
+ },
7
+ "include": ["src", "tests", "**/*.test.ts", "**/*.test.tsx", "**/*.spec.ts", "**/*.spec.tsx"]
8
+ }