@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
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
|
|
4
|
+
/** Filesystem facts required to discover an implicit TypeScript project. */
|
|
5
|
+
export interface TtscProjectDiscoveryFilesystem {
|
|
6
|
+
/** Override path parsing when the observed filesystem is not the host. */
|
|
7
|
+
platform?: NodeJS.Platform;
|
|
8
|
+
/** Read metadata while following links, like an ordinary config-file open. */
|
|
9
|
+
stat(
|
|
10
|
+
location: string,
|
|
11
|
+
): Pick<fs.Stats, "isFile"> & Partial<Pick<fs.Stats, "isDirectory">>;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** Directory enumeration required to discover every implicit child project. */
|
|
15
|
+
export interface TtscProjectTreeDiscoveryFilesystem extends TtscProjectDiscoveryFilesystem {
|
|
16
|
+
/** Enumerate one lexical directory and identify child directory links. */
|
|
17
|
+
readdir(
|
|
18
|
+
location: string,
|
|
19
|
+
): readonly (Pick<fs.Dirent, "isDirectory" | "name"> &
|
|
20
|
+
Partial<Pick<fs.Dirent, "isSymbolicLink">>)[];
|
|
21
|
+
/** Resolve physical directory identity for cycle-safe linked traversal. */
|
|
22
|
+
realpath?(location: string): string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** One exact file predicate consulted by nearest-project discovery. */
|
|
26
|
+
export interface TtscProjectTsconfigCandidate {
|
|
27
|
+
readonly file: string;
|
|
28
|
+
readonly fileExists: boolean;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** The selected config and the predicates that selected it. */
|
|
32
|
+
export interface TtscProjectTsconfigDiscovery {
|
|
33
|
+
readonly candidates: readonly TtscProjectTsconfigCandidate[];
|
|
34
|
+
readonly file: string | undefined;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const HOST_PROJECT_DISCOVERY_FILESYSTEM: TtscProjectDiscoveryFilesystem =
|
|
38
|
+
Object.freeze({
|
|
39
|
+
stat: fs.statSync,
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
const HOST_PROJECT_TREE_DISCOVERY_FILESYSTEM: TtscProjectTreeDiscoveryFilesystem =
|
|
43
|
+
Object.freeze({
|
|
44
|
+
readdir: (location: string) =>
|
|
45
|
+
fs.readdirSync(location, { withFileTypes: true }),
|
|
46
|
+
realpath: fs.realpathSync.native,
|
|
47
|
+
stat: fs.statSync,
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
/** Directories deliberately outside the shared lexical project walk. */
|
|
51
|
+
export function isIgnoredProjectDirectory(name: string): boolean {
|
|
52
|
+
// The residue of what used to be a fifteen-name list, kept to the VCS store,
|
|
53
|
+
// the package manager's tree, and ttsc's own plugin cache. Everything else
|
|
54
|
+
// is decided by the resolved project policy rather than a directory-name
|
|
55
|
+
// guess (samchon/ttsc#1307).
|
|
56
|
+
return name === ".git" || name === ".ttsc" || name === "node_modules";
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Find the nearest ancestor `tsconfig.json` that is proven to be a file.
|
|
61
|
+
*
|
|
62
|
+
* A directory, broken link, permission failure, or any other unprovable
|
|
63
|
+
* candidate cannot terminate the walk. `stat` deliberately follows links, so a
|
|
64
|
+
* link to a regular file retains its lexical config spelling.
|
|
65
|
+
*/
|
|
66
|
+
export function findNearestProjectTsconfig(
|
|
67
|
+
startDirectory: string,
|
|
68
|
+
filesystem: TtscProjectDiscoveryFilesystem = HOST_PROJECT_DISCOVERY_FILESYSTEM,
|
|
69
|
+
): string | undefined {
|
|
70
|
+
return findNearestProjectTsconfigImpl(startDirectory, filesystem);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Find the nearest config and retain the exact predicate observations used to
|
|
75
|
+
* select it. A cache host must not rediscover these candidates later: a file
|
|
76
|
+
* can disappear only for selection and return before that second observation.
|
|
77
|
+
*/
|
|
78
|
+
export function discoverNearestProjectTsconfig(
|
|
79
|
+
startDirectory: string,
|
|
80
|
+
filesystem: TtscProjectDiscoveryFilesystem = HOST_PROJECT_DISCOVERY_FILESYSTEM,
|
|
81
|
+
): TtscProjectTsconfigDiscovery {
|
|
82
|
+
const candidates: TtscProjectTsconfigCandidate[] = [];
|
|
83
|
+
return {
|
|
84
|
+
candidates,
|
|
85
|
+
file: findNearestProjectTsconfigImpl(
|
|
86
|
+
startDirectory,
|
|
87
|
+
filesystem,
|
|
88
|
+
candidates,
|
|
89
|
+
),
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** Shared walk with optional observation retention for cache hosts. */
|
|
94
|
+
function findNearestProjectTsconfigImpl(
|
|
95
|
+
startDirectory: string,
|
|
96
|
+
filesystem: TtscProjectDiscoveryFilesystem,
|
|
97
|
+
candidates?: TtscProjectTsconfigCandidate[],
|
|
98
|
+
): string | undefined {
|
|
99
|
+
const paths =
|
|
100
|
+
filesystem.platform === undefined
|
|
101
|
+
? path
|
|
102
|
+
: filesystem.platform === "win32"
|
|
103
|
+
? path.win32
|
|
104
|
+
: path.posix;
|
|
105
|
+
let current = paths.resolve(startDirectory);
|
|
106
|
+
while (true) {
|
|
107
|
+
const candidate = paths.join(current, "tsconfig.json");
|
|
108
|
+
let fileExists = false;
|
|
109
|
+
try {
|
|
110
|
+
fileExists = filesystem.stat(candidate).isFile();
|
|
111
|
+
} catch {
|
|
112
|
+
// An implicit candidate is selectable only when its file kind is proven.
|
|
113
|
+
}
|
|
114
|
+
candidates?.push({ file: candidate, fileExists });
|
|
115
|
+
if (fileExists) {
|
|
116
|
+
return candidate;
|
|
117
|
+
}
|
|
118
|
+
const parent = paths.dirname(current);
|
|
119
|
+
if (parent === current) {
|
|
120
|
+
return undefined;
|
|
121
|
+
}
|
|
122
|
+
current = parent;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Find every regular `tsconfig.json` below one project root.
|
|
128
|
+
*
|
|
129
|
+
* The traversal retains lexical project spellings while following child
|
|
130
|
+
* directory links and junctions. A physical ancestor set cuts cycles without
|
|
131
|
+
* collapsing two independent aliases of the same project. An incomplete
|
|
132
|
+
* traversal is reported rather than returned as a complete project map, so a
|
|
133
|
+
* cache-key caller can refuse reuse.
|
|
134
|
+
*/
|
|
135
|
+
export function findProjectTsconfigs(
|
|
136
|
+
root: string,
|
|
137
|
+
filesystem: TtscProjectTreeDiscoveryFilesystem = HOST_PROJECT_TREE_DISCOVERY_FILESYSTEM,
|
|
138
|
+
): { candidates: string[]; complete: boolean; files: string[] } {
|
|
139
|
+
const paths =
|
|
140
|
+
filesystem.platform === undefined
|
|
141
|
+
? path
|
|
142
|
+
: filesystem.platform === "win32"
|
|
143
|
+
? path.win32
|
|
144
|
+
: path.posix;
|
|
145
|
+
type PendingDirectory = {
|
|
146
|
+
ancestors: ReadonlySet<string>;
|
|
147
|
+
directory: string;
|
|
148
|
+
physicalAncestorsComplete: boolean;
|
|
149
|
+
};
|
|
150
|
+
const rootDirectory = paths.resolve(root);
|
|
151
|
+
const rootIdentity = projectDirectoryIdentity(
|
|
152
|
+
rootDirectory,
|
|
153
|
+
filesystem,
|
|
154
|
+
paths,
|
|
155
|
+
);
|
|
156
|
+
const pending: PendingDirectory[] = [
|
|
157
|
+
{
|
|
158
|
+
ancestors: new Set([
|
|
159
|
+
rootIdentity ??
|
|
160
|
+
canonicalProjectPath(rootDirectory, filesystem.platform),
|
|
161
|
+
]),
|
|
162
|
+
directory: rootDirectory,
|
|
163
|
+
physicalAncestorsComplete:
|
|
164
|
+
filesystem.realpath === undefined || rootIdentity !== undefined,
|
|
165
|
+
},
|
|
166
|
+
];
|
|
167
|
+
const candidates: string[] = [];
|
|
168
|
+
const files: string[] = [];
|
|
169
|
+
let complete =
|
|
170
|
+
filesystem.realpath === undefined || rootIdentity !== undefined;
|
|
171
|
+
while (pending.length !== 0) {
|
|
172
|
+
const current = pending.pop()!;
|
|
173
|
+
const directory = current.directory;
|
|
174
|
+
let entries: readonly (Pick<fs.Dirent, "isDirectory" | "name"> &
|
|
175
|
+
Partial<Pick<fs.Dirent, "isSymbolicLink">>)[];
|
|
176
|
+
try {
|
|
177
|
+
entries = filesystem.readdir(directory);
|
|
178
|
+
} catch {
|
|
179
|
+
complete = false;
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
182
|
+
for (const entry of entries) {
|
|
183
|
+
if (isIgnoredProjectDirectory(entry.name)) continue;
|
|
184
|
+
const child = paths.join(directory, entry.name);
|
|
185
|
+
const linked = entry.isSymbolicLink?.() === true;
|
|
186
|
+
let directoryEntry = entry.isDirectory();
|
|
187
|
+
if (!directoryEntry && linked) {
|
|
188
|
+
try {
|
|
189
|
+
directoryEntry = filesystem.stat(child).isDirectory?.() === true;
|
|
190
|
+
} catch (error) {
|
|
191
|
+
const code = (error as NodeJS.ErrnoException).code;
|
|
192
|
+
if (code !== "ENOENT" && code !== "ENOTDIR") complete = false;
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
if (!directoryEntry) continue;
|
|
197
|
+
const identity = projectDirectoryIdentity(child, filesystem, paths);
|
|
198
|
+
const physicalAncestorsComplete =
|
|
199
|
+
current.physicalAncestorsComplete &&
|
|
200
|
+
(filesystem.realpath === undefined || identity !== undefined);
|
|
201
|
+
if (filesystem.realpath !== undefined && identity === undefined) {
|
|
202
|
+
complete = false;
|
|
203
|
+
}
|
|
204
|
+
if (linked && (identity === undefined || !physicalAncestorsComplete)) {
|
|
205
|
+
complete = false;
|
|
206
|
+
continue;
|
|
207
|
+
}
|
|
208
|
+
const stableIdentity =
|
|
209
|
+
identity ?? canonicalProjectPath(child, filesystem.platform);
|
|
210
|
+
if (current.ancestors.has(stableIdentity)) continue;
|
|
211
|
+
pending.push({
|
|
212
|
+
ancestors: new Set([...current.ancestors, stableIdentity]),
|
|
213
|
+
directory: child,
|
|
214
|
+
physicalAncestorsComplete,
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
const candidate = paths.join(directory, "tsconfig.json");
|
|
218
|
+
candidates.push(candidate);
|
|
219
|
+
try {
|
|
220
|
+
if (filesystem.stat(candidate).isFile()) {
|
|
221
|
+
files.push(candidate);
|
|
222
|
+
}
|
|
223
|
+
} catch (error) {
|
|
224
|
+
const code = (error as NodeJS.ErrnoException).code;
|
|
225
|
+
if (code !== "ENOENT" && code !== "ENOTDIR") {
|
|
226
|
+
complete = false;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
candidates.sort();
|
|
231
|
+
files.sort();
|
|
232
|
+
return { candidates, complete, files };
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function projectDirectoryIdentity(
|
|
236
|
+
directory: string,
|
|
237
|
+
filesystem: TtscProjectTreeDiscoveryFilesystem,
|
|
238
|
+
paths: typeof path.posix | typeof path.win32,
|
|
239
|
+
): string | undefined {
|
|
240
|
+
if (filesystem.realpath === undefined) return undefined;
|
|
241
|
+
try {
|
|
242
|
+
return canonicalProjectPath(
|
|
243
|
+
paths.resolve(filesystem.realpath(directory)),
|
|
244
|
+
filesystem.platform,
|
|
245
|
+
);
|
|
246
|
+
} catch {
|
|
247
|
+
return undefined;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function canonicalProjectPath(
|
|
252
|
+
location: string,
|
|
253
|
+
platform: NodeJS.Platform | undefined,
|
|
254
|
+
): string {
|
|
255
|
+
return platform === "win32" ? location.toLowerCase() : location;
|
|
256
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The complete TypeScript source-extension contract shared by every adapter.
|
|
3
|
+
*
|
|
4
|
+
* Keep the loader beside the extension so filters, Bun's parser selection,
|
|
5
|
+
* Turbopack registration, and project discovery cannot drift independently.
|
|
6
|
+
*/
|
|
7
|
+
const TYPESCRIPT_TRANSFORM_SOURCES = [
|
|
8
|
+
{ extension: ".ts", bunLoader: "ts" },
|
|
9
|
+
{ extension: ".tsx", bunLoader: "tsx" },
|
|
10
|
+
{ extension: ".mts", bunLoader: "ts" },
|
|
11
|
+
{ extension: ".cts", bunLoader: "ts" },
|
|
12
|
+
] as const;
|
|
13
|
+
|
|
14
|
+
/** Every source extension accepted by the ttsc transform. */
|
|
15
|
+
export const TYPESCRIPT_TRANSFORM_EXTENSIONS: readonly string[] =
|
|
16
|
+
TYPESCRIPT_TRANSFORM_SOURCES.map(({ extension }) => extension);
|
|
17
|
+
|
|
18
|
+
/** The exact automatic and documented Turbopack rule set. */
|
|
19
|
+
export const TYPESCRIPT_TURBOPACK_RULE_GLOBS: readonly string[] =
|
|
20
|
+
TYPESCRIPT_TRANSFORM_SOURCES.map(({ extension }) => `*${extension}`);
|
|
21
|
+
|
|
22
|
+
const extensionAlternation =
|
|
23
|
+
TYPESCRIPT_TRANSFORM_EXTENSIONS.map(escapeRegExp).join("|");
|
|
24
|
+
|
|
25
|
+
/** Matches exactly the TypeScript source extensions the transform accepts. */
|
|
26
|
+
export const typescriptTransformSourcePattern = new RegExp(
|
|
27
|
+
`(?:${extensionAlternation})$`,
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
/** Bun's registration filter, additionally excluding virtual NUL ids. */
|
|
31
|
+
export const bunTypeScriptTransformSourcePattern = new RegExp(
|
|
32
|
+
`^[^\\x00]*(?:${extensionAlternation})$`,
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
/** Select Bun's parser for an exact TypeScript transform source. */
|
|
36
|
+
export function typescriptTransformBunLoader(
|
|
37
|
+
filePath: string,
|
|
38
|
+
): "ts" | "tsx" | undefined {
|
|
39
|
+
return TYPESCRIPT_TRANSFORM_SOURCES.find(({ extension }) =>
|
|
40
|
+
filePath.endsWith(extension),
|
|
41
|
+
)?.bunLoader;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Escape a literal extension for a generated regular expression. */
|
|
45
|
+
function escapeRegExp(value: string): string {
|
|
46
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
47
|
+
}
|