@ladamczyk/outdated 1.1.0 → 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/cli.js +1 -1
- package/lib/index.cjs +164 -0
- package/lib/index.mjs +140 -1
- package/lib/src/index.d.ts +1 -0
- package/package.json +1 -1
package/bin/cli.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{EExitCode as e,PIXEL_URL as t,STATS_URL as n,executeCommand as r,sendStats as i}from"@ladamczyk/qoq-utils";import a from"cac";import{basename as o,dirname as s,join as c,resolve as l}from"path";import{mkdirSync as u,readFileSync as d,writeFileSync as f}from"fs";import p from"picocolors";import{homedir as m}from"os";import{createInterface as h}from"readline/promises";var ee=`1.
|
|
2
|
+
import{EExitCode as e,PIXEL_URL as t,STATS_URL as n,executeCommand as r,sendStats as i}from"@ladamczyk/qoq-utils";import a from"cac";import{basename as o,dirname as s,join as c,resolve as l}from"path";import{mkdirSync as u,readFileSync as d,writeFileSync as f}from"fs";import p from"picocolors";import{homedir as m}from"os";import{createInterface as h}from"readline/promises";var ee=`1.1.0`;const te=[[`prod`,`dependencies`],[`optional`,`optionalDependencies`],[`dev`,`devDependencies`]],ne=[`file:`,`link:`,`workspace:`,`git+`,`git:`,`github:`,`http:`,`https:`],re=/^(?:@[A-Za-z0-9~-][A-Za-z0-9._~-]*\/)?[A-Za-z0-9~-][A-Za-z0-9._~-]*$/,g=e=>{try{let t=JSON.parse(d(e,`utf8`));return typeof t==`object`&&t?t:null}catch{return null}},ie=e=>{if(!e.startsWith(`npm:`))return null;let t=e.slice(4),n=t.lastIndexOf(`@`);return n>0?t.slice(0,n):t},ae=(e,t,n)=>{for(let[r,i]of te){let a=e[i];if(t.includes(r)&&typeof a==`object`&&a)for(let[e,t]of Object.entries(a))!n.has(e)&&typeof t==`string`&&n.set(e,{type:r,specifier:t})}};function oe(e,{include:t,ignore:n}){let r=c(e,`package.json`),i=g(r);if(!i)throw Error(`Cannot read or parse package.json at ${r}`);let a=/* @__PURE__ */ new Map;ae(i,t,a);let s=[],l=[];for(let[e,{type:t,specifier:r}]of a){if(n.includes(e))continue;let i=ne.find(e=>r.startsWith(e));if(i){l.push({name:e,type:t,specifier:r,reason:`unsupported specifier (${i})`});continue}let a=ie(r);s.push({name:e,type:t,specifier:r,queriedName:a,validName:re.test(a??e)})}return{project:typeof i.name==`string`&&i.name?i.name:o(e),entries:s,skipped:l}}function se(e,t){let n=g(c(e,`node_modules`,t,`package.json`))?.version;return typeof n==`string`?n:null}const _=e=>typeof e==`object`&&!!e&&!Array.isArray(e),v=e=>typeof e==`string`?e:null,y=e=>JSON.parse(e),ce=e=>{let t=Array.isArray(e)?e[0]:e,n=_(t)?t:{};return{current:v(n.current),wanted:v(n.wanted),latest:v(n.latest)}},b=e=>!_(e)||e.code===void 0&&e.summary===void 0?null:[v(e.code),v(e.summary)].filter(Boolean).join(` `),le=e=>{if(e.trim()===``)throw Error(`npm outdated printed empty output`);let t;try{t=y(e)}catch{throw Error(`npm outdated printed invalid JSON`)}if(!_(t))throw Error(`npm outdated printed non-object JSON`);let n=b(t.error);if(n!==null)throw Error(`npm outdated failed: ${n}`);return Object.fromEntries(Object.entries(t).map(([e,t])=>[e,ce(t)]))},ue=e=>_(e)&&typeof e.node==`string`&&e.node!==``?e.node:null,de=e=>{let t=_(e)?e.url:e;if(typeof t!=`string`||t===``)return null;let n=t.replace(/^git\+/,``).replace(/^(?:git|ssh):\/\/(?:git@)?/,`https://`).replace(/^git@([^:]+):/,`https://$1/`).replace(/^(?:github:)?([\w.-]+\/[\w.-]+)$/,`https://github.com/$1`).replace(/\.git$/,``);return/^https?:\/\//.test(n)?n:null},x={unknown:`unexpected npm view output`},fe=e=>{let t=e[`dist-tags`],n=_(t)?v(t.latest):null;if(n===null||!_(e.time))return x;let r=v(e.time[n]);if(r===null||Number.isNaN(Date.parse(r)))return{unknown:`no valid publish time for ${n}`};let i=e.deprecated;return{latest:n,lastPublish:r,deprecated:typeof i==`string`&&i!==``?i:null,latestNode:ue(e.engines),repository:de(e.repository)}},pe=e=>{let t;try{t=y(e)}catch{return{unknown:`invalid npm view JSON`}}return _(t)?`error`in t?{unknown:b(t.error)??`npm view error`}:fe(t):x};async function S(e,t,n){let r=Array(e.length),i=/* @__PURE__ */ new Set;for(let[a,o]of e.entries()){let e=n(o).then(t=>{r[a]=t,i.delete(e)});i.add(e),i.size===t&&await Promise.race(i)}return await Promise.all(i),r}const C=String.raw`'\''`,w=e=>process.platform===`win32`?`"${e.replaceAll(`"`,`""`)}"`:`'${e.replaceAll(`'`,C)}'`,T=()=>async e=>{if(e.some(e=>/[\0\n\r]/.test(e)))throw Error(`npm argument contains a control character`);let t=await r([`npm`,...e.map(w)].join(` `),[],[`ignore`,`pipe`,`pipe`],!0);return String(t)};function E(e,t){let n=e.getUTCFullYear()*12+e.getUTCMonth()+t,r=Math.floor(n/12),i=n-r*12,a=new Date(Date.UTC(r,i+1,0)).getUTCDate(),o=new Date(e);return o.setUTCFullYear(r,i,Math.min(e.getUTCDate(),a)),o}function D(e,t){let n=new Date(e);if(n>t)return`0d ago`;let r=t.getTime()-n.getTime(),i=Math.floor(r/864e5);if(i<30)return`${i}d ago`;let a=0;for(;E(n,a+1)<=t;)a++;return a<12?`${a}m ago`:`${Math.floor(a/12)}y ago`}const O=[`prod`,`dev`,`optional`],k=e=>{let t=/^v?(\d+)/.exec(e??``);return t?Number(t[1]):null},A=(e,t)=>{let n=k(e),r=k(t);return n!==null&&r!==null&&r>n},j=async(e,t)=>{let n,r=new Promise((e,r)=>{n=setTimeout(()=>{r(/* @__PURE__ */ Error(`npm view timed out after ${t}ms`))},t)});try{return await Promise.race([e,r])}finally{clearTimeout(n)}},M=async(e,t)=>{if(!e.validName)return{unknown:`invalid package name`};let n=[`view`,e.queriedName??e.name,`time`,`dist-tags`,`deprecated`,`engines`,`repository`,`--json`,`--prefix`,t.cwd];try{return pe(await j(t.runner(n),t.timeoutMs))}catch(e){return{unknown:e instanceof Error?e.message:String(e)}}},N=e=>e.queriedName===null?e.name:`${e.name}:${e.specifier.replace(/^npm:/,``)}`,P=(e,t,n,r)=>{let i=n?n.latest:r;return t!==null&&t.latestNode!==null&&t.deprecated===null&&e!==null&&i!==null&&i!==e},F=(e,t)=>{let n=[];return e.deprecated!==null&&n.push(`deprecated`),e.stale&&n.push(`stale`),e.outdated&&n.push(`outdated`),e.unknown!==null&&n.push(`unknown`),t&&n.push(`blocked`),n.length>0?n:[`ok`]},I=(e,t,n,r)=>{let i=`latest`in t?t:null,a=n?.current??se(r.cwd,e.name),o=i?.latest??n?.latest??null,s=i?new Date(i.lastPublish):null,c=n!==void 0,l={name:e.name,queriedName:e.queriedName,type:e.type,specifier:e.specifier,current:a,wanted:n?.wanted??null,latest:o,outdated:c,majorBump:c&&A(a,o),deprecated:i?.deprecated??null,latestNode:i?.latestNode??null,repository:i?.repository??null,lastPublish:s?s.toISOString():null,stale:s!==null&&s<E(r.now,-r.staleAfterMonths),unknown:`unknown`in t?t.unknown:null};return{...l,flags:F(l,P(o,i,n,a))}},L=(e,t)=>{let n=t=>e.filter(e=>e.flags.includes(t)).length;return{total:e.length,deprecated:n(`deprecated`),stale:n(`stale`),outdated:n(`outdated`),unknown:n(`unknown`),blocked:n(`blocked`),ok:n(`ok`),skipped:t}},R=async(e={})=>{let t=l(e.cwd??process.cwd()),n=e.staleAfterMonths??6,r=e.now??/* @__PURE__ */ new Date,i=e.runner??T(),{project:a,entries:o,skipped:s}=oe(t,{include:e.include??O,ignore:e.ignore??[]}),c=le(await i([`outdated`,`--json`,`--prefix`,t])),u={cwd:t,runner:i,timeoutMs:e.timeoutMs??3e4,now:r,staleAfterMonths:n,outdated:c},d=await S(o,e.concurrency??6,async e=>I(e,await M(e,u),c[N(e)],u));return{schemaVersion:1,generatedAt:r.toISOString(),staleAfterMonths:n,project:a,packages:d,summary:L(d,s.length),skipped:s.sort((e,t)=>e.name<t.name?-1:Number(e.name>t.name))}},z=[`Package`,`Current`,`Wanted`,`Latest`,`Location`,`Depended by`,`Last publish`,`Links`,`Flags`],B=/* @__PURE__ */ new Set([1,2,3]),me=[[`prod`,`dependencies`],[`dev`,`devDependencies`],[`optional`,`optionalDependencies`]],he=e=>`${JSON.stringify(e,null,2)}\n`,ge=e=>e.length>60?`${e.slice(0,59)}…`:e,_e=(e,t)=>t===`deprecated`?`deprecated ${ge(e.deprecated??``)}`:t===`blocked`?`latest needs node ${e.latestNode??``}`:t===`unknown`?`unknown ${e.unknown??``}`:t,ve=(e,t)=>e.lastPublish===null?`-`:`${e.lastPublish.slice(0,10)} (${D(e.lastPublish,t)})`,ye=e=>e.repository===null?`npm`:`npm / repo`,be=(e,t,n)=>[e.name,e.current??`-`,e.wanted??e.current??`-`,e.latest??`-`,`node_modules/${e.name}`,t,ve(e,n),ye(e)],xe=e=>({deprecated:e.red,stale:e.yellow,outdated:e.cyan,unknown:e.magenta,blocked:e.blue,ok:e.green}),Se=(e,t)=>{let n=xe(t);return e.flags.map(t=>n[t](_e(e,t))).join(` `)},Ce=(e,t)=>`\u001b]8;;${e}\u001b\\${t}\u001b]8;;\u001b\\`,we=e=>`https://www.npmjs.com/package/${e.queriedName??e.name}`,V=e=>e,Te=(e,t)=>e.outdated?e.current===e.wanted?t.yellow:t.red:V,Ee=(e,t)=>e.majorBump?t.red:e.outdated?t.magenta:V,De=e=>t=>t.split(` / `).map((t,n)=>Ce(n===0?we(e):e.repository??``,t)).join(` / `),Oe=(e,t,n)=>[Te(e,t),V,e.outdated?t.green:V,Ee(e,t),t.dim,t.dim,V,n?De(e):V],H=(e,t,n,r)=>{let i=` `.repeat(Math.max(0,t-e.length));return B.has(n)?i+r(e):r(e)+i},ke=(e,t,n,r,i)=>[...Oe(e,r,i).map((e,r)=>H(t[r]??``,n[r]??0,r,e)),Se(e,r)].join(` `).trimEnd(),Ae=(e,t,n,r,i)=>{let a=e.map(e=>({pkg:e,cells:be(e,t,n)})),o=z.map((e,t)=>Math.max(e.length,...a.map(e=>e.cells[t]?.length??0))),s=z.map((e,t)=>t===z.length-1?e:H(e,o[t]??0,t,V)),c=[r.underline(r.bold(s.join(` `)))];for(let[e,t]of me){let n=a.filter(t=>t.pkg.type===e);n.length>0&&(c.push(``,r.bold(`${t} (${n.length})`)),c.push(...n.map(e=>ke(e.pkg,e.cells,o,r,i))))}return c},je=(e,t)=>{let n=e.summary;return[`${n.total} packages`,t.red(`${n.deprecated} deprecated`),t.yellow(`${n.stale} stale`),t.cyan(`${n.outdated} outdated`),t.magenta(`${n.unknown} unknown`),t.blue(`${n.blocked} blocked`),t.green(`${n.ok} ok`)].join(` · `)},Me=(e,t)=>{let n=t.color??p.isColorSupported,r=p.createColors(n),i=t.onlyProblems?e.packages.filter(e=>e.flags.length!==1||e.flags[0]!==`ok`):e.packages,a=e.packages.length===0?[`No dependencies found`]:Ae(i,e.project,t.now,r,n);if(a.push(``,je(e,r)),e.skipped.length>0){a.push(``,r.bold(`Skipped:`));let t=Math.max(...e.skipped.map(e=>e.name.length));for(let n of e.skipped)a.push(` ${n.name.padEnd(t)} ${n.specifier} — ${n.reason}`)}return`${a.join(`
|
|
3
3
|
`)}\n`},U=[`prod`,`dev`,`optional`],Ne=[`stale`,`deprecated`,`outdated`,`unknown`],Pe=/* @__PURE__ */ new Set([`--`,`cwd`,`staleAfter`,`stale-after`,`include`,`ignore`,`concurrency`,`json`,`onlyProblems`,`only-problems`,`failOn`,`fail-on`,`h`,`v`,`help`,`version`]),W=e=>Array.isArray(e)?e.map(W).join(`,`):typeof e==`string`||typeof e==`number`||typeof e==`boolean`?String(e):JSON.stringify(e)??`undefined`,G=(e,t,n)=>e[t]??e[n],K=(e,t,n)=>{if(e===void 0)return n;let r=W(e);if(!/^[1-9]\d*$/.test(r))throw Error(`${t} must be a positive integer, got "${r}"`);return Number(r)},q=(e,t)=>{let n=W(e),r=n.split(`,`);if(typeof e==`boolean`||r.includes(``))throw Error(`${t} must be a comma-separated list without empty entries, got "${n}"`);return r},J=(e,t,n,r)=>{if(e===void 0)return r;let i=q(e,t),a=i.find(e=>!n.includes(e));if(a!==void 0)throw Error(`${t} has invalid value "${a}"; allowed: ${n.join(`, `)}`);return i},Y=(e,t)=>{if(e===void 0)return!1;if(typeof e!=`boolean`)throw Error(`${t} is a switch and takes no value, got "${W(e)}"`);return e},Fe=e=>{if(e!==void 0){if(typeof e!=`string`&&typeof e!=`number`)throw Error(`--cwd must be a single path, got "${W(e)}"`);return{cwd:String(e)}}},Ie=e=>{let t=Object.keys(e).find(e=>!Pe.has(e));if(t!==void 0)throw Error(`Unknown option: ${t}`);let{ignore:n}=e;return{...Fe(e.cwd),staleAfterMonths:K(G(e,`staleAfter`,`stale-after`),`--stale-after`,6),include:J(e.include,`--include`,U,U),ignore:n===void 0?[]:q(n,`--ignore`),concurrency:K(e.concurrency,`--concurrency`,6),json:Y(e.json,`--json`),onlyProblems:Y(G(e,`onlyProblems`,`only-problems`),`--only-problems`),failOn:J(G(e,`failOn`,`fail-on`),`--fail-on`,Ne,[])}},Le=(t,n)=>n.some(e=>t.summary[e]>0)?e.ERROR:e.OK,X=c(process.env.XDG_CONFIG_HOME??c(m(),`.config`),`outdated.json`),Z=e=>{try{let t=JSON.parse(d(e,`utf8`));return typeof t==`object`&&t?t:{}}catch{return{}}},Re=(e,t)=>{try{u(s(e),{recursive:!0}),f(e,`${JSON.stringify({...Z(e),stats:t},null,2)}\n`)}catch{}},ze=async()=>{process.stdout.write([p.bold(`
|
|
4
4
|
Outdated usage stats
|
|
5
5
|
`),`Send a count of outdated runs to ${n}? Each run posts one thing:\n`,` • the tool name — always the literal ${p.cyan(`"outdated"`)}\n`,p.gray(`Blocked POST? The same values go to ${t} as a GET.\n`),p.gray(`Never sent: your dependencies, package.json, paths, versions, findings,
|
package/lib/index.cjs
CHANGED
|
@@ -1,7 +1,31 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
//#region \0rolldown/runtime.js
|
|
3
|
+
var __create = Object.create;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __copyProps = (to, from, except, desc) => {
|
|
10
|
+
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
11
|
+
key = keys[i];
|
|
12
|
+
if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
|
|
13
|
+
get: ((k) => from[k]).bind(null, key),
|
|
14
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
return to;
|
|
18
|
+
};
|
|
19
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule || !__hasOwnProp.call(mod, "default") ? __defProp(target, "default", {
|
|
20
|
+
value: mod,
|
|
21
|
+
enumerable: true
|
|
22
|
+
}) : target, mod));
|
|
23
|
+
//#endregion
|
|
2
24
|
let path = require("path");
|
|
3
25
|
let fs = require("fs");
|
|
4
26
|
let _ladamczyk_qoq_utils = require("@ladamczyk/qoq-utils");
|
|
27
|
+
let picocolors = require("picocolors");
|
|
28
|
+
picocolors = __toESM(picocolors, 1);
|
|
5
29
|
//#region src/deps.ts
|
|
6
30
|
const FIELDS = [
|
|
7
31
|
["prod", "dependencies"],
|
|
@@ -187,6 +211,17 @@ function addMonths(date, months) {
|
|
|
187
211
|
result.setUTCFullYear(year, month, Math.min(date.getUTCDate(), lastDay));
|
|
188
212
|
return result;
|
|
189
213
|
}
|
|
214
|
+
function relativeAge(iso, now) {
|
|
215
|
+
const date = new Date(iso);
|
|
216
|
+
if (date > now) return "0d ago";
|
|
217
|
+
const diffMs = now.getTime() - date.getTime();
|
|
218
|
+
const days = Math.floor(diffMs / 864e5);
|
|
219
|
+
if (days < 30) return `${days}d ago`;
|
|
220
|
+
let months = 0;
|
|
221
|
+
while (addMonths(date, months + 1) <= now) months++;
|
|
222
|
+
if (months < 12) return `${months}m ago`;
|
|
223
|
+
return `${Math.floor(months / 12)}y ago`;
|
|
224
|
+
}
|
|
190
225
|
//#endregion
|
|
191
226
|
//#region src/check.ts
|
|
192
227
|
const ALL_TYPES = [
|
|
@@ -326,4 +361,133 @@ const check = async (options = {}) => {
|
|
|
326
361
|
};
|
|
327
362
|
};
|
|
328
363
|
//#endregion
|
|
364
|
+
//#region src/format.ts
|
|
365
|
+
const HEADER = [
|
|
366
|
+
"Package",
|
|
367
|
+
"Current",
|
|
368
|
+
"Wanted",
|
|
369
|
+
"Latest",
|
|
370
|
+
"Location",
|
|
371
|
+
"Depended by",
|
|
372
|
+
"Last publish",
|
|
373
|
+
"Links",
|
|
374
|
+
"Flags"
|
|
375
|
+
];
|
|
376
|
+
const RIGHT_ALIGNED = /* @__PURE__ */ new Set([
|
|
377
|
+
1,
|
|
378
|
+
2,
|
|
379
|
+
3
|
|
380
|
+
]);
|
|
381
|
+
const SECTIONS = [
|
|
382
|
+
["prod", "dependencies"],
|
|
383
|
+
["dev", "devDependencies"],
|
|
384
|
+
["optional", "optionalDependencies"]
|
|
385
|
+
];
|
|
386
|
+
const MAX_MESSAGE = 60;
|
|
387
|
+
const formatJson = (result) => `${JSON.stringify(result, null, 2)}\n`;
|
|
388
|
+
const truncate = (text) => text.length > MAX_MESSAGE ? `${text.slice(0, 59)}…` : text;
|
|
389
|
+
const flagText = (pkg, flag) => {
|
|
390
|
+
if (flag === "deprecated") return `deprecated ${truncate(pkg.deprecated ?? "")}`;
|
|
391
|
+
if (flag === "blocked") return `latest needs node ${pkg.latestNode ?? ""}`;
|
|
392
|
+
if (flag === "unknown") return `unknown ${pkg.unknown ?? ""}`;
|
|
393
|
+
return flag;
|
|
394
|
+
};
|
|
395
|
+
const publishText = (pkg, now) => pkg.lastPublish === null ? "-" : `${pkg.lastPublish.slice(0, 10)} (${relativeAge(pkg.lastPublish, now)})`;
|
|
396
|
+
const linksText = (pkg) => pkg.repository === null ? "npm" : "npm / repo";
|
|
397
|
+
const toCells = (pkg, project, now) => [
|
|
398
|
+
pkg.name,
|
|
399
|
+
pkg.current ?? "-",
|
|
400
|
+
pkg.wanted ?? pkg.current ?? "-",
|
|
401
|
+
pkg.latest ?? "-",
|
|
402
|
+
`node_modules/${pkg.name}`,
|
|
403
|
+
project,
|
|
404
|
+
publishText(pkg, now),
|
|
405
|
+
linksText(pkg)
|
|
406
|
+
];
|
|
407
|
+
const flagColors = (c) => ({
|
|
408
|
+
deprecated: c.red,
|
|
409
|
+
stale: c.yellow,
|
|
410
|
+
outdated: c.cyan,
|
|
411
|
+
unknown: c.magenta,
|
|
412
|
+
blocked: c.blue,
|
|
413
|
+
ok: c.green
|
|
414
|
+
});
|
|
415
|
+
const flagsCell = (pkg, c) => {
|
|
416
|
+
const colors = flagColors(c);
|
|
417
|
+
return pkg.flags.map((flag) => colors[flag](flagText(pkg, flag))).join(" ");
|
|
418
|
+
};
|
|
419
|
+
const hyperlink = (url, text) => `\u001b]8;;${url}\u001b\\${text}\u001b]8;;\u001b\\`;
|
|
420
|
+
const npmUrl = (pkg) => `https://www.npmjs.com/package/${pkg.queriedName ?? pkg.name}`;
|
|
421
|
+
const plain = (text) => text;
|
|
422
|
+
const nameColor = (pkg, c) => {
|
|
423
|
+
if (!pkg.outdated) return plain;
|
|
424
|
+
return pkg.current === pkg.wanted ? c.yellow : c.red;
|
|
425
|
+
};
|
|
426
|
+
const latestColor = (pkg, c) => {
|
|
427
|
+
if (pkg.majorBump) return c.red;
|
|
428
|
+
return pkg.outdated ? c.magenta : plain;
|
|
429
|
+
};
|
|
430
|
+
const linkPaint = (pkg) => (text) => text.split(" / ").map((label, i) => hyperlink(i === 0 ? npmUrl(pkg) : pkg.repository ?? "", label)).join(" / ");
|
|
431
|
+
const paints = (pkg, c, links) => [
|
|
432
|
+
nameColor(pkg, c),
|
|
433
|
+
plain,
|
|
434
|
+
pkg.outdated ? c.green : plain,
|
|
435
|
+
latestColor(pkg, c),
|
|
436
|
+
c.dim,
|
|
437
|
+
c.dim,
|
|
438
|
+
plain,
|
|
439
|
+
links ? linkPaint(pkg) : plain
|
|
440
|
+
];
|
|
441
|
+
const renderCell = (text, width, column, paint) => {
|
|
442
|
+
const gap = " ".repeat(Math.max(0, width - text.length));
|
|
443
|
+
return RIGHT_ALIGNED.has(column) ? gap + paint(text) : paint(text) + gap;
|
|
444
|
+
};
|
|
445
|
+
const renderRow = (pkg, cells, widths, c, links) => {
|
|
446
|
+
return [...paints(pkg, c, links).map((paint, i) => renderCell(cells[i] ?? "", widths[i] ?? 0, i, paint)), flagsCell(pkg, c)].join(" ").trimEnd();
|
|
447
|
+
};
|
|
448
|
+
const renderTable = (packages, project, now, c, links) => {
|
|
449
|
+
const rows = packages.map((pkg) => ({
|
|
450
|
+
pkg,
|
|
451
|
+
cells: toCells(pkg, project, now)
|
|
452
|
+
}));
|
|
453
|
+
const widths = HEADER.map((h, i) => Math.max(h.length, ...rows.map((r) => r.cells[i]?.length ?? 0)));
|
|
454
|
+
const header = HEADER.map((h, i) => i === HEADER.length - 1 ? h : renderCell(h, widths[i] ?? 0, i, plain));
|
|
455
|
+
const lines = [c.underline(c.bold(header.join(" ")))];
|
|
456
|
+
for (const [type, title] of SECTIONS) {
|
|
457
|
+
const section = rows.filter((r) => r.pkg.type === type);
|
|
458
|
+
if (section.length > 0) {
|
|
459
|
+
lines.push("", c.bold(`${title} (${section.length})`));
|
|
460
|
+
lines.push(...section.map((r) => renderRow(r.pkg, r.cells, widths, c, links)));
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
return lines;
|
|
464
|
+
};
|
|
465
|
+
const footer = (result, c) => {
|
|
466
|
+
const s = result.summary;
|
|
467
|
+
return [
|
|
468
|
+
`${s.total} packages`,
|
|
469
|
+
c.red(`${s.deprecated} deprecated`),
|
|
470
|
+
c.yellow(`${s.stale} stale`),
|
|
471
|
+
c.cyan(`${s.outdated} outdated`),
|
|
472
|
+
c.magenta(`${s.unknown} unknown`),
|
|
473
|
+
c.blue(`${s.blocked} blocked`),
|
|
474
|
+
c.green(`${s.ok} ok`)
|
|
475
|
+
].join(" · ");
|
|
476
|
+
};
|
|
477
|
+
const formatTable = (result, opts) => {
|
|
478
|
+
const color = opts.color ?? picocolors.default.isColorSupported;
|
|
479
|
+
const c = picocolors.default.createColors(color);
|
|
480
|
+
const visible = opts.onlyProblems ? result.packages.filter((p) => !(p.flags.length === 1 && p.flags[0] === "ok")) : result.packages;
|
|
481
|
+
const lines = result.packages.length === 0 ? ["No dependencies found"] : renderTable(visible, result.project, opts.now, c, color);
|
|
482
|
+
lines.push("", footer(result, c));
|
|
483
|
+
if (result.skipped.length > 0) {
|
|
484
|
+
lines.push("", c.bold("Skipped:"));
|
|
485
|
+
const width = Math.max(...result.skipped.map((s) => s.name.length));
|
|
486
|
+
for (const s of result.skipped) lines.push(` ${s.name.padEnd(width)} ${s.specifier} — ${s.reason}`);
|
|
487
|
+
}
|
|
488
|
+
return `${lines.join("\n")}\n`;
|
|
489
|
+
};
|
|
490
|
+
//#endregion
|
|
329
491
|
exports.check = check;
|
|
492
|
+
exports.formatJson = formatJson;
|
|
493
|
+
exports.formatTable = formatTable;
|
package/lib/index.mjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { basename, join, resolve } from "path";
|
|
2
2
|
import { readFileSync } from "fs";
|
|
3
3
|
import { executeCommand } from "@ladamczyk/qoq-utils";
|
|
4
|
+
import pc from "picocolors";
|
|
4
5
|
//#region src/deps.ts
|
|
5
6
|
const FIELDS = [
|
|
6
7
|
["prod", "dependencies"],
|
|
@@ -186,6 +187,17 @@ function addMonths(date, months) {
|
|
|
186
187
|
result.setUTCFullYear(year, month, Math.min(date.getUTCDate(), lastDay));
|
|
187
188
|
return result;
|
|
188
189
|
}
|
|
190
|
+
function relativeAge(iso, now) {
|
|
191
|
+
const date = new Date(iso);
|
|
192
|
+
if (date > now) return "0d ago";
|
|
193
|
+
const diffMs = now.getTime() - date.getTime();
|
|
194
|
+
const days = Math.floor(diffMs / 864e5);
|
|
195
|
+
if (days < 30) return `${days}d ago`;
|
|
196
|
+
let months = 0;
|
|
197
|
+
while (addMonths(date, months + 1) <= now) months++;
|
|
198
|
+
if (months < 12) return `${months}m ago`;
|
|
199
|
+
return `${Math.floor(months / 12)}y ago`;
|
|
200
|
+
}
|
|
189
201
|
//#endregion
|
|
190
202
|
//#region src/check.ts
|
|
191
203
|
const ALL_TYPES = [
|
|
@@ -325,4 +337,131 @@ const check = async (options = {}) => {
|
|
|
325
337
|
};
|
|
326
338
|
};
|
|
327
339
|
//#endregion
|
|
328
|
-
|
|
340
|
+
//#region src/format.ts
|
|
341
|
+
const HEADER = [
|
|
342
|
+
"Package",
|
|
343
|
+
"Current",
|
|
344
|
+
"Wanted",
|
|
345
|
+
"Latest",
|
|
346
|
+
"Location",
|
|
347
|
+
"Depended by",
|
|
348
|
+
"Last publish",
|
|
349
|
+
"Links",
|
|
350
|
+
"Flags"
|
|
351
|
+
];
|
|
352
|
+
const RIGHT_ALIGNED = /* @__PURE__ */ new Set([
|
|
353
|
+
1,
|
|
354
|
+
2,
|
|
355
|
+
3
|
|
356
|
+
]);
|
|
357
|
+
const SECTIONS = [
|
|
358
|
+
["prod", "dependencies"],
|
|
359
|
+
["dev", "devDependencies"],
|
|
360
|
+
["optional", "optionalDependencies"]
|
|
361
|
+
];
|
|
362
|
+
const MAX_MESSAGE = 60;
|
|
363
|
+
const formatJson = (result) => `${JSON.stringify(result, null, 2)}\n`;
|
|
364
|
+
const truncate = (text) => text.length > MAX_MESSAGE ? `${text.slice(0, 59)}…` : text;
|
|
365
|
+
const flagText = (pkg, flag) => {
|
|
366
|
+
if (flag === "deprecated") return `deprecated ${truncate(pkg.deprecated ?? "")}`;
|
|
367
|
+
if (flag === "blocked") return `latest needs node ${pkg.latestNode ?? ""}`;
|
|
368
|
+
if (flag === "unknown") return `unknown ${pkg.unknown ?? ""}`;
|
|
369
|
+
return flag;
|
|
370
|
+
};
|
|
371
|
+
const publishText = (pkg, now) => pkg.lastPublish === null ? "-" : `${pkg.lastPublish.slice(0, 10)} (${relativeAge(pkg.lastPublish, now)})`;
|
|
372
|
+
const linksText = (pkg) => pkg.repository === null ? "npm" : "npm / repo";
|
|
373
|
+
const toCells = (pkg, project, now) => [
|
|
374
|
+
pkg.name,
|
|
375
|
+
pkg.current ?? "-",
|
|
376
|
+
pkg.wanted ?? pkg.current ?? "-",
|
|
377
|
+
pkg.latest ?? "-",
|
|
378
|
+
`node_modules/${pkg.name}`,
|
|
379
|
+
project,
|
|
380
|
+
publishText(pkg, now),
|
|
381
|
+
linksText(pkg)
|
|
382
|
+
];
|
|
383
|
+
const flagColors = (c) => ({
|
|
384
|
+
deprecated: c.red,
|
|
385
|
+
stale: c.yellow,
|
|
386
|
+
outdated: c.cyan,
|
|
387
|
+
unknown: c.magenta,
|
|
388
|
+
blocked: c.blue,
|
|
389
|
+
ok: c.green
|
|
390
|
+
});
|
|
391
|
+
const flagsCell = (pkg, c) => {
|
|
392
|
+
const colors = flagColors(c);
|
|
393
|
+
return pkg.flags.map((flag) => colors[flag](flagText(pkg, flag))).join(" ");
|
|
394
|
+
};
|
|
395
|
+
const hyperlink = (url, text) => `\u001b]8;;${url}\u001b\\${text}\u001b]8;;\u001b\\`;
|
|
396
|
+
const npmUrl = (pkg) => `https://www.npmjs.com/package/${pkg.queriedName ?? pkg.name}`;
|
|
397
|
+
const plain = (text) => text;
|
|
398
|
+
const nameColor = (pkg, c) => {
|
|
399
|
+
if (!pkg.outdated) return plain;
|
|
400
|
+
return pkg.current === pkg.wanted ? c.yellow : c.red;
|
|
401
|
+
};
|
|
402
|
+
const latestColor = (pkg, c) => {
|
|
403
|
+
if (pkg.majorBump) return c.red;
|
|
404
|
+
return pkg.outdated ? c.magenta : plain;
|
|
405
|
+
};
|
|
406
|
+
const linkPaint = (pkg) => (text) => text.split(" / ").map((label, i) => hyperlink(i === 0 ? npmUrl(pkg) : pkg.repository ?? "", label)).join(" / ");
|
|
407
|
+
const paints = (pkg, c, links) => [
|
|
408
|
+
nameColor(pkg, c),
|
|
409
|
+
plain,
|
|
410
|
+
pkg.outdated ? c.green : plain,
|
|
411
|
+
latestColor(pkg, c),
|
|
412
|
+
c.dim,
|
|
413
|
+
c.dim,
|
|
414
|
+
plain,
|
|
415
|
+
links ? linkPaint(pkg) : plain
|
|
416
|
+
];
|
|
417
|
+
const renderCell = (text, width, column, paint) => {
|
|
418
|
+
const gap = " ".repeat(Math.max(0, width - text.length));
|
|
419
|
+
return RIGHT_ALIGNED.has(column) ? gap + paint(text) : paint(text) + gap;
|
|
420
|
+
};
|
|
421
|
+
const renderRow = (pkg, cells, widths, c, links) => {
|
|
422
|
+
return [...paints(pkg, c, links).map((paint, i) => renderCell(cells[i] ?? "", widths[i] ?? 0, i, paint)), flagsCell(pkg, c)].join(" ").trimEnd();
|
|
423
|
+
};
|
|
424
|
+
const renderTable = (packages, project, now, c, links) => {
|
|
425
|
+
const rows = packages.map((pkg) => ({
|
|
426
|
+
pkg,
|
|
427
|
+
cells: toCells(pkg, project, now)
|
|
428
|
+
}));
|
|
429
|
+
const widths = HEADER.map((h, i) => Math.max(h.length, ...rows.map((r) => r.cells[i]?.length ?? 0)));
|
|
430
|
+
const header = HEADER.map((h, i) => i === HEADER.length - 1 ? h : renderCell(h, widths[i] ?? 0, i, plain));
|
|
431
|
+
const lines = [c.underline(c.bold(header.join(" ")))];
|
|
432
|
+
for (const [type, title] of SECTIONS) {
|
|
433
|
+
const section = rows.filter((r) => r.pkg.type === type);
|
|
434
|
+
if (section.length > 0) {
|
|
435
|
+
lines.push("", c.bold(`${title} (${section.length})`));
|
|
436
|
+
lines.push(...section.map((r) => renderRow(r.pkg, r.cells, widths, c, links)));
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
return lines;
|
|
440
|
+
};
|
|
441
|
+
const footer = (result, c) => {
|
|
442
|
+
const s = result.summary;
|
|
443
|
+
return [
|
|
444
|
+
`${s.total} packages`,
|
|
445
|
+
c.red(`${s.deprecated} deprecated`),
|
|
446
|
+
c.yellow(`${s.stale} stale`),
|
|
447
|
+
c.cyan(`${s.outdated} outdated`),
|
|
448
|
+
c.magenta(`${s.unknown} unknown`),
|
|
449
|
+
c.blue(`${s.blocked} blocked`),
|
|
450
|
+
c.green(`${s.ok} ok`)
|
|
451
|
+
].join(" · ");
|
|
452
|
+
};
|
|
453
|
+
const formatTable = (result, opts) => {
|
|
454
|
+
const color = opts.color ?? pc.isColorSupported;
|
|
455
|
+
const c = pc.createColors(color);
|
|
456
|
+
const visible = opts.onlyProblems ? result.packages.filter((p) => !(p.flags.length === 1 && p.flags[0] === "ok")) : result.packages;
|
|
457
|
+
const lines = result.packages.length === 0 ? ["No dependencies found"] : renderTable(visible, result.project, opts.now, c, color);
|
|
458
|
+
lines.push("", footer(result, c));
|
|
459
|
+
if (result.skipped.length > 0) {
|
|
460
|
+
lines.push("", c.bold("Skipped:"));
|
|
461
|
+
const width = Math.max(...result.skipped.map((s) => s.name.length));
|
|
462
|
+
for (const s of result.skipped) lines.push(` ${s.name.padEnd(width)} ${s.specifier} — ${s.reason}`);
|
|
463
|
+
}
|
|
464
|
+
return `${lines.join("\n")}\n`;
|
|
465
|
+
};
|
|
466
|
+
//#endregion
|
|
467
|
+
export { check, formatJson, formatTable };
|
package/lib/src/index.d.ts
CHANGED