tsrx-typegen 0.2.2 → 0.3.1

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.
@@ -0,0 +1,394 @@
1
+ import { spawnSync } from "node:child_process";
2
+ import { mkdirSync, mkdtempSync, readFileSync, readdirSync, rmSync, statSync } from "node:fs";
3
+ import { tmpdir } from "node:os";
4
+ import { dirname, extname, isAbsolute, join, relative, resolve, sep } from "node:path";
5
+ //#region src/pack-check.ts
6
+ var moduleSpecifierNodes = (ts, sourceFile) => {
7
+ const nodes = [];
8
+ const visit = (node) => {
9
+ if ((ts.isImportDeclaration(node) || ts.isExportDeclaration(node)) && node.moduleSpecifier && ts.isStringLiteralLike(node.moduleSpecifier)) nodes.push(node.moduleSpecifier);
10
+ else if (ts.isImportTypeNode(node)) {
11
+ const argument = node.argument;
12
+ if (ts.isLiteralTypeNode(argument) && ts.isStringLiteralLike(argument.literal)) nodes.push(argument.literal);
13
+ } else if (ts.isExternalModuleReference(node) && node.expression && ts.isStringLiteral(node.expression)) nodes.push(node.expression);
14
+ ts.forEachChild(node, visit);
15
+ };
16
+ visit(sourceFile);
17
+ return nodes;
18
+ };
19
+ var pathInside = (root, file) => {
20
+ const rel = relative(root, file);
21
+ return rel === "" || rel !== ".." && !rel.startsWith(`..${sep}`) && !isAbsolute(rel);
22
+ };
23
+ var stringsIn = (value) => {
24
+ if (typeof value === "string") return [value];
25
+ if (!value || typeof value !== "object") return [];
26
+ return Object.values(value).flatMap(stringsIn);
27
+ };
28
+ var codeTarget = (target) => (!extname(target) || /\.(?:[cm]?[jt]sx?|tsrx)$/.test(target)) && !/\.d\.[cm]?ts$/.test(target);
29
+ function exportBranches(exportsMap, fallbackTypes = []) {
30
+ const branches = [];
31
+ for (const [subpath, mapping] of Object.entries(exportsMap ?? {})) {
32
+ const visit = (value, inheritedTypes) => {
33
+ if (typeof value === "string") {
34
+ if (codeTarget(value)) branches.push({
35
+ subpath,
36
+ runtime: value,
37
+ types: inheritedTypes.length ? inheritedTypes : fallbackTypes
38
+ });
39
+ return;
40
+ }
41
+ if (!value || typeof value !== "object" || Array.isArray(value)) return;
42
+ let types = inheritedTypes;
43
+ for (const [condition, child] of Object.entries(value)) if (condition === "types") {
44
+ if (!types.length) types = stringsIn(child);
45
+ } else visit(child, types);
46
+ };
47
+ visit(mapping, []);
48
+ }
49
+ return branches;
50
+ }
51
+ function packageFiles(root) {
52
+ const files = [];
53
+ const visit = (dir) => {
54
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
55
+ if (entry.name === "node_modules") continue;
56
+ const file = join(dir, entry.name);
57
+ if (entry.isDirectory()) visit(file);
58
+ else files.push(file);
59
+ }
60
+ };
61
+ visit(root);
62
+ return files;
63
+ }
64
+ function expandTarget(target, files, packageRoot) {
65
+ const extensionless = !extname(target);
66
+ if (!target.includes("*") && !extensionless) return [{
67
+ path: target,
68
+ capture: void 0
69
+ }];
70
+ if ((target.match(/\*/g) ?? []).length > 1) throw new Error(`packed export target uses more than one wildcard: ${target}`);
71
+ if (!target.startsWith("./")) throw new Error(`packed export target must be package-relative: ${target}`);
72
+ const pattern = target.slice(2);
73
+ const marker = pattern.indexOf("*");
74
+ const prefix = marker === -1 ? pattern : pattern.slice(0, marker);
75
+ const suffix = marker === -1 ? "" : pattern.slice(marker + 1);
76
+ const matches = files.flatMap((file) => {
77
+ const path = relative(packageRoot, file).replaceAll("\\", "/");
78
+ if (!codeTarget(path) || extensionless && !extname(path)) return [];
79
+ const matchedPath = extensionless ? path.slice(0, -extname(path).length) : path;
80
+ if (!matchedPath.startsWith(prefix) || !matchedPath.endsWith(suffix) || matchedPath.length < prefix.length + suffix.length || marker === -1 && matchedPath !== pattern) return [];
81
+ const capture = marker === -1 ? void 0 : matchedPath.slice(prefix.length, matchedPath.length - suffix.length);
82
+ return [{
83
+ path: `./${path}`,
84
+ capture
85
+ }];
86
+ });
87
+ if (!matches.length) throw new Error(`packed export target matches no files: ${target}`);
88
+ return matches;
89
+ }
90
+ function packedPath(packageRoot, target) {
91
+ if (typeof target !== "string" || !target.startsWith("./") || target.includes("*")) throw new Error(`packed export target is not a concrete package-relative path: ${target}`);
92
+ const file = resolve(packageRoot, target);
93
+ if (!pathInside(packageRoot, file)) throw new Error(`packed export target escapes the package: ${target}`);
94
+ try {
95
+ if (statSync(file).isFile()) return file;
96
+ } catch {}
97
+ throw new Error(`packed export target is missing from the tarball: ${target}`);
98
+ }
99
+ function sourceFile(ts, file) {
100
+ const extension = extname(file).toLowerCase();
101
+ const scriptKind = extension === ".tsx" || extension === ".tsrx" ? ts.ScriptKind.TSX : extension === ".jsx" ? ts.ScriptKind.JSX : ts.ScriptKind.TS;
102
+ return ts.createSourceFile(file, readFileSync(file, "utf8"), ts.ScriptTarget.Latest, true, scriptKind);
103
+ }
104
+ function localSourceModule(file, specifier) {
105
+ if (!specifier.startsWith(".")) return null;
106
+ const target = resolve(dirname(file), specifier);
107
+ const extension = extname(target);
108
+ const candidates = [target];
109
+ if (extension === ".js" || extension === ".mjs" || extension === ".cjs") {
110
+ const stem = target.slice(0, -extension.length);
111
+ candidates.push(`${stem}.ts`, `${stem}.tsx`, `${stem}.tsrx`, `${stem}.d.ts`);
112
+ } else if (![
113
+ ".ts",
114
+ ".tsx",
115
+ ".tsrx"
116
+ ].includes(extension)) {
117
+ candidates.push(`${target}.ts`, `${target}.tsx`, `${target}.tsrx`, `${target}.d.ts`);
118
+ candidates.push(join(target, "index.ts"), join(target, "index.tsx"), join(target, "index.tsrx"), join(target, "index.d.ts"));
119
+ }
120
+ for (const candidate of candidates) try {
121
+ if (statSync(candidate).isFile()) return candidate;
122
+ } catch {}
123
+ return null;
124
+ }
125
+ function staticRuntimeExports(ts, file, seen = /* @__PURE__ */ new Set()) {
126
+ const absolute = resolve(file);
127
+ if (seen.has(absolute)) return /* @__PURE__ */ new Set();
128
+ seen.add(absolute);
129
+ const source = sourceFile(ts, absolute);
130
+ const names = /* @__PURE__ */ new Set();
131
+ const addBindingName = (name) => {
132
+ if (ts.isIdentifier(name)) names.add(name.text);
133
+ else if (ts.isObjectBindingPattern(name) || ts.isArrayBindingPattern(name)) {
134
+ for (const element of name.elements) if (element && ts.isBindingElement(element)) addBindingName(element.name);
135
+ }
136
+ };
137
+ for (const statement of source.statements) {
138
+ const modifiers = ts.canHaveModifiers(statement) ? ts.getModifiers(statement) ?? [] : [];
139
+ const exported = modifiers.some((modifier) => modifier.kind === ts.SyntaxKind.ExportKeyword);
140
+ if (modifiers.some((modifier) => modifier.kind === ts.SyntaxKind.DefaultKeyword)) names.add("default");
141
+ if (exported) {
142
+ if (ts.isFunctionDeclaration(statement) || ts.isClassDeclaration(statement) || ts.isEnumDeclaration(statement) || ts.isModuleDeclaration(statement)) {
143
+ if (statement.name) names.add(statement.name.text);
144
+ }
145
+ if (ts.isVariableStatement(statement)) for (const declaration of statement.declarationList.declarations) addBindingName(declaration.name);
146
+ }
147
+ if (ts.isExportDeclaration(statement) && !statement.isTypeOnly) {
148
+ if (!statement.exportClause && statement.moduleSpecifier && ts.isStringLiteralLike(statement.moduleSpecifier)) {
149
+ const target = localSourceModule(absolute, statement.moduleSpecifier.text);
150
+ if (target) {
151
+ for (const name of staticRuntimeExports(ts, target, seen)) if (name !== "default") names.add(name);
152
+ }
153
+ } else if (statement.exportClause && ts.isNamedExports(statement.exportClause)) {
154
+ for (const element of statement.exportClause.elements) if (!element.isTypeOnly) names.add(element.name.text);
155
+ } else if (statement.exportClause && ts.isNamespaceExport(statement.exportClause)) names.add(statement.exportClause.name.text);
156
+ }
157
+ if (ts.isExportAssignment(statement)) names.add("default");
158
+ }
159
+ return names;
160
+ }
161
+ function compilerExports(ts, program, file, cache = /* @__PURE__ */ new Map(), pending = /* @__PURE__ */ new Set()) {
162
+ const absolute = resolve(file);
163
+ if (cache.has(absolute)) return cache.get(absolute);
164
+ if (pending.has(absolute)) return {
165
+ all: /* @__PURE__ */ new Set(),
166
+ values: /* @__PURE__ */ new Set()
167
+ };
168
+ const source = program.getSourceFile(file);
169
+ if (!source) return;
170
+ const checker = program.getTypeChecker();
171
+ const module = checker.getSymbolAtLocation(source);
172
+ if (!module) return;
173
+ const all = /* @__PURE__ */ new Set();
174
+ const values = /* @__PURE__ */ new Set();
175
+ for (const symbol of checker.getExportsOfModule(module)) {
176
+ all.add(symbol.name);
177
+ let resolved = symbol;
178
+ if (symbol.flags & ts.SymbolFlags.Alias) try {
179
+ resolved = checker.getAliasedSymbol(symbol);
180
+ } catch {}
181
+ if (resolved.flags & ts.SymbolFlags.Value) values.add(symbol.name);
182
+ }
183
+ const typeOnly = /* @__PURE__ */ new Set();
184
+ for (const statement of source.statements) {
185
+ if (!ts.isExportDeclaration(statement)) continue;
186
+ if (statement.exportClause && ts.isNamedExports(statement.exportClause)) {
187
+ for (const element of statement.exportClause.elements) if (statement.isTypeOnly || element.isTypeOnly) typeOnly.add(element.name.text);
188
+ continue;
189
+ }
190
+ if (!statement.isTypeOnly) continue;
191
+ if (!statement.exportClause && statement.moduleSpecifier && ts.isStringLiteralLike(statement.moduleSpecifier)) {
192
+ pending.add(absolute);
193
+ const target = ts.resolveModuleName(statement.moduleSpecifier.text, file, program.getCompilerOptions(), ts.sys).resolvedModule?.resolvedFileName;
194
+ const targetExports = target && compilerExports(ts, program, target, cache, pending);
195
+ pending.delete(absolute);
196
+ for (const name of targetExports?.all ?? []) typeOnly.add(name);
197
+ } else if (statement.exportClause && ts.isNamedExports(statement.exportClause)) for (const element of statement.exportClause.elements) typeOnly.add(element.name.text);
198
+ else if (statement.exportClause && ts.isNamespaceExport(statement.exportClause)) typeOnly.add(statement.exportClause.name.text);
199
+ }
200
+ if (typeOnly.size) {
201
+ const valueEdges = staticRuntimeExports(ts, file);
202
+ for (const name of values) if (typeOnly.has(name) && !valueEdges.has(name)) values.delete(name);
203
+ }
204
+ const result = {
205
+ all,
206
+ values
207
+ };
208
+ cache.set(absolute, result);
209
+ return result;
210
+ }
211
+ function declarationFiles(packageRoot, files) {
212
+ return files.filter((file) => /\.d\.[cm]?ts$/.test(relative(packageRoot, file)));
213
+ }
214
+ function moduleResolutionOptions(ts) {
215
+ return {
216
+ allowImportingTsExtensions: true,
217
+ module: ts.ModuleKind.NodeNext,
218
+ moduleResolution: ts.ModuleResolutionKind.NodeNext,
219
+ moduleSuffixes: [
220
+ "",
221
+ ".web",
222
+ ".ios",
223
+ ".android",
224
+ ".mobile",
225
+ ".native",
226
+ ".macos",
227
+ ".linux",
228
+ ".windows"
229
+ ],
230
+ noEmit: true,
231
+ skipLibCheck: true,
232
+ target: ts.ScriptTarget.Latest
233
+ };
234
+ }
235
+ function verifyDeclarationClosure(ts, packageRoot, files, manifest, failures) {
236
+ const options = moduleResolutionOptions(ts);
237
+ const host = ts.sys;
238
+ const declared = /* @__PURE__ */ new Set([
239
+ manifest.name,
240
+ ...Object.keys(manifest.dependencies ?? {}),
241
+ ...Object.keys(manifest.optionalDependencies ?? {}),
242
+ ...Object.keys(manifest.peerDependencies ?? {}),
243
+ ...manifest.bundledDependencies ?? []
244
+ ]);
245
+ for (const file of declarationFiles(packageRoot, files)) {
246
+ const source = sourceFile(ts, file);
247
+ for (const node of moduleSpecifierNodes(ts, source)) {
248
+ const specifier = node.text;
249
+ if (specifier.startsWith(".") || specifier.startsWith("#")) {
250
+ const resolved = ts.resolveModuleName(specifier, file, options, host).resolvedModule;
251
+ if (!resolved) failures.push(`${relative(packageRoot, file)} has unresolved declaration reference ${specifier}`);
252
+ else if (!pathInside(packageRoot, resolve(resolved.resolvedFileName))) failures.push(`${relative(packageRoot, file)} resolves ${specifier} outside the package`);
253
+ else if (!/\.d\.[cm]?ts$/.test(resolved.resolvedFileName)) failures.push(`${relative(packageRoot, file)} resolves ${specifier} to implementation source; publish its declaration`);
254
+ continue;
255
+ }
256
+ if (specifier.startsWith("node:")) {
257
+ if (!declared.has("@types/node")) failures.push(`${relative(packageRoot, file)} imports ${specifier}; declare @types/node as a dependency or peer`);
258
+ continue;
259
+ }
260
+ if (specifier.startsWith("/")) {
261
+ failures.push(`${relative(packageRoot, file)} has absolute declaration reference ${specifier}`);
262
+ continue;
263
+ }
264
+ const name = specifier.startsWith("@") ? specifier.split("/").slice(0, 2).join("/") : specifier.split("/")[0];
265
+ if (!declared.has(name)) failures.push(`${relative(packageRoot, file)} imports ${specifier}; declare ${name} in dependencies or peerDependencies`);
266
+ }
267
+ }
268
+ }
269
+ function verifyExportMap(ts, packageRoot, files, manifest, exportsMap, failures, options) {
270
+ const branches = exportBranches(exportsMap, [manifest.types ?? manifest.typings].filter(Boolean));
271
+ if (!branches.length) {
272
+ failures.push("package exports contain no runtime entrypoints to verify");
273
+ return;
274
+ }
275
+ const pairs = [];
276
+ for (const branch of branches) {
277
+ if (!branch.types.length) {
278
+ failures.push(`${branch.subpath} runtime ${branch.runtime} has no types condition`);
279
+ continue;
280
+ }
281
+ let runtimeMatches;
282
+ try {
283
+ runtimeMatches = expandTarget(branch.runtime, files, packageRoot);
284
+ } catch (error) {
285
+ failures.push(`${branch.subpath}: ${error.message}`);
286
+ continue;
287
+ }
288
+ for (const runtimeMatch of runtimeMatches) {
289
+ let runtimeFile;
290
+ try {
291
+ runtimeFile = packedPath(packageRoot, runtimeMatch.path);
292
+ } catch (error) {
293
+ failures.push(`${branch.subpath}: ${error.message}`);
294
+ continue;
295
+ }
296
+ for (const typeTarget of branch.types) {
297
+ const concreteType = typeTarget.includes("*") && runtimeMatch.capture !== void 0 ? typeTarget.replace("*", runtimeMatch.capture) : typeTarget;
298
+ let typeFile;
299
+ try {
300
+ typeFile = packedPath(packageRoot, concreteType);
301
+ } catch (error) {
302
+ failures.push(`${branch.subpath}: ${error.message}`);
303
+ continue;
304
+ }
305
+ pairs.push({
306
+ branch,
307
+ runtimeFile,
308
+ runtimePath: runtimeMatch.path,
309
+ typeFile,
310
+ concreteType
311
+ });
312
+ }
313
+ }
314
+ }
315
+ const programRoots = [...new Set(pairs.flatMap(({ runtimeFile, typeFile }) => [runtimeFile, typeFile]))].filter((file) => !file.endsWith(".tsrx"));
316
+ const program = ts.createProgram(programRoots, options);
317
+ for (const pair of pairs) {
318
+ const declarationExports = compilerExports(ts, program, pair.typeFile);
319
+ const runtimeExports = compilerExports(ts, program, pair.runtimeFile);
320
+ const declaredValues = declarationExports?.values ?? /* @__PURE__ */ new Set();
321
+ let runtimeValues;
322
+ if (pair.runtimeFile.endsWith(".ts") || pair.runtimeFile.endsWith(".tsx") || pair.runtimeFile.endsWith(".tsrx")) {
323
+ runtimeValues = staticRuntimeExports(ts, pair.runtimeFile);
324
+ for (const name of declarationExports?.all ?? []) if (!declaredValues.has(name)) runtimeValues.delete(name);
325
+ } else runtimeValues = runtimeExports?.values ?? staticRuntimeExports(ts, pair.runtimeFile);
326
+ const missing = [...runtimeValues].filter((name) => !declaredValues.has(name)).sort();
327
+ const extra = [...declaredValues].filter((name) => !runtimeValues.has(name)).sort();
328
+ if (missing.length || extra.length) failures.push(`${pair.branch.subpath} runtime/type value exports differ for ${pair.runtimePath} → ${pair.concreteType}${missing.length ? `; missing declarations: ${missing.join(", ")}` : ""}${extra.length ? `; declarations without runtime values: ${extra.join(", ")}` : ""}`);
329
+ }
330
+ }
331
+ function verifyPackedPackage(ts, packageRoot) {
332
+ const failures = [];
333
+ const manifestPath = join(packageRoot, "package.json");
334
+ let manifest;
335
+ try {
336
+ manifest = JSON.parse(readFileSync(manifestPath, "utf8"));
337
+ } catch (error) {
338
+ throw new Error(`packed package.json: ${error.message}`);
339
+ }
340
+ const files = packageFiles(packageRoot);
341
+ const maps = [manifest.exports, manifest.publishConfig?.exports].filter((value, index, all) => value && all.indexOf(value) === index);
342
+ if (maps.length) for (const exportsMap of maps) verifyExportMap(ts, packageRoot, files, manifest, exportsMap, failures, moduleResolutionOptions(ts));
343
+ else if (manifest.main || manifest.module) verifyExportMap(ts, packageRoot, files, manifest, { ".": manifest.module ?? manifest.main }, failures, moduleResolutionOptions(ts));
344
+ else failures.push("package has no exports, main, or module entrypoint");
345
+ verifyDeclarationClosure(ts, packageRoot, files, manifest, failures);
346
+ if (failures.length) throw new Error(failures.join("\n"));
347
+ }
348
+ function checkPackedPackage(projectRoot, ts) {
349
+ const temporary = mkdtempSync(join(tmpdir(), "tsrx-typegen-pack-"));
350
+ const packOutput = join(temporary, "pack");
351
+ const extractedRoot = join(temporary, "extracted");
352
+ mkdirSync(packOutput);
353
+ mkdirSync(extractedRoot);
354
+ try {
355
+ const packed = spawnSync("pnpm", [
356
+ "--config.ignore-scripts=true",
357
+ "pack",
358
+ "--pack-destination",
359
+ packOutput
360
+ ], {
361
+ cwd: projectRoot,
362
+ encoding: "utf8",
363
+ maxBuffer: 16777216
364
+ });
365
+ if (packed.stdout) process.stdout.write(packed.stdout);
366
+ if (packed.stderr) process.stderr.write(packed.stderr);
367
+ if (packed.error) throw packed.error;
368
+ if (packed.status !== 0) throw new Error(`pnpm pack exited with ${packed.status}`);
369
+ const tarballs = readdirSync(packOutput).filter((name) => name.endsWith(".tgz"));
370
+ if (tarballs.length !== 1) throw new Error(`expected one package tarball; found ${tarballs.length}`);
371
+ const extracted = spawnSync("tar", [
372
+ "-xzf",
373
+ join(packOutput, tarballs[0]),
374
+ "-C",
375
+ extractedRoot
376
+ ], {
377
+ encoding: "utf8",
378
+ maxBuffer: 16777216
379
+ });
380
+ if (extracted.stdout) process.stdout.write(extracted.stdout);
381
+ if (extracted.stderr) process.stderr.write(extracted.stderr);
382
+ if (extracted.error) throw extracted.error;
383
+ if (extracted.status !== 0) throw new Error(`tar extraction exited with ${extracted.status}`);
384
+ verifyPackedPackage(ts, join(extractedRoot, "package"));
385
+ console.log("tsrx-typegen: packed exports and declaration graph are valid");
386
+ } finally {
387
+ rmSync(temporary, {
388
+ recursive: true,
389
+ force: true
390
+ });
391
+ }
392
+ }
393
+ //#endregion
394
+ export { checkPackedPackage, verifyPackedPackage };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tsrx-typegen",
3
- "version": "0.2.2",
3
+ "version": "0.3.1",
4
4
  "description": "Generate consumer-ready TypeScript declarations for TSRX libraries",
