@rtorcato/js-tooling 2.12.0 → 2.14.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.
|
@@ -661,7 +661,7 @@ async function checkGitLabCI(dir) {
|
|
|
661
661
|
check: 'GitLab CI',
|
|
662
662
|
status: 'optional-missing',
|
|
663
663
|
detail: 'no .gitlab-ci.yml',
|
|
664
|
-
hint: '
|
|
664
|
+
hint: 'Run `npx @rtorcato/js-tooling fix gitlab-ci` to scaffold a starter GitLab pipeline',
|
|
665
665
|
};
|
|
666
666
|
}
|
|
667
667
|
export async function runDoctor(dir) {
|
package/dist/cli/commands/fix.js
CHANGED
|
@@ -5,8 +5,10 @@ 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';
|
|
11
|
+
import { generateConfigs } from '../generators/index.js';
|
|
10
12
|
import { composeVerifyScriptFromPkg } from '../generators/package-json.js';
|
|
11
13
|
import { generateCodeQLWorkflow, generateDependabotConfig } from '../generators/security.js';
|
|
12
14
|
import { generateVitestConfig } from '../generators/testing.js';
|
|
@@ -15,6 +17,7 @@ import { copyPreset } from '../utils/copy-preset.js';
|
|
|
15
17
|
import { LOCKFILE_NAME, readLockfile, updateLockfileConfig, writeLockfile, } from '../utils/lockfile.js';
|
|
16
18
|
import { runDoctor } from './doctor.js';
|
|
17
19
|
import { declinedInLock, lockfilePatchForTarget } from './fix-targets.js';
|
|
20
|
+
import { computeFileList } from './setup-presets.js';
|
|
18
21
|
function inferProjectConfig(pkg) {
|
|
19
22
|
const deps = {
|
|
20
23
|
...(pkg?.dependencies ?? {}),
|
|
@@ -241,6 +244,17 @@ const FIXERS = [
|
|
|
241
244
|
return { filesWritten: [written] };
|
|
242
245
|
},
|
|
243
246
|
},
|
|
247
|
+
{
|
|
248
|
+
target: 'gitlab-ci',
|
|
249
|
+
description: 'Scaffold .gitlab-ci.yml (lint/typecheck/test/build mirrored from GitHub Actions)',
|
|
250
|
+
appliesTo: ['GitLab CI'],
|
|
251
|
+
outputs: ['.gitlab-ci.yml'],
|
|
252
|
+
canFixDrift: true,
|
|
253
|
+
async run({ targetDir, pkg }) {
|
|
254
|
+
const written = await generateGitLabCI(inferProjectConfig(pkg), targetDir);
|
|
255
|
+
return { filesWritten: [written] };
|
|
256
|
+
},
|
|
257
|
+
},
|
|
244
258
|
{
|
|
245
259
|
target: 'editorconfig',
|
|
246
260
|
description: 'Scaffold .editorconfig (UTF-8, LF, tab indent)',
|
|
@@ -479,6 +493,60 @@ export async function fixCommand(target, options = {}) {
|
|
|
479
493
|
console.log();
|
|
480
494
|
return;
|
|
481
495
|
}
|
|
496
|
+
if (options.resync) {
|
|
497
|
+
if (target) {
|
|
498
|
+
console.error(chalk.red('\n❌ --resync cannot be combined with a [target] argument\n'));
|
|
499
|
+
process.exit(1);
|
|
500
|
+
}
|
|
501
|
+
const resyncLock = await readLockfile(targetDir);
|
|
502
|
+
if (!resyncLock) {
|
|
503
|
+
if (json) {
|
|
504
|
+
console.log(JSON.stringify({ directory: targetDir, error: 'no-lockfile', hint: 'run `fix lockfile` first' }, null, 2));
|
|
505
|
+
}
|
|
506
|
+
else {
|
|
507
|
+
console.error(chalk.red(`\n❌ No ${LOCKFILE_NAME} found — run \`fix lockfile\` first to record choices\n`));
|
|
508
|
+
}
|
|
509
|
+
process.exit(1);
|
|
510
|
+
}
|
|
511
|
+
const files = computeFileList(resyncLock.config);
|
|
512
|
+
if (!silent) {
|
|
513
|
+
console.log(chalk.cyan(`\n🔄 Resync from ${LOCKFILE_NAME} (${files.length} files in scope)\n`));
|
|
514
|
+
}
|
|
515
|
+
if (dryRun) {
|
|
516
|
+
if (json) {
|
|
517
|
+
console.log(JSON.stringify({ directory: targetDir, mode: 'resync', dryRun: true, files }, null, 2));
|
|
518
|
+
}
|
|
519
|
+
else {
|
|
520
|
+
for (const f of files)
|
|
521
|
+
console.log(chalk.cyan(` [dry-run] would write: ${f}`));
|
|
522
|
+
console.log();
|
|
523
|
+
}
|
|
524
|
+
return;
|
|
525
|
+
}
|
|
526
|
+
if (!assumeYes) {
|
|
527
|
+
const { confirm } = await inquirer.prompt([
|
|
528
|
+
{
|
|
529
|
+
type: 'confirm',
|
|
530
|
+
name: 'confirm',
|
|
531
|
+
message: `Re-scaffold ${files.length} file(s) from ${LOCKFILE_NAME}? Generators preserve existing customizations where possible, but README.md will be rewritten.`,
|
|
532
|
+
default: false,
|
|
533
|
+
},
|
|
534
|
+
]);
|
|
535
|
+
if (!confirm) {
|
|
536
|
+
console.log(chalk.gray(' skipped\n'));
|
|
537
|
+
return;
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
await generateConfigs(resyncLock.config, targetDir);
|
|
541
|
+
await writeLockfile(targetDir, resyncLock.config);
|
|
542
|
+
if (json) {
|
|
543
|
+
console.log(JSON.stringify({ directory: targetDir, mode: 'resync', dryRun: false, files }, null, 2));
|
|
544
|
+
}
|
|
545
|
+
else {
|
|
546
|
+
console.log(chalk.green(` ✅ resynced ${files.length} file(s)\n`));
|
|
547
|
+
}
|
|
548
|
+
return;
|
|
549
|
+
}
|
|
482
550
|
const pkg = await readPackageJson(targetDir);
|
|
483
551
|
const lock = await readLockfile(targetDir);
|
|
484
552
|
const results = await runDoctor(targetDir);
|
|
@@ -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
|
+
}
|
package/dist/cli/index.js
CHANGED
|
@@ -222,12 +222,14 @@ program
|
|
|
222
222
|
.option('--dry-run', 'Print what would change without writing files')
|
|
223
223
|
.option('--json', 'Emit machine-readable JSON output (implies --yes)')
|
|
224
224
|
.option('--list', 'List all registered fix targets and exit')
|
|
225
|
+
.option('--resync', 'Re-scaffold every file recorded in .js-tooling.json')
|
|
225
226
|
.action((target, options) => fixCommand(target, {
|
|
226
227
|
directory: options.directory,
|
|
227
228
|
yes: options.yes,
|
|
228
229
|
dryRun: options.dryRun,
|
|
229
230
|
json: options.json,
|
|
230
231
|
list: options.list,
|
|
232
|
+
resync: options.resync,
|
|
231
233
|
}));
|
|
232
234
|
program.hook('preAction', async (_, actionCommand) => {
|
|
233
235
|
const name = actionCommand.name();
|