#!/usr/bin/env node // For each npm package, prints its four newest major lines: first release, // latest version and when that latest version was published. A line with no // release for a long time has probably stopped receiving patches. // // Backs the version table in docs/GO-LIVE-PLAN.md §4. Re-run it before acting // on that table — the numbers there are only true for the day they were taken. // // node scripts/check-majors.mjs # the packages the plan tracks // node scripts/check-majors.mjs vite pinia # any packages // // Goes through the npm CLI rather than fetch() so the machine's registry or // mirror settings (.npmrc) apply. import { execSync } from 'node:child_process'; const TRACKED = [ 'vue', 'vite', '@vitejs/plugin-vue', 'vite-plugin-pwa', 'vue-router', 'pinia', 'typescript', 'vue-tsc', 'tailwindcss', 'ant-design-vue', 'vant', '@vueuse/core', '@nestjs/core', 'prisma', ]; // Package names only — they are interpolated into a shell command. const PACKAGE_NAME = /^(@[a-z0-9][\w.-]*\/)?[a-z0-9][\w.-]*$/i; const STALE_AFTER_DAYS = 180; const pkgs = process.argv.length > 2 ? process.argv.slice(2) : TRACKED; const now = Date.now(); for (const pkg of pkgs) { if (!PACKAGE_NAME.test(pkg)) { console.log(`\n${pkg}: not a package name, skipped`); continue; } let published; try { published = JSON.parse( execSync(`npm view ${pkg} time --json`, { encoding: 'utf8', timeout: 60_000, stdio: ['ignore', 'pipe', 'ignore'], }), ); } catch { console.log(`\n${pkg}: npm view failed`); continue; } const lines = new Map(); for (const [version, date] of Object.entries(published)) { // Stable releases only; this also drops the `created` / `modified` keys. if (!/^\d+\.\d+\.\d+$/.test(version)) continue; const major = Number(version.split('.')[0]); const line = lines.get(major) ?? { firstVersion: version, first: date, lastVersion: version, last: date }; if (date < line.first) Object.assign(line, { firstVersion: version, first: date }); if (date > line.last) Object.assign(line, { lastVersion: version, last: date }); lines.set(major, line); } console.log(`\n${pkg}`); const newestFirst = [...lines].sort(([a], [b]) => b - a).slice(0, 4); for (const [major, line] of newestFirst) { const idleDays = Math.floor((now - Date.parse(line.last)) / 86_400_000); const stale = idleDays > STALE_AFTER_DAYS ? ` <- no release for ${idleDays} days` : ''; console.log( ` v${String(major).padEnd(3)} first ${line.firstVersion.padEnd(9)} ${line.first.slice(0, 10)}` + ` latest ${line.lastVersion.padEnd(9)} ${line.last.slice(0, 10)}${stale}`, ); } }