import { readPackageJSON, writePackageJSON } from 'pkg-types'; import { accessSync, constants, existsSync } from 'node:fs'; import { spawnSync } from 'node:child_process'; import { styleText } from 'node:util'; import { replaceInFile, npmRun } from './lib'; /* Constants */ const FLAKE = process.env.FLAKE as string; const PINS = new Map([]); const updatePackageJson = async(workspaceDir: string, updates: object) => { const currentPackageJson = await readPackageJSON(`${workspaceDir}/package.json`); const outdated = JSON.parse(npmRun(['outdated', '--json'], workspaceDir)); const updateDeps = (deps: string) => { Object.keys(currentPackageJson[deps]).forEach(async(dep) => { if (dep === 'astal') { const latestCommit = JSON.parse(spawnSync( ['curl', '-s', 'https://api.github.com/repos/Aylur/astal/commits/main'].join(' '), [], { shell: true }, ).stdout.toString()).sha; currentPackageJson[deps][dep] = `https://gitpkg.vercel.app/Aylur/astal/lang/gjs/src?${latestCommit}`; return; } if (PINS.has(dep)) { currentPackageJson[deps][dep] = PINS.get(dep); return; } const versions = outdated[dep]; const current = versions?.wanted || versions?.current; if (!current) { return; } if (current !== versions.latest) { updates[dep] = `${current} -> ${versions.latest}`; } currentPackageJson[deps][dep] = versions.latest; }); }; if (currentPackageJson.dependencies) { updateDeps('dependencies'); } if (currentPackageJson.devDependencies) { updateDeps('devDependencies'); } await writePackageJSON(`${workspaceDir}/package.json`, currentPackageJson); }; const prefetchNpmDeps = (workspaceDir: string): string => { npmRun(['update', '--package-lock-only'], workspaceDir); return spawnSync( 'prefetch-npm-deps', [`${workspaceDir}/package-lock.json`], ).stdout.toString().replace('\n', ''); }; export default async(): Promise<string | null> => { console.log(styleText(['magenta'], '\nUpdating node modules:\n')); const updates = {}; const packages = spawnSync('find', [FLAKE, '-name', 'package.json']).stdout.toString().split('\n') .filter((f) => f !== '') .filter((f) => ![ '.direnv', 'node_modules', 'results', ].some((dirName) => f.includes(dirName))); for (const path of packages) { console.log(path); try { accessSync(path, constants.R_OK | constants.W_OK); const parentPath = path.replace('/package.json', ''); await updatePackageJson(parentPath, updates); if (existsSync(`${parentPath}/default.nix`)) { const hash = prefetchNpmDeps(parentPath); replaceInFile( /npmDepsHash = ".*";/, `npmDepsHash = "${hash}";`, `${parentPath}/default.nix`, ); } // Make sure we update the apps' config package-lock.json if (parentPath.includes('apps/config')) { npmRun(['i'], parentPath); } } catch (e) { console.warn(`Could not write to ${path}`); console.warn(e); } } return Object.entries(updates).length > 0 ? Object.entries(updates) .map(([key, dep]) => `${key}: ${dep}`) .join('\n') : null; };