saros-proxy 1.1.2 → 1.2.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.
@@ -1,6 +1,28 @@
1
1
  /**
2
2
  * update-check.ts — Check npm registry for newer versions.
3
3
  */
4
+ import { getVersion } from '../version.js';
5
+ /** Backwards-compatible alias for the shared cached version read. */
6
+ export { getVersion as getCurrentVersion };
7
+ export declare function isValidVersion(version: string): boolean;
8
+ /**
9
+ * Compare two semver-like version strings. Returns:
10
+ * -1 if a < b
11
+ * 0 if a == b
12
+ * 1 if a > b
13
+ *
14
+ * A prerelease sorts below its stable release (`1.1.3-rc.1 < 1.1.3`), as
15
+ * `saros-proxy update` uses this to decide whether to install.
16
+ */
17
+ export declare function compareVersions(a: string, b: string): number;
18
+ /**
19
+ * Read the `latest` version from the npm registry.
20
+ *
21
+ * Throws a human-readable error when the registry cannot answer — callers that
22
+ * only warn (the boot check) swallow it, `update --check` reports it.
23
+ * `fetchImpl` is injectable so tests never touch the network.
24
+ */
25
+ export declare function fetchLatestVersion(fetchImpl?: typeof fetch): Promise<string>;
4
26
  /**
5
27
  * Check npm registry for the latest version.
6
28
  * Prints a warning to stderr if a newer version is available.
@@ -1,61 +1,120 @@
1
1
  /**
2
2
  * update-check.ts — Check npm registry for newer versions.
3
3
  */
