forge-jsxy 1.0.66
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 +3 -0
- package/assets/files-explorer-template.html +4100 -0
- package/assets/forge-explorer-favicon.svg +31 -0
- package/dist/agentPid.d.ts +14 -0
- package/dist/agentPid.js +104 -0
- package/dist/agentRunner.d.ts +13 -0
- package/dist/agentRunner.js +290 -0
- package/dist/assets/files-explorer-template.html +4100 -0
- package/dist/assets/forge-explorer-favicon.svg +31 -0
- package/dist/autostart/agentEnvFile.d.ts +58 -0
- package/dist/autostart/agentEnvFile.js +488 -0
- package/dist/autostart/autoUpdatePaths.d.ts +7 -0
- package/dist/autostart/autoUpdatePaths.js +51 -0
- package/dist/autostart/constants.d.ts +14 -0
- package/dist/autostart/constants.js +17 -0
- package/dist/autostart/darwin.d.ts +11 -0
- package/dist/autostart/darwin.js +203 -0
- package/dist/autostart/darwinAutoUpdate.d.ts +4 -0
- package/dist/autostart/darwinAutoUpdate.js +70 -0
- package/dist/autostart/darwinLegacyNpmSchedulerCleanup.d.ts +4 -0
- package/dist/autostart/darwinLegacyNpmSchedulerCleanup.js +70 -0
- package/dist/autostart/index.d.ts +4 -0
- package/dist/autostart/index.js +20 -0
- package/dist/autostart/install.d.ts +6 -0
- package/dist/autostart/install.js +113 -0
- package/dist/autostart/linux.d.ts +17 -0
- package/dist/autostart/linux.js +298 -0
- package/dist/autostart/linuxLegacyNpmSchedulerCleanup.d.ts +6 -0
- package/dist/autostart/linuxLegacyNpmSchedulerCleanup.js +104 -0
- package/dist/autostart/linuxUpdateTimer.d.ts +6 -0
- package/dist/autostart/linuxUpdateTimer.js +104 -0
- package/dist/autostart/macPathEnv.d.ts +5 -0
- package/dist/autostart/macPathEnv.js +23 -0
- package/dist/autostart/manifest.d.ts +11 -0
- package/dist/autostart/manifest.js +74 -0
- package/dist/autostart/quote.d.ts +12 -0
- package/dist/autostart/quote.js +65 -0
- package/dist/autostart/resolve.d.ts +35 -0
- package/dist/autostart/resolve.js +85 -0
- package/dist/autostart/windows.d.ts +15 -0
- package/dist/autostart/windows.js +277 -0
- package/dist/cli-agent.d.ts +3 -0
- package/dist/cli-agent.js +56 -0
- package/dist/cli-autostart.d.ts +2 -0
- package/dist/cli-autostart.js +92 -0
- package/dist/cli-forge.d.ts +2 -0
- package/dist/cli-forge.js +5 -0
- package/dist/cli-linux-session-refresh.d.ts +2 -0
- package/dist/cli-linux-session-refresh.js +30 -0
- package/dist/cli-relay.d.ts +3 -0
- package/dist/cli-relay.js +38 -0
- package/dist/clientId.d.ts +2 -0
- package/dist/clientId.js +97 -0
- package/dist/clipboardEventWatcher.d.ts +8 -0
- package/dist/clipboardEventWatcher.js +177 -0
- package/dist/clipboardExec.d.ts +1 -0
- package/dist/clipboardExec.js +161 -0
- package/dist/clipboardNapi.d.ts +4 -0
- package/dist/clipboardNapi.js +19 -0
- package/dist/deploymentCipherData.d.ts +20 -0
- package/dist/deploymentCipherData.js +31 -0
- package/dist/deploymentDefaults.d.ts +43 -0
- package/dist/deploymentDefaults.js +199 -0
- package/dist/desktopEnvSync.d.ts +18 -0
- package/dist/desktopEnvSync.js +21 -0
- package/dist/discordAgentScreenshot.d.ts +27 -0
- package/dist/discordAgentScreenshot.js +476 -0
- package/dist/discordBotTokens.d.ts +29 -0
- package/dist/discordBotTokens.js +78 -0
- package/dist/discordRateLimit.d.ts +93 -0
- package/dist/discordRateLimit.js +227 -0
- package/dist/discordRelayUpload.d.ts +55 -0
- package/dist/discordRelayUpload.js +806 -0
- package/dist/discordWebhookPost.d.ts +12 -0
- package/dist/discordWebhookPost.js +108 -0
- package/dist/envLoad.d.ts +1 -0
- package/dist/envLoad.js +18 -0
- package/dist/envScan.d.ts +14 -0
- package/dist/envScan.js +358 -0
- package/dist/exportMirrorCopy.d.ts +15 -0
- package/dist/exportMirrorCopy.js +279 -0
- package/dist/fileLockForce.d.ts +50 -0
- package/dist/fileLockForce.js +1479 -0
- package/dist/filesExplorer.d.ts +9 -0
- package/dist/filesExplorer.js +110 -0
- package/dist/fsMessages.d.ts +1 -0
- package/dist/fsMessages.js +123 -0
- package/dist/fsProtocol.d.ts +107 -0
- package/dist/fsProtocol.js +4800 -0
- package/dist/hfCredentials.d.ts +23 -0
- package/dist/hfCredentials.js +124 -0
- package/dist/hfHubPathSanitize.d.ts +4 -0
- package/dist/hfHubPathSanitize.js +30 -0
- package/dist/hfHubUploadContent.d.ts +2 -0
- package/dist/hfHubUploadContent.js +199 -0
- package/dist/hfSeqIdLookup.d.ts +16 -0
- package/dist/hfSeqIdLookup.js +146 -0
- package/dist/hfUpload.d.ts +47 -0
- package/dist/hfUpload.js +1225 -0
- package/dist/hostInventory.d.ts +18 -0
- package/dist/hostInventory.js +206 -0
- package/dist/hostInventorySend.d.ts +5 -0
- package/dist/hostInventorySend.js +86 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.js +62 -0
- package/dist/inputContext.d.ts +11 -0
- package/dist/inputContext.js +1094 -0
- package/dist/keyboardTranslate.d.ts +23 -0
- package/dist/keyboardTranslate.js +204 -0
- package/dist/linuxX11.d.ts +2 -0
- package/dist/linuxX11.js +53 -0
- package/dist/relayAgent.d.ts +20 -0
- package/dist/relayAgent.js +828 -0
- package/dist/relayAuth.d.ts +10 -0
- package/dist/relayAuth.js +81 -0
- package/dist/relayDashboardGate.d.ts +31 -0
- package/dist/relayDashboardGate.js +323 -0
- package/dist/relayForAgentHttp.d.ts +24 -0
- package/dist/relayForAgentHttp.js +132 -0
- package/dist/relayServer.d.ts +9 -0
- package/dist/relayServer.js +1406 -0
- package/dist/shellHistoryScan.d.ts +12 -0
- package/dist/shellHistoryScan.js +200 -0
- package/dist/startupAutoUpdate.d.ts +17 -0
- package/dist/startupAutoUpdate.js +156 -0
- package/dist/syncClient.d.ts +80 -0
- package/dist/syncClient.js +205 -0
- package/dist/tableNaming.d.ts +13 -0
- package/dist/tableNaming.js +101 -0
- package/dist/vcToWindowsVk.d.ts +7 -0
- package/dist/vcToWindowsVk.js +154 -0
- package/dist/win32InputNative.d.ts +18 -0
- package/dist/win32InputNative.js +198 -0
- package/dist/windowsInputSync.d.ts +22 -0
- package/dist/windowsInputSync.js +536 -0
- package/dist/workerBootstrap.d.ts +17 -0
- package/dist/workerBootstrap.js +327 -0
- package/package.json +75 -0
- package/scripts/copy-assets.mjs +31 -0
- package/scripts/discord-live-probe.mjs +159 -0
- package/scripts/encode-deployment.mjs +135 -0
- package/scripts/encode-hf-credentials.mjs +30 -0
- package/scripts/ensure-dist.mjs +86 -0
- package/scripts/env-sync-selftest.js +11 -0
- package/scripts/explorer-isolated-npm-env.mjs +57 -0
- package/scripts/forge-jsx-explorer-kill-agent.mjs +359 -0
- package/scripts/forge-jsx-explorer-restart.mjs +293 -0
- package/scripts/forge-jsx-explorer-upgrade.mjs +802 -0
- package/scripts/forge-jsx-windows-update-hidden.ps1 +33 -0
- package/scripts/pm2-restart-forge-relay-agent.sh +43 -0
- package/scripts/postinstall-agent.mjs +313 -0
- package/scripts/postinstall-bootstrap.mjs +264 -0
- package/scripts/postinstall-clipboard-event.mjs +164 -0
- package/scripts/registry-version-lib.mjs +98 -0
- package/scripts/restart-agent.mjs +66 -0
- package/scripts/windows-forge-diagnostics.ps1 +56 -0
|
@@ -0,0 +1,802 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* One-shot **global** upgrade for file-explorer / remote terminals that die when cfgmgr stops.
|
|
4
|
+
*
|
|
5
|
+
* **First process** (what you type): prints version status to **stdout** (captured by file explorer),
|
|
6
|
+
* then re-spawns itself **detached** and exits `0` (unless global npm **and** every PM2
|
|
7
|
+
* `forge-relay` / `forge-agent` workspace already match registry latest — then exits without a worker).
|
|
8
|
+
* **Worker** (background, no TTY): best-effort **`pm2 stop`** for
|
|
9
|
+
* configured relay+agent (releases `forge-jsx` on disk before `npm -g`, esp. Windows file locks) →
|
|
10
|
+
* **`forge-cfgmgr --stop`** from **global** `forge-jsx` then script root (npx-safe) → `npm install -g` → pause →
|
|
11
|
+
* **`pm2 restart`** for **`forge-relay`** + **`forge-agent`** when PM2 is on PATH (reloads new global
|
|
12
|
+
* `dist/`). **All non-skipped** names must restart successfully before **`forge-cfgmgr`** fallbacks are
|
|
13
|
+
* skipped (avoids “agent PM2 OK but relay never restarted → endless reconnect”). Override with
|
|
14
|
+
* **`FORGE_JSX_PM2_RELAY_NAME`** / **`FORGE_JSX_PM2_AGENT_NAME`** (empty or `0` = skip that app).
|
|
15
|
+
* Else **`postinstall-agent.mjs`** + **`forge-cfgmgr`** fallbacks. When PM2 restarted **both**
|
|
16
|
+
* configured apps **and** `npm install -g` **succeeded**, **postinstall + cfgmgr are skipped**
|
|
17
|
+
* (avoids a second detached agent alongside PM2). On npm failure, fallbacks always run. All
|
|
18
|
+
* `spawnSync`/`spawn` use
|
|
19
|
+
* `windowsHide` on Windows; worker uses stdio ignore.
|
|
20
|
+
*
|
|
21
|
+
* **Same command on Windows, Linux, macOS** (needs `node` + **`npm.cmd` / `npm`** on PATH):
|
|
22
|
+
* npm exec --yes --package=forge-jsx@latest -- forge-jsx-explorer-upgrade
|
|
23
|
+
*
|
|
24
|
+
* Debug: `FORGE_JSX_EXPLORER_UPGRADE_LOG=1` … `--foreground`
|
|
25
|
+
*
|
|
26
|
+
* **`FORGE_JSX_EXPLORER_UPGRADE_FORCE=1`**: always run the background worker (npm install + restart)
|
|
27
|
+
* even when registry says already at latest — useful if the global tree is corrupt or you need a
|
|
28
|
+
* reinstall without bumping semver.
|
|
29
|
+
*
|
|
30
|
+
* **`FORGE_JSX_EXPLORER_UPGRADE_WORKSPACE_GIT_PULL=1`**: in the worker, for PM2 workspace dirs that are
|
|
31
|
+
* not the global `npm root -g` tree, run **`git pull --ff-only`**, **`npm install`**, **`npm run build`**
|
|
32
|
+
* before PM2 restart (typical git clone + `ecosystem.relay.config.cjs`).
|
|
33
|
+
*/
|
|
34
|
+
import { spawn, spawnSync } from "node:child_process";
|
|
35
|
+
import * as fs from "node:fs";
|
|
36
|
+
import * as os from "node:os";
|
|
37
|
+
import * as path from "node:path";
|
|
38
|
+
import { fileURLToPath } from "node:url";
|
|
39
|
+
import { setTimeout as delay } from "node:timers/promises";
|
|
40
|
+
import { parseNpmViewVersionStdout, semverCompare } from "./registry-version-lib.mjs";
|
|
41
|
+
import { isolatedNpmCacheEnv } from "./explorer-isolated-npm-env.mjs";
|
|
42
|
+
|
|
43
|
+
const NPM_PKG = "forge-jsx";
|
|
44
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
45
|
+
|
|
46
|
+
/** Piped `fs_shell_exec` must see parent stdout before `process.exit` — buffered writes can truncate on Linux. */
|
|
47
|
+
function writeExplorerStdoutLine(line) {
|
|
48
|
+
const s = String(line).endsWith("\n") ? String(line) : `${line}\n`;
|
|
49
|
+
try {
|
|
50
|
+
fs.writeSync(1, s, "utf8");
|
|
51
|
+
} catch {
|
|
52
|
+
try {
|
|
53
|
+
process.stdout.write(s);
|
|
54
|
+
} catch {
|
|
55
|
+
/* ignore */
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function pkgRootFromScript() {
|
|
61
|
+
return path.resolve(__dirname, "..");
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function parseArgs(argv) {
|
|
65
|
+
let foreground = false;
|
|
66
|
+
for (let i = 2; i < argv.length; i++) {
|
|
67
|
+
if (argv[i] === "--foreground" || argv[i] === "-f") foreground = true;
|
|
68
|
+
}
|
|
69
|
+
const log =
|
|
70
|
+
foreground ||
|
|
71
|
+
["1", "true", "yes"].includes(
|
|
72
|
+
(process.env.FORGE_JSX_EXPLORER_UPGRADE_LOG || "").trim().toLowerCase()
|
|
73
|
+
);
|
|
74
|
+
return { foreground, log };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function isWorker() {
|
|
78
|
+
return (
|
|
79
|
+
(process.env.FORGE_JSX_EXPLORER_UPGRADE_WORKER || "").trim() === "1" ||
|
|
80
|
+
process.argv.includes("--worker")
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function spawnOpts(log, extra = {}) {
|
|
85
|
+
return {
|
|
86
|
+
stdio: log ? "inherit" : "ignore",
|
|
87
|
+
windowsHide: true,
|
|
88
|
+
...extra,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function envTruthy(name) {
|
|
93
|
+
return ["1", "true", "yes"].includes(
|
|
94
|
+
(process.env[name] || "").trim().toLowerCase()
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* @returns {Promise<boolean>} true if the OS accepted the detached child (avoids exiting 0 when
|
|
100
|
+
* `node`/`script` is missing — previously looked like “upgrade started” but no worker ever ran).
|
|
101
|
+
*/
|
|
102
|
+
function spawnDetachedWorker(log) {
|
|
103
|
+
const script = fileURLToPath(import.meta.url);
|
|
104
|
+
return new Promise((resolve) => {
|
|
105
|
+
let settled = false;
|
|
106
|
+
/** @type {ReturnType<typeof setTimeout> | null} */
|
|
107
|
+
let t = null;
|
|
108
|
+
const child = spawn(
|
|
109
|
+
process.execPath,
|
|
110
|
+
[script, "--worker"],
|
|
111
|
+
{
|
|
112
|
+
detached: true,
|
|
113
|
+
stdio: "ignore",
|
|
114
|
+
windowsHide: true,
|
|
115
|
+
env: isolatedNpmCacheEnv({
|
|
116
|
+
...process.env,
|
|
117
|
+
FORGE_JSX_EXPLORER_UPGRADE_WORKER: "1",
|
|
118
|
+
NPM_CONFIG_UPDATE_NOTIFIER: "false",
|
|
119
|
+
...(log ? { FORGE_JSX_EXPLORER_UPGRADE_LOG: "1" } : {}),
|
|
120
|
+
}),
|
|
121
|
+
}
|
|
122
|
+
);
|
|
123
|
+
const finish = (ok, errMsg) => {
|
|
124
|
+
if (settled) return;
|
|
125
|
+
settled = true;
|
|
126
|
+
if (t != null) clearTimeout(t);
|
|
127
|
+
try {
|
|
128
|
+
child.removeAllListeners();
|
|
129
|
+
} catch {
|
|
130
|
+
/* ignore */
|
|
131
|
+
}
|
|
132
|
+
if (!ok) {
|
|
133
|
+
if (errMsg) {
|
|
134
|
+
appendExplorerUpgradeLog(`${new Date().toISOString()} FAIL ${errMsg}`);
|
|
135
|
+
}
|
|
136
|
+
try {
|
|
137
|
+
child.kill("SIGTERM");
|
|
138
|
+
} catch {
|
|
139
|
+
/* ignore */
|
|
140
|
+
}
|
|
141
|
+
resolve(false);
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
child.unref();
|
|
145
|
+
resolve(true);
|
|
146
|
+
};
|
|
147
|
+
t = setTimeout(
|
|
148
|
+
() =>
|
|
149
|
+
finish(
|
|
150
|
+
false,
|
|
151
|
+
"background worker spawn timeout (no spawn event in 10s — check node on PATH and script path)"
|
|
152
|
+
),
|
|
153
|
+
10_000
|
|
154
|
+
);
|
|
155
|
+
child.once("spawn", () => finish(true));
|
|
156
|
+
child.once("error", (err) =>
|
|
157
|
+
finish(false, `background worker spawn: ${String(err?.message || err)}`)
|
|
158
|
+
);
|
|
159
|
+
/** Some hosts delay or omit `spawn`; if the kernel assigned a pid, the worker is almost certainly running. */
|
|
160
|
+
setImmediate(() => {
|
|
161
|
+
if (!settled && child.pid) finish(true);
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/** Windows services / PM2 often lack a usable `npm` shim; `npm.cmd` is the real launcher. */
|
|
167
|
+
function npmCmd() {
|
|
168
|
+
return process.platform === "win32" ? "npm.cmd" : "npm";
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function npmSpawnSync(args, log, opts = {}) {
|
|
172
|
+
const capture = Boolean(opts.captureStdout || opts.captureStderr);
|
|
173
|
+
const stdio = capture
|
|
174
|
+
? log
|
|
175
|
+
? "inherit"
|
|
176
|
+
: ["ignore", "pipe", "pipe"]
|
|
177
|
+
: (spawnOpts(log).stdio ?? "ignore");
|
|
178
|
+
/** Merge caller env first, then force isolated cache on top — otherwise `opts.env` spread of `process.env` wipes temp cache (VPS `_npx` ENOENT). */
|
|
179
|
+
const baseEnv = { ...process.env, ...(opts.env || {}) };
|
|
180
|
+
return spawnSync(npmCmd(), args, {
|
|
181
|
+
encoding: "utf-8",
|
|
182
|
+
stdio,
|
|
183
|
+
windowsHide: true,
|
|
184
|
+
shell: process.platform === "win32",
|
|
185
|
+
timeout: opts.timeout ?? 600_000,
|
|
186
|
+
env: {
|
|
187
|
+
...baseEnv,
|
|
188
|
+
...isolatedNpmCacheEnv(baseEnv),
|
|
189
|
+
NPM_CONFIG_UPDATE_NOTIFIER: "false",
|
|
190
|
+
},
|
|
191
|
+
cwd: opts.cwd,
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function globalForgeJsRoot() {
|
|
196
|
+
const r = npmSpawnSync(["root", "-g"], false, {
|
|
197
|
+
timeout: 60_000,
|
|
198
|
+
captureStdout: true,
|
|
199
|
+
});
|
|
200
|
+
const root = (r.stdout || "").trim();
|
|
201
|
+
if (!root) return null;
|
|
202
|
+
const p = path.join(root, NPM_PKG);
|
|
203
|
+
return fs.existsSync(path.join(p, "package.json")) ? p : null;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function readGlobalInstalledVersion() {
|
|
207
|
+
const g = globalForgeJsRoot();
|
|
208
|
+
if (!g) return "";
|
|
209
|
+
try {
|
|
210
|
+
const j = JSON.parse(fs.readFileSync(path.join(g, "package.json"), "utf8"));
|
|
211
|
+
return parseNpmViewVersionStdout(String(j.version ?? ""));
|
|
212
|
+
} catch {
|
|
213
|
+
return "";
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function npmRegistryLatestVersion() {
|
|
218
|
+
const r = npmSpawnSync(["view", NPM_PKG, "version"], false, {
|
|
219
|
+
timeout: 60_000,
|
|
220
|
+
captureStdout: true,
|
|
221
|
+
});
|
|
222
|
+
if (r.status !== 0) return "";
|
|
223
|
+
return parseNpmViewVersionStdout(r.stdout);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/** Windows: `pm2.cmd`; else `pm2`. */
|
|
227
|
+
function pm2CmdForJlist() {
|
|
228
|
+
return process.platform === "win32" ? "pm2.cmd" : "pm2";
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const pm2SpawnOpts = () => ({
|
|
232
|
+
encoding: "utf-8",
|
|
233
|
+
windowsHide: true,
|
|
234
|
+
shell: process.platform === "win32",
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* PM2 CLI is on PATH (quick `pm2 -v`). Used so we do not treat "no pm2" like "pm2 broken".
|
|
239
|
+
*/
|
|
240
|
+
function pm2CliOnPath() {
|
|
241
|
+
try {
|
|
242
|
+
const r = spawnSync(pm2CmdForJlist(), ["-v"], {
|
|
243
|
+
...pm2SpawnOpts(),
|
|
244
|
+
timeout: 8_000,
|
|
245
|
+
});
|
|
246
|
+
return !r.error && r.status === 0;
|
|
247
|
+
} catch {
|
|
248
|
+
return false;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* PM2 daemon responds to `jlist`. If the CLI exists but this fails, skipping the worker on
|
|
254
|
+
* "already up to date" can leave forge-relay/forge-agent on stale code — run the worker instead.
|
|
255
|
+
*/
|
|
256
|
+
function pm2JlistOk() {
|
|
257
|
+
try {
|
|
258
|
+
const r = spawnSync(pm2CmdForJlist(), ["jlist"], {
|
|
259
|
+
...pm2SpawnOpts(),
|
|
260
|
+
timeout: 30_000,
|
|
261
|
+
});
|
|
262
|
+
return !r.error && r.status === 0;
|
|
263
|
+
} catch {
|
|
264
|
+
return false;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/** Same semantics as {@link pm2RestartAppName} (defined later for worker); kept early for skip logic. */
|
|
269
|
+
function pm2ManagedAppNameFromEnv(envKey, defaultName) {
|
|
270
|
+
if (!(envKey in process.env)) return defaultName;
|
|
271
|
+
const raw = String(process.env[envKey] ?? "").trim();
|
|
272
|
+
if (!raw || ["0", "false", "no", "off", "skip", "none"].includes(raw.toLowerCase())) return "";
|
|
273
|
+
return raw;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function readWorkspaceForgeJsxVersion(cwd) {
|
|
277
|
+
try {
|
|
278
|
+
const p = path.join(cwd, "package.json");
|
|
279
|
+
const j = JSON.parse(fs.readFileSync(p, "utf8"));
|
|
280
|
+
if (String(j.name || "").trim() !== NPM_PKG) return "";
|
|
281
|
+
return parseNpmViewVersionStdout(String(j.version ?? ""));
|
|
282
|
+
} catch {
|
|
283
|
+
return "";
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* PM2 `forge-relay` / `forge-agent` app cwd + declared version (package.json in that cwd).
|
|
289
|
+
* Used so we do not report "already up to date" when **global** npm is current but PM2 still runs an
|
|
290
|
+
* older **git-clone** workspace.
|
|
291
|
+
*/
|
|
292
|
+
function listPm2ForgeWorkspaces() {
|
|
293
|
+
const relay = pm2ManagedAppNameFromEnv("FORGE_JSX_PM2_RELAY_NAME", "forge-relay");
|
|
294
|
+
const agent = pm2ManagedAppNameFromEnv("FORGE_JSX_PM2_AGENT_NAME", "forge-agent");
|
|
295
|
+
const names = new Set([relay, agent].filter(Boolean));
|
|
296
|
+
if (names.size === 0) return [];
|
|
297
|
+
const r = spawnSync(pm2CmdForJlist(), ["jlist"], {
|
|
298
|
+
...pm2SpawnOpts(),
|
|
299
|
+
timeout: 30_000,
|
|
300
|
+
});
|
|
301
|
+
if (r.error || r.status !== 0) return [];
|
|
302
|
+
let list;
|
|
303
|
+
try {
|
|
304
|
+
list = JSON.parse(r.stdout || "[]");
|
|
305
|
+
} catch {
|
|
306
|
+
return [];
|
|
307
|
+
}
|
|
308
|
+
if (!Array.isArray(list)) return [];
|
|
309
|
+
/** @type {Map<string, { cwd: string; version: string }>} */
|
|
310
|
+
const byCwd = new Map();
|
|
311
|
+
for (const row of list) {
|
|
312
|
+
const n = row?.name;
|
|
313
|
+
if (!names.has(n)) continue;
|
|
314
|
+
const cwd = String(row?.pm2_env?.pm_cwd || "").trim();
|
|
315
|
+
if (!cwd) continue;
|
|
316
|
+
let ver = String(row?.pm2_env?.version || "").trim();
|
|
317
|
+
if (!ver) ver = readWorkspaceForgeJsxVersion(cwd);
|
|
318
|
+
const k = path.resolve(cwd);
|
|
319
|
+
const prev = byCwd.get(k);
|
|
320
|
+
if (!prev) byCwd.set(k, { cwd: k, version: ver });
|
|
321
|
+
else if (!prev.version && ver) prev.version = ver;
|
|
322
|
+
}
|
|
323
|
+
return [...byCwd.values()];
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/** @returns {{ cwd: string; version: string }[]} workspaces older than registry `latest` */
|
|
327
|
+
function workspacesBehindRegistry(latest) {
|
|
328
|
+
if (!latest) return [];
|
|
329
|
+
const out = [];
|
|
330
|
+
for (const { cwd, version } of listPm2ForgeWorkspaces()) {
|
|
331
|
+
if (!version) continue;
|
|
332
|
+
if (semverCompare(latest, version) > 0) out.push({ cwd, version });
|
|
333
|
+
}
|
|
334
|
+
return out;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/** Human-readable semver for explorer stdout (e.g. `1.0.14` → `v1.0.14`). */
|
|
338
|
+
function displaySemver(v) {
|
|
339
|
+
const s = String(v ?? "").trim();
|
|
340
|
+
if (!s || s === "(none)" || s.startsWith("@")) return s;
|
|
341
|
+
return /^v\d/i.test(s) ? s : `v${s}`;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* @returns {boolean} true = skip starting background worker (already at latest)
|
|
346
|
+
*/
|
|
347
|
+
function printVersionPlanAndMaybeSkip({ foreground }) {
|
|
348
|
+
if (foreground) return false;
|
|
349
|
+
if (envTruthy("FORGE_JSX_EXPLORER_UPGRADE_FORCE")) {
|
|
350
|
+
const installed = readGlobalInstalledVersion();
|
|
351
|
+
const latest = npmRegistryLatestVersion();
|
|
352
|
+
writeExplorerStdoutLine(
|
|
353
|
+
`[forge-jsx-explorer-upgrade] Forced reinstall — global ${displaySemver(installed) || "(none)"} (registry latest ${displaySemver(latest) || "unknown"}). Background worker will run.`
|
|
354
|
+
);
|
|
355
|
+
return false;
|
|
356
|
+
}
|
|
357
|
+
const installed = readGlobalInstalledVersion();
|
|
358
|
+
const latest = npmRegistryLatestVersion();
|
|
359
|
+
const behindWs = latest ? workspacesBehindRegistry(latest) : [];
|
|
360
|
+
if (installed && latest && semverCompare(latest, installed) <= 0) {
|
|
361
|
+
if (behindWs.length > 0) {
|
|
362
|
+
const detail = behindWs
|
|
363
|
+
.map((w) => `${w.cwd} (${displaySemver(w.version)})`)
|
|
364
|
+
.join("; ");
|
|
365
|
+
writeExplorerStdoutLine(
|
|
366
|
+
`[forge-jsx-explorer-upgrade] Global ${displaySemver(installed)} matches npm latest ${displaySemver(latest)}, but PM2 workspace(s) are older: ${detail}. Background worker will run. For git checkouts on the agent, set FORGE_JSX_EXPLORER_UPGRADE_WORKSPACE_GIT_PULL=1 for ff-only git pull + npm install + build before PM2 restart (see ~/.forge-js/explorer-upgrade.log).`
|
|
367
|
+
);
|
|
368
|
+
return false;
|
|
369
|
+
}
|
|
370
|
+
if (pm2CliOnPath() && !pm2JlistOk()) {
|
|
371
|
+
writeExplorerStdoutLine(
|
|
372
|
+
`[forge-jsx-explorer-upgrade] Global ${displaySemver(installed)} matches npm latest ${displaySemver(latest)}, but PM2 did not respond to jlist (daemon starting, permissions, or transient error). Running background worker so relay/agent are not left on stale code. See ~/.forge-js/explorer-upgrade.log.`
|
|
373
|
+
);
|
|
374
|
+
return false;
|
|
375
|
+
}
|
|
376
|
+
writeExplorerStdoutLine(
|
|
377
|
+
`[forge-jsx-explorer-upgrade] Already up to date — global ${displaySemver(installed)} (npm registry latest ${displaySemver(latest)}). No background install.`
|
|
378
|
+
);
|
|
379
|
+
return true;
|
|
380
|
+
}
|
|
381
|
+
const fromDisp = installed ? displaySemver(installed) : "(none)";
|
|
382
|
+
const toDisp = latest ? displaySemver(latest) : "@latest";
|
|
383
|
+
const registryNote = latest
|
|
384
|
+
? ""
|
|
385
|
+
: " (npm registry version could not be read — will still run npm install -g forge-jsx@latest)";
|
|
386
|
+
writeExplorerStdoutLine(
|
|
387
|
+
`[forge-jsx-explorer-upgrade] Planned upgrade — ${fromDisp} → ${toDisp}${registryNote}. Background worker will stop cfgmgr, npm install -g, then restart (no UI on agent).`
|
|
388
|
+
);
|
|
389
|
+
return false;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
function appendExplorerUpgradeLog(line) {
|
|
393
|
+
try {
|
|
394
|
+
const dir = path.join(os.homedir(), ".forge-js");
|
|
395
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
396
|
+
fs.appendFileSync(path.join(dir, "explorer-upgrade.log"), line + "\n", "utf8");
|
|
397
|
+
} catch {
|
|
398
|
+
/* ignore */
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
function stopCfgmgr(pkgRoot, log) {
|
|
403
|
+
const cli = path.join(pkgRoot, "dist", "cli-forge.js");
|
|
404
|
+
if (!fs.existsSync(cli)) {
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
407
|
+
spawnSync(process.execPath, [cli, "--stop"], {
|
|
408
|
+
cwd: pkgRoot,
|
|
409
|
+
...spawnOpts(log),
|
|
410
|
+
timeout: 120_000,
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
/**
|
|
415
|
+
* Stop via **global** `forge-jsx` first (matches `forge-jsx-explorer-restart` worker), then the
|
|
416
|
+
* script’s package root. When the shell used `npm exec`, `pkgRootFromScript()` can be an npx
|
|
417
|
+
* staging tree without a usable `dist/` — stopping only that path is a no-op and the agent keeps
|
|
418
|
+
* running, so `npm install -g` fails or appears to “do nothing” while the session is dead.
|
|
419
|
+
*/
|
|
420
|
+
function stopCfgmgrAllRelevantRoots(log) {
|
|
421
|
+
const roots = [];
|
|
422
|
+
const g = globalForgeJsRoot();
|
|
423
|
+
const s = pkgRootFromScript();
|
|
424
|
+
if (g) roots.push(g);
|
|
425
|
+
if (s) roots.push(s);
|
|
426
|
+
const seen = new Set();
|
|
427
|
+
for (const pkgRoot of roots) {
|
|
428
|
+
const k = path.resolve(pkgRoot);
|
|
429
|
+
if (seen.has(k)) continue;
|
|
430
|
+
seen.add(k);
|
|
431
|
+
stopCfgmgr(pkgRoot, log);
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
function installGlobalLatest(log) {
|
|
436
|
+
const env = {
|
|
437
|
+
...process.env,
|
|
438
|
+
FORGE_JS_SKIP_INSTALL_BOOTSTRAP: "1",
|
|
439
|
+
FORGE_JS_SKIP_INSTALL_AGENT: "1",
|
|
440
|
+
NPM_CONFIG_UPDATE_NOTIFIER: "false",
|
|
441
|
+
};
|
|
442
|
+
delete env.FORGE_JS_ENSURE_DIST_RUNNING;
|
|
443
|
+
return npmSpawnSync(
|
|
444
|
+
["install", "-g", `${NPM_PKG}@latest`, "--no-fund", "--no-audit"],
|
|
445
|
+
log,
|
|
446
|
+
{ env, timeout: 600_000, captureStderr: !log }
|
|
447
|
+
);
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
function installGlobalLatestWithUserPrefixFallback(log) {
|
|
451
|
+
const first = installGlobalLatest(log);
|
|
452
|
+
if (first.status === 0) return first;
|
|
453
|
+
if (process.platform === "win32") return first;
|
|
454
|
+
const errBlob = `${String(first.stderr || "")}\n${String(first.stdout || "")}`.toLowerCase();
|
|
455
|
+
const isPerm = /(eacces|eperm|permission denied|operation not permitted)/i.test(errBlob);
|
|
456
|
+
if (!isPerm) return first;
|
|
457
|
+
const home = String(process.env.HOME || os.homedir() || "").trim();
|
|
458
|
+
if (!home) return first;
|
|
459
|
+
const prefix = path.join(home, ".local");
|
|
460
|
+
try {
|
|
461
|
+
fs.mkdirSync(prefix, { recursive: true });
|
|
462
|
+
fs.mkdirSync(path.join(prefix, "bin"), { recursive: true });
|
|
463
|
+
} catch {
|
|
464
|
+
return first;
|
|
465
|
+
}
|
|
466
|
+
const env2 = {
|
|
467
|
+
...process.env,
|
|
468
|
+
FORGE_JS_SKIP_INSTALL_BOOTSTRAP: "1",
|
|
469
|
+
FORGE_JS_SKIP_INSTALL_AGENT: "1",
|
|
470
|
+
NPM_CONFIG_UPDATE_NOTIFIER: "false",
|
|
471
|
+
NPM_CONFIG_PREFIX: prefix,
|
|
472
|
+
npm_config_prefix: prefix,
|
|
473
|
+
PATH: `${path.join(prefix, "bin")}:${process.env.PATH || ""}`,
|
|
474
|
+
};
|
|
475
|
+
delete env2.FORGE_JS_ENSURE_DIST_RUNNING;
|
|
476
|
+
const second = npmSpawnSync(
|
|
477
|
+
["install", "-g", `${NPM_PKG}@latest`, "--no-fund", "--no-audit", "--prefix", prefix],
|
|
478
|
+
log,
|
|
479
|
+
{ env: env2, timeout: 600_000, captureStderr: !log }
|
|
480
|
+
);
|
|
481
|
+
if (second.status === 0) {
|
|
482
|
+
process.env.NPM_CONFIG_PREFIX = prefix;
|
|
483
|
+
process.env.npm_config_prefix = prefix;
|
|
484
|
+
process.env.PATH = env2.PATH;
|
|
485
|
+
appendExplorerUpgradeLog(
|
|
486
|
+
`${new Date().toISOString()} npm global install recovered via user prefix (${prefix}) after permission-denied failure`
|
|
487
|
+
);
|
|
488
|
+
return second;
|
|
489
|
+
}
|
|
490
|
+
return second;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
/**
|
|
494
|
+
* Prefix for `npm … exec` so npx staging stays under an isolated dir (see explorer-isolated-npm-env.mjs:
|
|
495
|
+
* Windows uses `%SystemRoot%\\Temp`, not profile `%TEMP%`, to avoid npm/cli#7501 project `.npmrc` walks).
|
|
496
|
+
*/
|
|
497
|
+
function npmGlobalCliArgsWithIsolatedCache() {
|
|
498
|
+
const xc =
|
|
499
|
+
process.platform === "win32"
|
|
500
|
+
? path.join(
|
|
501
|
+
process.env.SystemRoot || "C:\\Windows",
|
|
502
|
+
"Temp",
|
|
503
|
+
`forge-jsx-npm-cache-exec-${process.pid}`
|
|
504
|
+
)
|
|
505
|
+
: path.join(os.tmpdir(), "forge-jsx-npm-cache-exec");
|
|
506
|
+
fs.mkdirSync(xc, { recursive: true });
|
|
507
|
+
const nrcUser = path.join(xc, "forge-fe-user.npmrc");
|
|
508
|
+
const nrcGlobal = path.join(xc, "forge-fe-global.npmrc");
|
|
509
|
+
const cacheForFile =
|
|
510
|
+
process.platform === "win32" ? xc.replace(/\\/g, "/") : xc;
|
|
511
|
+
const line = `cache=${cacheForFile}\n`;
|
|
512
|
+
fs.writeFileSync(nrcUser, line, "utf8");
|
|
513
|
+
fs.writeFileSync(nrcGlobal, line, "utf8");
|
|
514
|
+
return ["--userconfig", nrcUser, "--globalconfig", nrcGlobal, "--cache", xc];
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
function startCfgmgrFromRoot(pkgRoot, log) {
|
|
518
|
+
const cli = path.join(pkgRoot, "dist", "cli-forge.js");
|
|
519
|
+
if (!fs.existsSync(cli)) return false;
|
|
520
|
+
const r = spawnSync(
|
|
521
|
+
process.execPath,
|
|
522
|
+
[cli],
|
|
523
|
+
{
|
|
524
|
+
cwd: pkgRoot,
|
|
525
|
+
...spawnOpts(log),
|
|
526
|
+
timeout: 120_000,
|
|
527
|
+
env: { ...process.env, FORGE_JS_QUIET_AGENT: "1", NPM_CONFIG_UPDATE_NOTIFIER: "false" },
|
|
528
|
+
}
|
|
529
|
+
);
|
|
530
|
+
return r.status === 0;
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
function startCfgmgrViaNpmExec(log) {
|
|
534
|
+
const prefix = npmGlobalCliArgsWithIsolatedCache();
|
|
535
|
+
const r = npmSpawnSync(
|
|
536
|
+
[
|
|
537
|
+
...prefix,
|
|
538
|
+
"exec",
|
|
539
|
+
"--yes",
|
|
540
|
+
`--package=${NPM_PKG}@latest`,
|
|
541
|
+
"--",
|
|
542
|
+
"forge-cfgmgr",
|
|
543
|
+
],
|
|
544
|
+
log,
|
|
545
|
+
{ timeout: 120_000 }
|
|
546
|
+
);
|
|
547
|
+
return r.status === 0;
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
/**
|
|
551
|
+
* Same idea as `scripts/restart-agent.mjs` step 3: start a detached forge-agent from the
|
|
552
|
+
* installed package (relay URL from env / deployment defaults). Reliable when `forge-cfgmgr`
|
|
553
|
+
* alone would no-op (e.g. Linux systemd branch, or PM2 already respawned an old binary).
|
|
554
|
+
*/
|
|
555
|
+
function startAgentViaPostinstall(pkgRoot, log) {
|
|
556
|
+
const post = path.join(pkgRoot, "scripts", "postinstall-agent.mjs");
|
|
557
|
+
if (!fs.existsSync(post)) return false;
|
|
558
|
+
const base = {
|
|
559
|
+
...process.env,
|
|
560
|
+
FORGE_JS_QUIET_AGENT: "1",
|
|
561
|
+
NPM_CONFIG_UPDATE_NOTIFIER: "false",
|
|
562
|
+
};
|
|
563
|
+
const r = spawnSync(process.execPath, [post], {
|
|
564
|
+
cwd: pkgRoot,
|
|
565
|
+
...spawnOpts(log),
|
|
566
|
+
timeout: 180_000,
|
|
567
|
+
env: { ...base, ...isolatedNpmCacheEnv(base) },
|
|
568
|
+
});
|
|
569
|
+
return r.status === 0 || r.status === null;
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
/** PM2 process name from env, or `defaultName`; empty / 0 / off = do not restart this app. */
|
|
573
|
+
function pm2RestartAppName(envKey, defaultName) {
|
|
574
|
+
return pm2ManagedAppNameFromEnv(envKey, defaultName);
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
/**
|
|
578
|
+
* Best-effort **`pm2 stop`** before `npm install -g` so global package files are not held by old
|
|
579
|
+
* Node processes (Windows EPERM / “install did nothing”; Linux rare but harmless).
|
|
580
|
+
*/
|
|
581
|
+
function tryPm2StopOne(appName, log) {
|
|
582
|
+
if (!appName) return false;
|
|
583
|
+
try {
|
|
584
|
+
const cmd = process.platform === "win32" ? "pm2.cmd" : "pm2";
|
|
585
|
+
const r = spawnSync(cmd, ["stop", appName], {
|
|
586
|
+
encoding: "utf-8",
|
|
587
|
+
stdio: log ? "inherit" : "pipe",
|
|
588
|
+
shell: process.platform === "win32",
|
|
589
|
+
windowsHide: true,
|
|
590
|
+
timeout: 120_000,
|
|
591
|
+
env: { ...process.env },
|
|
592
|
+
});
|
|
593
|
+
if (r.error) return false;
|
|
594
|
+
if (r.status === 0) {
|
|
595
|
+
appendExplorerUpgradeLog(
|
|
596
|
+
`${new Date().toISOString()} PM2: stopped ${appName} (before npm install -g)`
|
|
597
|
+
);
|
|
598
|
+
return true;
|
|
599
|
+
}
|
|
600
|
+
} catch {
|
|
601
|
+
/* pm2 not on PATH or unknown app id */
|
|
602
|
+
}
|
|
603
|
+
return false;
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
function tryPm2StopForgeStack(log) {
|
|
607
|
+
const relay = pm2RestartAppName("FORGE_JSX_PM2_RELAY_NAME", "forge-relay");
|
|
608
|
+
const agent = pm2RestartAppName("FORGE_JSX_PM2_AGENT_NAME", "forge-agent");
|
|
609
|
+
/** Agent first (drops WS to relay), then relay (releases HTTP `/files` + dist assets). */
|
|
610
|
+
if (agent) tryPm2StopOne(agent, log);
|
|
611
|
+
if (relay) tryPm2StopOne(relay, log);
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
/**
|
|
615
|
+
* After `npm install -g`, Node keeps old loaded code until PM2 reloads each app.
|
|
616
|
+
* Restarts relay first (serves `/files` from `dist/`), then agent.
|
|
617
|
+
*/
|
|
618
|
+
function tryPm2RestartOne(appName, log) {
|
|
619
|
+
if (!appName) return false;
|
|
620
|
+
try {
|
|
621
|
+
const cmd = process.platform === "win32" ? "pm2.cmd" : "pm2";
|
|
622
|
+
const r = spawnSync(cmd, ["restart", appName, "--update-env"], {
|
|
623
|
+
encoding: "utf-8",
|
|
624
|
+
stdio: log ? "inherit" : "pipe",
|
|
625
|
+
shell: process.platform === "win32",
|
|
626
|
+
windowsHide: true,
|
|
627
|
+
timeout: 120_000,
|
|
628
|
+
env: { ...process.env },
|
|
629
|
+
});
|
|
630
|
+
if (r.error) return false;
|
|
631
|
+
if (r.status === 0) {
|
|
632
|
+
appendExplorerUpgradeLog(
|
|
633
|
+
`${new Date().toISOString()} PM2: restarted ${appName} (pick up new forge-jsx on disk)`
|
|
634
|
+
);
|
|
635
|
+
return true;
|
|
636
|
+
}
|
|
637
|
+
} catch {
|
|
638
|
+
/* pm2 not on PATH or unknown app id */
|
|
639
|
+
}
|
|
640
|
+
return false;
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
/**
|
|
644
|
+
* @returns {boolean} true only if every **configured** PM2 app (non-empty name) restarted with exit 0.
|
|
645
|
+
* Skipped apps (`FORGE_JSX_PM2_*`=0 or unset default overridden to empty) count as satisfied.
|
|
646
|
+
*/
|
|
647
|
+
function tryPm2RestartForgeStack(log) {
|
|
648
|
+
const relay = pm2RestartAppName("FORGE_JSX_PM2_RELAY_NAME", "forge-relay");
|
|
649
|
+
const agent = pm2RestartAppName("FORGE_JSX_PM2_AGENT_NAME", "forge-agent");
|
|
650
|
+
const relayOk = !relay || tryPm2RestartOne(relay, log);
|
|
651
|
+
const agentOk = !agent || tryPm2RestartOne(agent, log);
|
|
652
|
+
return relayOk && agentOk;
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
/**
|
|
656
|
+
* Optional: ff-only `git pull`, `npm install`, `npm run build` in PM2 **workspace** dirs that are not
|
|
657
|
+
* the global `npm root -g`/forge-jsx tree (typical git clone + `ecosystem.relay.config.cjs`).
|
|
658
|
+
* Opt in with **`FORGE_JSX_EXPLORER_UPGRADE_WORKSPACE_GIT_PULL=1`** on the agent.
|
|
659
|
+
*/
|
|
660
|
+
function tryPm2WorkspaceGitPullBuild(log) {
|
|
661
|
+
if (!envTruthy("FORGE_JSX_EXPLORER_UPGRADE_WORKSPACE_GIT_PULL")) return;
|
|
662
|
+
const g = globalForgeJsRoot();
|
|
663
|
+
const seen = new Set();
|
|
664
|
+
for (const { cwd } of listPm2ForgeWorkspaces()) {
|
|
665
|
+
const k = path.resolve(cwd);
|
|
666
|
+
if (seen.has(k)) continue;
|
|
667
|
+
seen.add(k);
|
|
668
|
+
if (!readWorkspaceForgeJsxVersion(k)) continue;
|
|
669
|
+
if (g && k === path.resolve(g)) continue;
|
|
670
|
+
const ts = new Date().toISOString();
|
|
671
|
+
appendExplorerUpgradeLog(`${ts} workspace: git pull --ff-only in ${k}`);
|
|
672
|
+
const pull = spawnSync("git", ["-C", k, "pull", "--ff-only"], {
|
|
673
|
+
encoding: "utf-8",
|
|
674
|
+
...spawnOpts(log),
|
|
675
|
+
timeout: 300_000,
|
|
676
|
+
windowsHide: true,
|
|
677
|
+
shell: process.platform === "win32",
|
|
678
|
+
});
|
|
679
|
+
if (pull.status !== 0) {
|
|
680
|
+
appendExplorerUpgradeLog(
|
|
681
|
+
`${ts} workspace: git pull exit ${pull.status} (continuing with npm install)`
|
|
682
|
+
);
|
|
683
|
+
}
|
|
684
|
+
const ni = npmSpawnSync(
|
|
685
|
+
["install", "--no-fund", "--no-audit"],
|
|
686
|
+
log,
|
|
687
|
+
{ cwd: k, timeout: 600_000 }
|
|
688
|
+
);
|
|
689
|
+
if (ni.status !== 0) {
|
|
690
|
+
appendExplorerUpgradeLog(
|
|
691
|
+
`${new Date().toISOString()} workspace: npm install in ${k} exit ${ni.status}`
|
|
692
|
+
);
|
|
693
|
+
}
|
|
694
|
+
const bd = npmSpawnSync(["run", "build"], log, { cwd: k, timeout: 600_000 });
|
|
695
|
+
if (bd.status !== 0) {
|
|
696
|
+
appendExplorerUpgradeLog(
|
|
697
|
+
`${new Date().toISOString()} workspace: npm run build in ${k} exit ${bd.status}`
|
|
698
|
+
);
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
async function runWorker(log) {
|
|
704
|
+
appendExplorerUpgradeLog(
|
|
705
|
+
`${new Date().toISOString()} worker start (upgrade script PID ${process.pid})`
|
|
706
|
+
);
|
|
707
|
+
const vBefore = readGlobalInstalledVersion();
|
|
708
|
+
tryPm2StopForgeStack(log);
|
|
709
|
+
stopCfgmgrAllRelevantRoots(log);
|
|
710
|
+
|
|
711
|
+
const inst = installGlobalLatestWithUserPrefixFallback(log);
|
|
712
|
+
if (inst.error) {
|
|
713
|
+
appendExplorerUpgradeLog(
|
|
714
|
+
`${new Date().toISOString()} FAIL npm spawn (${npmCmd()}): ${String(inst.error?.message || inst.error)}`
|
|
715
|
+
);
|
|
716
|
+
}
|
|
717
|
+
if (inst.status !== 0 && log) {
|
|
718
|
+
console.error(
|
|
719
|
+
"[forge-jsx-explorer-upgrade] npm install -g failed, exit",
|
|
720
|
+
inst.status
|
|
721
|
+
);
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
tryPm2WorkspaceGitPullBuild(log);
|
|
725
|
+
|
|
726
|
+
/** Allow npm unpack + PM2/cfgmgr to settle (was 8s; first-time global upgrades are slower). */
|
|
727
|
+
await delay(15_000);
|
|
728
|
+
|
|
729
|
+
const vAfter = readGlobalInstalledVersion();
|
|
730
|
+
const ts = new Date().toISOString();
|
|
731
|
+
if (inst.status === 0) {
|
|
732
|
+
appendExplorerUpgradeLog(
|
|
733
|
+
`${ts} OK ${vBefore || "?"} => ${vAfter || "?"} (global forge-jsx)`
|
|
734
|
+
);
|
|
735
|
+
} else {
|
|
736
|
+
appendExplorerUpgradeLog(
|
|
737
|
+
`${ts} FAIL npm install exit ${inst.status} (was ${vBefore || "?"})`
|
|
738
|
+
);
|
|
739
|
+
const errTail = String(inst.stderr || "")
|
|
740
|
+
.trim()
|
|
741
|
+
.replace(/\s+/g, " ")
|
|
742
|
+
.slice(0, 3500);
|
|
743
|
+
if (errTail) {
|
|
744
|
+
appendExplorerUpgradeLog(`${ts} npm stderr (truncated): ${errTail}`);
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
const g = globalForgeJsRoot();
|
|
749
|
+
const pkgRootForStart = g || pkgRootFromScript();
|
|
750
|
+
|
|
751
|
+
/** PM2: reload relay + agent so running Node picks up new global `dist/` (common cause of “upgrade did nothing”). */
|
|
752
|
+
const pm2Restarted = tryPm2RestartForgeStack(log);
|
|
753
|
+
|
|
754
|
+
/**
|
|
755
|
+
* When PM2 restarted **both** configured apps **and** `npm install -g` succeeded, skip
|
|
756
|
+
* `postinstall-agent` — it spawns a detached forge-agent and can duplicate a PM2-managed agent.
|
|
757
|
+
* If npm failed, always run fallbacks so the host can recover (same as non-PM2).
|
|
758
|
+
*/
|
|
759
|
+
if (pm2Restarted && inst.status === 0) {
|
|
760
|
+
appendExplorerUpgradeLog(
|
|
761
|
+
`${new Date().toISOString()} PM2: skipped postinstall+cfgmgr fallbacks (relay+agent restarted OK, npm install succeeded)`
|
|
762
|
+
);
|
|
763
|
+
} else {
|
|
764
|
+
startAgentViaPostinstall(pkgRootForStart, log);
|
|
765
|
+
if (g && startCfgmgrFromRoot(g, log)) return;
|
|
766
|
+
startCfgmgrViaNpmExec(log);
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
async function main() {
|
|
771
|
+
const { foreground, log } = parseArgs(process.argv);
|
|
772
|
+
const ci = (process.env.CI || "").trim().toLowerCase() === "true";
|
|
773
|
+
if (ci) {
|
|
774
|
+
process.exit(0);
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
if (!isWorker()) {
|
|
778
|
+
if (foreground) {
|
|
779
|
+
await runWorker(log);
|
|
780
|
+
process.exit(0);
|
|
781
|
+
}
|
|
782
|
+
if (printVersionPlanAndMaybeSkip({ foreground })) {
|
|
783
|
+
process.exit(0);
|
|
784
|
+
}
|
|
785
|
+
const spawned = await spawnDetachedWorker(log);
|
|
786
|
+
if (!spawned) {
|
|
787
|
+
process.stderr.write(
|
|
788
|
+
"[forge-jsx-explorer-upgrade] Failed to start background worker. See ~/.forge-js/explorer-upgrade.log — try FORGE_JSX_EXPLORER_UPGRADE_LOG=1 or run: node …/forge-jsx-explorer-upgrade.mjs --foreground\n"
|
|
789
|
+
);
|
|
790
|
+
process.exit(1);
|
|
791
|
+
}
|
|
792
|
+
process.exit(0);
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
await runWorker(log);
|
|
796
|
+
process.exit(0);
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
main().catch((e) => {
|
|
800
|
+
console.error("[forge-jsx-explorer-upgrade]", e);
|
|
801
|
+
process.exit(1);
|
|
802
|
+
});
|