scripts/deno/check-npm-scripts.ts (131 lines of code) (raw):
/**
* Runs all npm-scripts in the workspace one at a time, with no caching or old build files in place.
*
* This should help pinpoint eny issues with how we've orchestrated running them, defined dependent tasks etc.
*
* If you only want to run a specific package, you can pass the package name as an argument, e.g.
*
* ```sh
* deno run -A scripts/deno/check-npm-scripts.ts @guardian/libs
* ```
*/
import { exists } from 'https://deno.land/std@0.224.0/fs/exists.ts';
import * as fmt from 'https://deno.land/std@0.224.0/fmt/colors.ts';
import { expandGlob } from 'https://deno.land/std@0.224.0/fs/expand_glob.ts';
import { existsSync } from 'https://deno.land/std@0.224.0/fs/exists.ts';
import { relative } from 'https://deno.land/std@0.224.0/path/mod.ts';
interface Package {
path: string;
scripts: { [key: string]: string };
}
interface ErrorLog {
packageName: string;
script: string;
error: string;
}
async function getWorkspacePackages(pkgName: string | undefined) {
try {
const command = new Deno.Command('pnpm', {
args: [...(pkgName ? [`--filter`, pkgName] : ['-r']), 'ls', '--json'],
});
const { success, stderr, stdout } = await command.output();
if (!success) {
throw new Error(new TextDecoder().decode(stderr));
}
const result = new TextDecoder().decode(stdout);
const packages = JSON.parse(result) as { path: string }[];
const packageDetails: Package[] = [];
for (const pkg of packages) {
const packageJsonPath = `${pkg.path}/package.json`;
if (await exists(packageJsonPath)) {
const packageJson = JSON.parse(
await Deno.readTextFile(packageJsonPath),
);
if (packageJson.scripts) {
packageDetails.push({
path: pkg.path,
scripts: packageJson.scripts,
});
}
}
}
return packageDetails;
} catch (error) {
console.error('Failed to get workspace packages');
console.error(error);
}
}
async function deleteDirs(dirName: string) {
const distDirectories = expandGlob(`**/${dirName}`, {
globstar: true,
exclude: ['**/node_modules/**'],
});
for await (const distDir of distDirectories) {
if (distDir.isDirectory && existsSync(distDir.path)) {
const relativePath = relative(Deno.cwd(), distDir.path);
try {
await Deno.remove(distDir.path, { recursive: true });
console.log(fmt.green('✓') + fmt.dim(` deleted ${relativePath}`));
} catch (error) {
console.error(`Failed to remove: ${relativePath}`);
console.error(error);
}
}
}
}
async function runNpmScript(pkgPath: string, script: string) {
// Skip these scripts as they are long-running
if (
[
'dev',
'start',
'storybook',
'create-icons',
'update-readme',
'e2e:ui',
].includes(script)
) {
return { result: 'skipping', error: '' };
}
const command = new Deno.Command('pnpm', {
args: ['run', script],
cwd: pkgPath,
});
const { success, stdout, stderr } = await command.output();
if (success) {
return new TextDecoder().decode(stdout);
}
throw new Error(new TextDecoder().decode(stderr));
}
async function main(pkgName: string | undefined) {
const packages = (await getWorkspacePackages(pkgName)) ?? [];
const errorLogs: ErrorLog[] = [];
for (const pkg of packages) {
for (const [scriptName] of Object.entries(pkg.scripts)) {
console.log(`Cleaning wireit caches...`);
await deleteDirs('.wireit');
console.log(`Cleaning dist folders...`);
await deleteDirs('dist');
const relativePath = [relative(Deno.cwd(), pkg.path), 'package.json']
.filter(Boolean)
.join('/');
try {
console.log(
`Running ${fmt.blue(scriptName)} from ${fmt.cyan(relativePath)}...`,
);
await runNpmScript(pkg.path, scriptName);
console.log(fmt.green('✓') + fmt.dim(` success`));
} catch (error) {
console.log(fmt.red(`❌ Failed`));
console.error(error.message);
errorLogs.push({
packageName: pkg.path,
script: scriptName,
error: error.message,
});
}
console.log('');
}
}
if (errorLogs.length > 0) {
console.log(`\n ${errorLogs.length} scripts failed:`);
for (const log of errorLogs) {
console.log(`Package: ${log.packageName}`);
console.log(`Script: ${log.script}`);
console.log(`Error: ${log.error}\n`);
}
} else {
console.log('All scripts ran successfully.');
}
}
if (import.meta.main) {
await main(Deno.args[0]);
}