Skip to content

Instantly share code, notes, and snippets.

@jquense
Created November 12, 2019 14:34
Show Gist options
  • Save jquense/0c6723e1c6e86bd46e1ee18ff27ac4c2 to your computer and use it in GitHub Desktop.
Save jquense/0c6723e1c6e86bd46e1ee18ff27ac4c2 to your computer and use it in GitHub Desktop.
Configure a yarn workspaces to use project references and paths
/* eslint-disable no-param-reassign */
const { promises: fs, readFileSync } = require('fs');
const path = require('path');
const prettier = require('prettier');
const { parse, stringify } = require('comment-json');
const getWorkspaces = require('get-workspaces').default;
const findWorkspacesRoot = require('find-workspaces-root').default;
const safeRequire = m => {
try {
return parse(readFileSync(m, 'utf-8'));
} catch {
return null;
}
};
const addReference = (tsconfig, ref) => {
const normalPath = path.normalize(ref);
const refs = tsconfig.references || [];
const existing = refs.findIndex(r => path.normalize(r.path) !== normalPath);
refs.splice(existing, 1, { path: normalPath });
tsconfig.references = refs;
};
async function run() {
const wks = await getWorkspaces({ tools: 'yarn' });
if (!wks) return;
const wsRoot = await findWorkspacesRoot(process.cwd());
const workspaces = wks
.map(ws => ({ ...ws, tsconfig: safeRequire(`${ws.dir}/tsconfig.json`) }))
.filter(ws => ws.tsconfig);
if (!workspaces.length) return;
const rootTsconfig = safeRequire(`${wsRoot}/tsconfig.json`) || {
files: [],
references: [],
};
const workspaceByName = new Map(workspaces.map(ws => [ws.name, ws]));
const getLocalDeps = ({
dependencies = {},
devDependencies = {},
peerDependencies = {},
}) => {
return new Set(
[
...Object.keys(dependencies),
...Object.keys(devDependencies),
...Object.keys(peerDependencies),
]
.filter(k => workspaceByName.has(k))
.map(k => workspaceByName.get(k)),
);
};
await Promise.all(
workspaces.map(({ name, dir, config, tsconfig }) => {
addReference(rootTsconfig, path.relative(wsRoot, dir));
const deps = getLocalDeps(config);
if (!deps.size) return null;
for (const dep of deps) {
const publishDir =
dep.config.publishConfig && dep.config.publishConfig.directory;
addReference(tsconfig, path.relative(dir, dep.dir));
// When the dependency publishes a differenct directory than it's root
// we also need to configure a `paths` for any cherry-picked imports
if (publishDir) {
tsconfig.compilerOptions = tsconfig.compilerOptions || {};
const basePath = path.resolve(
dir,
tsconfig.compilerOptions.baseUrl || '.',
);
const relPath = path.relative(basePath, dep.dir);
tsconfig.compilerOptions.paths =
tsconfig.compilerOptions.paths || {};
tsconfig.compilerOptions.paths[`${dep.name}/*`] = [
`${relPath}/${publishDir}/*`,
];
}
}
console.log(`${name}: updating tsconfig.json`);
const filepath = `${dir}/tsconfig.json`;
return fs.writeFile(
filepath,
prettier.format(stringify(tsconfig, null, 2), { filepath }),
);
}),
);
const filepath = `${wsRoot}/tsconfig.json`;
await fs.writeFile(
filepath,
prettier.format(stringify(rootTsconfig, null, 2), { filepath }),
);
}
run();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment