dts-inline.mjs96 lines · main
1#!/usr/bin/env node
2/**
3 * Post-process tsup DTS output — inline workspace-only type re-exports.
4 *
5 * tsup's `dts.resolve` option can't reliably resolve types from
6 * workspace packages whose `exports.types` points at built `.d.ts`
7 * files. The JS bundle is fine (tsup's `noExternal` inlines that
8 * correctly); only the emitted `.d.ts` files are affected.
9 *
10 * This script reads the re-export stubs tsup emits and replaces them
11 * with the actual type declarations from the workspace package, so
12 * TypeScript consumers outside the monorepo get fully self-contained
13 * type definitions.
14 */
15
16import { readFile, writeFile } from 'node:fs/promises';
17import { resolve, dirname } from 'node:path';
18import { fileURLToPath } from 'node:url';
19
20const here = dirname(fileURLToPath(import.meta.url));
21
22// Each entry: [distRelativePath, searchRegex, sourcePackageName]
23const REPLACEMENTS = [
24 [
25 'dist/schema/index.d.ts',
26 "export \\* from '@briven/schema';",
27 '@briven/schema',
28 ],
29 [
30 'dist/server/index.d.ts',
31 "import \\{ Ctx \\} from '@briven/schema';\\nexport \\{ Ctx \\} from '@briven/schema';",
32 '@briven/schema',
33 ],
34];
35
36async function main() {
37 for (const [file, searchPattern, sourcePkg] of REPLACEMENTS) {
38 const filePath = resolve(here, '..', file);
39
40 /** @type {string} */
41 let content;
42 try {
43 content = await readFile(filePath, 'utf8');
44 } catch {
45 console.warn(`[dts-inline] ${file} not found — skipping`);
46 continue;
47 }
48
49 // Resolve the workspace package's DTS entry via its exports map.
50 // Workspace packages aren't in node_modules, so use the known
51 // monorepo layout: packages/<pkg-name>/package.json.
52 const pkgDir = resolve(here, '..', '..', sourcePkg.split('/').pop());
53 const pkgJsonPath = resolve(pkgDir, 'package.json');
54
55 let pkgJson;
56 try {
57 pkgJson = JSON.parse(await readFile(pkgJsonPath, 'utf8'));
58 } catch {
59 console.warn(`[dts-inline] ${pkgJsonPath} not readable — skipping ${file}`);
60 continue;
61 }
62 const typesExport = pkgJson.exports && pkgJson.exports['.'] && pkgJson.exports['.'].types;
63 if (!typesExport) {
64 console.warn(`[dts-inline] ${sourcePkg} has no exports['.'].types — skipping ${file}`);
65 continue;
66 }
67
68 const typesPath = resolve(dirname(pkgJsonPath), typesExport);
69 /** @type {string} */
70 let typesContent;
71 try {
72 typesContent = await readFile(typesPath, 'utf8');
73 } catch {
74 console.warn(`[dts-inline] ${typesPath} not readable — skipping ${file}`);
75 continue;
76 }
77
78 const regex = new RegExp(searchPattern, 'g');
79 if (!regex.test(content)) {
80 console.warn(`[dts-inline] pattern not found in ${file} — skipping`);
81 continue;
82 }
83
84 // Reset regex lastIndex after test()
85 regex.lastIndex = 0;
86 const newContent = content.replace(regex, typesContent.trim());
87
88 await writeFile(filePath, newContent, 'utf8');
89 console.log(`[dts-inline] ${file} — inlined ${typesContent.length} chars from ${sourcePkg}`);
90 }
91}
92
93main().catch((err) => {
94 console.error(`[dts-inline] fatal: ${err.message}`);
95 process.exit(1);
96});