@rtorcato/repo-tooling 3.8.2 → 3.8.4

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.
@@ -24,13 +24,17 @@ export async function checkNestedLanguages(dir, rootLanguage) {
24
24
  hint: 'Run doctor with `--directory <path>` to audit a nested project on its own',
25
25
  };
26
26
  }
27
+ /** Applies a `FileCheck` matcher of either form to a file's contents. */
28
+ export function matches(matcher, contents) {
29
+ return typeof matcher === 'function' ? matcher(contents) : matcher.test(contents);
30
+ }
27
31
  export async function checkFile(dir, spec) {
28
32
  for (const candidate of spec.candidates) {
29
33
  const filepath = path.join(dir, candidate);
30
34
  if (!(await fs.pathExists(filepath)))
31
35
  continue;
32
36
  const contents = await fs.readFile(filepath, 'utf-8');
33
- if (spec.matcher.test(contents)) {
37
+ if (matches(spec.matcher, contents)) {
34
38
  return {
35
39
  check: spec.check,
36
40
  status: 'ok',
@@ -42,22 +42,69 @@ export function evaluateNodeVersion(version) {
42
42
  detail: display,
43
43
  };
44
44
  }
45
+ /**
46
+ * JSONC → object, or null when it genuinely won't parse. `biome.jsonc` is a
47
+ * declared candidate and the format allows comments and trailing commas, so
48
+ * bare `JSON.parse` would reject configs Biome itself accepts.
49
+ *
50
+ * The first alternative consumes whole string literals, so a `//` or `/*`
51
+ * inside one (a `$schema` URL, most obviously) is never mistaken for a comment.
52
+ */
53
+ function parseJsonc(text) {
54
+ const withoutComments = text.replace(/("(?:\\.|[^"\\])*")|\/\/[^\n]*|\/\*[\s\S]*?\*\//g, (_, str) => str ?? '');
55
+ try {
56
+ return JSON.parse(withoutComments.replace(/,(\s*[}\]])/g, '$1'));
57
+ }
58
+ catch {
59
+ return null;
60
+ }
61
+ }
62
+ const BIOME_PRESET_REF = /@rtorcato\/(?:js|repo)-tooling\/biome/;
63
+ /**
64
+ * Both shapes of a `biome.json` this package produces (#378): the thin
65
+ * `extends` pointer `fix biome` writes, and the whole preset inlined, which is
66
+ * what `copy biome` drops — it carries no `extends` at all, so matching only
67
+ * the first left every `copy biome` repo permanently drifted with no way out.
68
+ *
69
+ * Read structurally, not as text. Accepting the inline form on the presence of
70
+ * a `$schema` URL and a `preset` key alone would pass a config that keeps both
71
+ * markers and turns the linter off — the very drift this check exists to catch
72
+ * — and the markers could even sit in a `biome.jsonc` comment. So the config is
73
+ * parsed, `linter.enabled: false` disqualifies either shape, and anything
74
+ * unparseable counts as drift rather than being waved through.
75
+ */
76
+ function matchesBiomeConfig(contents) {
77
+ const config = parseJsonc(contents);
78
+ if (!config)
79
+ return false;
80
+ const linter = config.linter;
81
+ if (linter?.enabled === false)
82
+ return false;
83
+ if (BIOME_PRESET_REF.test(JSON.stringify(config.extends ?? '')))
84
+ return true;
85
+ // The inlined preset: biome's own `$schema` plus the `linter.rules.preset`
86
+ // key the shipped `tooling/biome/biome.json` sets.
87
+ const schema = config.$schema;
88
+ return (typeof schema === 'string' &&
89
+ schema.includes('biomejs.dev/schemas/') &&
90
+ linter?.rules?.preset !== undefined);
91
+ }
45
92
  export const FILE_CHECKS = [
46
93
  {
47
94
  check: 'TypeScript',
48
95
  candidates: ['tsconfig.json'],
49
96
  expected: `extends "${PACKAGE}/typescript/*"`,
50
97
  matcher: /@rtorcato\/(?:js|repo)-tooling\/typescript\//,
51
- hint: 'Set `"extends": "@rtorcato/repo-tooling/typescript/base"` in tsconfig.json',
98
+ // `fix tsconfig`, not `copy tsconfig`: copy drops the whole preset inline,
99
+ // which carries no `extends` for the matcher above to find (#381).
100
+ hint: 'Run `npx @rtorcato/repo-tooling fix tsconfig` to scaffold',
52
101
  },
53
102
  {
54
103
  check: 'Biome',
55
104
  candidates: ['biome.json', 'biome.jsonc'],
56
- expected: `extends "${PACKAGE}/biome"`,
57
- matcher: /@rtorcato\/(?:js|repo)-tooling\/biome/,
105
+ expected: `extends "${PACKAGE}/biome" or inlines the preset, with the linter on`,
106
+ matcher: matchesBiomeConfig,
58
107
  optional: true,
59
- // `fix biome`, not `copy biome`: copy drops the whole preset inline, which
60
- // carries no `extends` for the matcher above to find (#365).
61
108
  hint: 'Run `npx @rtorcato/repo-tooling fix biome` to scaffold',
62
109
  },
63
110
  {
@@ -48,6 +48,7 @@ import { WORKSPACE_FILE, dependsOnEsbuild, ensurePnpmSettings, familyGlob, } fro
48
48
  import { generatePostcss } from '../../cli/generators/postcss.js';
49
49
  import { generateCypressConfig, generateVitestConfig } from '../../cli/generators/testing.js';
50
50
  import { generateTreeshakeCheck, inferSubpathsFromExports } from '../../cli/generators/treeshake.js';
51
+ import { generateTSConfig } from '../../cli/generators/tsconfig.js';
51
52
  import { generateTailwind } from '../../cli/generators/tailwind.js';
52
53
  import { generateTurborepo } from '../../cli/generators/turborepo.js';
53
54
  import { generateNx } from '../../cli/generators/nx.js';
@@ -70,7 +71,12 @@ export function inferProjectConfig(pkg) {
70
71
  return {
71
72
  projectName: pkg?.name ?? 'project',
72
73
  projectType,
73
- typescript: { enabled: true, config: projectType === 'nextjs-app' ? 'next' : 'base' },
74
+ typescript: {
75
+ enabled: true,
76
+ // Same mapping the react-app/nextjs-app setup presets use — a react-app on
77
+ // the base preset has no DOM libs, so `document`/`window` don't typecheck.
78
+ config: projectType === 'nextjs-app' ? 'next' : projectType === 'react-app' ? 'react' : 'base',
79
+ },
74
80
  linting: {
75
81
  tool: 'biome',
76
82
  eslintConfig: projectType === 'nextjs-app' ? 'nextjs' : 'base',
@@ -170,9 +176,18 @@ export const FIXERS = [
170
176
  appliesTo: ['TypeScript'],
171
177
  outputs: ['tsconfig.json', 'package.json (scripts)'],
172
178
  canFixDrift: true,
173
- async run({ targetDir }) {
174
- const result = await copyPreset('tsconfig', targetDir);
175
- const filesWritten = [result.target];
179
+ async run({ targetDir, pkg }) {
180
+ // Shares generateTSConfig with the setup path — copyPreset used to inline
181
+ // the whole base preset, which carries no `extends` for doctor's matcher
182
+ // and threw away the project-type settings setup emits (#381).
183
+ const config = inferProjectConfig(pkg);
184
+ // Bun leaves no package.json trace, so inferProjectConfig can't see it; a
185
+ // bunfig.toml is the signal, and without it this would strip `types: ['bun']`
186
+ // off a Bun repo by dropping it back onto the base preset.
187
+ if (await fs.pathExists(path.join(targetDir, 'bunfig.toml')))
188
+ config.bun = true;
189
+ await generateTSConfig(config, targetDir);
190
+ const filesWritten = ['tsconfig.json'];
176
191
  // Without `typecheck` the generated CI drops its typecheck job entirely
177
192
  // (#377). Shared with non-JS paths, so this is a no-op when there's no
178
193
  // package.json to add it to.
@@ -18,7 +18,7 @@
18
18
  */
19
19
  import path from 'node:path';
20
20
  import fs from 'fs-extra';
21
- import { checkFile } from '../../base/checks.js';
21
+ import { checkFile, matches } from '../../base/checks.js';
22
22
  import { PYTHON_GITIGNORE_SENTINELS } from './gitignore.js';
23
23
  import { PYTHON_HOOKS_DIR } from './git-hooks.js';
24
24
  const PYTHON_TOOL_CHECKS = [
@@ -68,7 +68,7 @@ async function checkPythonTool(dir, spec) {
68
68
  const filepath = path.join(dir, candidate);
69
69
  if (!(await fs.pathExists(filepath)))
70
70
  continue;
71
- if (spec.dedicated.matcher.test(await fs.readFile(filepath, 'utf-8'))) {
71
+ if (matches(spec.dedicated.matcher, await fs.readFile(filepath, 'utf-8'))) {
72
72
  return {
73
73
  check: spec.dedicated.check,
74
74
  status: 'ok',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rtorcato/repo-tooling",
3
- "version": "3.8.2",
3
+ "version": "3.8.4",
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": [