@ttsc/unplugin 0.28.5 → 0.29.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.
- package/README.md +15 -11
- package/lib/api.js +10 -0
- package/lib/api.js.map +1 -1
- package/lib/api.mjs +3 -2
- package/lib/api.mjs.map +1 -1
- package/lib/bun-register.d.cts +10 -7
- package/lib/bun-register.d.mts +10 -7
- package/lib/bun-register.d.ts +10 -7
- package/lib/bun-register.js +90 -24
- package/lib/bun-register.js.map +1 -1
- package/lib/bun-register.mjs +90 -24
- package/lib/bun-register.mjs.map +1 -1
- package/lib/bun.d.cts +10 -8
- package/lib/bun.d.mts +10 -8
- package/lib/bun.d.ts +10 -8
- package/lib/bun.js +19 -17
- package/lib/bun.js.map +1 -1
- package/lib/bun.mjs +20 -18
- package/lib/bun.mjs.map +1 -1
- package/lib/core/index.d.cts +10 -8
- package/lib/core/index.d.mts +10 -8
- package/lib/core/index.d.ts +10 -8
- package/lib/core/index.js +88 -16
- package/lib/core/index.js.map +1 -1
- package/lib/core/index.mjs +81 -18
- package/lib/core/index.mjs.map +1 -1
- package/lib/core/projectDiscovery.d.cts +55 -0
- package/lib/core/projectDiscovery.d.mts +55 -0
- package/lib/core/projectDiscovery.d.ts +55 -0
- package/lib/core/projectDiscovery.js +187 -0
- package/lib/core/projectDiscovery.js.map +1 -0
- package/lib/core/projectDiscovery.mjs +182 -0
- package/lib/core/projectDiscovery.mjs.map +1 -0
- package/lib/core/sourceExtensions.d.cts +10 -0
- package/lib/core/sourceExtensions.d.mts +10 -0
- package/lib/core/sourceExtensions.d.ts +10 -0
- package/lib/core/sourceExtensions.js +38 -0
- package/lib/core/sourceExtensions.js.map +1 -0
- package/lib/core/sourceExtensions.mjs +32 -0
- package/lib/core/sourceExtensions.mjs.map +1 -0
- package/lib/core/transform.d.cts +96 -16
- package/lib/core/transform.d.mts +96 -16
- package/lib/core/transform.d.ts +96 -16
- package/lib/core/transform.js +1295 -309
- package/lib/core/transform.js.map +1 -1
- package/lib/core/transform.mjs +1290 -310
- package/lib/core/transform.mjs.map +1 -1
- package/lib/core/tsconfigPaths.d.cts +55 -3
- package/lib/core/tsconfigPaths.d.mts +55 -3
- package/lib/core/tsconfigPaths.d.ts +55 -3
- package/lib/core/tsconfigPaths.js +260 -46
- package/lib/core/tsconfigPaths.js.map +1 -1
- package/lib/core/tsconfigPaths.mjs +255 -47
- package/lib/core/tsconfigPaths.mjs.map +1 -1
- package/lib/core/viteServe.d.cts +18 -21
- package/lib/core/viteServe.d.mts +18 -21
- package/lib/core/viteServe.d.ts +18 -21
- package/lib/core/viteServe.js +88 -47
- package/lib/core/viteServe.js.map +1 -1
- package/lib/core/viteServe.mjs +89 -49
- package/lib/core/viteServe.mjs.map +1 -1
- package/lib/next.d.cts +7 -0
- package/lib/next.d.mts +7 -0
- package/lib/next.d.ts +7 -0
- package/lib/next.js +186 -54
- package/lib/next.js.map +1 -1
- package/lib/next.mjs +186 -55
- package/lib/next.mjs.map +1 -1
- package/lib/turbopack.d.cts +6 -4
- package/lib/turbopack.d.mts +6 -4
- package/lib/turbopack.d.ts +6 -4
- package/lib/turbopack.js +11 -10
- package/lib/turbopack.js.map +1 -1
- package/lib/turbopack.mjs +11 -10
- package/lib/turbopack.mjs.map +1 -1
- package/package.json +7 -4
- package/src/bun-register.ts +112 -25
- package/src/bun.ts +60 -50
- package/src/core/index.ts +114 -19
- package/src/core/projectDiscovery.ts +256 -0
- package/src/core/sourceExtensions.ts +47 -0
- package/src/core/transform.ts +1671 -361
- package/src/core/tsconfigPaths.ts +406 -46
- package/src/core/viteServe.ts +125 -74
- package/src/next.ts +230 -54
- package/src/turbopack.ts +11 -10
|
@@ -2,6 +2,8 @@ import fs from "node:fs";
|
|
|
2
2
|
import { createRequire } from "node:module";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
|
|
5
|
+
import { TYPESCRIPT_TRANSFORM_EXTENSIONS } from "./sourceExtensions";
|
|
6
|
+
|
|
5
7
|
/**
|
|
6
8
|
* Read the effective `compilerOptions.paths` of a tsconfig, following its
|
|
7
9
|
* `extends` chain, and absolutize every mapping target.
|
|
@@ -27,7 +29,8 @@ import path from "node:path";
|
|
|
27
29
|
export function readEffectiveTsconfigPaths(
|
|
28
30
|
tsconfig: string,
|
|
29
31
|
): Record<string, string[]> {
|
|
30
|
-
const
|
|
32
|
+
const resolved = path.resolve(tsconfig);
|
|
33
|
+
const declared = findDeclaredPaths(resolved, new Set());
|
|
31
34
|
if (declared === null) {
|
|
32
35
|
return {};
|
|
33
36
|
}
|
|
@@ -38,7 +41,9 @@ export function readEffectiveTsconfigPaths(
|
|
|
38
41
|
}
|
|
39
42
|
const absolute = targets
|
|
40
43
|
.filter((target): target is string => typeof target === "string")
|
|
41
|
-
.map((target) =>
|
|
44
|
+
.map((target) =>
|
|
45
|
+
absolutizePathsTarget(declared.baseDir, target, path.dirname(resolved)),
|
|
46
|
+
);
|
|
42
47
|
if (absolute.length !== 0) {
|
|
43
48
|
output[key] = absolute;
|
|
44
49
|
}
|
|
@@ -46,11 +51,59 @@ export function readEffectiveTsconfigPaths(
|
|
|
46
51
|
return output;
|
|
47
52
|
}
|
|
48
53
|
|
|
49
|
-
/** Source extensions TypeScript always admits as program inputs. */
|
|
50
|
-
const TYPESCRIPT_INPUT_EXTENSIONS = [".ts", ".tsx", ".mts", ".cts"];
|
|
51
54
|
/** Extensions admitted only when `allowJs` widens the program. */
|
|
52
55
|
const JAVASCRIPT_INPUT_EXTENSIONS = [".js", ".jsx", ".mjs", ".cjs"];
|
|
53
56
|
|
|
57
|
+
/** Compiler options that exclude exactly one resolved output directory. */
|
|
58
|
+
const OUTPUT_DIRECTORY_OPTIONS = ["outDir", "declarationDir"] as const;
|
|
59
|
+
|
|
60
|
+
/** Scalar compiler paths TypeScript substitutes from the final config owner. */
|
|
61
|
+
export const CONFIG_DIR_TEMPLATE_SCALAR_OPTIONS = [
|
|
62
|
+
"baseUrl",
|
|
63
|
+
"declarationDir",
|
|
64
|
+
"generateCpuProfile",
|
|
65
|
+
"generateTrace",
|
|
66
|
+
"outDir",
|
|
67
|
+
"outFile",
|
|
68
|
+
"rootDir",
|
|
69
|
+
"tsBuildInfoFile",
|
|
70
|
+
] as const;
|
|
71
|
+
|
|
72
|
+
/** List compiler paths TypeScript substitutes from the final config owner. */
|
|
73
|
+
export const CONFIG_DIR_TEMPLATE_LIST_OPTIONS = [
|
|
74
|
+
"rootDirs",
|
|
75
|
+
"typeRoots",
|
|
76
|
+
] as const;
|
|
77
|
+
|
|
78
|
+
/** Top-level file specifications that accept the same template. */
|
|
79
|
+
const CONFIG_DIR_TEMPLATE_FILE_SPECS = ["exclude", "files", "include"] as const;
|
|
80
|
+
|
|
81
|
+
/** One config-chain input captured for generation-state comparison. */
|
|
82
|
+
export interface ITsconfigSourceSnapshotEntry {
|
|
83
|
+
/** Exact bytes decoded as UTF-8, or `null` when a probed config is absent. */
|
|
84
|
+
contents: string | null;
|
|
85
|
+
/** Canonical absolute spelling when the input exists. */
|
|
86
|
+
path: string;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Capture the leaf config and its complete `extends` graph in one deterministic
|
|
91
|
+
* list.
|
|
92
|
+
*
|
|
93
|
+
* Transform wrappers derive configuration before the compiler reads it again.
|
|
94
|
+
* Comparing this snapshot across the compile prevents a wrapper derived from
|
|
95
|
+
* one config state from being paired with membership and output from another.
|
|
96
|
+
*/
|
|
97
|
+
export function readTsconfigSourceSnapshot(
|
|
98
|
+
tsconfig: string,
|
|
99
|
+
): ITsconfigSourceSnapshotEntry[] {
|
|
100
|
+
const output = new Map<string, string | null>();
|
|
101
|
+
collectTsconfigSourceSnapshot(path.resolve(tsconfig), new Set(), output);
|
|
102
|
+
return [...output.entries()]
|
|
103
|
+
.sort(([left], [right]) => (left < right ? -1 : left > right ? 1 : 0))
|
|
104
|
+
.map(([source, contents]) => ({ contents, path: source }));
|
|
105
|
+
}
|
|
106
|
+
|
|
54
107
|
/**
|
|
55
108
|
* What the resolved configuration says can and cannot enter the program.
|
|
56
109
|
*
|
|
@@ -63,6 +116,22 @@ const JAVASCRIPT_INPUT_EXTENSIONS = [".js", ".jsx", ".mjs", ".cjs"];
|
|
|
63
116
|
* dropped from the walk entirely (samchon/ttsc#1307).
|
|
64
117
|
*/
|
|
65
118
|
export interface ITtscProjectMembershipPolicy {
|
|
119
|
+
/**
|
|
120
|
+
* Absolute directory exclusions separated by the configuration entry that
|
|
121
|
+
* contributed them.
|
|
122
|
+
*
|
|
123
|
+
* Optional for compatibility with hosts that constructed this public policy
|
|
124
|
+
* before exclusion provenance was exposed. Without it, an overlay preserves
|
|
125
|
+
* every entry in `excludedDirectories` as an explicit exclusion because
|
|
126
|
+
* silently admitting a directory is the unsafe fallback.
|
|
127
|
+
*/
|
|
128
|
+
directoryExclusionOrigins?: Readonly<{
|
|
129
|
+
declarationDir?: string;
|
|
130
|
+
exclude: readonly string[];
|
|
131
|
+
outDir?: string;
|
|
132
|
+
/** Whether output options supply TypeScript's implicit default exclude. */
|
|
133
|
+
useImplicitOutputExclusions?: boolean;
|
|
134
|
+
}>;
|
|
66
135
|
/** Absolute directories the resolved configuration keeps out of the program. */
|
|
67
136
|
excludedDirectories: readonly string[];
|
|
68
137
|
/** Lowercased extensions a file needs to be a possible program input. */
|
|
@@ -79,6 +148,23 @@ export interface ITtscProjectMembershipPolicy {
|
|
|
79
148
|
sources: readonly string[];
|
|
80
149
|
}
|
|
81
150
|
|
|
151
|
+
/** Flatten provenance into the directory list consumed by the project walk. */
|
|
152
|
+
function flattenDirectoryExclusionOrigins(
|
|
153
|
+
origins: NonNullable<
|
|
154
|
+
ITtscProjectMembershipPolicy["directoryExclusionOrigins"]
|
|
155
|
+
>,
|
|
156
|
+
): string[] {
|
|
157
|
+
return [
|
|
158
|
+
...(origins.useImplicitOutputExclusions === false
|
|
159
|
+
? []
|
|
160
|
+
: OUTPUT_DIRECTORY_OPTIONS.flatMap((key) => {
|
|
161
|
+
const directory = origins[key];
|
|
162
|
+
return directory === undefined ? [] : [directory];
|
|
163
|
+
})),
|
|
164
|
+
...origins.exclude,
|
|
165
|
+
];
|
|
166
|
+
}
|
|
167
|
+
|
|
82
168
|
/**
|
|
83
169
|
* The policy every project falls back to when no configuration is available.
|
|
84
170
|
*
|
|
@@ -88,9 +174,13 @@ export interface ITtscProjectMembershipPolicy {
|
|
|
88
174
|
*/
|
|
89
175
|
export const PERMISSIVE_PROJECT_MEMBERSHIP_POLICY: ITtscProjectMembershipPolicy =
|
|
90
176
|
{
|
|
177
|
+
directoryExclusionOrigins: {
|
|
178
|
+
exclude: [],
|
|
179
|
+
useImplicitOutputExclusions: true,
|
|
180
|
+
},
|
|
91
181
|
excludedDirectories: [],
|
|
92
182
|
inputExtensions: [
|
|
93
|
-
...
|
|
183
|
+
...TYPESCRIPT_TRANSFORM_EXTENSIONS,
|
|
94
184
|
...JAVASCRIPT_INPUT_EXTENSIONS,
|
|
95
185
|
".json",
|
|
96
186
|
],
|
|
@@ -105,8 +195,8 @@ export const PERMISSIVE_PROJECT_MEMBERSHIP_POLICY: ITtscProjectMembershipPolicy
|
|
|
105
195
|
* program at all, so a `bundle.a1b2c3.js` emitted beside the sources is not a
|
|
106
196
|
* membership change for a project that admits no JavaScript. `outDir`,
|
|
107
197
|
* `declarationDir`, and the plain entries of `exclude` name the directories the
|
|
108
|
-
* program does not contain
|
|
109
|
-
*
|
|
198
|
+
* program does not contain. TypeScript supplies the two output directories as
|
|
199
|
+
* implicit exclusions only when no top-level `exclude` replaces that default.
|
|
110
200
|
*
|
|
111
201
|
* A glob in `exclude` is skipped rather than approximated. Failing to exclude
|
|
112
202
|
* costs a walk; excluding the wrong tree hides real sources, and this function
|
|
@@ -132,7 +222,7 @@ export function readProjectMembershipPolicy(
|
|
|
132
222
|
sources,
|
|
133
223
|
)?.value === true;
|
|
134
224
|
|
|
135
|
-
const inputExtensions = [...
|
|
225
|
+
const inputExtensions = [...TYPESCRIPT_TRANSFORM_EXTENSIONS];
|
|
136
226
|
if (flag("allowJs")) {
|
|
137
227
|
inputExtensions.push(...JAVASCRIPT_INPUT_EXTENSIONS);
|
|
138
228
|
}
|
|
@@ -140,48 +230,85 @@ export function readProjectMembershipPolicy(
|
|
|
140
230
|
inputExtensions.push(".json");
|
|
141
231
|
}
|
|
142
232
|
|
|
143
|
-
const
|
|
144
|
-
|
|
233
|
+
const directoryExclusionOrigins: {
|
|
234
|
+
declarationDir?: string;
|
|
235
|
+
exclude: string[];
|
|
236
|
+
outDir?: string;
|
|
237
|
+
useImplicitOutputExclusions: boolean;
|
|
238
|
+
} = { exclude: [], useImplicitOutputExclusions: true };
|
|
239
|
+
for (const key of OUTPUT_DIRECTORY_OPTIONS) {
|
|
145
240
|
const declared = findDeclaredValue(
|
|
146
241
|
resolved,
|
|
147
242
|
(parsed) => {
|
|
148
|
-
const
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
243
|
+
const options = (
|
|
244
|
+
parsed as { compilerOptions?: Record<string, unknown> }
|
|
245
|
+
).compilerOptions;
|
|
246
|
+
if (
|
|
247
|
+
options === undefined ||
|
|
248
|
+
!Object.prototype.hasOwnProperty.call(options, key)
|
|
249
|
+
) {
|
|
250
|
+
return undefined;
|
|
251
|
+
}
|
|
252
|
+
const value = options[key];
|
|
253
|
+
return typeof value === "string" || value === null
|
|
254
|
+
? { value }
|
|
152
255
|
: undefined;
|
|
153
256
|
},
|
|
154
257
|
new Set(),
|
|
155
258
|
sources,
|
|
156
259
|
);
|
|
157
|
-
if (declared !== null) {
|
|
158
|
-
|
|
260
|
+
if (declared !== null && declared.value.value !== null) {
|
|
261
|
+
directoryExclusionOrigins[key] = resolveConfigDirTemplatePath(
|
|
262
|
+
declared.baseDir,
|
|
263
|
+
declared.value.value,
|
|
264
|
+
path.dirname(resolved),
|
|
265
|
+
);
|
|
159
266
|
}
|
|
160
267
|
}
|
|
161
268
|
const excluded = findDeclaredValue(
|
|
162
269
|
resolved,
|
|
163
270
|
(parsed) => {
|
|
164
|
-
|
|
165
|
-
|
|
271
|
+
if (!Object.prototype.hasOwnProperty.call(parsed, "exclude")) {
|
|
272
|
+
return undefined;
|
|
273
|
+
}
|
|
274
|
+
return { value: (parsed as { exclude?: unknown }).exclude };
|
|
166
275
|
},
|
|
167
276
|
new Set(),
|
|
168
277
|
sources,
|
|
169
278
|
);
|
|
170
|
-
|
|
171
|
-
|
|
279
|
+
directoryExclusionOrigins.useImplicitOutputExclusions =
|
|
280
|
+
excluded === null || excluded.value.value === null;
|
|
281
|
+
if (excluded !== null && Array.isArray(excluded.value.value)) {
|
|
282
|
+
for (const entry of excluded.value.value) {
|
|
172
283
|
if (typeof entry !== "string" || entry.length === 0) {
|
|
173
284
|
continue;
|
|
174
285
|
}
|
|
175
286
|
// `dist/**` names exactly one directory; `**/*.spec.ts` names a set this
|
|
176
287
|
// walk cannot evaluate without a matcher, so it is left in.
|
|
177
|
-
const
|
|
288
|
+
const normalizedEntry = normalizeTypeScriptPathSeparators(entry);
|
|
289
|
+
const plain = normalizedEntry.endsWith("/**")
|
|
290
|
+
? normalizedEntry.slice(0, -3)
|
|
291
|
+
: normalizedEntry;
|
|
178
292
|
if (plain.length === 0 || /[*?]/.test(plain)) {
|
|
179
293
|
continue;
|
|
180
294
|
}
|
|
181
|
-
|
|
295
|
+
directoryExclusionOrigins.exclude.push(
|
|
296
|
+
resolveConfigDirTemplatePath(
|
|
297
|
+
excluded.baseDir,
|
|
298
|
+
plain,
|
|
299
|
+
path.dirname(resolved),
|
|
300
|
+
),
|
|
301
|
+
);
|
|
182
302
|
}
|
|
183
303
|
}
|
|
184
|
-
return {
|
|
304
|
+
return {
|
|
305
|
+
directoryExclusionOrigins,
|
|
306
|
+
excludedDirectories: flattenDirectoryExclusionOrigins(
|
|
307
|
+
directoryExclusionOrigins,
|
|
308
|
+
),
|
|
309
|
+
inputExtensions,
|
|
310
|
+
sources: [...sources],
|
|
311
|
+
};
|
|
185
312
|
}
|
|
186
313
|
|
|
187
314
|
/**
|
|
@@ -215,32 +342,196 @@ export function mergeMembershipPolicyOverlay(
|
|
|
215
342
|
applyFlag("allowJs", JAVASCRIPT_INPUT_EXTENSIONS);
|
|
216
343
|
applyFlag("resolveJsonModule", [".json"]);
|
|
217
344
|
|
|
218
|
-
const
|
|
219
|
-
|
|
345
|
+
const inheritedOrigins = policy.directoryExclusionOrigins;
|
|
346
|
+
const directoryExclusionOrigins: {
|
|
347
|
+
declarationDir?: string;
|
|
348
|
+
exclude: string[];
|
|
349
|
+
outDir?: string;
|
|
350
|
+
useImplicitOutputExclusions: boolean;
|
|
351
|
+
} = {
|
|
352
|
+
declarationDir: inheritedOrigins?.declarationDir,
|
|
353
|
+
exclude: [...(inheritedOrigins?.exclude ?? policy.excludedDirectories)],
|
|
354
|
+
outDir: inheritedOrigins?.outDir,
|
|
355
|
+
useImplicitOutputExclusions:
|
|
356
|
+
inheritedOrigins?.useImplicitOutputExclusions ?? true,
|
|
357
|
+
};
|
|
358
|
+
for (const key of OUTPUT_DIRECTORY_OPTIONS) {
|
|
220
359
|
const value = compilerOptions[key];
|
|
221
|
-
if (
|
|
222
|
-
|
|
360
|
+
if (value === null) {
|
|
361
|
+
delete directoryExclusionOrigins[key];
|
|
362
|
+
} else if (typeof value === "string") {
|
|
363
|
+
directoryExclusionOrigins[key] = resolveConfigDirTemplatePath(
|
|
364
|
+
baseDir,
|
|
365
|
+
value,
|
|
366
|
+
);
|
|
223
367
|
}
|
|
224
368
|
}
|
|
225
369
|
return {
|
|
226
|
-
|
|
370
|
+
directoryExclusionOrigins,
|
|
371
|
+
excludedDirectories: flattenDirectoryExclusionOrigins(
|
|
372
|
+
directoryExclusionOrigins,
|
|
373
|
+
),
|
|
227
374
|
inputExtensions: [...inputExtensions],
|
|
228
375
|
sources: policy.sources,
|
|
229
376
|
};
|
|
230
377
|
}
|
|
231
378
|
|
|
379
|
+
/**
|
|
380
|
+
* Materialize inherited compiler paths whose `${configDir}` owner would move
|
|
381
|
+
* when a generated wrapper becomes the final config in the chain.
|
|
382
|
+
*
|
|
383
|
+
* Only template-bearing options are returned. Ordinary inherited paths are
|
|
384
|
+
* already made absolute while TypeScript parses their declaring config, while
|
|
385
|
+
* template paths deliberately survive until the final consumer is known.
|
|
386
|
+
*/
|
|
387
|
+
export function readEffectiveTsconfigTemplateCompilerOptions(
|
|
388
|
+
tsconfig: string,
|
|
389
|
+
): Record<string, unknown> {
|
|
390
|
+
const resolved = path.resolve(tsconfig);
|
|
391
|
+
const configDir = path.dirname(resolved);
|
|
392
|
+
const output: Record<string, unknown> = {};
|
|
393
|
+
for (const key of CONFIG_DIR_TEMPLATE_SCALAR_OPTIONS) {
|
|
394
|
+
const declared = findDeclaredCompilerOption(resolved, key);
|
|
395
|
+
if (
|
|
396
|
+
declared !== null &&
|
|
397
|
+
typeof declared.value === "string" &&
|
|
398
|
+
startsWithConfigDirTemplate(declared.value)
|
|
399
|
+
) {
|
|
400
|
+
output[key] = resolveConfigDirTemplatePath(
|
|
401
|
+
declared.baseDir,
|
|
402
|
+
declared.value,
|
|
403
|
+
configDir,
|
|
404
|
+
);
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
for (const key of CONFIG_DIR_TEMPLATE_LIST_OPTIONS) {
|
|
408
|
+
const declared = findDeclaredCompilerOption(resolved, key);
|
|
409
|
+
if (
|
|
410
|
+
declared !== null &&
|
|
411
|
+
Array.isArray(declared.value) &&
|
|
412
|
+
declared.value.some(
|
|
413
|
+
(entry) =>
|
|
414
|
+
typeof entry === "string" && startsWithConfigDirTemplate(entry),
|
|
415
|
+
)
|
|
416
|
+
) {
|
|
417
|
+
output[key] = declared.value.map((entry) =>
|
|
418
|
+
typeof entry === "string"
|
|
419
|
+
? resolveConfigDirTemplatePath(declared.baseDir, entry, configDir)
|
|
420
|
+
: entry,
|
|
421
|
+
);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
const declaredPaths = findDeclaredPaths(resolved, new Set());
|
|
426
|
+
if (
|
|
427
|
+
declaredPaths !== null &&
|
|
428
|
+
Object.values(declaredPaths.paths).some(
|
|
429
|
+
(targets) =>
|
|
430
|
+
Array.isArray(targets) &&
|
|
431
|
+
targets.some(
|
|
432
|
+
(target) =>
|
|
433
|
+
typeof target === "string" && startsWithConfigDirTemplate(target),
|
|
434
|
+
),
|
|
435
|
+
)
|
|
436
|
+
) {
|
|
437
|
+
output.paths = Object.fromEntries(
|
|
438
|
+
Object.entries(declaredPaths.paths).map(([key, targets]) => [
|
|
439
|
+
key,
|
|
440
|
+
Array.isArray(targets)
|
|
441
|
+
? targets.map((target) =>
|
|
442
|
+
typeof target === "string"
|
|
443
|
+
? absolutizePathsTarget(
|
|
444
|
+
declaredPaths.baseDir,
|
|
445
|
+
target,
|
|
446
|
+
configDir,
|
|
447
|
+
)
|
|
448
|
+
: target,
|
|
449
|
+
)
|
|
450
|
+
: targets,
|
|
451
|
+
]),
|
|
452
|
+
);
|
|
453
|
+
}
|
|
454
|
+
return output;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
/**
|
|
458
|
+
* Materialize inherited file specifications whose `${configDir}` owner would
|
|
459
|
+
* otherwise move to a generated wrapper's scratch directory.
|
|
460
|
+
*/
|
|
461
|
+
export function readEffectiveTsconfigTemplateFileSpecs(
|
|
462
|
+
tsconfig: string,
|
|
463
|
+
): Record<string, unknown> {
|
|
464
|
+
const resolved = path.resolve(tsconfig);
|
|
465
|
+
const configDir = path.dirname(resolved);
|
|
466
|
+
const output: Record<string, unknown> = {};
|
|
467
|
+
for (const key of CONFIG_DIR_TEMPLATE_FILE_SPECS) {
|
|
468
|
+
const declared = findDeclaredValue(
|
|
469
|
+
resolved,
|
|
470
|
+
(parsed) =>
|
|
471
|
+
Object.prototype.hasOwnProperty.call(parsed, key)
|
|
472
|
+
? (parsed as Record<string, unknown>)[key]
|
|
473
|
+
: undefined,
|
|
474
|
+
new Set(),
|
|
475
|
+
);
|
|
476
|
+
if (
|
|
477
|
+
declared === null ||
|
|
478
|
+
!Array.isArray(declared.value) ||
|
|
479
|
+
!declared.value.some(
|
|
480
|
+
(entry) =>
|
|
481
|
+
typeof entry === "string" && startsWithConfigDirTemplate(entry),
|
|
482
|
+
)
|
|
483
|
+
) {
|
|
484
|
+
continue;
|
|
485
|
+
}
|
|
486
|
+
output[key] = declared.value.map((entry) =>
|
|
487
|
+
typeof entry === "string"
|
|
488
|
+
? absolutizePathsTarget(declared.baseDir, entry, configDir)
|
|
489
|
+
: entry,
|
|
490
|
+
);
|
|
491
|
+
}
|
|
492
|
+
return output;
|
|
493
|
+
}
|
|
494
|
+
|
|
232
495
|
/**
|
|
233
496
|
* Anchor a single `paths` target at `baseDir` unless it is already absolute,
|
|
234
497
|
* normalizing to forward slashes. The `*` wildcard survives `path.resolve` as a
|
|
235
498
|
* literal segment, so patterns like `./src/*` stay patterns.
|
|
236
499
|
*/
|
|
237
|
-
export function absolutizePathsTarget(
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
500
|
+
export function absolutizePathsTarget(
|
|
501
|
+
baseDir: string,
|
|
502
|
+
target: string,
|
|
503
|
+
configDir: string = baseDir,
|
|
504
|
+
): string {
|
|
505
|
+
const resolved = resolveConfigDirTemplatePath(baseDir, target, configDir);
|
|
241
506
|
return resolved.replace(/\\/g, "/");
|
|
242
507
|
}
|
|
243
508
|
|
|
509
|
+
/** Resolve one config path with TypeScript's leading `${configDir}` template. */
|
|
510
|
+
export function resolveConfigDirTemplatePath(
|
|
511
|
+
baseDir: string,
|
|
512
|
+
target: string,
|
|
513
|
+
configDir: string = baseDir,
|
|
514
|
+
): string {
|
|
515
|
+
const template = "${configDir}";
|
|
516
|
+
const normalized = normalizeTypeScriptPathSeparators(target);
|
|
517
|
+
return startsWithConfigDirTemplate(normalized)
|
|
518
|
+
? path.resolve(configDir, normalized.replace(template, "./"))
|
|
519
|
+
: path.resolve(baseDir, normalized);
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
/** Match the prefix predicate used by TypeScript-Go before substitution. */
|
|
523
|
+
function startsWithConfigDirTemplate(target: string): boolean {
|
|
524
|
+
const template = "${configDir}";
|
|
525
|
+
return (
|
|
526
|
+
target.slice(0, template.length).toLowerCase() === template.toLowerCase()
|
|
527
|
+
);
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
/** Match TypeScript's platform-independent treatment of config separators. */
|
|
531
|
+
function normalizeTypeScriptPathSeparators(target: string): string {
|
|
532
|
+
return target.replace(/\\/g, "/");
|
|
533
|
+
}
|
|
534
|
+
|
|
244
535
|
/**
|
|
245
536
|
* The `paths` object found while walking one tsconfig's `extends` chain,
|
|
246
537
|
* together with the directory of the config that declared it (the anchor for
|
|
@@ -251,6 +542,28 @@ interface IDeclaredPaths {
|
|
|
251
542
|
paths: Record<string, unknown>;
|
|
252
543
|
}
|
|
253
544
|
|
|
545
|
+
/** Locate one compiler option while retaining its declaring directory. */
|
|
546
|
+
function findDeclaredCompilerOption(
|
|
547
|
+
tsconfig: string,
|
|
548
|
+
key: string,
|
|
549
|
+
): { baseDir: string; value: unknown } | null {
|
|
550
|
+
const declared = findDeclaredValue(
|
|
551
|
+
tsconfig,
|
|
552
|
+
(parsed) => {
|
|
553
|
+
const options = (parsed as { compilerOptions?: Record<string, unknown> })
|
|
554
|
+
.compilerOptions;
|
|
555
|
+
return options !== undefined &&
|
|
556
|
+
Object.prototype.hasOwnProperty.call(options, key)
|
|
557
|
+
? { value: options[key] }
|
|
558
|
+
: undefined;
|
|
559
|
+
},
|
|
560
|
+
new Set(),
|
|
561
|
+
);
|
|
562
|
+
return declared === null
|
|
563
|
+
? null
|
|
564
|
+
: { baseDir: declared.baseDir, value: declared.value.value };
|
|
565
|
+
}
|
|
566
|
+
|
|
254
567
|
/**
|
|
255
568
|
* Locate the nearest `compilerOptions.paths` declaration in the `extends` chain
|
|
256
569
|
* rooted at `tsconfig`. The own config wins over its bases; within an `extends`
|
|
@@ -326,7 +639,8 @@ function findDeclaredValue<T>(
|
|
|
326
639
|
return { baseDir: path.dirname(canonical), value: own };
|
|
327
640
|
}
|
|
328
641
|
|
|
329
|
-
for (const
|
|
642
|
+
for (const rawSpecifier of extendsSpecifiers(parsed.extends).reverse()) {
|
|
643
|
+
const specifier = normalizeTypeScriptPathSeparators(rawSpecifier);
|
|
330
644
|
const base = resolveExtendsConfig(canonical, specifier);
|
|
331
645
|
if (base === null) {
|
|
332
646
|
// Record where a relative or absolute specifier *would* have resolved,
|
|
@@ -337,18 +651,11 @@ function findDeclaredValue<T>(
|
|
|
337
651
|
// keeps a policy the next run's walk already disagrees with. A bare
|
|
338
652
|
// specifier is skipped, since it has no single candidate path.
|
|
339
653
|
if (isRelativeSpecifier(specifier) || path.isAbsolute(specifier)) {
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
collect?.add(candidate);
|
|
346
|
-
// Case-sensitive, because `resolveExistingExtendsPath` is: it appends
|
|
347
|
-
// `.json` unless the spelling already ends in exactly that, so a
|
|
348
|
-
// `./base.JSON` specifier really does resolve to `base.JSON.json` on a
|
|
349
|
-
// case-sensitive filesystem, and the stamp has to know that name.
|
|
350
|
-
if (!candidate.endsWith(".json")) {
|
|
351
|
-
collect?.add(`${candidate}.json`);
|
|
654
|
+
for (const candidate of missingExtendsCandidates(
|
|
655
|
+
canonical,
|
|
656
|
+
specifier,
|
|
657
|
+
)) {
|
|
658
|
+
collect?.add(candidate);
|
|
352
659
|
}
|
|
353
660
|
}
|
|
354
661
|
continue;
|
|
@@ -361,6 +668,56 @@ function findDeclaredValue<T>(
|
|
|
361
668
|
return null;
|
|
362
669
|
}
|
|
363
670
|
|
|
671
|
+
/** Capture every config source independently of which option declares a value. */
|
|
672
|
+
function collectTsconfigSourceSnapshot(
|
|
673
|
+
tsconfig: string,
|
|
674
|
+
seen: Set<string>,
|
|
675
|
+
output: Map<string, string | null>,
|
|
676
|
+
): void {
|
|
677
|
+
const canonical = resolveRealPath(tsconfig);
|
|
678
|
+
if (seen.has(canonical)) return;
|
|
679
|
+
seen.add(canonical);
|
|
680
|
+
|
|
681
|
+
let contents: string;
|
|
682
|
+
let parsed: { extends?: unknown };
|
|
683
|
+
try {
|
|
684
|
+
contents = fs.readFileSync(canonical, "utf8");
|
|
685
|
+
output.set(canonical, contents);
|
|
686
|
+
parsed = parseJsonc(contents) as typeof parsed;
|
|
687
|
+
} catch {
|
|
688
|
+
output.set(canonical, null);
|
|
689
|
+
return;
|
|
690
|
+
}
|
|
691
|
+
if (typeof parsed !== "object" || parsed === null) return;
|
|
692
|
+
|
|
693
|
+
for (const rawSpecifier of extendsSpecifiers(parsed.extends)) {
|
|
694
|
+
const specifier = normalizeTypeScriptPathSeparators(rawSpecifier);
|
|
695
|
+
const base = resolveExtendsConfig(canonical, specifier);
|
|
696
|
+
if (base !== null) {
|
|
697
|
+
collectTsconfigSourceSnapshot(base, seen, output);
|
|
698
|
+
continue;
|
|
699
|
+
}
|
|
700
|
+
if (isRelativeSpecifier(specifier) || path.isAbsolute(specifier)) {
|
|
701
|
+
for (const candidate of missingExtendsCandidates(canonical, specifier)) {
|
|
702
|
+
if (!output.has(candidate)) output.set(candidate, null);
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
/** Every exact spelling the file resolver probes for a missing config. */
|
|
709
|
+
function missingExtendsCandidates(
|
|
710
|
+
tsconfig: string,
|
|
711
|
+
specifier: string,
|
|
712
|
+
): string[] {
|
|
713
|
+
const candidate = path.resolve(path.dirname(tsconfig), specifier);
|
|
714
|
+
// Case-sensitive, because `resolveExistingExtendsPath` appends `.json`
|
|
715
|
+
// unless the spelling already ends in exactly that suffix.
|
|
716
|
+
return candidate.endsWith(".json")
|
|
717
|
+
? [candidate]
|
|
718
|
+
: [candidate, `${candidate}.json`];
|
|
719
|
+
}
|
|
720
|
+
|
|
364
721
|
/** Normalize the `extends` field into a list of string specifiers. */
|
|
365
722
|
function extendsSpecifiers(extended: unknown): string[] {
|
|
366
723
|
if (typeof extended === "string") {
|
|
@@ -450,7 +807,10 @@ function resolvePackageManifestTsconfig(
|
|
|
450
807
|
return null;
|
|
451
808
|
}
|
|
452
809
|
return resolveExistingExtendsPath(
|
|
453
|
-
path.resolve(
|
|
810
|
+
path.resolve(
|
|
811
|
+
path.dirname(manifestPath),
|
|
812
|
+
normalizeTypeScriptPathSeparators(field),
|
|
813
|
+
),
|
|
454
814
|
);
|
|
455
815
|
}
|
|
456
816
|
|