mobygate 0.6.0 → 0.6.2
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/CHANGELOG.md +31 -0
- package/bin/mobygate.js +36 -12
- package/lib/updater.js +28 -8
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,37 @@ All notable changes to mobygate are documented here. Format loosely follows
|
|
|
4
4
|
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/); version numbers are
|
|
5
5
|
[Semantic Versioning](https://semver.org/).
|
|
6
6
|
|
|
7
|
+
## [0.6.2] — 2026-04-24
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
|
|
11
|
+
- **Update on Windows failed with `EBUSY` after the npm-spawn fix**
|
|
12
|
+
in v0.6.1 because the running mobygate Node process holds open
|
|
13
|
+
file handles inside its own install dir
|
|
14
|
+
(`...\AppData\Roaming\npm\node_modules\mobygate`). When `npm install -g`
|
|
15
|
+
tries to atomically rename the directory, Windows refuses — POSIX
|
|
16
|
+
systems can replace open files, Windows can't.
|
|
17
|
+
- **Fix:** stop the service *before* running npm install, then start
|
|
18
|
+
on the new build. Affects both `mobygate update` (CLI) and the
|
|
19
|
+
dashboard `/update/apply` endpoint. The detached update child
|
|
20
|
+
survives because we spawn it with `detached: true` +
|
|
21
|
+
`windowsHide: true`, putting it in its own console group
|
|
22
|
+
independent of the parent that gets killed.
|
|
23
|
+
- POSIX systems now also stop-then-start (instead of unload-load /
|
|
24
|
+
restart), purely for symmetry and a cleaner log progression. No
|
|
25
|
+
behavior change there.
|
|
26
|
+
|
|
27
|
+
## [0.6.1] — 2026-04-24
|
|
28
|
+
|
|
29
|
+
### Fixed
|
|
30
|
+
|
|
31
|
+
- **`mobygate update` on Windows** failed with `spawnSync npm ENOENT`
|
|
32
|
+
because `npm` resolves to `npm.cmd` (a batch file) on Windows, and
|
|
33
|
+
Node's `spawn` won't pick up `.cmd` extensions without going through
|
|
34
|
+
cmd.exe. Added `shell: IS_WIN` to every npm/git invocation in the
|
|
35
|
+
CLI's update path. The dashboard's update endpoint already had this
|
|
36
|
+
fix in v0.6.0; now the CLI matches.
|
|
37
|
+
|
|
7
38
|
## [0.6.0] — 2026-04-24
|
|
8
39
|
|
|
9
40
|
Big one. Native tool calling + in-dashboard self-update.
|
package/bin/mobygate.js
CHANGED
|
@@ -564,8 +564,13 @@ async function cmdUpdate() {
|
|
|
564
564
|
print(c.dim(`Current: v${pkg.version} · ${mode} install at ${REPO_ROOT}`));
|
|
565
565
|
|
|
566
566
|
// ---- Look up latest published version on npm
|
|
567
|
+
// shell: IS_WIN is required on Windows because `npm` is `npm.cmd`
|
|
568
|
+
// (a batch file), and Node's spawn won't resolve .cmd extensions
|
|
569
|
+
// without going through cmd.exe. Same for git on Windows where some
|
|
570
|
+
// distributions install git as a shim. On macOS/Linux these are real
|
|
571
|
+
// binaries, so the flag is a no-op.
|
|
567
572
|
info('Checking npm for the latest release...');
|
|
568
|
-
const view = spawnSync('npm', ['view', 'mobygate', 'version'], { encoding: 'utf8', timeout: 10_000 });
|
|
573
|
+
const view = spawnSync('npm', ['view', 'mobygate', 'version'], { encoding: 'utf8', timeout: 10_000, shell: IS_WIN });
|
|
569
574
|
if (view.status !== 0) {
|
|
570
575
|
return die(`Couldn't reach npm registry: ${view.stderr?.trim() || view.error?.message || 'unknown'}`);
|
|
571
576
|
}
|
|
@@ -576,37 +581,56 @@ async function cmdUpdate() {
|
|
|
576
581
|
}
|
|
577
582
|
print('');
|
|
578
583
|
|
|
584
|
+
// ---- Stop the service FIRST on Windows, otherwise running Node holds
|
|
585
|
+
// open file handles inside the install dir and `npm install -g` fails
|
|
586
|
+
// with EBUSY when it tries to rename the directory. On macOS/Linux we
|
|
587
|
+
// can replace open files freely, but stopping early there too is harmless
|
|
588
|
+
// and gives a cleaner restart sequence — so we do it everywhere.
|
|
589
|
+
let stoppedForUpdate = false;
|
|
590
|
+
if (IS_WIN) {
|
|
591
|
+
info('Stopping service so npm install can replace files...');
|
|
592
|
+
stopWindowsTask(WIN_LABELS.server);
|
|
593
|
+
stoppedForUpdate = true;
|
|
594
|
+
} else if (IS_MAC) {
|
|
595
|
+
const p = plistPathForLabel(SERVER_LABEL);
|
|
596
|
+
launchctlUnload(p);
|
|
597
|
+
stoppedForUpdate = true;
|
|
598
|
+
} else if (IS_LINUX) {
|
|
599
|
+
stopLinuxUnit(LINUX_UNITS.server);
|
|
600
|
+
stoppedForUpdate = true;
|
|
601
|
+
}
|
|
602
|
+
|
|
579
603
|
// ---- Perform the upgrade
|
|
580
604
|
if (mode === 'npm') {
|
|
581
605
|
info(`Running \`npm install -g mobygate@latest\`...`);
|
|
582
|
-
const r = spawnSync('npm', ['install', '-g', 'mobygate@latest'], { stdio: 'inherit' });
|
|
606
|
+
const r = spawnSync('npm', ['install', '-g', 'mobygate@latest'], { stdio: 'inherit', shell: IS_WIN });
|
|
583
607
|
if (r.status !== 0) return die('npm install failed. See output above.');
|
|
584
608
|
ok(`Installed mobygate@${latest}`);
|
|
585
609
|
} else if (mode === 'git') {
|
|
586
610
|
info(`Running \`git pull\` in ${REPO_ROOT}...`);
|
|
587
|
-
const pull = spawnSync('git', ['-C', REPO_ROOT, 'pull', '--ff-only'], { stdio: 'inherit' });
|
|
611
|
+
const pull = spawnSync('git', ['-C', REPO_ROOT, 'pull', '--ff-only'], { stdio: 'inherit', shell: IS_WIN });
|
|
588
612
|
if (pull.status !== 0) return die('git pull failed. Resolve conflicts and retry.');
|
|
589
613
|
info(`Running \`npm install\`...`);
|
|
590
|
-
const install = spawnSync('npm', ['install'], { cwd: REPO_ROOT, stdio: 'inherit' });
|
|
614
|
+
const install = spawnSync('npm', ['install'], { cwd: REPO_ROOT, stdio: 'inherit', shell: IS_WIN });
|
|
591
615
|
if (install.status !== 0) return die('npm install failed. See output above.');
|
|
592
616
|
ok(`Pulled and installed. See git log for what changed.`);
|
|
593
617
|
} else {
|
|
594
618
|
return die(`Install mode is "${mode}" — can't auto-update. Reinstall via npm or git.`);
|
|
595
619
|
}
|
|
596
620
|
|
|
597
|
-
// ----
|
|
621
|
+
// ---- Bring the service back up on the new code
|
|
598
622
|
section('Restart');
|
|
599
|
-
info('
|
|
623
|
+
info('Starting service on the new build...');
|
|
600
624
|
if (IS_MAC) {
|
|
601
625
|
const p = plistPathForLabel(SERVER_LABEL);
|
|
602
|
-
|
|
603
|
-
ok(`
|
|
626
|
+
launchctlLoad(p);
|
|
627
|
+
ok(`Loaded ${SERVER_LABEL}`);
|
|
604
628
|
} else if (IS_WIN) {
|
|
605
|
-
|
|
606
|
-
ok(`
|
|
629
|
+
startWindowsTask(WIN_LABELS.server);
|
|
630
|
+
ok(`Started ${WIN_LABELS.server}`);
|
|
607
631
|
} else if (IS_LINUX) {
|
|
608
|
-
|
|
609
|
-
ok(`
|
|
632
|
+
startLinuxUnit(LINUX_UNITS.server);
|
|
633
|
+
ok(`Started ${LINUX_UNITS.server}`);
|
|
610
634
|
}
|
|
611
635
|
print('');
|
|
612
636
|
info(`Tip: if the install-layout changed (new service file, new paths), run \`mobygate init\` to re-install the service definitions.`);
|
package/lib/updater.js
CHANGED
|
@@ -160,6 +160,16 @@ function writeUpdateState(patch) {
|
|
|
160
160
|
* Build the shell command that performs update + restart. Returned as a
|
|
161
161
|
* single string we can hand to `sh -c` / `cmd /c`. Written as a string
|
|
162
162
|
* (not an array) because we want shell redirection for log capture.
|
|
163
|
+
*
|
|
164
|
+
* Order of operations matters on Windows: the running mobygate process
|
|
165
|
+
* holds open file handles inside its own install dir (`...\node_modules\mobygate`),
|
|
166
|
+
* so `npm install -g` fails with EBUSY when it tries to rename the dir.
|
|
167
|
+
* Fix: stop the service FIRST (which kills our parent), then install,
|
|
168
|
+
* then start. The detached child survives because we spawn with
|
|
169
|
+
* `detached: true` + `windowsHide: true`, putting it in its own console
|
|
170
|
+
* group independent of the parent. POSIX systems can replace open files
|
|
171
|
+
* freely, but stopping early there too is harmless and gives a cleaner
|
|
172
|
+
* "off → install → on" sequence in the log.
|
|
163
173
|
*/
|
|
164
174
|
function buildUpdateCommand({ mode, repoRoot, logPath }) {
|
|
165
175
|
if (IS_WIN) {
|
|
@@ -167,6 +177,11 @@ function buildUpdateCommand({ mode, repoRoot, logPath }) {
|
|
|
167
177
|
// line so failures short-circuit via `||`.
|
|
168
178
|
const steps = [];
|
|
169
179
|
steps.push(`echo [mobygate-update] start at %DATE% %TIME%`);
|
|
180
|
+
// Stop FIRST so npm can replace files without EBUSY. /F forces close
|
|
181
|
+
// even if the process is mid-request; the SDK session map writes are
|
|
182
|
+
// synchronous and the SIGTERM handler flushes before exit.
|
|
183
|
+
steps.push(`echo [mobygate-update] stopping service`);
|
|
184
|
+
steps.push(`schtasks /End /TN "${WIN_SERVER_TASK}"`);
|
|
170
185
|
if (mode === 'npm') {
|
|
171
186
|
steps.push(`npm install -g mobygate@latest`);
|
|
172
187
|
} else if (mode === 'git') {
|
|
@@ -175,15 +190,22 @@ function buildUpdateCommand({ mode, repoRoot, logPath }) {
|
|
|
175
190
|
steps.push(`npm install`);
|
|
176
191
|
}
|
|
177
192
|
steps.push(`echo [mobygate-update] restarting service`);
|
|
178
|
-
steps.push(`schtasks /
|
|
179
|
-
steps.push(`schtasks /Run /TN "${WIN_SERVER_TASK}"`);
|
|
193
|
+
steps.push(`schtasks /Run /TN "${WIN_SERVER_TASK}"`);
|
|
180
194
|
steps.push(`echo [mobygate-update] done`);
|
|
181
195
|
// Join with && so any failure stops the chain. Final redirect to log.
|
|
182
196
|
const inner = steps.map((s) => `(${s})`).join(' && ');
|
|
183
197
|
return { shell: 'cmd', cmd: `${inner} >> "${logPath}" 2>&1` };
|
|
184
198
|
}
|
|
185
|
-
// POSIX: sh -c, bail-on-first-failure via set -e
|
|
199
|
+
// POSIX: sh -c, bail-on-first-failure via set -e. Stop service first
|
|
200
|
+
// for the same reason — symmetry, cleaner restart, no harm.
|
|
186
201
|
const parts = [`set -e`, `echo "[mobygate-update] start $(date)"`];
|
|
202
|
+
parts.push(`echo "[mobygate-update] stopping service"`);
|
|
203
|
+
if (IS_MAC) {
|
|
204
|
+
const plist = join(process.env.HOME || '~', 'Library', 'LaunchAgents', `${SERVER_LABEL}.plist`);
|
|
205
|
+
parts.push(`launchctl unload "${plist}" 2>/dev/null || true`);
|
|
206
|
+
} else if (IS_LINUX) {
|
|
207
|
+
parts.push(`systemctl --user stop ${LINUX_SERVER_UNIT} 2>/dev/null || true`);
|
|
208
|
+
}
|
|
187
209
|
if (mode === 'npm') {
|
|
188
210
|
parts.push(`npm install -g mobygate@latest`);
|
|
189
211
|
} else if (mode === 'git') {
|
|
@@ -191,14 +213,12 @@ function buildUpdateCommand({ mode, repoRoot, logPath }) {
|
|
|
191
213
|
parts.push(`git pull --ff-only`);
|
|
192
214
|
parts.push(`npm install`);
|
|
193
215
|
}
|
|
194
|
-
parts.push(`echo "[mobygate-update]
|
|
216
|
+
parts.push(`echo "[mobygate-update] starting service on new build"`);
|
|
195
217
|
if (IS_MAC) {
|
|
196
218
|
const plist = join(process.env.HOME || '~', 'Library', 'LaunchAgents', `${SERVER_LABEL}.plist`);
|
|
197
|
-
|
|
198
|
-
parts.push(`launchctl unload "${plist}" 2>/dev/null || true`);
|
|
199
|
-
parts.push(`launchctl load "${plist}"`);
|
|
219
|
+
parts.push(`launchctl load "${plist}"`);
|
|
200
220
|
} else if (IS_LINUX) {
|
|
201
|
-
parts.push(`systemctl --user
|
|
221
|
+
parts.push(`systemctl --user start ${LINUX_SERVER_UNIT}`);
|
|
202
222
|
}
|
|
203
223
|
parts.push(`echo "[mobygate-update] done"`);
|
|
204
224
|
const script = parts.join('\n');
|