om-memory-system 5.9.0 → 5.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/omms-launch.mjs +8 -4
- package/dist/cli/web-command.js +25 -9
- package/dist/cli/web-step-aside.d.ts +17 -0
- package/dist/cli/web-step-aside.js +34 -0
- package/dist/services/web-update.d.ts +6 -1
- package/dist/services/web-update.js +12 -1
- package/dist/web/assets/{index-Bhk1P5Z7.js → index-BX_zZiKa.js} +18 -18
- package/dist/web/assets/index-Be6Uh64d.css +2 -0
- package/dist/web/index.html +2 -2
- package/package.json +1 -1
- package/dist/web/assets/index-2BNRLGqe.css +0 -2
package/bin/omms-launch.mjs
CHANGED
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
//
|
|
6
6
|
// node omms-launch.mjs [--at-least-own-version] <om-memory-system arguments>
|
|
7
7
|
//
|
|
8
|
-
// Candidates: the
|
|
9
|
-
//
|
|
8
|
+
// Candidates: the global install beside the running Node.js, the copy named in
|
|
9
|
+
// ~/.omms/runtime.json, and the copy that holds this file. On a tie the first wins. With
|
|
10
10
|
// `--at-least-own-version`, the version in the `package.json` beside this file is
|
|
11
11
|
// the minimum. When no candidate reaches it, the launcher runs
|
|
12
12
|
// `npx --yes om-memory-system@<that version>`.
|
|
@@ -96,7 +96,11 @@ export function globalPackageRoots(execPath, platform = process.platform) {
|
|
|
96
96
|
return brew ? [direct, join(brew[1], "lib", "node_modules", "om-memory-system")] : [direct];
|
|
97
97
|
}
|
|
98
98
|
|
|
99
|
-
/**
|
|
99
|
+
/**
|
|
100
|
+
* The global installs, the record's copy, and the own copy, in that order. Invalid
|
|
101
|
+
* ones are null. On a tie the global install wins, so a host's cache copy does not
|
|
102
|
+
* keep serving after the global install catches up.
|
|
103
|
+
*/
|
|
100
104
|
export function findCandidates({ dir, execPath, ownRoot }) {
|
|
101
105
|
let recorded = null;
|
|
102
106
|
try {
|
|
@@ -106,7 +110,7 @@ export function findCandidates({ dir, execPath, ownRoot }) {
|
|
|
106
110
|
/* No record, or an unreadable one: the other candidates still work. */
|
|
107
111
|
}
|
|
108
112
|
const globals = globalPackageRoots(execPath).map((root) => readCopy(root));
|
|
109
|
-
return [
|
|
113
|
+
return [...globals, recorded, ownRoot ? readCopy(ownRoot) : null];
|
|
110
114
|
}
|
|
111
115
|
|
|
112
116
|
/**
|
package/dist/cli/web-command.js
CHANGED
|
@@ -130,6 +130,17 @@ export async function runWebCommand(args, options = {}, online, handover = {}, u
|
|
|
130
130
|
process.once("SIGTERM", stop);
|
|
131
131
|
// The page's Stop and Restart buttons reach the same process through this callback.
|
|
132
132
|
const { createPowerAction } = await import("./web-power.js");
|
|
133
|
+
// A restart whose copy failed serves again from this process.
|
|
134
|
+
// Safe to call twice: it replaces the keeper and the signal handlers instead of adding more.
|
|
135
|
+
const resumeServer = async () => {
|
|
136
|
+
clearInterval(keeper);
|
|
137
|
+
keeper = setInterval(() => { }, 60_000);
|
|
138
|
+
process.off("SIGINT", stop);
|
|
139
|
+
process.off("SIGTERM", stop);
|
|
140
|
+
process.once("SIGINT", stop);
|
|
141
|
+
process.once("SIGTERM", stop);
|
|
142
|
+
await server.start();
|
|
143
|
+
};
|
|
133
144
|
const powerAction = createPowerAction({
|
|
134
145
|
stopServer: async () => {
|
|
135
146
|
clearInterval(keeper);
|
|
@@ -137,13 +148,7 @@ export async function runWebCommand(args, options = {}, online, handover = {}, u
|
|
|
137
148
|
process.off("SIGTERM", stop);
|
|
138
149
|
await server.stop();
|
|
139
150
|
},
|
|
140
|
-
|
|
141
|
-
resumeServer: async () => {
|
|
142
|
-
keeper = setInterval(() => { }, 60_000);
|
|
143
|
-
process.once("SIGINT", stop);
|
|
144
|
-
process.once("SIGTERM", stop);
|
|
145
|
-
await server.start();
|
|
146
|
-
},
|
|
151
|
+
resumeServer,
|
|
147
152
|
baseUrl: server.getUrl(),
|
|
148
153
|
loginItem: action === "--login-item",
|
|
149
154
|
autostart: options,
|
|
@@ -157,11 +162,22 @@ export async function runWebCommand(args, options = {}, online, handover = {}, u
|
|
|
157
162
|
// Every restart onto the new copy runs the launcher, the login item's included.
|
|
158
163
|
const canRestart = () => existsSync(launcherPath(ommsDir()));
|
|
159
164
|
server.setWebUpdate(startWebUpdate(() => powerAction("update"), canRestart, () => server.powerActionRunning));
|
|
160
|
-
// A newer
|
|
165
|
+
// A newer caller asked this web app to give up the port. If the caller's copy
|
|
166
|
+
// never takes it, restart onto the newest copy instead of leaving it empty.
|
|
167
|
+
const { leaveAfterStepAside } = await import("./web-step-aside.js");
|
|
168
|
+
const { log } = await import("../services/logger.js");
|
|
161
169
|
server.setOnStepAside(async () => {
|
|
162
170
|
clearInterval(keeper);
|
|
163
171
|
await server.stop();
|
|
164
|
-
|
|
172
|
+
await leaveAfterStepAside({
|
|
173
|
+
ownerAnswers: () => server.checkServerAvailable(),
|
|
174
|
+
restartNewest: () => powerAction("update"),
|
|
175
|
+
resume: resumeServer,
|
|
176
|
+
sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
|
|
177
|
+
now: () => Date.now(),
|
|
178
|
+
exit: (code) => process.exit(code),
|
|
179
|
+
log,
|
|
180
|
+
});
|
|
165
181
|
});
|
|
166
182
|
return 0;
|
|
167
183
|
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { LogLevel } from "../services/logger.js";
|
|
2
|
+
/** How long the old web app waits for the caller's copy to answer. */
|
|
3
|
+
export declare const NEW_OWNER_WAIT_MS = 30000;
|
|
4
|
+
export interface StepAsideDeps {
|
|
5
|
+
/** True when an OMMS web app answers on the configured port. */
|
|
6
|
+
ownerAnswers: () => Promise<boolean>;
|
|
7
|
+
/** Restart onto the newest copy through the launcher. Returns a failure code when it did not. */
|
|
8
|
+
restartNewest: () => Promise<string | undefined>;
|
|
9
|
+
/** Serve again from this process. */
|
|
10
|
+
resume: () => Promise<void>;
|
|
11
|
+
sleep: (ms: number) => Promise<void>;
|
|
12
|
+
now: () => number;
|
|
13
|
+
exit: (code: number) => void;
|
|
14
|
+
log: (message: string, data: Record<string, unknown>, level?: LogLevel) => void;
|
|
15
|
+
}
|
|
16
|
+
/** Call after this web app stopped serving. It never leaves the port empty when it can help it. */
|
|
17
|
+
export declare function leaveAfterStepAside(deps: StepAsideDeps): Promise<void>;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// What a standalone web app does after it stops serving for a newer caller. The
|
|
2
|
+
// caller starts the newer copy, but a caller can quit first (a host that restarts
|
|
3
|
+
// for its own update), which left the port empty until some other host started a
|
|
4
|
+
// web app. So the old web app waits for a new owner, and restarts onto the newest
|
|
5
|
+
// copy itself when none comes.
|
|
6
|
+
/** How long the old web app waits for the caller's copy to answer. */
|
|
7
|
+
export const NEW_OWNER_WAIT_MS = 30_000;
|
|
8
|
+
const POLL_MS = 500;
|
|
9
|
+
/** Call after this web app stopped serving. It never leaves the port empty when it can help it. */
|
|
10
|
+
export async function leaveAfterStepAside(deps) {
|
|
11
|
+
const began = deps.now();
|
|
12
|
+
while (deps.now() - began < NEW_OWNER_WAIT_MS) {
|
|
13
|
+
if (await deps.ownerAnswers()) {
|
|
14
|
+
deps.exit(0);
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
await deps.sleep(POLL_MS);
|
|
18
|
+
}
|
|
19
|
+
deps.log("Web app step-aside", { code: "no-new-owner" }, "warn");
|
|
20
|
+
// A successful restart exits this process.
|
|
21
|
+
const code = await deps.restartNewest();
|
|
22
|
+
if (code === undefined)
|
|
23
|
+
return;
|
|
24
|
+
// A failed restart can already serve again from this process.
|
|
25
|
+
if (await deps.ownerAnswers())
|
|
26
|
+
return;
|
|
27
|
+
try {
|
|
28
|
+
await deps.resume();
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
deps.log("Web app step-aside", { code: "resume-failed" }, "error");
|
|
32
|
+
deps.exit(1);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -37,7 +37,7 @@ export interface WebUpdateDeps {
|
|
|
37
37
|
shell: boolean;
|
|
38
38
|
path?: string;
|
|
39
39
|
}) => InstallChild;
|
|
40
|
-
/** The version of the global install
|
|
40
|
+
/** The version of the global install (also read before npm runs), or null. */
|
|
41
41
|
globalVersion: () => string | null;
|
|
42
42
|
/** False when no restart could serve the new copy, such as a missing launcher. Checked before npm runs. */
|
|
43
43
|
canRestart: () => boolean;
|
|
@@ -80,6 +80,11 @@ export declare class WebUpdate {
|
|
|
80
80
|
/** Read npm `latest`. A failed read keeps the last result. Calls during a read share it. */
|
|
81
81
|
check(): Promise<void>;
|
|
82
82
|
private read;
|
|
83
|
+
/**
|
|
84
|
+
* The older of the running copy and the global install. A web app can run from a
|
|
85
|
+
* host's cache copy that is already current while the global install stays behind.
|
|
86
|
+
*/
|
|
87
|
+
private installedVersion;
|
|
83
88
|
/** Start the install the Update button starts, once a check found a release. */
|
|
84
89
|
private maybeAutoInstall;
|
|
85
90
|
status(): UpdateStatus;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { posix, win32 } from "node:path";
|
|
2
2
|
import { availableUpdate, latestNpmVersion } from "./update-check.js";
|
|
3
|
+
import { compareVersions } from "./version-compare.js";
|
|
3
4
|
// The web app's npm release check. It reads npm `latest` at start, every 10
|
|
4
5
|
// minutes, and when an open page reads the status and the last check is over a
|
|
5
6
|
// minute old. It keeps the result in memory for `GET /api/web/status`. It imports
|
|
@@ -58,9 +59,19 @@ export class WebUpdate {
|
|
|
58
59
|
this.deps.log("Web app update check failed", { code: "unreachable" }, "error");
|
|
59
60
|
return;
|
|
60
61
|
}
|
|
61
|
-
this.available = availableUpdate(this.
|
|
62
|
+
this.available = availableUpdate(this.installedVersion(), latest);
|
|
62
63
|
this.maybeAutoInstall();
|
|
63
64
|
}
|
|
65
|
+
/**
|
|
66
|
+
* The older of the running copy and the global install. A web app can run from a
|
|
67
|
+
* host's cache copy that is already current while the global install stays behind.
|
|
68
|
+
*/
|
|
69
|
+
installedVersion() {
|
|
70
|
+
const global = this.deps.globalVersion();
|
|
71
|
+
return global !== null && compareVersions(global, this.deps.version) === -1
|
|
72
|
+
? global
|
|
73
|
+
: this.deps.version;
|
|
74
|
+
}
|
|
64
75
|
/** Start the install the Update button starts, once a check found a release. */
|
|
65
76
|
maybeAutoInstall() {
|
|
66
77
|
if (this.state === "installing" || this.state === "restarting")
|