// // Copyright 2021 DXOS.org // import chalk from 'chalk'; import { readFileSync, readdirSync } from 'fs'; import { extname, join } from 'path'; import { sortPackageJson } from 'sort-package-json'; // TODO(burdon): When is babel required? // TODO(burdon): Check package.json scripts for for playright, concurrently, etc. const WHITELIST_DEV_DEPS = [ '@types/node', '@types/react', '@types/react-dom', '@dxos/eslint-plugin', '@dxos/toolchain-node-library', '@dxos/esbuild-plugins', '@dxos/esbuild-server', 'core-js', 'eslint', 'eslint-plugin-react', 'ts-node', 'typescript' ]; const WHITELIST_PEER_DEPS = [ '@emotion/react', '@emotion/styled', '@mui/icons-material', '@mui/lab', '@mui/material', 'react', 'react-dom' ]; const ALLOWED_EXTENSIONS = [ '.ts', '.tsx', '.js', '.jsx' ]; const BLACKLISTED_NAMES = [ '.rush', 'build', 'dist', 'node_modules', 'temp', ]; function * getAllSourceFiles (dir: string = process.cwd()): Generator { for (const entry of readdirSync(dir, { withFileTypes: true })) { if (BLACKLISTED_NAMES.includes(entry.name)) { continue; } if (entry.isDirectory()) { yield * getAllSourceFiles(join(dir, entry.name)); } else if (entry.isFile()) { if (ALLOWED_EXTENSIONS.includes(extname(entry.name))) { yield join(dir, entry.name); } } } } async function readFiles (files: string[]): Promise> { const res: Record = {}; await Promise.all(files.map(async (file) => { res[file] = await readFileSync(file, 'utf8'); })); return res; } export const checkStaleDeps = async (filename = 'package.json', options: { whitelist?: boolean } = {}) => { const { whitelist = false } = options; const { scripts, dependencies, devDependencies, peerDependencies } = JSON.parse(readFileSync(filename, 'utf8')); // Check scripts for devDependencies. const tools = Object.values(scripts).reduce>((result, value) => { const commands = (value as string).split('&&'); for (const command of commands) { const tool = command.trim().split(' ')[0]; if (tool) { result.add(tool); } } return result; }, new Set()); const allDeps = Object.keys({ ...(dependencies ?? {}), ...(devDependencies ?? {}), ...(peerDependencies ?? {}) }); const sourceFiles = Array.from(getAllSourceFiles()); const sourceFilesContent = await readFiles(sourceFiles); console.log(chalk.green('Unreferenced by sources (check peerDependencies and tools):')); for (const dep of allDeps) { if (tools.has(dep)) { continue; } if (whitelist && [...WHITELIST_DEV_DEPS, ...WHITELIST_PEER_DEPS].includes(dep)) { continue; } const searchStrings = [dep]; if (dep.startsWith('@types/')) { searchStrings.push(dep.slice('@types/'.length)); } const depExists = Object.values(sourceFilesContent).some(content => searchStrings.some(ss => content.includes(ss))); if (!depExists) { console.log('-', chalk`{blue ${dep}}`); } } };