@rtorcato/repo-tooling 3.8.4 → 3.8.5

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.
@@ -212,6 +212,10 @@ export function computeFileList(config) {
212
212
  if (config.typescript.enabled) {
213
213
  files.push('tsconfig.json', 'reset.d.ts');
214
214
  }
215
+ // Libraries get a bundle-size budget plus the `size-limit` script that runs it
216
+ // (#382). The scaffold's exports are root-only, so it's always the static form.
217
+ if (config.projectType === 'library')
218
+ files.push('.size-limit.json');
215
219
  if (config.linting.tool === 'biome' || config.linting.tool === 'both') {
216
220
  files.push(BIOME_CONFIG);
217
221
  }
@@ -8,7 +8,7 @@ import { ensurePnpmSettings, familyGlob } from './pnpm-workspace.js';
8
8
  import { generateGitConfigs } from './git.js';
9
9
  import { generateGitHubActions } from './github-actions.js';
10
10
  import { generateLintingConfigs } from './linting.js';
11
- import { generateKnipConfig, generateMiscBaseline, generateVscodeExtensions } from './misc.js';
11
+ import { generateKnipConfig, generateMiscBaseline, generateSizeLimitConfig, generateVscodeExtensions, } from './misc.js';
12
12
  import { generatePackageJson } from './package-json.js';
13
13
  import { generateReadme } from './readme.js';
14
14
  import { generateSecurityConfigs } from './security.js';
@@ -33,6 +33,12 @@ export async function generateConfigs(config, targetDir) {
33
33
  await generatePackageJson(config, targetDir);
34
34
  // Universal baseline: .editorconfig, .nvmrc, engines.node, knip.json
35
35
  await generateMiscBaseline(targetDir);
36
+ // Bundle-size budget for publishable libraries. getScripts() emits the
37
+ // matching `size-limit` script, so the budget is runnable from the first
38
+ // commit rather than an inert file implying enforcement (#382).
39
+ if (config.projectType === 'library') {
40
+ await generateSizeLimitConfig(targetDir);
41
+ }
36
42
  // Recommend the editor extensions matching the enabled tools
37
43
  await generateVscodeExtensions(config, targetDir);
38
44
  // Generate TypeScript configuration
@@ -141,6 +141,10 @@ function getScripts(config, opts = {}) {
141
141
  // profile fits the dual (cjs+esm) exports the library scaffold emits.
142
142
  if (config.projectType === 'library') {
143
143
  scripts['attw'] = 'attw --pack';
144
+ // The budget scaffolded alongside it (generateConfigs) is inert without a
145
+ // way to run it — a size budget nothing runs implies an enforcement that
146
+ // isn't there (#382).
147
+ Object.assign(scripts, SIZE_LIMIT_SCRIPTS);
144
148
  }
145
149
  // Git hooks
146
150
  if (config.gitHooks) {
@@ -169,6 +173,14 @@ function getScripts(config, opts = {}) {
169
173
  * refused to compose a chain, both because no target ever created that script
170
174
  * (#364).
171
175
  */
176
+ /**
177
+ * The one definition of the `size-limit` script, shared by `getScripts()` (setup)
178
+ * and the `size-limit` fixer, so the two paths can't drift (#382 — the divergence
179
+ * behind #371 and #377).
180
+ */
181
+ export const SIZE_LIMIT_SCRIPTS = { 'size-limit': 'size-limit' };
182
+ /** Shared by the same two paths, for the same reason. */
183
+ export const SIZE_LIMIT_VERSION = '^11.2.0';
172
184
  export async function ensureScripts(targetDir, scripts) {
173
185
  const pkgPath = path.join(targetDir, 'package.json');
174
186
  if (!(await fs.pathExists(pkgPath)))
@@ -352,8 +364,11 @@ function getDependencies(config) {
352
364
  deps['lint-staged'] = '^16.0.0';
353
365
  }
354
366
  // are-the-types-wrong — validate published type resolution for libraries.
367
+ // size-limit enforces the bundle-size budget scaffolded next to it; without
368
+ // the dependency the script dies with "command not found" (#382).
355
369
  if (config.projectType === 'library') {
356
370
  deps['@arethetypeswrong/cli'] = '^0.18.2';
371
+ deps['size-limit'] = SIZE_LIMIT_VERSION;
357
372
  }
358
373
  // Commit linting (enforcement) + commitizen (assistance)
359
374
  if (config.commitLint) {
@@ -11,7 +11,7 @@ import { BIOME_CONFIG, generateBiomeConfig, generateESLintConfig, generatePretti
11
11
  import { alignNodeVersion, ensureEnginesNode, ensurePackageManager, generateKnipConfig, generateNvmrc, generateSizeLimitConfig, generateVscodeExtensions, } from '../../cli/generators/misc.js';
12
12
  import { scriptsOf } from './ci.js';
13
13
  import { usesPnpm } from './checks.js';
14
- import { composeVerifyScriptFromPkg, ensureScripts } from '../../cli/generators/package-json.js';
14
+ import { SIZE_LIMIT_SCRIPTS, SIZE_LIMIT_VERSION, composeVerifyScriptFromPkg, ensureScripts, } from '../../cli/generators/package-json.js';
15
15
  /** Kept in step with the biome branch of getScripts() in package-json.ts. */
16
16
  const BIOME_SCRIPTS = {
17
17
  lint: 'biome lint .',
@@ -32,6 +32,24 @@ const PRETTIER_SCRIPTS = {
32
32
  const KNIP_SCRIPTS = {
33
33
  knip: 'knip',
34
34
  };
35
+ /**
36
+ * Add `size-limit` to devDependencies unless the repo already pins it (either
37
+ * dependency list). Returns true when package.json was written.
38
+ */
39
+ async function ensureSizeLimitDependency(targetDir) {
40
+ const pkgPath = path.join(targetDir, 'package.json');
41
+ if (!(await fs.pathExists(pkgPath)))
42
+ return false;
43
+ const pkg = (await fs.readJson(pkgPath));
44
+ const deps = pkg.dependencies ?? {};
45
+ const devDeps = { ...(pkg.devDependencies ?? {}) };
46
+ if (devDeps['size-limit'] || deps['size-limit'])
47
+ return false;
48
+ devDeps['size-limit'] = SIZE_LIMIT_VERSION;
49
+ pkg.devDependencies = devDeps;
50
+ await fs.writeJson(pkgPath, pkg, { spaces: 2 });
51
+ return true;
52
+ }
35
53
  /** Kept in step with the typescript branch of getScripts() in package-json.ts. */
36
54
  const TSCONFIG_SCRIPTS = {
37
55
  typecheck: 'tsc --noEmit',
@@ -555,13 +573,23 @@ export const FIXERS = [
555
573
  },
556
574
  {
557
575
  target: 'size-limit',
558
- description: 'Scaffold a size-limit budget — exports-driven .size-limit.cjs for multi-subpath libraries, else a static .size-limit.json',
576
+ description: 'Scaffold a size-limit budget — exports-driven .size-limit.cjs for multi-subpath libraries, else a static .size-limit.json — plus the `size-limit` script and devDependency',
559
577
  appliesTo: ['size-limit'],
560
- outputs: ['.size-limit.cjs', '.size-limit.json'],
578
+ outputs: ['.size-limit.cjs', '.size-limit.json', 'package.json (devDependencies + scripts)'],
579
+ // safe-merge: installs the CLI and only fills in a missing script.
580
+ riskLevel: 'safe-merge',
561
581
  canFixDrift: true,
562
582
  async run({ targetDir }) {
563
583
  const written = await generateSizeLimitConfig(targetDir);
564
- return { filesWritten: [written] };
584
+ const filesWritten = [written];
585
+ // A budget with nothing to run it is worse than no budget — the file's
586
+ // presence implies an enforcement that isn't there (#382). The script
587
+ // needs the CLI installed too, or it dies with "command not found".
588
+ const installed = await ensureSizeLimitDependency(targetDir);
589
+ const scripted = await ensureScripts(targetDir, SIZE_LIMIT_SCRIPTS);
590
+ if (installed || scripted)
591
+ filesWritten.push('package.json');
592
+ return { filesWritten };
565
593
  },
566
594
  },
567
595
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rtorcato/repo-tooling",
3
- "version": "3.8.4",
3
+ "version": "3.8.5",
4
4
  "description": "One CLI to scaffold, audit and fix your repo's whole toolchain — linting, tests, commits, releases & CI.",
5
5
  "type": "module",
6
6
  "keywords": [