4
- import { readFileSync } from 'node:fs';
5
- import { resolve, dirname } from 'node:path';
6
- import { fileURLToPath } from 'node:url';
7
- let currentVersion;
8
- function getCurrentVersion() {
9
- if (!currentVersion) {
10
- try {
11
- const pkgPath = resolve(dirname(fileURLToPath(import.meta.url)), '..', '..', 'package.json');
12
- const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
13
- currentVersion = pkg.version;
14
- }
15
- catch {
16
- currentVersion = '0.0.0';
17
- }
18
- }
19
- return currentVersion;
4
+ import { getVersion } from '../version.js';
5
+ const REGISTRY_URL = 'https://registry.npmjs.org/saros-proxy/latest';
6
+ const REGISTRY_TIMEOUT_MS = 5000;
7
+ /**
8
+ * A registry version must be plain semver before it is interpolated into an
9
+ * `npm install` argument (on Windows that runs through a shell).
10
+ */
11
+ const VERSION_PATTERN = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/;
12
+ /** Backwards-compatible alias for the shared cached version read. */
13
+ export { getVersion as getCurrentVersion };
14
+ export function isValidVersion(version) {
15
+ return VERSION_PATTERN.test(version);
16
+ }
17
+ /**
18
+ * Split semver into numeric core and prerelease. Build metadata (`+…`) is
19
+ * ignored: it does not participate in precedence.
20
+ */
21
+ function parseVersion(version) {
22
+ const withoutBuild = version.trim().replace(/^v/, '').split('+')[0];
23
+ const dash = withoutBuild.indexOf('-');
24
+ const core = (dash === -1 ? withoutBuild : withoutBuild.slice(0, dash))
25
+ .split('.')
26
+ .map(Number);
27
+ return {
28
+ core,
29
+ prerelease: dash === -1 ? null : withoutBuild.slice(dash + 1).split('.'),
30
+ };
20
31
  }
21
32
  /**
22
33
  * Compare two semver-like version strings. Returns:
23
34
  * -1 if a < b
24
35
  * 0 if a == b
25
36
  * 1 if a > b
37
+ *
38
+ * A prerelease sorts below its stable release (`1.1.3-rc.1 < 1.1.3`), as
39
+ * `saros-proxy update` uses this to decide whether to install.
26
40
  */
27
- function compareVersions(a, b) {
28
- const aParts = a.split('.').map(Number);
29
- const bParts = b.split('.').map(Number);
30
- for (let i = 0; i < Math.max(aParts.length, bParts.length); i++) {
31
- const aVal = aParts[i] ?? 0;
32
- const bVal = bParts[i] ?? 0;
41
+ export function compareVersions(a, b) {
42
+ const left = parseVersion(a);
43
+ const right = parseVersion(b);
44
+ const coreLength = Math.max(left.core.length, right.core.length);
45
+ for (let i = 0; i < coreLength; i++) {
46
+ const aVal = left.core[i] ?? 0;
47
+ const bVal = right.core[i] ?? 0;
33
48
  if (aVal < bVal)
34
49
  return -1;
35
50
  if (aVal > bVal)
36
51
  return 1;
37
52
  }
53
+ if (left.prerelease === null && right.prerelease === null)
54
+ return 0;
55
+ if (left.prerelease === null)
56
+ return 1;
57
+ if (right.prerelease === null)
58
+ return -1;
59
+ const prereleaseLength = Math.max(left.prerelease.length, right.prerelease.length);
60
+ for (let i = 0; i < prereleaseLength; i++) {
61
+ const aId = left.prerelease[i];
62
+ const bId = right.prerelease[i];
63
+ if (aId === undefined)
64
+ return -1;
65
+ if (bId === undefined)
66
+ return 1;
67
+ const aNumeric = /^\d+$/.test(aId);
68
+ const bNumeric = /^\d+$/.test(bId);
69
+ if (aNumeric && bNumeric) {
70
+ const diff = Number(aId) - Number(bId);
71
+ if (diff !== 0)
72
+ return diff < 0 ? -1 : 1;
73
+ }
74
+ else if (aNumeric !== bNumeric) {
75
+ // Numeric identifiers have lower precedence than alphanumeric ones.
76
+ return aNumeric ? -1 : 1;
77
+ }
78
+ else if (aId !== bId) {
79
+ return aId < bId ? -1 : 1;
80
+ }
81
+ }
38
82
  return 0;
39
83
  }
84
+ /**
85
+ * Read the `latest` version from the npm registry.
86
+ *
87
+ * Throws a human-readable error when the registry cannot answer — callers that
88
+ * only warn (the boot check) swallow it, `update --check` reports it.
89
+ * `fetchImpl` is injectable so tests never touch the network.
90
+ */
91
+ export async function fetchLatestVersion(fetchImpl = fetch) {
92
+ const res = await fetchImpl(REGISTRY_URL, {
93
+ signal: AbortSignal.timeout(REGISTRY_TIMEOUT_MS),
94
+ });
95
+ if (!res.ok) {
96
+ throw new Error(`registry responded with HTTP ${res.status}`);
97
+ }
98
+ const data = (await res.json());
99
+ if (typeof data.version !== 'string' || data.version.length === 0) {
100
+ throw new Error('registry response has no version field');
101
+ }
102
+ if (!isValidVersion(data.version)) {
103
+ throw new Error(`registry returned an invalid version: ${data.version}`);
104
+ }
105
+ return data.version;
106
+ }
40
107
  /**
41
108
  * Check npm registry for the latest version.
42
109
  * Prints a warning to stderr if a newer version is available.
43
110
  * Never throws — failures are silently ignored.
44
111
  */
45
112
  export async function checkForUpdate() {
46
- const current = getCurrentVersion();
113
+ const current = getVersion();
47
114
  try {
48
- const res = await fetch('https://registry.npmjs.org/saros-proxy/latest', {
49
- signal: AbortSignal.timeout(5000),
50
- });
51
- if (!res.ok)
52
- return;
53
- const data = (await res.json());
54
- const latest = data.version;
55
- if (!latest)
56
- return;
115
+ const latest = await fetchLatestVersion();
57
116
  if (compareVersions(current, latest) < 0) {
58
- console.warn(`\n⚠️ Update available: ${current} → ${latest}. Run: npm install -g saros-proxy\n`);
117
+ console.warn(`\n⚠️ Update available: ${current} → ${latest}. Run: saros-proxy update\n`);
59
118
  }
60
119
  }
61
120
  catch {
@@ -1 +1 @@
1
- {"version":3,"file":"update-check.js","sourceRoot":"","sources":["../../src/cli/update-check.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAC7C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,IAAI,cAAkC,CAAC;AAEvC,SAAS,iBAAiB;IACxB,IAAI,CAAC,cAAc,EAAE,CAAC;QACpB,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,aAAa,CAAC,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,cAAc,CAAC,CAAC;YAC7F,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,OAAO,EAAE,OAAO,CAAC,CAAwB,CAAC;YAC9E,cAAc,GAAG,GAAG,CAAC,OAAO,CAAC;QAC/B,CAAC;QAAC,MAAM,CAAC;YACP,cAAc,GAAG,OAAO,CAAC;QAC3B,CAAC;IACH,CAAC;IACD,OAAO,cAAc,CAAC;AACxB,CAAC;AAED;;;;;GAKG;AACH,SAAS,eAAe,CAAC,CAAS,EAAE,CAAS;IAC3C,MAAM,MAAM,GAAG,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IACxC,MAAM,MAAM,GAAG,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IACxC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;QAChE,MAAM,IAAI,GAAG,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;QAC5B,MAAM,IAAI,GAAG,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;QAC5B,IAAI,IAAI,GAAG,IAAI;YAAE,OAAO,CAAC,CAAC,CAAC;QAC3B,IAAI,IAAI,GAAG,IAAI;YAAE,OAAO,CAAC,CAAC;IAC5B,CAAC;IACD,OAAO,CAAC,CAAC;AACX,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc;IAClC,MAAM,OAAO,GAAG,iBAAiB,EAAE,CAAC;IACpC,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,+CAA+C,EAAE;YACvE,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,IAAI,CAAC;SAClC,CAAC,CAAC;QACH,IAAI,CAAC,GAAG,CAAC,EAAE;YAAE,OAAO;QACpB,MAAM,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAAyB,CAAC;QACxD,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC;QAC5B,IAAI,CAAC,MAAM;YAAE,OAAO;QACpB,IAAI,eAAe,CAAC,OAAO,EAAE,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC;YACzC,OAAO,CAAC,IAAI,CACV,2BAA2B,OAAO,MAAM,MAAM,qCAAqC,CACpF,CAAC;QACJ,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,6CAA6C;IAC/C,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"update-check.js","sourceRoot":"","sources":["../../src/cli/update-check.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,99 @@
1
+ /**
2
+ * update.ts — `saros-proxy update`: replace a global npm install and restart
3
+ * whatever was serving.
4
+ *
5
+ * Safety rules:
6
+ * - only a package living in the npm global prefix is updated (--force
7
+ * overrides), so a git checkout is never replaced behind the user's back;
8
+ * - the running proxy is stopped first and is always restarted after a
9
+ * failed install, so the host is never left without a proxy;
10
+ * - success is verified by reading the installed package.json and by
11
+ * checking the restarted proxy's /health version, not by npm's exit code.
12
+ */
13
+ export interface UpdateArgs {
14
+ check: boolean;
15
+ yes: boolean;
16
+ force: boolean;
17
+ /** Port override for stop/start/health (`--port`). */
18
+ port?: number;
19
+ /** Config path override for stop/start/health (`--config`). */
20
+ config?: string;
21
+ error?: string;
22
+ }
23
+ /** Where and how to stop/start the proxy for this update. */
24
+ export interface ServiceOptions {
25
+ port?: number;
26
+ configPath?: string;
27
+ /** Entry point of the package that must serve after the update. */
28
+ entryPoint?: string;
29
+ }
30
+ export interface DaemonStateSnapshot {
31
+ running: boolean;
32
+ port: number;
33
+ pid?: string | null;
34
+ health?: Record<string, unknown>;
35
+ }
36
+ export interface UpdateDependencies {
37
+ currentVersion: string;
38
+ /** Registry lookup; rejects with a human-readable reason. */
39
+ fetchLatestVersion: () => Promise<string>;
40
+ /** Run npm/systemctl. Throws on non-zero; returns trimmed stdout. */
41
+ exec: (command: string, args: readonly string[], options?: ExecOptions) => string;
42
+ packageRoot: string;
43
+ readInstalledVersion: (packageRoot: string) => string | null;
44
+ isTTY: boolean;
45
+ confirm: (message: string) => Promise<boolean>;
46
+ getDaemonState: () => Promise<DaemonStateSnapshot>;
47
+ daemonStop: (options: ServiceOptions) => Promise<boolean>;
48
+ daemonStart: (options: ServiceOptions) => Promise<void>;
49
+ systemdActive: () => boolean;
50
+ systemdStop: () => void;
51
+ systemdStart: () => void;
52
+ probeHealth: (port: number) => Promise<{
53
+ ok: boolean;
54
+ body?: Record<string, unknown>;
55
+ }>;
56
+ daemonLogPath: string;
57
+ sleep: (ms: number) => Promise<void>;
58
+ healthAttempts: number;
59
+ healthDelayMs: number;
60
+ platform: NodeJS.Platform;
61
+ }
62
+ export interface ExecOptions {
63
+ /** Inherit stdio so npm's progress/errors reach the user directly. */
64
+ inherit?: boolean;
65
+ }
66
+ /** Restart path: systemd unit, PID-file daemon, or nothing was running. */
67
+ export type RestartMode = 'systemd' | 'daemon' | 'none';
68
+ /** Parse `update` arguments. The first invalid option is reported. */
69
+ export declare function parseUpdateArgs(argv: readonly string[]): UpdateArgs;
70
+ /**
71
+ * The proxy to restart: an active systemd unit outranks the PID-file daemon
72
+ * (a unit-managed process usually has no PID file), and nothing is started
73
+ * when neither was running before the update.
74
+ */
75
+ export declare function chooseRestartMode(daemonRunning: boolean, systemdActive: boolean): RestartMode;
76
+ /** Is the running package the one npm considers global? */
77
+ export declare function isGlobalInstall(packageRoot: string, npmGlobalRoot: string, platform?: NodeJS.Platform): boolean;
78
+ /** Read `<packageRoot>/package.json`'s version, or null when unusable. */
79
+ export declare function readPackageVersion(packageRoot: string): string | null;
80
+ /**
81
+ * Poll /health until it answers `status: ok` with the expected version.
82
+ * The version check is what distinguishes the freshly installed proxy from
83
+ * the old process that is still shutting down on the same port; `null`
84
+ * accepts any version, for a rollback that cannot know which release npm
85
+ * left on disk.
86
+ */
87
+ export declare function waitForUpdatedProxy(probe: () => Promise<{
88
+ ok: boolean;
89
+ body?: Record<string, unknown>;
90
+ }>, expectedVersion: string | null, attempts: number, delayMs: number, sleep: (ms: number) => Promise<void>): Promise<{
91
+ ok: boolean;
92
+ body?: Record<string, unknown>;
93
+ }>;
94
+ /**
95
+ * Run `saros-proxy update [--check] [--yes] [--force]`.
96
+ * Returns the process exit code; never calls process.exit so the whole flow
97
+ * is testable.
98
+ */
99
+ export declare function runUpdateCommand(argv: readonly string[], overrides?: Partial<UpdateDependencies>): Promise<number>;