@somewhere-tech/cli 0.16.0 → 0.17.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/dist/commands/update.js +62 -0
- package/dist/commands/update.js.map +1 -0
- package/dist/index.js +13 -1
- package/dist/index.js.map +1 -1
- package/dist/lib/notify/index.js +54 -0
- package/dist/lib/notify/index.js.map +1 -0
- package/dist/lib/notify/providers/advisory.js +30 -0
- package/dist/lib/notify/providers/advisory.js.map +1 -0
- package/dist/lib/notify/providers/update.js +85 -0
- package/dist/lib/notify/providers/update.js.map +1 -0
- package/dist/lib/notify/types.js +2 -0
- package/dist/lib/notify/types.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { dirname, join } from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import { execSync } from 'node:child_process';
|
|
5
|
+
import { dim, error, info, success, teal } from '../lib/output.js';
|
|
6
|
+
const PACKAGE = '@somewhere-tech/cli';
|
|
7
|
+
/** Read the installed CLI's own version from its package.json (dist/commands/ →
|
|
8
|
+
* package root is two levels up). */
|
|
9
|
+
function currentVersion() {
|
|
10
|
+
try {
|
|
11
|
+
const pkg = JSON.parse(readFileSync(join(dirname(fileURLToPath(import.meta.url)), '..', '..', 'package.json'), 'utf8'));
|
|
12
|
+
return pkg.version;
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
return '0.0.0';
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
export function registerUpdate(program) {
|
|
19
|
+
program
|
|
20
|
+
.command('update')
|
|
21
|
+
.description('Update the somewhere CLI to the latest published version (via npm).')
|
|
22
|
+
.option('--check', 'Only report whether an update is available; do not install.')
|
|
23
|
+
.action((opts) => {
|
|
24
|
+
const current = currentVersion();
|
|
25
|
+
let latest;
|
|
26
|
+
try {
|
|
27
|
+
latest = execSync(`npm view ${PACKAGE} version`, {
|
|
28
|
+
encoding: 'utf8',
|
|
29
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
30
|
+
}).trim();
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
error('Could not reach npm to check the latest version. Check your connection and try again.');
|
|
34
|
+
process.exit(1);
|
|
35
|
+
}
|
|
36
|
+
if (!latest) {
|
|
37
|
+
error('Could not determine the latest version from npm.');
|
|
38
|
+
process.exit(1);
|
|
39
|
+
}
|
|
40
|
+
if (latest === current) {
|
|
41
|
+
success(`You're on the latest version (${teal(current)}).`);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
info(`Update available: ${dim(current)} → ${teal(latest)}`);
|
|
45
|
+
if (opts.check) {
|
|
46
|
+
info(`Run ${teal('somewhere update')} to install it.`);
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
info(`Updating ${PACKAGE} …`);
|
|
50
|
+
try {
|
|
51
|
+
// stdio inherited so npm's own progress shows; -g may need elevated perms,
|
|
52
|
+
// in which case npm's EACCES message is surfaced and we point at the manual fix.
|
|
53
|
+
execSync(`npm install -g ${PACKAGE}@latest`, { stdio: 'inherit' });
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
error(`Update failed. Try it manually: npm install -g ${PACKAGE}@latest`);
|
|
57
|
+
process.exit(1);
|
|
58
|
+
}
|
|
59
|
+
success(`Updated to ${latest}. Run ${teal('somewhere --version')} to confirm.`);
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=update.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"update.js","sourceRoot":"","sources":["../../src/commands/update.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAE9C,OAAO,EAAE,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAEnE,MAAM,OAAO,GAAG,qBAAqB,CAAC;AAEtC;sCACsC;AACtC,SAAS,cAAc;IACrB,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CACpB,YAAY,CAAC,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,cAAc,CAAC,EAAE,MAAM,CAAC,CACzE,CAAC;QACzB,OAAO,GAAG,CAAC,OAAO,CAAC;IACrB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,OAAO,CAAC;IACjB,CAAC;AACH,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,OAAgB;IAC7C,OAAO;SACJ,OAAO,CAAC,QAAQ,CAAC;SACjB,WAAW,CAAC,qEAAqE,CAAC;SAClF,MAAM,CAAC,SAAS,EAAE,6DAA6D,CAAC;SAChF,MAAM,CAAC,CAAC,IAAyB,EAAE,EAAE;QACpC,MAAM,OAAO,GAAG,cAAc,EAAE,CAAC;QAEjC,IAAI,MAAc,CAAC;QACnB,IAAI,CAAC;YACH,MAAM,GAAG,QAAQ,CAAC,YAAY,OAAO,UAAU,EAAE;gBAC/C,QAAQ,EAAE,MAAM;gBAChB,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,CAAC;aACpC,CAAC,CAAC,IAAI,EAAE,CAAC;QACZ,CAAC;QAAC,MAAM,CAAC;YACP,KAAK,CAAC,uFAAuF,CAAC,CAAC;YAC/F,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;QAED,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,KAAK,CAAC,kDAAkD,CAAC,CAAC;YAC1D,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;QAED,IAAI,MAAM,KAAK,OAAO,EAAE,CAAC;YACvB,OAAO,CAAC,iCAAiC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;YAC5D,OAAO;QACT,CAAC;QAED,IAAI,CAAC,qBAAqB,GAAG,CAAC,OAAO,CAAC,MAAM,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;QAC5D,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,OAAO,IAAI,CAAC,kBAAkB,CAAC,iBAAiB,CAAC,CAAC;YACvD,OAAO;QACT,CAAC;QAED,IAAI,CAAC,YAAY,OAAO,IAAI,CAAC,CAAC;QAC9B,IAAI,CAAC;YACH,2EAA2E;YAC3E,iFAAiF;YACjF,QAAQ,CAAC,kBAAkB,OAAO,SAAS,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC,CAAC;QACrE,CAAC;QAAC,MAAM,CAAC;YACP,KAAK,CAAC,mDAAmD,OAAO,SAAS,CAAC,CAAC;YAC3E,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;QACD,OAAO,CAAC,cAAc,MAAM,SAAS,IAAI,CAAC,qBAAqB,CAAC,cAAc,CAAC,CAAC;IAClF,CAAC,CAAC,CAAC;AACP,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -24,6 +24,8 @@ import { registerCheck } from './commands/check.js';
|
|
|
24
24
|
import { registerApi } from './commands/api.js';
|
|
25
25
|
import { registerMcp } from './commands/mcp.js';
|
|
26
26
|
import { registerSwpx } from './commands/swpx.js';
|
|
27
|
+
import { registerUpdate } from './commands/update.js';
|
|
28
|
+
import { collectNotices } from './lib/notify/index.js';
|
|
27
29
|
const pkg = JSON.parse(readFileSync(join(dirname(fileURLToPath(import.meta.url)), '..', 'package.json'), 'utf8'));
|
|
28
30
|
const program = new Command()
|
|
29
31
|
.name('somewhere')
|
|
@@ -55,5 +57,15 @@ registerCheck(program);
|
|
|
55
57
|
registerApi(program);
|
|
56
58
|
registerMcp(program);
|
|
57
59
|
registerSwpx(program);
|
|
58
|
-
program
|
|
60
|
+
registerUpdate(program);
|
|
61
|
+
// User-notification pipeline (update-available, advisories, announcements…).
|
|
62
|
+
// Centrally gated to interactive, non-CI, non-pass-through commands and emitted to
|
|
63
|
+
// STDERR as a parting line AFTER the command — never stdout, agent, or swpx/swpm
|
|
64
|
+
// safety output. Fail-open: collectNotices swallows all errors.
|
|
65
|
+
void (async () => {
|
|
66
|
+
const notices = await collectNotices(process.argv);
|
|
67
|
+
if (notices.length)
|
|
68
|
+
process.on('exit', () => process.stderr.write('\n' + notices.join('\n') + '\n'));
|
|
69
|
+
program.parse();
|
|
70
|
+
})();
|
|
59
71
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAC5D,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { dirname, join } from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import { updateProvider } from './providers/update.js';
|
|
5
|
+
// import { advisoryProvider } from './providers/advisory.js'; // dormant — see that file
|
|
6
|
+
/** Registered notice sources. Add a provider here to surface a new kind of notice;
|
|
7
|
+
* it inherits the gate + stderr emission below, so it can never reach stdout,
|
|
8
|
+
* agent/piped output, or swpx/swpm verdict grading. */
|
|
9
|
+
const PROVIDERS = [updateProvider];
|
|
10
|
+
/** `somewhere <sub>` forms that must stay silent: the verdict/safety pass-throughs
|
|
11
|
+
* (their own bins already bypass this entry, but `somewhere swpx …` reaches here)
|
|
12
|
+
* and the machine-oriented npx/npm aliases. */
|
|
13
|
+
const SKIP_SUBCOMMANDS = new Set(['swpx', 'swpm', 'x', 'm', 'npx', 'npm']);
|
|
14
|
+
function currentVersion() {
|
|
15
|
+
try {
|
|
16
|
+
// dist/lib/notify/index.js → package root is three levels up.
|
|
17
|
+
const pkg = JSON.parse(readFileSync(join(dirname(fileURLToPath(import.meta.url)), '..', '..', '..', 'package.json'), 'utf8'));
|
|
18
|
+
return pkg.version;
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
return '0.0.0';
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
/** The one gate every provider inherits. If this returns false, NO provider runs,
|
|
25
|
+
* so notices can never appear on non-interactive output, in CI, during a
|
|
26
|
+
* safety/pass-through command, or when the user opted out. */
|
|
27
|
+
function notificationsAllowed(argv) {
|
|
28
|
+
if (!process.stderr.isTTY)
|
|
29
|
+
return false; // agents, pipes, redirects, files
|
|
30
|
+
if (process.env.CI)
|
|
31
|
+
return false; // CI logs
|
|
32
|
+
if (process.env.SOMEWHERE_NO_NOTIFICATIONS)
|
|
33
|
+
return false; // global opt-out
|
|
34
|
+
const sub = argv[2];
|
|
35
|
+
if (!sub || sub.startsWith('-') || SKIP_SUBCOMMANDS.has(sub))
|
|
36
|
+
return false;
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
/** Run every provider (gated, in parallel, fail-open) and return the notices to
|
|
40
|
+
* display. Print these to STDERR after the command (e.g. via process.on('exit'))
|
|
41
|
+
* so they never touch stdout and land as a parting line. */
|
|
42
|
+
export async function collectNotices(argv) {
|
|
43
|
+
try {
|
|
44
|
+
if (!notificationsAllowed(argv))
|
|
45
|
+
return [];
|
|
46
|
+
const ctx = { argv, currentVersion: currentVersion() };
|
|
47
|
+
const results = await Promise.all(PROVIDERS.map((p) => p.getNotice(ctx).catch(() => null)));
|
|
48
|
+
return results.filter((n) => typeof n === 'string' && n.length > 0);
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
return []; // never let the notification pipeline break the CLI
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/lib/notify/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,yFAAyF;AAEzF;;wDAEwD;AACxD,MAAM,SAAS,GAAqB,CAAC,cAAc,CAAC,CAAC;AAErD;;gDAEgD;AAChD,MAAM,gBAAgB,GAAG,IAAI,GAAG,CAAC,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC;AAE3E,SAAS,cAAc;IACrB,IAAI,CAAC;QACH,8DAA8D;QAC9D,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CACpB,YAAY,CAAC,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,cAAc,CAAC,EAAE,MAAM,CAAC,CAC/E,CAAC;QACzB,OAAO,GAAG,CAAC,OAAO,CAAC;IACrB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,OAAO,CAAC;IACjB,CAAC;AACH,CAAC;AAED;;+DAE+D;AAC/D,SAAS,oBAAoB,CAAC,IAAc;IAC1C,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK;QAAE,OAAO,KAAK,CAAC,CAAC,kCAAkC;IAC3E,IAAI,OAAO,CAAC,GAAG,CAAC,EAAE;QAAE,OAAO,KAAK,CAAC,CAAC,UAAU;IAC5C,IAAI,OAAO,CAAC,GAAG,CAAC,0BAA0B;QAAE,OAAO,KAAK,CAAC,CAAC,iBAAiB;IAC3E,MAAM,GAAG,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;IACpB,IAAI,CAAC,GAAG,IAAI,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,gBAAgB,CAAC,GAAG,CAAC,GAAG,CAAC;QAAE,OAAO,KAAK,CAAC;IAC3E,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;6DAE6D;AAC7D,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,IAAc;IACjD,IAAI,CAAC;QACH,IAAI,CAAC,oBAAoB,CAAC,IAAI,CAAC;YAAE,OAAO,EAAE,CAAC;QAC3C,MAAM,GAAG,GAAkB,EAAE,IAAI,EAAE,cAAc,EAAE,cAAc,EAAE,EAAE,CAAC;QACtE,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAC5F,OAAO,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAe,EAAE,CAAC,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IACnF,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC,CAAC,oDAAoD;IACjE,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/** DORMANT — the pipe for "a package you previously ran through swpx/swpm now has
|
|
2
|
+
* a security advisory." Not registered in ../index.ts yet; wire it when we want it.
|
|
3
|
+
*
|
|
4
|
+
* How to turn it on (kept separate from the verdict path so it can never affect
|
|
5
|
+
* safety grading):
|
|
6
|
+
*
|
|
7
|
+
* 1. RECORD USAGE — in the swpx/swpm bins, after a run resolves, append
|
|
8
|
+
* {name, version, ts} to ~/.somewhere/used-packages.json. This write MUST be
|
|
9
|
+
* fail-open and off the critical path (a try/catch around an async write); it
|
|
10
|
+
* must never block the install or change the verdict output.
|
|
11
|
+
*
|
|
12
|
+
* 2. CHECK HERE — read that file, throttle (e.g. a `checkedAt` stamp, once / 6h),
|
|
13
|
+
* batch-query the verdict API for those package@versions, and return a notice
|
|
14
|
+
* if any flipped to suspicious/blocked SINCE the user used them (compare
|
|
15
|
+
* against a stored last-seen verdict so we only alert on the transition, not
|
|
16
|
+
* every run). Keep it to one line, e.g.:
|
|
17
|
+
* "⚠ A package you used (foo@1.2.3) now has an advisory. swpx check foo@1.2.3"
|
|
18
|
+
*
|
|
19
|
+
* 3. REGISTER — add `advisoryProvider` to PROVIDERS in ../index.ts.
|
|
20
|
+
*
|
|
21
|
+
* It inherits the central gate, so it's already stderr-only, interactive-only,
|
|
22
|
+
* and silent during swpx/swpm runs — it surfaces as ambient awareness on normal
|
|
23
|
+
* `somewhere` usage, never inline with a live verdict. */
|
|
24
|
+
export const advisoryProvider = {
|
|
25
|
+
id: 'pkg-advisory',
|
|
26
|
+
async getNotice() {
|
|
27
|
+
return null; // pipe laid; dormant until the steps above are built
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
//# sourceMappingURL=advisory.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"advisory.js","sourceRoot":"","sources":["../../../../src/lib/notify/providers/advisory.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;2DAsB2D;AAC3D,MAAM,CAAC,MAAM,gBAAgB,GAAmB;IAC9C,EAAE,EAAE,cAAc;IAClB,KAAK,CAAC,SAAS;QACb,OAAO,IAAI,CAAC,CAAC,qDAAqD;IACpE,CAAC;CACF,CAAC"}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { homedir } from 'node:os';
|
|
3
|
+
import { dirname, join } from 'node:path';
|
|
4
|
+
import { dim, teal } from '../../output.js';
|
|
5
|
+
const PACKAGE = '@somewhere-tech/cli';
|
|
6
|
+
const CACHE_PATH = join(homedir(), '.somewhere', 'update-check.json');
|
|
7
|
+
const REGISTRY = `https://registry.npmjs.org/${PACKAGE.replace('/', '%2F')}/latest`;
|
|
8
|
+
const ONE_DAY = 24 * 60 * 60 * 1000;
|
|
9
|
+
const FETCH_TIMEOUT_MS = 1000;
|
|
10
|
+
/** Numeric major.minor.patch compare, ignoring pre-release tags. Returns true when
|
|
11
|
+
* `a` is strictly newer than `b`, false on anything unparseable (exported for
|
|
12
|
+
* tests — a string compare would call 1.9.0 newer than 1.10.0). */
|
|
13
|
+
export function isNewer(a, b) {
|
|
14
|
+
const parse = (v) => v.split('-')[0].split('.').map((n) => Number(n));
|
|
15
|
+
const pa = parse(a);
|
|
16
|
+
const pb = parse(b);
|
|
17
|
+
if (pa.length < 3 || pb.length < 3 || [...pa, ...pb].some((n) => Number.isNaN(n)))
|
|
18
|
+
return false;
|
|
19
|
+
for (let i = 0; i < 3; i++) {
|
|
20
|
+
if (pa[i] !== pb[i])
|
|
21
|
+
return pa[i] > pb[i];
|
|
22
|
+
}
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
function readCache() {
|
|
26
|
+
try {
|
|
27
|
+
return JSON.parse(readFileSync(CACHE_PATH, 'utf8'));
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
function writeCache(latest) {
|
|
34
|
+
try {
|
|
35
|
+
const dir = dirname(CACHE_PATH);
|
|
36
|
+
if (!existsSync(dir))
|
|
37
|
+
mkdirSync(dir, { recursive: true });
|
|
38
|
+
writeFileSync(CACHE_PATH, JSON.stringify({ checkedAt: Date.now(), latest }) + '\n', { mode: 0o600 });
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
// best-effort cache; a failed write just means we re-check next time
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
async function fetchLatest() {
|
|
45
|
+
try {
|
|
46
|
+
const controller = new AbortController();
|
|
47
|
+
const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
|
|
48
|
+
const res = await fetch(REGISTRY, { signal: controller.signal, headers: { accept: 'application/json' } });
|
|
49
|
+
clearTimeout(timer);
|
|
50
|
+
if (!res.ok)
|
|
51
|
+
return null;
|
|
52
|
+
const body = (await res.json());
|
|
53
|
+
return typeof body.version === 'string' ? body.version : null;
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
/** Notice provider: a newer @somewhere-tech/cli is published. Self-throttled to one
|
|
60
|
+
* npm check per day (cached in ~/.somewhere/update-check.json). The pipeline's
|
|
61
|
+
* central gate already ensured this only runs on interactive, non-CI, non-pass-
|
|
62
|
+
* through commands, so this provider just answers "is there an update?". */
|
|
63
|
+
export const updateProvider = {
|
|
64
|
+
id: 'cli-update',
|
|
65
|
+
async getNotice({ currentVersion }) {
|
|
66
|
+
try {
|
|
67
|
+
let cache = readCache();
|
|
68
|
+
if (!cache || Date.now() - cache.checkedAt > ONE_DAY) {
|
|
69
|
+
const latest = await fetchLatest();
|
|
70
|
+
if (latest) {
|
|
71
|
+
writeCache(latest);
|
|
72
|
+
cache = { checkedAt: Date.now(), latest };
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
if (!cache?.latest || !isNewer(cache.latest, currentVersion))
|
|
76
|
+
return null;
|
|
77
|
+
return (` ${teal('▲ Update available')} ${dim(currentVersion)} → ${teal(cache.latest)}\n` +
|
|
78
|
+
` Run ${teal('somewhere update')} to upgrade. ${dim('(SOMEWHERE_NO_NOTIFICATIONS=1 to silence)')}`);
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
//# sourceMappingURL=update.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"update.js","sourceRoot":"","sources":["../../../../src/lib/notify/providers/update.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/lib/notify/types.ts"],"names":[],"mappings":""}
|