@ladamczyk/outdated 1.0.0 → 1.1.0
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 +10 -1
- package/lib/index.mjs +10 -1
- 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.0.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
|
@@ -114,6 +114,12 @@ const parseOutdated = (stdout) => {
|
|
|
114
114
|
return Object.fromEntries(Object.entries(parsed).map(([name, v]) => [name, toEntry(v)]));
|
|
115
115
|
};
|
|
116
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
|
+
};
|
|
117
123
|
const UNEXPECTED = { unknown: "unexpected npm view output" };
|
|
118
124
|
const buildView = (parsed) => {
|
|
119
125
|
const tags = parsed["dist-tags"];
|
|
@@ -126,7 +132,8 @@ const buildView = (parsed) => {
|
|
|
126
132
|
latest,
|
|
127
133
|
lastPublish,
|
|
128
134
|
deprecated: typeof dep === "string" && dep !== "" ? dep : null,
|
|
129
|
-
latestNode: nodeRange(parsed.engines)
|
|
135
|
+
latestNode: nodeRange(parsed.engines),
|
|
136
|
+
repository: repoUrl(parsed.repository)
|
|
130
137
|
};
|
|
131
138
|
};
|
|
132
139
|
const parseView = (stdout) => {
|
|
@@ -218,6 +225,7 @@ const lookup = async (entry, ctx) => {
|
|
|
218
225
|
"dist-tags",
|
|
219
226
|
"deprecated",
|
|
220
227
|
"engines",
|
|
228
|
+
"repository",
|
|
221
229
|
"--json",
|
|
222
230
|
"--prefix",
|
|
223
231
|
ctx.cwd
|
|
@@ -260,6 +268,7 @@ const buildRow = (entry, view, hit, ctx) => {
|
|
|
260
268
|
majorBump: outdated && isMajorBump(current, latest),
|
|
261
269
|
deprecated: viewed?.deprecated ?? null,
|
|
262
270
|
latestNode: viewed?.latestNode ?? null,
|
|
271
|
+
repository: viewed?.repository ?? null,
|
|
263
272
|
lastPublish: published ? published.toISOString() : null,
|
|
264
273
|
stale: published !== null && published < addMonths(ctx.now, -ctx.staleAfterMonths),
|
|
265
274
|
unknown: "unknown" in view ? view.unknown : null
|
package/lib/index.mjs
CHANGED
|
@@ -113,6 +113,12 @@ const parseOutdated = (stdout) => {
|
|
|
113
113
|
return Object.fromEntries(Object.entries(parsed).map(([name, v]) => [name, toEntry(v)]));
|
|
114
114
|
};
|
|
115
115
|
const nodeRange = (engines) => isRecord(engines) && typeof engines.node === "string" && engines.node !== "" ? engines.node : null;
|
|
116
|
+
const repoUrl = (repository) => {
|
|
117
|
+
const raw = isRecord(repository) ? repository.url : repository;
|
|
118
|
+
if (typeof raw !== "string" || raw === "") return null;
|
|
119
|
+
const url = raw.replace(/^git\+/, "").replace(/^(?:git|ssh):\/\/(?:git@)?/, "https://").replace(/^git@([^:]+):/, "https://$1/").replace(/^(?:github:)?([\w.-]+\/[\w.-]+)$/, "https://github.com/$1").replace(/\.git$/, "");
|
|
120
|
+
return /^https?:\/\//.test(url) ? url : null;
|
|
121
|
+
};
|
|
116
122
|
const UNEXPECTED = { unknown: "unexpected npm view output" };
|
|
117
123
|
const buildView = (parsed) => {
|
|
118
124
|
const tags = parsed["dist-tags"];
|
|
@@ -125,7 +131,8 @@ const buildView = (parsed) => {
|
|
|
125
131
|
latest,
|
|
126
132
|
lastPublish,
|
|
127
133
|
deprecated: typeof dep === "string" && dep !== "" ? dep : null,
|
|
128
|
-
latestNode: nodeRange(parsed.engines)
|
|
134
|
+
latestNode: nodeRange(parsed.engines),
|
|
135
|
+
repository: repoUrl(parsed.repository)
|
|
129
136
|
};
|
|
130
137
|
};
|
|
131
138
|
const parseView = (stdout) => {
|
|
@@ -217,6 +224,7 @@ const lookup = async (entry, ctx) => {
|
|
|
217
224
|
"dist-tags",
|
|
218
225
|
"deprecated",
|
|
219
226
|
"engines",
|
|
227
|
+
"repository",
|
|
220
228
|
"--json",
|
|
221
229
|
"--prefix",
|
|
222
230
|
ctx.cwd
|
|
@@ -259,6 +267,7 @@ const buildRow = (entry, view, hit, ctx) => {
|
|
|
259
267
|
majorBump: outdated && isMajorBump(current, latest),
|
|
260
268
|
deprecated: viewed?.deprecated ?? null,
|
|
261
269
|
latestNode: viewed?.latestNode ?? null,
|
|
270
|
+
repository: viewed?.repository ?? null,
|
|
262
271
|
lastPublish: published ? published.toISOString() : null,
|
|
263
272
|
stale: published !== null && published < addMonths(ctx.now, -ctx.staleAfterMonths),
|
|
264
273
|
unknown: "unknown" in view ? view.unknown : null
|
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.0",
|
|
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": {
|