UNPKG

tshy

Version:

TypeScript HYbridizer - Hybrid (CommonJS/ESM) TypeScript node package builder

117 lines 4.29 kB
// this is the thing that supports top-level package.json imports // via symlinks, not the tshy.imports which are just config. import { writeFileSync } from 'fs'; import { symlink } from 'fs/promises'; import { mkdirp } from 'mkdirp'; import { dirname, relative, resolve, sep } from 'path'; import { getAllConditionalValues, getUniqueConditionSets, resolveAllLocalImports, } from 'resolve-import'; import { rimraf } from 'rimraf'; import { fileURLToPath } from 'url'; import * as console from './console.js'; const dirsMade = new Set(); // write out the steps to the save file script export const save = (f) => { const links = [...saveSet.entries()]; if (!links.length) return false; const dirs = new Set(links.map(([dest]) => dirname(dest))); console.debug('save import linker', f); writeFileSync(f, `import { mkdirSync } from 'node:fs' import { symlink } from 'node:fs/promises' const dirs = ${JSON.stringify([...dirs])} const links = [ ${links.map(l => ` ${JSON.stringify(l)},\n`).join('')}] const e = (er) => { if (er.code !== 'EEXIST') throw er } for (const d of dirs) mkdirSync(d, { recursive: true }) Promise.all(links.map(([dest, src]) => symlink(src, dest).catch(e))) `); return true; }; let targets = undefined; // Get the targets that will have to be linked, because they're not // a target in ./src const getTargets = async (imports) => { const conds = getAllConditionalValues(imports).filter(c => !c.startsWith('./src/')); if (!conds.some(c => c.includes('*'))) { // fast path return (targets = conds.filter(c => c.startsWith('./'))); } const sets = getUniqueConditionSets(imports); const t = new Set(); const pj = resolve('package.json'); for (const conditions of sets) { const imps = await resolveAllLocalImports(pj, { conditions }); for (const url of Object.values(imps)) { // node builtin if (typeof url === 'string') continue; const p = fileURLToPath(url); const rel = relative(process.cwd(), p); // if it's empty, a dep in node_modules, or a built module, skip if (!rel || rel.startsWith('..' + sep) || rel.startsWith('src' + sep) || rel.startsWith('node_modules' + sep)) continue; t.add('./' + rel.replace(/\\/g, '/')); } } return (targets = [...t]); }; const saveSet = new Map(); // create symlinks for the package imports in the target dir export const link = async (pkg, dir, save = false) => { const { imports } = pkg; if (!imports) return; if (!targets) targets = await getTargets(imports); if (!targets.length) return; console.debug(`link import targets in ${dir}`, targets); const rel = relative(resolve(dir), process.cwd()); const lps = []; for (const t of targets) { const l = t.replace(/^\.\//, ''); const df = dirname(l); const dfrel = df === '.' ? '' : (df .split('/') .map(() => '../') .join('')); const dest = dir + '/' + l; const src = rel + '/' + dfrel + l; if (save) saveSet.set(dest, src); lps.push(mkdirp(dirname(dest)) .then(d => { // if we aren't saving, then this is a transient link // save the dirs created so that we can clean them up if (!save && d) dirsMade.add(d); return rimraf(dest); }) .then(() => symlink(src, dest))); } await Promise.all(lps); }; // remove symlinks created for package imports in the target dir export const unlink = async (pkg, dir) => { const { imports } = pkg; if (!imports) return; // will always have targets by this point /* c8 ignore start */ if (!targets) targets = await getTargets(imports); /* c8 ignore stop */ console.debug(`unlink import targets in ${dir}`, targets); const lps = []; for (const t of targets) { const dest = resolve(dir, t); lps.push(rimraf(dest)); } for (const d of dirsMade) lps.push(rimraf(d)); await Promise.all(lps); }; //# sourceMappingURL=unbuilt-imports.js.map