@rtorcato/js-tooling 2.13.0 → 2.15.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
|
|
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) {
|
|
@@ -661,7 +677,7 @@ async function checkGitLabCI(dir) {
|
|
|
661
677
|
check: 'GitLab CI',
|
|
662
678
|
status: 'optional-missing',
|
|
663
679
|
detail: 'no .gitlab-ci.yml',
|
|
664
|
-
hint: '
|
|
680
|
+
hint: 'Run `npx @rtorcato/js-tooling fix gitlab-ci` to scaffold a starter GitLab pipeline',
|
|
665
681
|
};
|
|
666
682
|
}
|
|
667
683
|
export async function runDoctor(dir) {
|
|
@@ -21,6 +21,7 @@ export const FIX_TARGETS = {
|
|
|
21
21
|
Dependabot: 'dependabot',
|
|
22
22
|
CodeQL: 'codeql',
|
|
23
23
|
CODEOWNERS: 'codeowners',
|
|
24
|
+
'GitLab CI': 'gitlab-ci',
|
|
24
25
|
lockfile: 'lockfile',
|
|
25
26
|
'.js-tooling.json': 'lockfile',
|
|
26
27
|
};
|
|
@@ -109,6 +110,7 @@ export function lockfilePatchForTarget(target, lock) {
|
|
|
109
110
|
case 'semantic-release':
|
|
110
111
|
return c.semanticRelease ? null : { semanticRelease: true };
|
|
111
112
|
case 'dependabot':
|
|
113
|
+
case 'renovate':
|
|
112
114
|
case 'codeql':
|
|
113
115
|
return c.securityAutomation ? null : { securityAutomation: true };
|
|
114
116
|
case 'tsconfig':
|
package/dist/cli/commands/fix.js
CHANGED
|
@@ -5,11 +5,12 @@ import inquirer from 'inquirer';
|
|
|
5
5
|
import { generateSemanticReleaseConfig } from '../generators/build.js';
|
|
6
6
|
import { generateCommitlintConfig, generateHuskyConfig, generatePrePushHook, } from '../generators/git.js';
|
|
7
7
|
import { generateGitHubActions } from '../generators/github-actions.js';
|
|
8
|
+
import { generateGitLabCI } from '../generators/gitlab-ci.js';
|
|
8
9
|
import { generateESLintConfig, generatePrettierConfig } from '../generators/linting.js';
|
|
9
10
|
import { ensureEnginesNode, generateCodeowners, generateEditorConfig, generateKnipConfig, generateNvmrc, generateSizeLimitConfig, } from '../generators/misc.js';
|
|
10
11
|
import { generateConfigs } from '../generators/index.js';
|
|
11
12
|
import { composeVerifyScriptFromPkg } from '../generators/package-json.js';
|
|
12
|
-
import { generateCodeQLWorkflow, generateDependabotConfig } from '../generators/security.js';
|
|
13
|
+
import { generateCodeQLWorkflow, generateDependabotConfig, generateRenovateConfig, } from '../generators/security.js';
|
|
13
14
|
import { generateVitestConfig } from '../generators/testing.js';
|
|
14
15
|
import { generateTreeshakeCheck, inferSubpathsFromExports } from '../generators/treeshake.js';
|
|
15
16
|
import { copyPreset } from '../utils/copy-preset.js';
|
|
@@ -221,6 +222,17 @@ const FIXERS = [
|
|
|
221
222
|
return { filesWritten: ['.github/dependabot.yml'] };
|
|
222
223
|
},
|
|
223
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
|
+
},
|
|
224
236
|
{
|
|
225
237
|
target: 'codeql',
|
|
226
238
|
description: 'Scaffold .github/workflows/codeql.yml (security scanning)',
|
|
@@ -243,6 +255,17 @@ const FIXERS = [
|
|
|
243
255
|
return { filesWritten: [written] };
|
|
244
256
|
},
|
|
245
257
|
},
|
|
258
|
+
{
|
|
259
|
+
target: 'gitlab-ci',
|
|
260
|
+
description: 'Scaffold .gitlab-ci.yml (lint/typecheck/test/build mirrored from GitHub Actions)',
|
|
261
|
+
appliesTo: ['GitLab CI'],
|
|
262
|
+
outputs: ['.gitlab-ci.yml'],
|
|
263
|
+
canFixDrift: true,
|
|
264
|
+
async run({ targetDir, pkg }) {
|
|
265
|
+
const written = await generateGitLabCI(inferProjectConfig(pkg), targetDir);
|
|
266
|
+
return { filesWritten: [written] };
|
|
267
|
+
},
|
|
268
|
+
},
|
|
246
269
|
{
|
|
247
270
|
target: 'editorconfig',
|
|
248
271
|
description: 'Scaffold .editorconfig (UTF-8, LF, tab indent)',
|
|
@@ -381,6 +404,18 @@ const FIXERS = [
|
|
|
381
404
|
export function getFixers() {
|
|
382
405
|
return FIXERS;
|
|
383
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
|
+
}
|
|
384
419
|
function findFixer(target) {
|
|
385
420
|
const normalized = target.toLowerCase();
|
|
386
421
|
return FIXERS.find((f) => f.target.toLowerCase() === normalized);
|
|
@@ -575,7 +610,14 @@ export async function fixCommand(target, options = {}) {
|
|
|
575
610
|
// fixable when the user explicitly targets it — treat it as optional-missing
|
|
576
611
|
// so the override + lockfile resync paths run.
|
|
577
612
|
const lockfileDemoted = lock !== null && declinedInLock(lock, result.check);
|
|
578
|
-
|
|
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;
|
|
579
621
|
if (effectiveResult.status === 'ok') {
|
|
580
622
|
actions.push(recordFor(fixer.target, result.check, 'ok', 'already-ok', []));
|
|
581
623
|
if (json)
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import fs from 'fs-extra';
|
|
3
|
+
export async function generateGitLabCI(config, targetDir) {
|
|
4
|
+
const yamlPath = path.join(targetDir, '.gitlab-ci.yml');
|
|
5
|
+
await fs.writeFile(yamlPath, renderGitLabCI(config));
|
|
6
|
+
return '.gitlab-ci.yml';
|
|
7
|
+
}
|
|
8
|
+
function lintCommand(config) {
|
|
9
|
+
if (config.linting.tool === 'biome' || config.linting.tool === 'both')
|
|
10
|
+
return 'pnpm check';
|
|
11
|
+
return 'pnpm lint';
|
|
12
|
+
}
|
|
13
|
+
function renderGitLabCI(config) {
|
|
14
|
+
const hasTypeScript = config.typescript.enabled;
|
|
15
|
+
const hasTests = config.testing.framework !== 'none';
|
|
16
|
+
const hasLint = config.linting.tool !== 'none';
|
|
17
|
+
const hasBuild = config.bundler !== 'none';
|
|
18
|
+
const stages = [];
|
|
19
|
+
if (hasLint || hasTypeScript || hasTests)
|
|
20
|
+
stages.push('test');
|
|
21
|
+
if (hasBuild)
|
|
22
|
+
stages.push('build');
|
|
23
|
+
if (stages.length === 0)
|
|
24
|
+
stages.push('test');
|
|
25
|
+
const jobs = [];
|
|
26
|
+
if (hasLint) {
|
|
27
|
+
jobs.push(`lint:
|
|
28
|
+
stage: test
|
|
29
|
+
script:
|
|
30
|
+
- ${lintCommand(config)}`);
|
|
31
|
+
}
|
|
32
|
+
if (hasTypeScript) {
|
|
33
|
+
jobs.push(`typecheck:
|
|
34
|
+
stage: test
|
|
35
|
+
script:
|
|
36
|
+
- pnpm typecheck`);
|
|
37
|
+
}
|
|
38
|
+
if (hasTests) {
|
|
39
|
+
const testCmd = config.testing.framework === 'vitest'
|
|
40
|
+
? 'pnpm exec vitest run'
|
|
41
|
+
: config.testing.framework === 'playwright'
|
|
42
|
+
? 'pnpm test:e2e'
|
|
43
|
+
: 'pnpm test';
|
|
44
|
+
jobs.push(`test:
|
|
45
|
+
stage: test
|
|
46
|
+
script:
|
|
47
|
+
- ${testCmd}`);
|
|
48
|
+
}
|
|
49
|
+
if (hasBuild) {
|
|
50
|
+
jobs.push(`build:
|
|
51
|
+
stage: build
|
|
52
|
+
script:
|
|
53
|
+
- pnpm build
|
|
54
|
+
artifacts:
|
|
55
|
+
paths:
|
|
56
|
+
- dist/
|
|
57
|
+
expire_in: 1 week`);
|
|
58
|
+
}
|
|
59
|
+
return `# .gitlab-ci.yml — generated by @rtorcato/js-tooling
|
|
60
|
+
# Customize stages and jobs to fit your pipeline.
|
|
61
|
+
|
|
62
|
+
image: node:20
|
|
63
|
+
|
|
64
|
+
stages:
|
|
65
|
+
${stages.map((s) => ` - ${s}`).join('\n')}
|
|
66
|
+
|
|
67
|
+
variables:
|
|
68
|
+
PNPM_CACHE_FOLDER: .pnpm-store
|
|
69
|
+
|
|
70
|
+
cache:
|
|
71
|
+
key:
|
|
72
|
+
files:
|
|
73
|
+
- pnpm-lock.yaml
|
|
74
|
+
paths:
|
|
75
|
+
- .pnpm-store
|
|
76
|
+
- node_modules
|
|
77
|
+
|
|
78
|
+
default:
|
|
79
|
+
before_script:
|
|
80
|
+
- corepack enable
|
|
81
|
+
- corepack prepare pnpm@latest --activate
|
|
82
|
+
- pnpm config set store-dir "$PNPM_CACHE_FOLDER"
|
|
83
|
+
- pnpm install --frozen-lockfile
|
|
84
|
+
|
|
85
|
+
${jobs.join('\n\n')}
|
|
86
|
+
`;
|
|
87
|
+
}
|
|
@@ -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.15.0",
|
|
4
4
|
"description": "JavaScript and TypeScript tooling for Node.js, React, Next.js, and Vitest.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"keywords": [
|
|
@@ -240,7 +240,7 @@
|
|
|
240
240
|
"ts-jest": "^29.0.0",
|
|
241
241
|
"tsup": "^8.0.0",
|
|
242
242
|
"typescript": ">=5.0.0",
|
|
243
|
-
"typescript-eslint": "
|
|
243
|
+
"typescript-eslint": "^8.0.0",
|
|
244
244
|
"vitest": ">=3.0.0"
|
|
245
245
|
},
|
|
246
246
|
"peerDependenciesMeta": {
|