Refactor inline-loot-core-types script to streamline TypeScript declaration handling and improve output organization. Remove legacy code and directly copy loot-core declaration tree, updating index.d.ts to reference local imports.

This commit is contained in:
Matiss Janis Aboltins
2026-03-04 23:29:39 +00:00
parent f1dc0b4a6e
commit da1ab9e85d
2 changed files with 29 additions and 134 deletions

View File

@@ -1,6 +1,6 @@
/**
* Post-build script: generates @types/loot-core.d.ts with declare module blocks
* so the published package is self-contained (no external loot-core types).
* Post-build script: copies loot-core declaration tree into @types/loot-core
* and rewrites index.d.ts to reference it so the published package is self-contained.
* Run after vite build; requires loot-core declarations (yarn workspace loot-core exec tsc).
*/
import fs from 'fs';
@@ -11,134 +11,43 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url));
const apiRoot = path.resolve(__dirname, '..');
const typesDir = path.join(apiRoot, '@types');
const indexDts = path.join(typesDir, 'index.d.ts');
const lootCoreDeclRoot = path.resolve(
apiRoot,
'../loot-core/lib-dist/decl/src',
);
function collectLootCorePaths(content) {
const paths = new Set();
const re = /['"]loot-core\/([^'"]+)['"]/g;
let m;
while ((m = re.exec(content)) !== null) paths.add(m[1]);
return paths;
}
function resolveDeclFile(subpath) {
const base = path.join(lootCoreDeclRoot, subpath);
const withExt = base + '.d.ts';
const index = path.join(base, 'index.d.ts');
if (fs.existsSync(withExt)) return withExt;
if (fs.existsSync(index)) return index;
return null;
}
function resolveRelativePath(fromPath, relSpecifier) {
const dir = path.dirname(fromPath);
const resolved = path.join(dir, relSpecifier).replace(/\\/g, '/');
return path.normalize(resolved).replace(/\\/g, '/');
}
function collectReexports(paths) {
const expanded = new Set(paths);
const reExportFrom =
/export\s+(?:\*\s+from|type\s+\*\s+from|(?:\{[^}]*\})\s+from)\s+['"](\.\.?\/[^'"]+)['"]/g;
const reImportFrom =
/import\s+(?:type\s+)?(?:\{[^}]*\}|\*\s+as\s+\w+)\s+from\s+['"](\.\.?\/[^'"]+)['"]/g;
let changed = true;
while (changed) {
changed = false;
for (const p of expanded) {
const file = resolveDeclFile(p);
if (!file) continue;
const content = fs.readFileSync(file, 'utf8');
for (const re of [reExportFrom, reImportFrom]) {
re.lastIndex = 0;
let m;
while ((m = re.exec(content)) !== null) {
const normalized = resolveRelativePath(p, m[1]);
if (!expanded.has(normalized)) {
expanded.add(normalized);
changed = true;
}
}
}
}
}
return expanded;
}
function resolveToLootCorePath(currentPath, specifier) {
const dir = path.dirname(currentPath);
const resolved = path.join(dir, specifier).replace(/\\/g, '/');
const normalized = path.normalize(resolved).replace(/\\/g, '/');
return normalized;
}
function rewriteRelativeImports(content, currentPath) {
function toLootCore(rel) {
const normalized = resolveToLootCorePath(currentPath, rel);
return `loot-core/${normalized}`;
}
return content
.replace(
/(export\s+(?:\*\s+from|type\s+\*\s+from)\s+)['"](\.\.?\/[^'"]+)['"]/g,
(_, prefix, rel) => `${prefix}'${toLootCore(rel)}';`,
)
.replace(
/(export\s+(?:type\s+)?(?:\{[^}]*\})\s+from\s+)['"](\.\.?\/[^'"]+)['"]/g,
(_, prefix, rel) => `${prefix}'${toLootCore(rel)}';`,
)
.replace(
/(import\s+(?:type\s+)?(?:\{[^}]*\}|\*\s+as\s+\w+)\s+from\s+)['"](\.\.?\/[^'"]+)['"]/g,
(_, prefix, rel) => `${prefix}'${toLootCore(rel)}'`,
)
.replace(
/(import\s+)['"](\.\.?\/[^'"]+)['"]/g,
(_, prefix, rel) => `${prefix}'${toLootCore(rel)}'`,
);
}
function buildLootCoreDts(paths) {
const blocks = [];
for (const p of [...paths].sort()) {
const file = resolveDeclFile(p);
if (!file) continue;
let content = fs.readFileSync(file, 'utf8');
content = rewriteRelativeImports(content, p);
blocks.push(`declare module 'loot-core/${p}' {\n${content}\n}\n`);
}
return (
'// Inlined loot-core type declarations for @actual-app/api publish.\n' +
'// Do not edit by hand; generated by scripts/inline-loot-core-types.mjs.\n\n' +
blocks.join('\n')
);
}
const lootCoreDeclSrc = path.resolve(apiRoot, '../loot-core/lib-dist/decl/src');
const lootCoreTypesDir = path.join(typesDir, 'loot-core');
function main() {
if (!fs.existsSync(indexDts)) {
console.error('Missing @types/index.d.ts; run vite build first.');
process.exit(1);
}
if (!fs.existsSync(lootCoreDeclRoot)) {
if (!fs.existsSync(lootCoreDeclSrc)) {
console.error(
'Missing loot-core declarations; run: yarn workspace loot-core exec tsc',
);
process.exit(1);
}
let indexContent = fs.readFileSync(indexDts, 'utf8');
const paths = collectLootCorePaths(indexContent);
const allPaths = collectReexports(paths);
const lootCoreDts = buildLootCoreDts(allPaths);
const lootCoreDtsPath = path.join(typesDir, 'loot-core.d.ts');
fs.writeFileSync(lootCoreDtsPath, lootCoreDts, 'utf8');
const reference = '/// <reference path="./loot-core.d.ts" />\n';
if (!indexContent.startsWith(reference.trim())) {
indexContent = reference + indexContent;
fs.writeFileSync(indexDts, indexContent, 'utf8');
// Remove existing loot-core output (dir or legacy single file)
if (fs.existsSync(lootCoreTypesDir)) {
fs.rmSync(lootCoreTypesDir, { recursive: true });
}
const legacyDts = path.join(typesDir, 'loot-core.d.ts');
if (fs.existsSync(legacyDts)) {
fs.rmSync(legacyDts);
}
// Copy declaration tree as-is (relative imports inside files resolve within the tree)
fs.cpSync(lootCoreDeclSrc, lootCoreTypesDir, { recursive: true });
// Rewrite index.d.ts: remove reference, point imports at local ./loot-core/
let indexContent = fs.readFileSync(indexDts, 'utf8');
indexContent = indexContent.replace(
/\/\/\/ <reference path="\.\/loot-core\.d\.ts" \/>\n?/,
'',
);
indexContent = indexContent
.replace(/'loot-core\//g, "'./loot-core/")
.replace(/"loot-core\//g, '"./loot-core/');
fs.writeFileSync(indexDts, indexContent, 'utf8');
}
main();

View File

@@ -24,10 +24,8 @@ __metadata:
resolution: "@actual-app/api@workspace:packages/api"
dependencies:
"@actual-app/crdt": "workspace:^"
"@microsoft/api-extractor": "npm:^7.50.1"
better-sqlite3: "npm:^12.6.2"
compare-versions: "npm:^6.1.1"
dts-bundle-generator: "npm:^9.5.1"
loot-core: "workspace:^"
node-fetch: "npm:^3.3.2"
rollup-plugin-visualizer: "npm:^6.0.5"
@@ -15086,18 +15084,6 @@ __metadata:
languageName: node
linkType: hard
"dts-bundle-generator@npm:^9.5.1":
version: 9.5.1
resolution: "dts-bundle-generator@npm:9.5.1"
dependencies:
typescript: "npm:>=5.0.2"
yargs: "npm:^17.6.0"
bin:
dts-bundle-generator: dist/bin/dts-bundle-generator.js
checksum: 10/8abddebcaab0d542afcb62971526beb5ea09f977b92cb06723e6046bde7d2bc9513c8508b937f62d7a46b28827b26fda4c31ed85fe2902e01685741f823a50eb
languageName: node
linkType: hard
"dunder-proto@npm:^1.0.0, dunder-proto@npm:^1.0.1":
version: 1.0.1
resolution: "dunder-proto@npm:1.0.1"
@@ -28042,7 +28028,7 @@ __metadata:
languageName: node
linkType: hard
"typescript@npm:>=5.0.2, typescript@npm:^5.0.4, typescript@npm:^5.9.3":
"typescript@npm:^5.0.4, typescript@npm:^5.9.3":
version: 5.9.3
resolution: "typescript@npm:5.9.3"
bin:
@@ -28062,7 +28048,7 @@ __metadata:
languageName: node
linkType: hard
"typescript@patch:typescript@npm%3A>=5.0.2#optional!builtin<compat/typescript>, typescript@patch:typescript@npm%3A^5.0.4#optional!builtin<compat/typescript>, typescript@patch:typescript@npm%3A^5.9.3#optional!builtin<compat/typescript>":
"typescript@patch:typescript@npm%3A^5.0.4#optional!builtin<compat/typescript>, typescript@patch:typescript@npm%3A^5.9.3#optional!builtin<compat/typescript>":
version: 5.9.3
resolution: "typescript@patch:typescript@npm%3A5.9.3#optional!builtin<compat/typescript>::version=5.9.3&hash=5786d5"
bin:
@@ -29912,7 +29898,7 @@ __metadata:
languageName: node
linkType: hard
"yargs@npm:^17.0.1, yargs@npm:^17.5.1, yargs@npm:^17.6.0, yargs@npm:^17.6.2":
"yargs@npm:^17.0.1, yargs@npm:^17.5.1, yargs@npm:^17.6.2":
version: 17.7.2
resolution: "yargs@npm:17.7.2"
dependencies: