dts-inline.mjs 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596
  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. import { readFile, writeFile } from 'node:fs/promises';
  16. import { resolve, dirname } from 'node:path';
  17. import { fileURLToPath } from 'node:url';
  18. const here = dirname(fileURLToPath(import.meta.url));
  19. // Each entry: [distRelativePath, searchRegex, sourcePackageName]
  20. const REPLACEMENTS = [
  21. [
  22. 'dist/schema/index.d.ts',
  23. "export \\* from '@briven/schema';",
  24. '@briven/schema',
  25. ],
  26. [
  27. 'dist/server/index.d.ts',
  28. "import \\{ Ctx \\} from '@briven/schema';\\nexport \\{ Ctx \\} from '@briven/schema';",
  29. '@briven/schema',
  30. ],
  31. ];
  32. async function main() {
  33. for (const [file, searchPattern, sourcePkg] of REPLACEMENTS) {
  34. const filePath = resolve(here, '..', file);
  35. /** @type {string} */
  36. let content;
  37. try {
  38. content = await readFile(filePath, 'utf8');
  39. } catch {
  40. console.warn(`[dts-inline] ${file} not found — skipping`);
  41. continue;
  42. }
  43. // Resolve the workspace package's DTS entry via its exports map.
  44. // Workspace packages aren't in node_modules, so use the known
  45. // monorepo layout: packages/<pkg-name>/package.json.
  46. const pkgDir = resolve(here, '..', '..', sourcePkg.split('/').pop());
  47. const pkgJsonPath = resolve(pkgDir, 'package.json');
  48. let pkgJson;
  49. try {
  50. pkgJson = JSON.parse(await readFile(pkgJsonPath, 'utf8'));
  51. } catch {
  52. console.warn(`[dts-inline] ${pkgJsonPath} not readable — skipping ${file}`);
  53. continue;
  54. }
  55. const typesExport = pkgJson.exports && pkgJson.exports['.'] && pkgJson.exports['.'].types;
  56. if (!typesExport) {
  57. console.warn(`[dts-inline] ${sourcePkg} has no exports['.'].types — skipping ${file}`);
  58. continue;
  59. }
  60. const typesPath = resolve(dirname(pkgJsonPath), typesExport);
  61. /** @type {string} */
  62. let typesContent;
  63. try {
  64. typesContent = await readFile(typesPath, 'utf8');
  65. } catch {
  66. console.warn(`[dts-inline] ${typesPath} not readable — skipping ${file}`);
  67. continue;
  68. }
  69. const regex = new RegExp(searchPattern, 'g');
  70. if (!regex.test(content)) {
  71. console.warn(`[dts-inline] pattern not found in ${file} — skipping`);
  72. continue;
  73. }
  74. // Reset regex lastIndex after test()
  75. regex.lastIndex = 0;
  76. const newContent = content.replace(regex, typesContent.trim());
  77. await writeFile(filePath, newContent, 'utf8');
  78. console.log(`[dts-inline] ${file} — inlined ${typesContent.length} chars from ${sourcePkg}`);
  79. }
  80. }
  81. main().catch((err) => {
  82. console.error(`[dts-inline] fatal: ${err.message}`);
  83. process.exit(1);
  84. });