cli: update watch targets when depedencies of watched package change

This commit is contained in:
Patrik Oldsberg
2020-03-05 11:49:31 +01:00
parent 5fff89ab05
commit 363739f2e9
3 changed files with 99 additions and 21 deletions
@@ -1,10 +1,9 @@
import { resolve as resolvePath } from 'path';
import { readFileSync } from 'fs';
import chalk from 'chalk';
import { createLoggerFactory } from './logger';
import { findAllDeps } from './packages';
import { startWatchers } from './watcher';
import { getPackageDeps } from './packages';
import { startWatcher, startPackageWatcher } from './watcher';
import { startCompiler } from './compiler';
import { startChild } from './child';
@@ -26,10 +25,6 @@ const WATCH_LOCATIONS = ['package.json', 'src', 'assets'];
*/
export default async (_command: any, args: string[]) => {
const localPackagePath = resolvePath('package.json');
const packageJson = JSON.parse(readFileSync(localPackagePath, 'utf8'));
// Find all direct and transitive local dependencies of the current package.
const allDeps = await findAllDeps(packageJson.name, PACKAGE_BLACKLIST);
// Rotate through different prefix colors to make it easier to differenciate between different deps
const logFactory = createLoggerFactory([
@@ -40,14 +35,22 @@ export default async (_command: any, args: string[]) => {
chalk.cyan,
]);
// Find all direct and transitive local dependencies of the current package.
const deps = await getPackageDeps(localPackagePath, PACKAGE_BLACKLIST);
// We lazily watch all our deps, as in we don't start the actual watch compiler until a change is detected
await startWatchers(allDeps, WATCH_LOCATIONS, pkg => {
const watcher = await startWatcher(deps, WATCH_LOCATIONS, pkg => {
startCompiler(pkg, logFactory(pkg.name)).promise.catch(error => {
process.stderr.write(`${error}\n`);
});
});
if (args.length) {
await startPackageWatcher(localPackagePath, async () => {
const newDeps = await getPackageDeps(localPackagePath, PACKAGE_BLACKLIST);
await watcher.update(newDeps);
});
if (args?.length) {
startChild(args);
}
};
@@ -1,4 +1,8 @@
import { resolve as resolvePath } from 'path';
import fs from 'fs';
import { promisify } from 'util';
const readFile = promisify(fs.readFile);
const LernaProject = require('@lerna/project');
const PackageGraph = require('@lerna/package-graph');
@@ -44,3 +48,10 @@ export async function findAllDeps(
return [...deps.values()];
}
export async function getPackageDeps(packagePath: string, blacklist: string[]) {
const packageData = await readFile(packagePath, 'utf8');
const packageJson = JSON.parse(packageData);
return await findAllDeps(packageJson.name, blacklist);
}
@@ -1,6 +1,12 @@
import { resolve as resolvePath } from 'path';
import chalk from 'chalk';
import chokidar from 'chokidar';
import { Package } from './packages';
import { createLogger } from './logger';
export type Watcher = {
update(newPackages: Package[]): Promise<void>;
};
/*
* Watch for changes inside a collection of packages. When a change is detected, stop
@@ -8,15 +14,17 @@ import { Package } from './packages';
*
* The returned promise is resolved once all watchers are ready.
*/
export async function startWatchers(
export async function startWatcher(
packages: Package[],
paths: string[],
callback: (pkg: Package) => void,
): Promise<void> {
const readyPromises = [];
): Promise<Watcher> {
const watchedPackageLocations = new Set<string>();
const logger = createLogger();
for (const pkg of packages) {
const watchPackage = async (pkg: Package) => {
let signalled = false;
watchedPackageLocations.add(pkg.location);
const watchLocations = paths.map(path => resolvePath(pkg.location, path));
const watcher = chokidar
@@ -33,13 +41,69 @@ export async function startWatchers(
watcher.close();
});
readyPromises.push(
new Promise((resolve, reject) => {
watcher.on('ready', resolve);
watcher.on('error', reject);
}),
);
}
return new Promise((resolve, reject) => {
watcher.on('ready', resolve);
watcher.on('error', reject);
});
};
await Promise.all(readyPromises);
const update = async (newPackages: Package[]) => {
const promises = new Array<Promise<unknown>>();
for (const pkg of newPackages) {
if (watchedPackageLocations.has(pkg.location)) {
continue;
}
logger.out(chalk.green(`Starting watch of new dependency ${pkg.name}`));
promises.push(watchPackage(pkg));
}
await Promise.all(promises);
};
await Promise.all(packages.map(watchPackage));
return { update };
}
/**
* Watch a package.json for updates
*/
export function startPackageWatcher(packagePath: string, callback: () => void) {
let changed = false;
let working = false;
const notifyDeps = async () => {
changed = true;
if (working) {
return;
}
working = true;
changed = false;
try {
await callback();
} finally {
working = false;
// Keep going if a change was emitted while working
if (changed) {
notifyDeps();
}
}
};
const watcher = chokidar
.watch(packagePath, {
ignoreInitial: true,
disableGlobbing: true,
})
.on('all', () => {
notifyDeps();
});
return new Promise((resolve, reject) => {
watcher.on('ready', resolve);
watcher.on('error', reject);
});
}