@williamthorsen/tsconfig 0.6.1 → 0.7.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.
@@ -1,6 +1,6 @@
1
1
  /** @noformat -- @generated. Do not edit. Compiled by rdy. */
2
2
  /* eslint-disable */
3
- export const __readyupVersion = "0.34.0";
3
+ export const __readyupVersion = "0.35.0";
4
4
 
5
5
 
6
6
  // .readyup/kits/default.ts
@@ -71,6 +71,46 @@ function toStringArray(value) {
71
71
  return value.filter((entry) => typeof entry === "string");
72
72
  }
73
73
 
74
+ // src/readiness/findEscapingPaths.ts
75
+ import { dirname, join, relative } from "node:path/posix";
76
+ var CONFIG_DIR_TEMPLATE = "${configDir}";
77
+ var FIELDS = ["exclude", "files", "include"];
78
+ var ROOTED_PATH = /^(?:\/|[A-Za-z]:)/;
79
+ function findEscapingPaths(entries) {
80
+ const judged = entries[0];
81
+ if (judged === void 0) return [];
82
+ const judgedDir = dirname(judged.path);
83
+ return FIELDS.flatMap((field) => findEscapingPathsInField(entries, field, judgedDir));
84
+ }
85
+ function findDeclaringEntry(entries, field) {
86
+ for (const entry of entries) {
87
+ const declared = entry.config[field];
88
+ if (declared) return entry;
89
+ }
90
+ return void 0;
91
+ }
92
+ function findEscapingPathsInField(entries, field, judgedDir) {
93
+ const declaring = findDeclaringEntry(entries, field);
94
+ if (declaring === void 0) return [];
95
+ const declared = declaring.config[field];
96
+ if (!Array.isArray(declared)) return [];
97
+ const declaredDir = dirname(declaring.path);
98
+ return declared.filter((value) => typeof value === "string").flatMap((value) => {
99
+ const outward = findOutwardPath(value, declaredDir, judgedDir);
100
+ return outward === void 0 ? [] : [{ declaredIn: declaring.path, field, path: outward }];
101
+ });
102
+ }
103
+ function findOutwardPath(value, declaredDir, judgedDir) {
104
+ const normalized = value.split("\\").join("/");
105
+ if (ROOTED_PATH.test(normalized)) return normalized;
106
+ const resolved = startsWithConfigDir(normalized) ? join(judgedDir, normalized.slice(CONFIG_DIR_TEMPLATE.length)) : join(declaredDir, normalized);
107
+ const fromJudged = relative(judgedDir, resolved);
108
+ return fromJudged === ".." || fromJudged.startsWith("../") ? fromJudged : void 0;
109
+ }
110
+ function startsWithConfigDir(value) {
111
+ return value.slice(0, CONFIG_DIR_TEMPLATE.length).toLowerCase() === CONFIG_DIR_TEMPLATE.toLowerCase();
112
+ }
113
+
74
114
  // src/readiness/findRedundantOptions.ts
75
115
  import { isDeepStrictEqual } from "node:util";
76
116
  function findRedundantOptions(entries, baseIndex) {
@@ -140,7 +180,7 @@ var cache = {
140
180
  chains: /* @__PURE__ */ new Map()
141
181
  };
142
182
  var default_default = defineRdyKit({
143
- description: `Adoption and alignment checks for a project consuming ${PACKAGE_NAME}`,
183
+ description: `Adoption, alignment, and hygiene checks for a project consuming ${PACKAGE_NAME}`,
144
184
  defaultSeverity: "warn",
145
185
  checklists: [
146
186
  {
@@ -177,6 +217,17 @@ var default_default = defineRdyKit({
177
217
  fix: "Raise engines.node to a major implementing the base's ES year: below it, code that typechecks fails at runtime"
178
218
  }
179
219
  ]
220
+ },
221
+ {
222
+ name: "hygiene",
223
+ checks: [
224
+ {
225
+ name: "No tsconfig's include, exclude, or files names a path outside its own directory",
226
+ skip: skipUnlessSomeTsconfigWasFound,
227
+ check: noEscapingPaths,
228
+ fix: "Declare the field named above in the config that owns the directory, listing every path it needs, since a local declaration replaces the inherited one rather than merging and discards the default exclude of node_modules, bower_components, jspm_packages, and outDir; or prefix each path in the config being extended with ${configDir}, which resolves to the consuming config's directory"
229
+ }
230
+ ]
180
231
  }
181
232
  ]
182
233
  });
@@ -193,6 +244,10 @@ function classifyTsconfig(path) {
193
244
  function dedupe(values) {
194
245
  return [...new Set(values)];
195
246
  }
247
+ function describeEscape(configPath, escaping) {
248
+ const source = escaping.declaredIn === configPath ? "" : `, from ${escaping.declaredIn}`;
249
+ return `${escaping.field}: ${escaping.path}${source}`;
250
+ }
196
251
  function everyEsYearMatchesTheBase() {
197
252
  const baseEsYear = readBaseEsYear();
198
253
  if (baseEsYear === void 0) return true;
@@ -267,6 +322,23 @@ function nodeFloorSupportsBaseEsYear() {
267
322
  const verdict = judgeNodeFloor();
268
323
  return verdict.kind === "skip" ? true : { ok: verdict.kind === "pass", detail: verdict.detail };
269
324
  }
325
+ function noEscapingPaths() {
326
+ const judged = findTsconfigs().flatMap((path) => {
327
+ const chain = readChain(path);
328
+ return chain === void 0 ? [] : [{ escapes: findEscapingPaths(chain.entries), path }];
329
+ });
330
+ const offenders = judged.filter((judgedConfig) => judgedConfig.escapes.length > 0);
331
+ const progress = {
332
+ type: "fraction",
333
+ passedCount: judged.length - offenders.length,
334
+ count: judged.length
335
+ };
336
+ if (offenders.length === 0) return { ok: true, progress };
337
+ const details = offenders.flatMap(
338
+ (offender) => offender.escapes.map((escaping) => `${offender.path} (${describeEscape(offender.path, escaping)})`)
339
+ );
340
+ return { ok: false, detail: details.join(", "), progress };
341
+ }
270
342
  function noRedundantOptions() {
271
343
  const offenders = listAdoptedTsconfigs().flatMap(
272
344
  (adoption) => findRedundantOptions(adoption.chain.entries, adoption.baseIndex).map(
@@ -307,6 +379,9 @@ function skipUnlessSomeTsconfigIsAccountable() {
307
379
  if (classified.length === 0) return "no workspace tsconfig was found";
308
380
  return classified.some((adoption) => adoption.kind !== "external-base") ? false : "every workspace tsconfig extends a base belonging to another package";
309
381
  }
382
+ function skipUnlessSomeTsconfigWasFound() {
383
+ return findTsconfigs().length === 0 ? "no workspace tsconfig was found" : false;
384
+ }
310
385
  export {
311
386
  default_default as default
312
387
  };
@@ -4,13 +4,14 @@
4
4
  {
5
5
  "checklists": [
6
6
  "adoption",
7
- "alignment"
7
+ "alignment",
8
+ "hygiene"
8
9
  ],
9
- "description": "Adoption and alignment checks for a project consuming @williamthorsen/tsconfig",
10
+ "description": "Adoption, alignment, and hygiene checks for a project consuming @williamthorsen/tsconfig",
10
11
  "esbuildVersion": "0.28.2",
11
12
  "inputs": [
12
13
  {
13
- "hash": "defbbe48",
14
+ "hash": "f4ec4ffd",
14
15
  "kind": "module",
15
16
  "path": "kits/default.ts"
16
17
  },
@@ -29,6 +30,11 @@
29
30
  "kind": "module",
30
31
  "path": "../src/readiness/es-year.ts"
31
32
  },
33
+ {
34
+ "hash": "bfbb0002",
35
+ "kind": "module",
36
+ "path": "../src/readiness/findEscapingPaths.ts"
37
+ },
32
38
  {
33
39
  "hash": "acd6ba2b",
34
40
  "kind": "module",
@@ -47,10 +53,10 @@
47
53
  ],
48
54
  "name": "default",
49
55
  "path": "kits/default.js",
50
- "readyupVersion": "0.34.0",
56
+ "readyupVersion": "0.35.0",
51
57
  "source": "kits/default.ts",
52
- "sourceHash": "defbbe48",
53
- "targetHash": "20355baf"
58
+ "sourceHash": "f4ec4ffd",
59
+ "targetHash": "2bcc6e9b"
54
60
  }
55
61
  ]
56
62
  }
package/README.md CHANGED
@@ -3,13 +3,13 @@
3
3
  Shared TypeScript base config for Node-only projects. Inlines the settings of [`@tsconfig/strictest`](https://www.npmjs.com/package/@tsconfig/strictest) and adds the Node and build options it leaves out, so a consuming repo declares only what is genuinely its own.
4
4
 
5
5
  <!-- section:release-notes -->
6
- ## Release notes — v0.6.1 (2026-08-27)
6
+ ## Release notes — v0.7.0 (2026-08-30)
7
7
 
8
- ### 🐛 Bug fixes
8
+ ### 🎉 Features
9
9
 
10
- - Restore the repo root to both readiness kits' search directories (#187)
10
+ - Report an include, exclude, or files path outside a tsconfig's directory (#197)
11
11
 
12
- Fixes an issue where the readiness kits in `@williamthorsen/tsconfig` and `@williamthorsen/eslint-config-typescript` skipped the repo root in a monorepo running readyup below 0.33.0. Each kit now supplies the root itself, so the sweep is whole under any readyup version.
12
+ Adds a `hygiene` checklist to the `@williamthorsen/tsconfig` readiness kit, reporting at `warn` a workspace tsconfig whose effective `include`, `exclude`, or `files` names a path outside the directory holding it, whether that config declared the field itself or inherited it from a base.
13
13
  <!-- /section:release-notes -->
14
14
 
15
15
  ## Installation
@@ -100,10 +100,11 @@ export default defineRdyConfig({
100
100
 
101
101
  The `readyup >=0.33.0` optional peer names the version the kit is developed and tested against. Below 0.33.0, readyup does not report the repo root among a monorepo's workspaces, so the kit supplies the root itself and sweeps every member package alongside it.
102
102
 
103
- The kit walks each workspace tsconfig's `extends` chain and reports four things:
103
+ The kit walks each workspace tsconfig's `extends` chain and reports five things:
104
104
 
105
105
  - **Adoption**: a tsconfig reaching neither this base nor a framework base. A tsconfig naming this base through a specifier that does not resolve is reported separately, as declared but not installed.
106
106
  - **Re-declaration**: an option restated with the base's own value. A key that a framework base in the same chain also declares is exempt, since restating it is how a config wins against that base.
107
+ - **Escaping paths**: an `include`, `exclude`, or `files` path resolving outside the directory that holds the tsconfig. A config that is extended hands these fields down resolved against its own directory, so a package inheriting them is governed by paths pointing outside itself; the remedy is a complete local declaration, or `${configDir}` in the config being extended.
107
108
  - **ES year**: a `target` or `lib` declaring a year other than the base's, read from the base you extend rather than from a constant compiled into the kit.
108
109
  - **Node floor**: an `engines.node` below the major that implements that ES year. This is the one failure `tsc` cannot surface on its own, which is why it alone is an error.
109
110
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@williamthorsen/tsconfig",
3
- "version": "0.6.1",
3
+ "version": "0.7.0",
4
4
  "description": "Shared TypeScript base config for Node-only projects",
5
5
  "keywords": [
6
6
  "tsconfig",
@@ -31,7 +31,7 @@
31
31
  "devDependencies": {
32
32
  "@tsconfig/strictest": "2.0.8",
33
33
  "esbuild": "0.28.2",
34
- "readyup": "0.34.0"
34
+ "readyup": "0.35.0"
35
35
  },
36
36
  "peerDependencies": {
37
37
  "readyup": ">=0.33.0"