@ladamczyk/outdated 1.0.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/README.md +22 -1
- package/bin/cli.js +8 -3
- package/lib/index.cjs +174 -1
- package/lib/index.mjs +150 -2
- package/lib/src/index.d.ts +1 -0
- package/lib/src/npm.d.ts +1 -0
- package/lib/src/stats.d.ts +2 -0
- package/lib/src/stats.test.d.ts +1 -0
- package/lib/src/types.d.ts +1 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -10,7 +10,7 @@ outdated [options]
|
|
|
10
10
|
|
|
11
11
|
The table mirrors `npm outdated` (`Package`, `Current`, `Wanted`, `Latest`, `Location`, `Depended by`) and adds `Last publish` and `Flags` at the end. It is split into `dependencies`, `devDependencies` and `optionalDependencies` sections (empty ones are omitted); packages keep their `package.json` order and the columns line up across sections. `Wanted` falls back to `Current` for packages `npm outdated` does not list.
|
|
12
12
|
|
|
13
|
-
Flags are coloured: deprecated red, stale yellow, outdated cyan, unknown magenta, blocked blue, ok green; a major version bump shows `Latest` in red.
|
|
13
|
+
Flags are coloured: deprecated red, stale yellow, outdated cyan, unknown magenta, blocked blue, ok green; a major version bump shows `Latest` in red. The `Links` column holds `npm` and, when the package declares a repository, `repo`; each label is a hyperlink in terminals that support them. Colours and links turn off automatically when output is piped or `NO_COLOR` is set, and `--json` never contains either.
|
|
14
14
|
|
|
15
15
|
## Options
|
|
16
16
|
|
|
@@ -57,6 +57,7 @@ When `--json` is used, the output conforms to the following schema (schemaVersio
|
|
|
57
57
|
"stale": true,
|
|
58
58
|
"unknown": "reason npm view failed or null",
|
|
59
59
|
"latestNode": "engines.node of the latest release, or null",
|
|
60
|
+
"repository": "https URL of the package repository, or null",
|
|
60
61
|
"flags": ["deprecated", "stale", "outdated", "unknown", "blocked"]
|
|
61
62
|
}
|
|
62
63
|
],
|
|
@@ -104,3 +105,23 @@ Note that `--only-problems` filters the table output but never filters the JSON
|
|
|
104
105
|
**Windows behaviour is untested/unverified.**
|
|
105
106
|
|
|
106
107
|
Only the npm CLI is used. Registry, proxy, authentication, and certificate settings are inherited from `.npmrc`.
|
|
108
|
+
|
|
109
|
+
## Anonymous usage stats
|
|
110
|
+
|
|
111
|
+
Opt-in, off until a human says yes. The first interactive run asks; the answer is stored as `stats: true|false` in `~/.config/outdated.json` (or `$XDG_CONFIG_HOME/outdated.json`) and never asked again — edit or delete the key to change it. Runs that can't ask (`CI=true`, a pipe, `--json`) are never prompted and never counted.
|
|
112
|
+
|
|
113
|
+
A counted run posts one constant to `https://stats.adamczyk.ovh` and nothing else:
|
|
114
|
+
|
|
115
|
+
```jsonc
|
|
116
|
+
{ "tool": "outdated", "options": [] } // `options` is always empty
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Where an outbound POST never leaves the network, the same run counts as a plain image GET instead — `https://adamczyk.ovh/img/stats/pixel.png?tool=outdated`.
|
|
120
|
+
|
|
121
|
+
Never sent: your dependencies, package.json, paths, versions, findings, the flags you typed, project or package names, or anything identifying the user or machine. Sends are fire-and-forget with a 2s timeout; a failure is swallowed and never affects the exit code.
|
|
122
|
+
|
|
123
|
+
## Last 12 months usage statistics
|
|
124
|
+
|
|
125
|
+
Regenerated on each release, for daily updated numbers click on chart or visit [https://adamczyk.ovh/docs/outdated#usage-statistics](https://adamczyk.ovh/docs/outdated#usage-statistics).
|
|
126
|
+
|
|
127
|
+
[](https://adamczyk.ovh/img/stats/outdated.png)
|
package/bin/cli.js
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{EExitCode as e,executeCommand as
|
|
3
|
-
`)}\n`},
|
|
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
|
+
`)}\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
|
+
Outdated usage stats
|
|
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,
|
|
6
|
+
the flags you typed, project or package names, and nothing identifying
|
|
7
|
+
you or your machine.
|
|
8
|
+
`),p.gray(`Stored as \`stats: true|false\` in ${X} — edit it any time.\n\n`)].join(``));let e=h({input:process.stdin,output:process.stdout});try{return(await e.question(`Send anonymous usage stats? [y/N] `)).trim().toLowerCase()===`y`}finally{e.close()}},Be=async(e=X)=>{let t=Z(e).stats;if(typeof t==`boolean`)return t;if(process.env.CI===`true`||!process.stdin.isTTY||!process.stdout.isTTY)return;let n=await ze();return Re(e,n),n},Ve=async()=>i(`outdated`);var Q=class extends Error{};const He=()=>a(`outdated`).usage(`[options]`).help(e=>{throw new Q(e.map(e=>e.title?`${e.title}:\n${e.body}`:e.body).join(`
|
|
4
9
|
|
|
5
|
-
`))}).option(`-v, --version`,`Display version number`).option(`--cwd <dir>`,`Project folder (default: current directory)`).option(`--stale-after <months>`,`Months without a release before a package is stale (default: 6)`).option(`--include <types>`,`Dependency types to check: prod,dev,optional (default: all)`).option(`--ignore <pkgs>`,`Comma-separated package names to skip`).option(`--concurrency <n>`,`Parallel npm view lookups (default: 6)`).option(`--json`,`Output machine-readable JSON`).option(`--only-problems`,`Hide ok packages from the table`).option(`--fail-on <list>`,`Exit 1 when any of stale,deprecated,outdated,unknown is found`),
|
|
10
|
+
`))}).option(`-v, --version`,`Display version number`).option(`--cwd <dir>`,`Project folder (default: current directory)`).option(`--stale-after <months>`,`Months without a release before a package is stale (default: 6)`).option(`--include <types>`,`Dependency types to check: prod,dev,optional (default: all)`).option(`--ignore <pkgs>`,`Comma-separated package names to skip`).option(`--concurrency <n>`,`Parallel npm view lookups (default: 6)`).option(`--json`,`Output machine-readable JSON`).option(`--only-problems`,`Hide ok packages from the table`).option(`--fail-on <list>`,`Exit 1 when any of stale,deprecated,outdated,unknown is found`),Ue=async(t,n,r)=>{let{options:i}=He().parse([`node`,`outdated`,...t],{run:!1});if(i.version)return{stdout:`${ee}\n`,code:e.OK};let a=Ie(i);!a.json&&await Be()&&Ve();let o=await R({cwd:a.cwd,staleAfterMonths:a.staleAfterMonths,include:a.include,ignore:a.ignore,concurrency:a.concurrency,runner:n,now:r});return{stdout:a.json?he(o):Me(o,{onlyProblems:a.onlyProblems,now:r}),code:Le(o,a.failOn)}},{stdout:$,stderr:We,code:Ge}=await(async(t,n={})=>{try{return{...await Ue(t,n.runner,n.now??/* @__PURE__ */ new Date),stderr:``}}catch(t){return t instanceof Q?{stdout:`${t.message}\n`,stderr:``,code:e.OK}:{stdout:``,stderr:`✖ ${t instanceof Error?t.message:String(t)}\n`,code:e.EXCEPTION}}})(process.argv.slice(2));process.stderr.write(We),process.stdout.write($,()=>process.exit(Ge));
|
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"],
|
|
@@ -114,6 +138,12 @@ const parseOutdated = (stdout) => {
|
|
|
114
138
|
return Object.fromEntries(Object.entries(parsed).map(([name, v]) => [name, toEntry(v)]));
|
|
115
139
|
};
|
|
116
140
|
const nodeRange = (engines) => isRecord(engines) && typeof engines.node === "string" && engines.node !== "" ? engines.node : null;
|
|
141
|
+
const repoUrl = (repository) => {
|
|
142
|
+
const raw = isRecord(repository) ? repository.url : repository;
|
|
143
|
+
if (typeof raw !== "string" || raw === "") return null;
|
|
144
|
+
const url = raw.replace(/^git\+/, "").replace(/^(?:git|ssh):\/\/(?:git@)?/, "https://").replace(/^git@([^:]+):/, "https://$1/").replace(/^(?:github:)?([\w.-]+\/[\w.-]+)$/, "https://github.com/$1").replace(/\.git$/, "");
|
|
145
|
+
return /^https?:\/\//.test(url) ? url : null;
|
|
146
|
+
};
|
|
117
147
|
const UNEXPECTED = { unknown: "unexpected npm view output" };
|
|
118
148
|
const buildView = (parsed) => {
|
|
119
149
|
const tags = parsed["dist-tags"];
|
|
@@ -126,7 +156,8 @@ const buildView = (parsed) => {
|
|
|
126
156
|
latest,
|
|
127
157
|
lastPublish,
|
|
128
158
|
deprecated: typeof dep === "string" && dep !== "" ? dep : null,
|
|
129
|
-
latestNode: nodeRange(parsed.engines)
|
|
159
|
+
latestNode: nodeRange(parsed.engines),
|
|
160
|
+
repository: repoUrl(parsed.repository)
|
|
130
161
|
};
|
|
131
162
|
};
|
|
132
163
|
const parseView = (stdout) => {
|
|
@@ -180,6 +211,17 @@ function addMonths(date, months) {
|
|
|
180
211
|
result.setUTCFullYear(year, month, Math.min(date.getUTCDate(), lastDay));
|
|
181
212
|
return result;
|
|
182
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
|
+
}
|
|
183
225
|
//#endregion
|
|
184
226
|
//#region src/check.ts
|
|
185
227
|
const ALL_TYPES = [
|
|
@@ -218,6 +260,7 @@ const lookup = async (entry, ctx) => {
|
|
|
218
260
|
"dist-tags",
|
|
219
261
|
"deprecated",
|
|
220
262
|
"engines",
|
|
263
|
+
"repository",
|
|
221
264
|
"--json",
|
|
222
265
|
"--prefix",
|
|
223
266
|
ctx.cwd
|
|
@@ -260,6 +303,7 @@ const buildRow = (entry, view, hit, ctx) => {
|
|
|
260
303
|
majorBump: outdated && isMajorBump(current, latest),
|
|
261
304
|
deprecated: viewed?.deprecated ?? null,
|
|
262
305
|
latestNode: viewed?.latestNode ?? null,
|
|
306
|
+
repository: viewed?.repository ?? null,
|
|
263
307
|
lastPublish: published ? published.toISOString() : null,
|
|
264
308
|
stale: published !== null && published < addMonths(ctx.now, -ctx.staleAfterMonths),
|
|
265
309
|
unknown: "unknown" in view ? view.unknown : null
|
|
@@ -317,4 +361,133 @@ const check = async (options = {}) => {
|
|
|
317
361
|
};
|
|
318
362
|
};
|
|
319
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
|
|
320
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"],
|
|
@@ -113,6 +114,12 @@ const parseOutdated = (stdout) => {
|
|
|
113
114
|
return Object.fromEntries(Object.entries(parsed).map(([name, v]) => [name, toEntry(v)]));
|
|
114
115
|
};
|
|
115
116
|
const nodeRange = (engines) => isRecord(engines) && typeof engines.node === "string" && engines.node !== "" ? engines.node : null;
|
|
117
|
+
const repoUrl = (repository) => {
|
|
118
|
+
const raw = isRecord(repository) ? repository.url : repository;
|
|
119
|
+
if (typeof raw !== "string" || raw === "") return null;
|
|
120
|
+
const url = raw.replace(/^git\+/, "").replace(/^(?:git|ssh):\/\/(?:git@)?/, "https://").replace(/^git@([^:]+):/, "https://$1/").replace(/^(?:github:)?([\w.-]+\/[\w.-]+)$/, "https://github.com/$1").replace(/\.git$/, "");
|
|
121
|
+
return /^https?:\/\//.test(url) ? url : null;
|
|
122
|
+
};
|
|
116
123
|
const UNEXPECTED = { unknown: "unexpected npm view output" };
|
|
117
124
|
const buildView = (parsed) => {
|
|
118
125
|
const tags = parsed["dist-tags"];
|
|
@@ -125,7 +132,8 @@ const buildView = (parsed) => {
|
|
|
125
132
|
latest,
|
|
126
133
|
lastPublish,
|
|
127
134
|
deprecated: typeof dep === "string" && dep !== "" ? dep : null,
|
|
128
|
-
latestNode: nodeRange(parsed.engines)
|
|
135
|
+
latestNode: nodeRange(parsed.engines),
|
|
136
|
+
repository: repoUrl(parsed.repository)
|
|
129
137
|
};
|
|
130
138
|
};
|
|
131
139
|
const parseView = (stdout) => {
|
|
@@ -179,6 +187,17 @@ function addMonths(date, months) {
|
|
|
179
187
|
result.setUTCFullYear(year, month, Math.min(date.getUTCDate(), lastDay));
|
|
180
188
|
return result;
|
|
181
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
|
+
}
|
|
182
201
|
//#endregion
|
|
183
202
|
//#region src/check.ts
|
|
184
203
|
const ALL_TYPES = [
|
|
@@ -217,6 +236,7 @@ const lookup = async (entry, ctx) => {
|
|
|
217
236
|
"dist-tags",
|
|
218
237
|
"deprecated",
|
|
219
238
|
"engines",
|
|
239
|
+
"repository",
|
|
220
240
|
"--json",
|
|
221
241
|
"--prefix",
|
|
222
242
|
ctx.cwd
|
|
@@ -259,6 +279,7 @@ const buildRow = (entry, view, hit, ctx) => {
|
|
|
259
279
|
majorBump: outdated && isMajorBump(current, latest),
|
|
260
280
|
deprecated: viewed?.deprecated ?? null,
|
|
261
281
|
latestNode: viewed?.latestNode ?? null,
|
|
282
|
+
repository: viewed?.repository ?? null,
|
|
262
283
|
lastPublish: published ? published.toISOString() : null,
|
|
263
284
|
stale: published !== null && published < addMonths(ctx.now, -ctx.staleAfterMonths),
|
|
264
285
|
unknown: "unknown" in view ? view.unknown : null
|
|
@@ -316,4 +337,131 @@ const check = async (options = {}) => {
|
|
|
316
337
|
};
|
|
317
338
|
};
|
|
318
339
|
//#endregion
|
|
319
|
-
|
|
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
package/lib/src/npm.d.ts
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/lib/src/types.d.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ladamczyk/outdated",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.1",
|
|
4
4
|
"description": "CLI that reports outdated dependencies",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cli",
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
},
|
|
52
52
|
"dependencies": {
|
|
53
53
|
"@ladamczyk/qoq-utils": "6.1.10",
|
|
54
|
-
"cac": "7.0.
|
|
54
|
+
"cac": "7.0.1",
|
|
55
55
|
"picocolors": "1.1.1"
|
|
56
56
|
},
|
|
57
57
|
"devDependencies": {
|