5
5
  "keywords": [
6
6
  "build-tool",
@@ -19,17 +19,25 @@
19
19
  "directory": "packages/tsrx-typegen"
20
20
  },
21
21
  "bin": {
22
- "tsrx-typegen": "./src/cli.mjs"
22
+ "tsrx-typegen": "./bin/tsrx-typegen.mjs"
23
23
  },
24
24
  "files": [
25
- "src"
25
+ "src",
26
+ "dist",
27
+ "bin"
26
28
  ],
27
29
  "type": "module",
30
+ "devDependencies": {
31
+ "@types/node": "24.10.1",
32
+ "vite": "8.3.0"
33
+ },
28
34
  "peerDependencies": {
29
35
  "@tsrx/typescript-plugin": ">=0.4.11 <1",
30
36
  "typescript": ">=5.9.3 <6"
31
37
  },
32
38
  "scripts": {
33
- "test": "node tests/pack-check.mjs && node tests/packed-consumer.mjs"
39
+ "build": "vite build",
40
+ "test": "pnpm build && node tests/pack-check.mjs && node tests/packed-consumer.mjs",
41
+ "typecheck": "tsc -p tsconfig.json"
34
42
  }
35
43
  }
@@ -13,7 +13,7 @@ import {
13
13
 
14
14
  import { tmpdir } from 'node:os'
15
15
  import { dirname, extname, isAbsolute, join, normalize, relative, resolve, sep } from 'node:path'
16
- import { checkPackedPackage } from './pack-check.mjs'
16
+ import { checkPackedPackage } from './pack-check.ts'
17
17
 
18
18
  const manifestName = '.tsrx-typegen-manifest.json'
19
19
  const defaultExtensions = {
@@ -91,7 +91,7 @@ function configuredTargetNames(projectRoot, selectedTarget) {
91
91
  throw new Error(`${path}: ${error.message}`)
92
92
  }
93
93
 
94
- const targets = config?.targets
94
+ const targets: Record<string, any> | undefined = config?.targets
95
95
  if (
96
96
  !targets ||
97
97
  typeof targets !== 'object' ||
@@ -183,7 +183,7 @@ function commonDirectory(paths) {
183
183
  }
184
184
 
185
185
  const segments = paths.map((path) => resolve(dirname(path)).split(sep))
186
- const common = []
186
+ const common: any[] = []
187
187
  for (let index = 0; segments.every((parts) => parts[index] === segments[0][index]); index += 1) {
188
188
  common.push(segments[0][index])
189
189
  }
@@ -192,8 +192,8 @@ function commonDirectory(paths) {
192
192
  }
193
193
 
194
194
  function sourceDeclarationMap(rootDir, fileNames, extensions, overrides) {
195
- const sourceOutputs = new Map()
196
- const runtimeByDeclaration = new Map()
195
+ const sourceOutputs = new Map<any, any>()
196
+ const runtimeByDeclaration = new Map<any, any>()
197
197
  for (const source of fileNames) {
198
198
  const sourceExtension = extname(source).toLowerCase()
199
199
  if (!Object.hasOwn(extensions, sourceExtension) || /\.d\.[cm]?ts$/i.test(source)) {
@@ -254,7 +254,7 @@ function configOptions(projectRoot, projectArgument, targetArgument) {
254
254
  throw new Error(`${path}: expected a JSON object`)
255
255
  }
256
256
 
257
- const targets = config.targets ?? {}
257
+ const targets: Record<string, any> = config.targets ?? {}
258
258
  if (!targets || typeof targets !== 'object' || Array.isArray(targets)) {
259
259
  throw new Error(`${path}: targets must be an object`)
260
260
  }
@@ -310,7 +310,7 @@ function configOptions(projectRoot, projectArgument, targetArgument) {
310
310
  throw new Error(`${path}: target ${selectedName} must declare entrypoints`)
311
311
  }
312
312
 
313
- for (const [subpath, entry] of Object.entries(selected.entrypoints)) {
313
+ for (const [subpath, entry] of Object.entries<any>(selected.entrypoints)) {
314
314
  if (
315
315
  !entry ||
316
316
  typeof entry !== 'object' ||
@@ -393,7 +393,7 @@ function configOptions(projectRoot, projectArgument, targetArgument) {
393
393
  }
394
394
 
395
395
  function applyOverrides(ts, projectRoot, outputDir, generated, overrides) {
396
- const normalized = new Map()
396
+ const normalized = new Map<any, any>()
397
397
  for (const [outputPath, sourcePath] of Object.entries(overrides)) {
398
398
  if (typeof sourcePath !== 'string' || !sourcePath) {
399
399
  throw new Error(`override for ${outputPath} must name a source declaration file`)
@@ -429,7 +429,7 @@ function applyOverrides(ts, projectRoot, outputDir, generated, overrides) {
429
429
  return new Set(normalized.keys())
430
430
  }
431
431
 
432
- function collectFiles(root, output = []) {
432
+ function collectFiles(root, output: any[] = []) {
433
433
  if (!statSync(root).isDirectory()) {
434
434
  output.push(root)
435
435
  return output
@@ -448,7 +448,7 @@ function collectFiles(root, output = []) {
448
448
  }
449
449
 
450
450
  function moduleSpecifierNodes(ts, sourceFile) {
451
- const nodes = []
451
+ const nodes: any[] = []
452
452
  const visit = (node) => {
453
453
  if (
454
454
  (ts.isImportDeclaration(node) || ts.isExportDeclaration(node)) &&
@@ -490,7 +490,7 @@ function assertNoTsrxModuleReferences(ts, path, text) {
490
490
  function rewriteDeclaration(ts, path, outputDir, extensions, generatedFiles, runtimeByDeclaration) {
491
491
  const text = readFileSync(path, 'utf8')
492
492
  const source = ts.createSourceFile(path, text, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS)
493
- const edits = []
493
+ const edits: any[] = []
494
494
  for (const node of moduleSpecifierNodes(ts, source)) {
495
495
  const specifier = node.text
496
496
  if (!specifier.startsWith('.')) {
@@ -529,7 +529,7 @@ function rewriteDeclaration(ts, path, outputDir, extensions, generatedFiles, run
529
529
 
530
530
  const declaration = relative(outputDir, target)
531
531
  const runtimeExtension = runtimeByDeclaration.get(declaration) ?? '.js'
532
- const isIndex = /[\\/]index\.d\.[cm]?ts$/.test(target)
532
+ const isIndex = dirname(target) === targetStem
533
533
  const baseSpecifier = specifier.endsWith('/') ? specifier.slice(0, -1) : specifier
534
534
  replacement = isIndex
535
535
  ? `${baseSpecifier}/index${runtimeExtension}`
@@ -610,7 +610,7 @@ function pathInside(root, file) {
610
610
 
611
611
  function existingOutputPath(outputDir, file) {
612
612
  let current = outputDir
613
- const actualParts = []
613
+ const actualParts: any[] = []
614
614
 
615
615
  for (const part of normalize(file).split(sep)) {
616
616
  let entries
@@ -672,7 +672,7 @@ function writeOutput(outputDir, generated, oldFiles, overrideFiles) {
672
672
  }
673
673
 
674
674
  function compareOutput(outputDir, generated, oldFiles, overrideFiles) {
675
- const errors = []
675
+ const errors: any[] = []
676
676
  if (!oldFiles) {
677
677
  errors.push(`missing ${manifestName}; run tsrx-typegen --project <tsconfig> first`)
678
678
  }
@@ -742,12 +742,12 @@ function verifyPublicTypes(projectRoot, generated, outDir, target, sourceOutputs
742
742
  }
743
743
 
744
744
  const exportsMap = manifest.publishConfig?.exports ?? manifest.exports
745
- const failures = []
745
+ const failures: any[] = []
746
746
  if (target && exportsMap && typeof exportsMap === 'object') {
747
747
  const configuredSubpaths = new Set(Object.keys(target.entrypoints))
748
748
  for (const [subpath, mapping] of Object.entries(exportsMap)) {
749
- const runtimeTargets = stringsIn(mapping).filter((entry) =>
750
- /\.(?:[cm]?[jt]sx?|tsrx)$/.test(entry),
749
+ const runtimeTargets = stringsIn(mapping).filter(
750
+ (entry) => !extname(entry) || /\.(?:[cm]?[jt]sx?|tsrx)$/.test(entry),
751
751
  )
752
752
 
753
753
  if (runtimeTargets.length && !configuredSubpaths.has(subpath)) {
@@ -755,7 +755,7 @@ function verifyPublicTypes(projectRoot, generated, outDir, target, sourceOutputs
755
755
  }
756
756
  }
757
757
 
758
- for (const [subpath, entry] of Object.entries(target.entrypoints)) {
758
+ for (const [subpath, entry] of Object.entries<any>(target.entrypoints)) {
759
759
  const mapping = exportsMap[subpath]
760
760
  if (!mapping) {
761
761
  failures.push(
@@ -794,8 +794,8 @@ function verifyPublicTypes(projectRoot, generated, outDir, target, sourceOutputs
794
794
 
795
795
  if (exportsMap && typeof exportsMap === 'object') {
796
796
  for (const [subpath, mapping] of Object.entries(exportsMap)) {
797
- const runtimeTargets = stringsIn(mapping).filter((target) =>
798
- /\.(?:[cm]?[jt]sx?|tsrx)$/.test(target),
797
+ const runtimeTargets = stringsIn(mapping).filter(
798
+ (target) => !extname(target) || /\.(?:[cm]?[jt]sx?|tsrx)$/.test(target),
799
799
  )
800
800
 
801
801
  if (!runtimeTargets.length) {
@@ -925,6 +925,23 @@ function runTarget(projectRoot, args) {
925
925
  return false
926
926
  }
927
927
 
928
+ // TypeScript does not emit declaration inputs. Preserve included, local
929
+ // declarations beside emitted modules so their references remain closed.
930
+ for (const source of fileNames) {
931
+ if (!/\.d\.[cm]?ts$/.test(source) || !pathInside(rootDir, source)) {
932
+ continue
933
+ }
934
+
935
+ const declaration = relative(rootDir, source)
936
+ if (sourceOutputs.has(declaration)) {
937
+ continue
938
+ }
939
+
940
+ const output = join(outputDir, declaration)
941
+ mkdirSync(dirname(output), { recursive: true })
942
+ writeFileSync(output, readFileSync(source))
943
+ }
944
+
928
945
  const generated = normalizeOutput(ts, outputDir, sourceExtensions, runtimeByDeclaration)
929
946
  const managedOverrides = applyOverrides(ts, projectRoot, outDir, generated, overrides)
930
947
  verifySourceDeclarations(generated, sourceOutputs)