GeezerVisonThe World Wide Web, restored to 800 pixels of glory. |
| Home|About|Gaming cabinet|Archives|How it works|Starter sites |
GeezerVison is showing github.com — bQuery/scripts/postbuild-types.mjs at dev · bQuery/bQuery Expand file tree/ Copy pathpostbuild-types.mjsMore file actions 160 lines (141 loc) · 6.12 KB / Copy pathpostbuild-types.mjsFile metadata and controls160 lines (141 loc) · 6.12 KB 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 #!/usr/bin/env bun /** * Add explicit `.js` extensions to relative imports in the emitted `.d.ts` * files, so the published types resolve the way consumers actually load them * (#218). * * `tsc` re-emits relative specifiers exactly as the source writes them, and * this codebase writes them extensionless (`export * from './core/index'`). * That is fine for a bundler, but this is an ESM package, and under * `moduleResolution: node16`/`nodenext` ESM resolution requires an explicit * extension. A consumer with `skipLibCheck: false` gets a wall of TS2834: * * dist/index.d.ts(10,15): error TS2834: Relative import paths need explicit * file extensions in ECMAScript imports ... * * `attw` reports the same thing as 315 InternalResolutionError problems. * Most projects run `skipLibCheck: true`, which is why it went unnoticed. * * The real fix is extensions in the source imports, but that touches every * file in `src/`. Rewriting the emitted declarations gets the same result for * consumers, and only the declarations need it — Vite already resolves the * runtime bundles. * * Only specifiers that resolve to a file actually present in `dist/` are * rewritten; anything else is left alone. * * Usage: bun scripts/postbuild-types.mjs */ import { existsSync } from 'node:fs'; import { readFile, readdir, writeFile } from 'node:fs/promises'; import { dirname, join, resolve } from 'node:path'; import { fileURLToPath, pathToFileURL } from 'node:url'; const __dirname = fileURLToPath(new URL('.', import.meta.url)); const repoRoot = resolve(__dirname, '..'); const distDir = join(repoRoot, 'dist'); /** `from '…'`, `import('…')` and bare `import '…'`, single or double quoted. */ const SPECIFIER = /(\bfrom\s*|\bimport\s*\(\s*|\bimport\s+)(['"])(\.{1,2}\/[^'"]*)\2/g; /** * A line that is comment text rather than code. * * The rewrite is otherwise comment-blind, and that is not hypothetical: it * turned `{@link import('./css')}` in `src/component/types.ts` into * `'./css.js'` in the emitted declaration. Harmless there, but an `@example` * block showing *consumer* code — `from './core'` — would be rewritten to * bQuery's internal dist layout and shipped into IDE hover docs. * * Applied without splitting the source: `SPECIFIER`'s `\\s*` and `\\s+` match * newlines, so a wrapped `import(\\n './x')` only matches against the whole * text. Splitting into lines to skip comments would silently stop rewriting * those, which is a rewrite that fails quietly rather than loudly. */ const commentRanges = (source) => { const ranges = []; let offset = 0; for (const line of source.split('\n')) { const trimmed = line.trimStart(); if (trimmed.startsWith('*') || trimmed.startsWith('//') || trimmed.startsWith('/*')) { ranges.push([offset, offset + line.length]); } offset += line.length + 1; } return ranges; }; /** Whether an offset falls inside a comment line. */ const inComment = (ranges, index) => ranges.some(([start, end]) => index >= start && index < end); /** Already has an extension we must not touch. */ const HAS_EXTENSION = /\.(js|mjs|cjs|json|node)$/; /** Collect every `.d.ts` under a directory. */ export async function collectDeclarations(dir) { const found = []; for (const entry of await readdir(dir, { withFileTypes: true })) { const path = join(dir, entry.name); if (entry.isDirectory()) found.push(...(await collectDeclarations(path))); else if (entry.name.endsWith('.d.ts')) found.push(path); } return found; } /** * Resolve an extensionless relative specifier the way Node ESM would need it * written. Returns the rewritten specifier, or null to leave it alone. */ export function resolveSpecifier(specifier, fromDir, exists = existsSync) { if (HAS_EXTENSION.test(specifier)) return null; const target = join(fromDir, specifier); if (exists(`${target}.d.ts`)) return `${specifier}.js`; if (exists(join(target, 'index.d.ts'))) return `${specifier}/index.js`; return null; } /** Rewrite one declaration file's specifiers. Returns the new source, or null. */ export function rewriteSource(source, fromDir, exists = existsSync) { let changed = 0; const ranges = commentRanges(source); const next = source.replace(SPECIFIER, (match, keyword, quote, specifier, index) => { if (inComment(ranges, index)) return match; const rewritten = resolveSpecifier(specifier, fromDir, exists); if (rewritten === null) return match; changed++; return `${keyword}${quote}${rewritten}${quote}`; }); return changed > 0 ? { source: next, changed } : null; } export async function fixDeclarationExtensions() { const files = await collectDeclarations(distDir); let filesChanged = 0; let specifiersChanged = 0; for (const file of files) { const source = await readFile(file, 'utf8'); const result = rewriteSource(source, dirname(file)); if (!result) continue; await writeFile(file, result.source); filesChanged++; specifiersChanged += result.changed; } return { fileCount: files.length, filesChanged, specifiersChanged }; } export async function main() { if (!existsSync(distDir)) { console.error('✗ No dist/ directory — run the type build first.'); process.exit(1); } const { fileCount, filesChanged, specifiersChanged } = await fixDeclarationExtensions(); // Finding nothing is a build problem, not a no-op. A changed `outDir` or a // `vite build` with `emptyOutDir` reordered after `build:types` would leave // the declarations unrewritten and ship the extensionless imports this // script exists to fix — with `bun run build` still green. The same rule // `check:full-bundle` states: silently skipping is not allowed. if (fileCount === 0) { console.error('✗ No .d.ts files under dist/ — the type build emitted nothing to rewrite.'); process.exit(1); } console.log( `✓ Declaration imports: rewrote ${specifiersChanged} specifier(s) across ` + `${filesChanged} of ${fileCount} .d.ts files.` ); } export function isDirectExecution(argvEntry = process.argv[1]) { return Boolean(argvEntry) && import.meta.url === pathToFileURL(resolve(argvEntry)).href; } if (isDirectExecution()) { await main(); } |
| You are visitor 01402520 — GeezerVison does not store the pages you look up. Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required. |