synabun 2026.4.26-beta.10 → 2026.4.26-beta.12
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.
|
@@ -5094,15 +5094,51 @@
|
|
|
5094
5094
|
}
|
|
5095
5095
|
.update-backup-btn:hover { background: rgba(52,199,89,0.16); }
|
|
5096
5096
|
.update-backup-btn:disabled { opacity: 0.5; pointer-events: none; }
|
|
5097
|
-
.update-backup-status {
|
|
5098
|
-
display: flex;
|
|
5099
|
-
align-items: center;
|
|
5100
|
-
gap: 8px;
|
|
5097
|
+
.update-backup-status {
|
|
5098
|
+
display: flex;
|
|
5099
|
+
align-items: center;
|
|
5100
|
+
gap: 8px;
|
|
5101
5101
|
font-size: 11px;
|
|
5102
5102
|
color: var(--t-muted);
|
|
5103
|
-
padding: 4px 0;
|
|
5104
|
-
}
|
|
5105
|
-
.update-
|
|
5103
|
+
padding: 4px 0;
|
|
5104
|
+
}
|
|
5105
|
+
.update-run-status {
|
|
5106
|
+
align-items: flex-start;
|
|
5107
|
+
gap: 10px;
|
|
5108
|
+
padding: 10px 12px;
|
|
5109
|
+
border-radius: var(--r-card);
|
|
5110
|
+
border: 1px solid rgba(255,255,255,0.08);
|
|
5111
|
+
background: rgba(255,255,255,0.045);
|
|
5112
|
+
color: var(--t-secondary);
|
|
5113
|
+
font-size: 12px;
|
|
5114
|
+
line-height: 1.4;
|
|
5115
|
+
}
|
|
5116
|
+
.update-run-status .update-backup-icon {
|
|
5117
|
+
flex: 0 0 auto;
|
|
5118
|
+
margin-top: 1px;
|
|
5119
|
+
}
|
|
5120
|
+
.update-run-status strong {
|
|
5121
|
+
display: block;
|
|
5122
|
+
color: var(--t-primary);
|
|
5123
|
+
font-size: 12px;
|
|
5124
|
+
font-weight: 600;
|
|
5125
|
+
margin-bottom: 2px;
|
|
5126
|
+
}
|
|
5127
|
+
.update-run-detail,
|
|
5128
|
+
.update-run-hint {
|
|
5129
|
+
display: block;
|
|
5130
|
+
color: var(--t-muted);
|
|
5131
|
+
}
|
|
5132
|
+
.update-run-hint {
|
|
5133
|
+
margin-top: 5px;
|
|
5134
|
+
opacity: .82;
|
|
5135
|
+
}
|
|
5136
|
+
.update-run-status code {
|
|
5137
|
+
font-family: var(--ff-mono);
|
|
5138
|
+
font-size: 11px;
|
|
5139
|
+
color: var(--t-secondary);
|
|
5140
|
+
}
|
|
5141
|
+
.update-backup-icon { font-size: 14px; font-weight: 700; }
|
|
5106
5142
|
.update-backup-icon.spin { animation: updateSpin 1s linear infinite; }
|
|
5107
5143
|
@keyframes updateSpin {
|
|
5108
5144
|
from { transform: rotate(0deg); }
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
import { showUpdateToast } from './ui-tool-updates.js';
|
|
8
8
|
|
|
9
9
|
const $ = (id) => document.getElementById(id);
|
|
10
|
+
const SYNABUN_GITHUB_URL = 'https://github.com/danilokhury/Synabun';
|
|
10
11
|
|
|
11
12
|
let _versionData = null;
|
|
12
13
|
let _btnWired = false;
|
|
@@ -187,27 +188,37 @@ function openUpdateModal() {
|
|
|
187
188
|
|
|
188
189
|
function renderStep3() {
|
|
189
190
|
const installPlan = _versionData.installPlan || {};
|
|
190
|
-
const canAutoUpdate = installPlan.canAutoUpdate
|
|
191
|
-
const
|
|
191
|
+
const canAutoUpdate = installPlan.canAutoUpdate === true;
|
|
192
|
+
const repoUrl = installPlan.openUrl || SYNABUN_GITHUB_URL;
|
|
193
|
+
const installCmd = canAutoUpdate
|
|
194
|
+
? (installPlan.displayCommand || 'npm i -g synabun@latest')
|
|
195
|
+
: repoUrl;
|
|
192
196
|
const installIntro = canAutoUpdate
|
|
193
197
|
? 'SynaBun will close and run this command in a new terminal window:'
|
|
194
|
-
: '
|
|
198
|
+
: 'This SynaBun install was not detected as an npm install. Open the GitHub repository to update from your checkout or installer:';
|
|
199
|
+
const title = canAutoUpdate ? 'Run the Update' : 'Update from GitHub';
|
|
195
200
|
const installSource = canAutoUpdate
|
|
196
201
|
? 'Installing from npm.'
|
|
197
|
-
: (installPlan.manualHint || '
|
|
202
|
+
: (installPlan.manualHint || 'Open the SynaBun repository for the correct update path.');
|
|
198
203
|
const autoRestartControl = canAutoUpdate ? `
|
|
199
204
|
<label class="update-skip-label" style="margin:14px 0 8px;display:flex;align-items:center;gap:8px;cursor:pointer">
|
|
200
205
|
<input type="checkbox" id="update-auto-restart" checked>
|
|
201
206
|
<span>Relaunch SynaBun after update</span>
|
|
202
207
|
</label>` : '';
|
|
208
|
+
const manualButton = canAutoUpdate ? `
|
|
209
|
+
<button class="action-btn action-btn--ghost" id="update-manual" title="Run the command yourself in your own terminal">Manual</button>` : '';
|
|
203
210
|
const updateNowButton = canAutoUpdate ? `
|
|
204
211
|
<button class="action-btn action-btn--primary" id="update-run-now">
|
|
205
212
|
<svg viewBox="0 0 24 24" width="13" height="13" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-2px;margin-right:6px"><polyline points="8 17 12 21 16 17"/><line x1="12" y1="12" x2="12" y2="21"/><path d="M20.88 18.09A5 5 0 0 0 18 9h-1.26A8 8 0 1 0 3 16.29"/></svg>
|
|
206
213
|
Update Now
|
|
207
|
-
</button>` :
|
|
214
|
+
</button>` : `
|
|
215
|
+
<button class="action-btn action-btn--primary" id="update-run-now" data-url="${esc(repoUrl)}">
|
|
216
|
+
<svg viewBox="0 0 24 24" width="13" height="13" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-2px;margin-right:6px"><path d="M15 3h6v6"/><path d="M10 14 21 3"/><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/></svg>
|
|
217
|
+
Open GitHub
|
|
218
|
+
</button>`;
|
|
208
219
|
|
|
209
220
|
return `
|
|
210
|
-
<h3 class="update-modal-title"
|
|
221
|
+
<h3 class="update-modal-title">${title}</h3>
|
|
211
222
|
<p style="font-size:12px;color:var(--t-muted);margin:4px 0 12px">
|
|
212
223
|
${esc(installIntro)}
|
|
213
224
|
</p>
|
|
@@ -227,12 +238,12 @@ function openUpdateModal() {
|
|
|
227
238
|
|
|
228
239
|
<div class="tag-delete-modal-actions" style="margin-top:14px;flex-wrap:wrap;gap:8px">
|
|
229
240
|
<button class="action-btn action-btn--ghost" id="update-back">Back</button>
|
|
230
|
-
|
|
241
|
+
${manualButton}
|
|
231
242
|
${updateNowButton}
|
|
232
243
|
</div>
|
|
233
244
|
|
|
234
245
|
<p id="update-manual-note" style="display:none;font-size:11px;color:var(--t-muted);opacity:.75;margin:10px 0 0">
|
|
235
|
-
After running the command, restart SynaBun
|
|
246
|
+
After running the command, restart SynaBun with <code>synabun</code>.
|
|
236
247
|
</p>`;
|
|
237
248
|
}
|
|
238
249
|
|
|
@@ -298,7 +309,9 @@ function openUpdateModal() {
|
|
|
298
309
|
}
|
|
299
310
|
|
|
300
311
|
async function handleRunUpdate() {
|
|
301
|
-
|
|
312
|
+
const installPlan = _versionData?.installPlan || {};
|
|
313
|
+
const canAutoUpdate = installPlan.canAutoUpdate === true;
|
|
314
|
+
const repoUrl = installPlan.openUrl || SYNABUN_GITHUB_URL;
|
|
302
315
|
|
|
303
316
|
const autoRestartCb = overlay.querySelector('#update-auto-restart');
|
|
304
317
|
const autoRestart = !!autoRestartCb?.checked;
|
|
@@ -310,16 +323,27 @@ function openUpdateModal() {
|
|
|
310
323
|
const icon = overlay.querySelector('#update-run-icon');
|
|
311
324
|
const text = overlay.querySelector('#update-run-text');
|
|
312
325
|
|
|
313
|
-
if (!runBtn || !backBtn || !
|
|
326
|
+
if (!runBtn || !backBtn || !status || !icon || !text) return;
|
|
327
|
+
|
|
328
|
+
if (!canAutoUpdate) {
|
|
329
|
+
window.open(repoUrl, '_blank', 'noopener,noreferrer');
|
|
330
|
+
status.style.display = 'flex';
|
|
331
|
+
icon.className = 'update-backup-icon';
|
|
332
|
+
icon.textContent = '↗';
|
|
333
|
+
icon.style.color = '#4fc3f7';
|
|
334
|
+
text.innerHTML = `<strong>GitHub repository opened</strong><span class="update-run-detail">Use the repository instructions to update this non-npm installation.</span>`;
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
314
337
|
|
|
315
338
|
runBtn.disabled = true;
|
|
316
339
|
backBtn.disabled = true;
|
|
317
|
-
manualBtn.disabled = true;
|
|
340
|
+
if (manualBtn) manualBtn.disabled = true;
|
|
318
341
|
runBtn.innerHTML = '<span class="update-spinner"></span> Launching updater...';
|
|
319
342
|
status.style.display = 'flex';
|
|
320
343
|
icon.className = 'update-backup-icon spin';
|
|
321
344
|
icon.textContent = '⟳';
|
|
322
|
-
|
|
345
|
+
icon.style.color = '';
|
|
346
|
+
text.innerHTML = '<strong>Launching updater</strong><span class="update-run-detail">Opening a terminal window for the npm update.</span>';
|
|
323
347
|
|
|
324
348
|
try {
|
|
325
349
|
const res = await fetch('/api/system/run-update', {
|
|
@@ -332,12 +356,12 @@ function openUpdateModal() {
|
|
|
332
356
|
try { const b = await res.json(); msg = b.error || msg; } catch {}
|
|
333
357
|
throw new Error(msg);
|
|
334
358
|
}
|
|
335
|
-
|
|
359
|
+
await res.json();
|
|
336
360
|
|
|
337
361
|
icon.className = 'update-backup-icon';
|
|
338
362
|
icon.textContent = '✓';
|
|
339
363
|
icon.style.color = '#34c759';
|
|
340
|
-
text.innerHTML =
|
|
364
|
+
text.innerHTML = `<strong>Updater terminal launched</strong><span class="update-run-detail">SynaBun is shutting down so npm can replace the package. Watch the terminal for install progress${autoRestart ? '; SynaBun will relaunch when it finishes.' : '; run <code>synabun</code> when it finishes.'}</span><span class="update-run-hint">If no terminal window appears, run <code>${esc(installPlan.displayCommand || 'npm i -g synabun@latest')}</code> manually.</span>`;
|
|
341
365
|
runBtn.innerHTML = 'Updater running…';
|
|
342
366
|
|
|
343
367
|
// Server is shutting down. Optionally redirect to a friendly offline
|
|
@@ -351,7 +375,7 @@ function openUpdateModal() {
|
|
|
351
375
|
text.textContent = 'Failed to launch updater: ' + err.message;
|
|
352
376
|
runBtn.disabled = false;
|
|
353
377
|
backBtn.disabled = false;
|
|
354
|
-
manualBtn.disabled = false;
|
|
378
|
+
if (manualBtn) manualBtn.disabled = false;
|
|
355
379
|
runBtn.innerHTML = `<svg viewBox="0 0 24 24" width="13" height="13" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-2px;margin-right:6px"><polyline points="8 17 12 21 16 17"/><line x1="12" y1="12" x2="12" y2="21"/><path d="M20.88 18.09A5 5 0 0 0 18 9h-1.26A8 8 0 1 0 3 16.29"/></svg> Retry`;
|
|
356
380
|
}
|
|
357
381
|
}
|
|
@@ -1594,11 +1594,15 @@ async function gracefulShutdown(reason = 'shutdown') {
|
|
|
1594
1594
|
|
|
1595
1595
|
try { closeDb(); } catch {}
|
|
1596
1596
|
|
|
1597
|
-
// 5. Belt-and-suspenders: tree-kill THIS process's descendants on Windows.
|
|
1598
|
-
// Synabun spawns CLI links, pty terminals, cloudflare tunnel etc. that may
|
|
1599
|
-
// not be reachable through the registries above. The taskkill /T flag walks
|
|
1600
|
-
// the process tree and terminates every descendant.
|
|
1601
|
-
|
|
1597
|
+
// 5. Belt-and-suspenders: tree-kill THIS process's descendants on Windows.
|
|
1598
|
+
// Synabun spawns CLI links, pty terminals, cloudflare tunnel etc. that may
|
|
1599
|
+
// not be reachable through the registries above. The taskkill /T flag walks
|
|
1600
|
+
// the process tree and terminates every descendant.
|
|
1601
|
+
//
|
|
1602
|
+
// Do not run this for click-to-update. The updater terminal is deliberately
|
|
1603
|
+
// launched before this server exits; taskkill /T would kill that brand-new
|
|
1604
|
+
// terminal too, which looks like a brief blink and no update on Windows.
|
|
1605
|
+
if (process.platform === 'win32' && reason !== 'run-update') {
|
|
1602
1606
|
try {
|
|
1603
1607
|
// Use spawn(detached) so taskkill survives our own exit and finishes
|
|
1604
1608
|
// killing children even if we exit before it completes.
|
|
@@ -1635,9 +1639,45 @@ app.post('/api/server/shutdown', async (req, res) => {
|
|
|
1635
1639
|
}, 200);
|
|
1636
1640
|
});
|
|
1637
1641
|
|
|
1638
|
-
function shellSingleQuote(s) {
|
|
1639
|
-
return `'${String(s).replace(/'/g, `'\\''`)}'`;
|
|
1640
|
-
}
|
|
1642
|
+
function shellSingleQuote(s) {
|
|
1643
|
+
return `'${String(s).replace(/'/g, `'\\''`)}'`;
|
|
1644
|
+
}
|
|
1645
|
+
|
|
1646
|
+
function collectWindowsDescendantPids(rootPid) {
|
|
1647
|
+
if (process.platform !== 'win32' || !rootPid) return [];
|
|
1648
|
+
try {
|
|
1649
|
+
const script = [
|
|
1650
|
+
`$root=${Number(rootPid)}`,
|
|
1651
|
+
'$procs=Get-CimInstance Win32_Process | Select-Object ProcessId,ParentProcessId',
|
|
1652
|
+
'$seen=@{}',
|
|
1653
|
+
'$front=@($root)',
|
|
1654
|
+
'$out=@()',
|
|
1655
|
+
'while ($front.Count -gt 0) {',
|
|
1656
|
+
' $next=@()',
|
|
1657
|
+
' foreach ($p in $procs) {',
|
|
1658
|
+
' if ($front -contains $p.ParentProcessId -and -not $seen.ContainsKey($p.ProcessId)) {',
|
|
1659
|
+
' $seen[$p.ProcessId]=$true',
|
|
1660
|
+
' $out += $p.ProcessId',
|
|
1661
|
+
' $next += $p.ProcessId',
|
|
1662
|
+
' }',
|
|
1663
|
+
' }',
|
|
1664
|
+
' $front=$next',
|
|
1665
|
+
'}',
|
|
1666
|
+
'$out -join ","',
|
|
1667
|
+
].join('; ');
|
|
1668
|
+
const result = spawnSync('powershell.exe', ['-NoProfile', '-NonInteractive', '-Command', script], {
|
|
1669
|
+
encoding: 'utf8',
|
|
1670
|
+
windowsHide: true,
|
|
1671
|
+
timeout: 5000,
|
|
1672
|
+
});
|
|
1673
|
+
const out = result.status === 0 ? String(result.stdout || '').trim() : '';
|
|
1674
|
+
return out
|
|
1675
|
+
? out.split(',').map(v => Number(v)).filter(pid => Number.isFinite(pid) && pid > 0 && pid !== rootPid)
|
|
1676
|
+
: [];
|
|
1677
|
+
} catch {
|
|
1678
|
+
return [];
|
|
1679
|
+
}
|
|
1680
|
+
}
|
|
1641
1681
|
|
|
1642
1682
|
function cleanupOldUpdaterStages(baseDir) {
|
|
1643
1683
|
try {
|
|
@@ -1653,7 +1693,7 @@ function cleanupOldUpdaterStages(baseDir) {
|
|
|
1653
1693
|
} catch {}
|
|
1654
1694
|
}
|
|
1655
1695
|
|
|
1656
|
-
function stageSynabunUpdater({ installPlan, serverPid, autoRestart }) {
|
|
1696
|
+
function stageSynabunUpdater({ installPlan, serverPid, autoRestart, cleanupPids = [] }) {
|
|
1657
1697
|
const sourceUpdater = resolve(PACKAGE_ROOT, 'updater.mjs');
|
|
1658
1698
|
if (!existsSync(sourceUpdater)) {
|
|
1659
1699
|
throw new Error(`updater.mjs not found at ${sourceUpdater}. SynaBun install may be corrupt - reinstall manually: ${installPlan.displayCommand}`);
|
|
@@ -1678,20 +1718,22 @@ function stageSynabunUpdater({ installPlan, serverPid, autoRestart }) {
|
|
|
1678
1718
|
displayCommand: installPlan.displayCommand,
|
|
1679
1719
|
source: installPlan.source,
|
|
1680
1720
|
current: installPlan.current,
|
|
1681
|
-
target: installPlan.target,
|
|
1682
|
-
channel: installPlan.channel,
|
|
1683
|
-
|
|
1721
|
+
target: installPlan.target,
|
|
1722
|
+
channel: installPlan.channel,
|
|
1723
|
+
cleanupPids,
|
|
1724
|
+
};
|
|
1684
1725
|
writeFileSync(payloadPath, JSON.stringify(payload, null, 2) + '\n', 'utf8');
|
|
1685
1726
|
|
|
1686
|
-
if (process.platform === 'win32') {
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
'
|
|
1690
|
-
'
|
|
1691
|
-
'
|
|
1692
|
-
'
|
|
1693
|
-
'set "
|
|
1694
|
-
|
|
1727
|
+
if (process.platform === 'win32') {
|
|
1728
|
+
const nodeBin = String(process.execPath || 'node').replace(/"/g, '');
|
|
1729
|
+
writeFileSync(runnerPath, [
|
|
1730
|
+
'@echo off',
|
|
1731
|
+
'setlocal enableextensions',
|
|
1732
|
+
'title SynaBun Updater',
|
|
1733
|
+
'cd /d "%~dp0"',
|
|
1734
|
+
'set "EXIT_CODE=0"',
|
|
1735
|
+
`set "NODE_BIN=${nodeBin}"`,
|
|
1736
|
+
'if defined SYNABUN_NODE_BIN set "NODE_BIN=%SYNABUN_NODE_BIN%"',
|
|
1695
1737
|
'"%NODE_BIN%" "%~dp0updater.mjs" --payload "%~dp0payload.json" --no-hold',
|
|
1696
1738
|
'set "EXIT_CODE=%ERRORLEVEL%"',
|
|
1697
1739
|
'echo.',
|
|
@@ -1750,8 +1792,9 @@ app.post('/api/system/run-update', async (req, res) => {
|
|
|
1750
1792
|
}
|
|
1751
1793
|
if (installPlan.canAutoUpdate === false) {
|
|
1752
1794
|
return res.status(409).json({
|
|
1753
|
-
error: installPlan.manualHint || `This installation cannot be auto-updated.
|
|
1795
|
+
error: installPlan.manualHint || `This installation cannot be auto-updated. Open ${installPlan.openUrl || SYNABUN_GITHUB_REPO_URL}`,
|
|
1754
1796
|
installPlan,
|
|
1797
|
+
openUrl: installPlan.openUrl || SYNABUN_GITHUB_REPO_URL,
|
|
1755
1798
|
});
|
|
1756
1799
|
}
|
|
1757
1800
|
if (!installPlan.installSpec) {
|
|
@@ -1760,19 +1803,20 @@ app.post('/api/system/run-update', async (req, res) => {
|
|
|
1760
1803
|
|
|
1761
1804
|
const plat = process.platform;
|
|
1762
1805
|
const serverPid = process.pid;
|
|
1763
|
-
const
|
|
1806
|
+
const cleanupPids = collectWindowsDescendantPids(serverPid);
|
|
1807
|
+
const staged = stageSynabunUpdater({ installPlan, serverPid, autoRestart, cleanupPids });
|
|
1764
1808
|
|
|
1765
1809
|
let spawned = null;
|
|
1766
1810
|
let launchError = null;
|
|
1767
1811
|
|
|
1768
|
-
if (plat === 'win32') {
|
|
1769
|
-
try {
|
|
1770
|
-
spawned = spawn('powershell.exe', [
|
|
1771
|
-
'-NoProfile',
|
|
1772
|
-
'-ExecutionPolicy', 'Bypass',
|
|
1773
|
-
'-Command',
|
|
1774
|
-
'$p=$env:SYNABUN_UPDATER_RUNNER; $w=$env:SYNABUN_UPDATER_CWD; Start-Process -FilePath $p -WorkingDirectory $w -WindowStyle Normal',
|
|
1775
|
-
], {
|
|
1812
|
+
if (plat === 'win32') {
|
|
1813
|
+
try {
|
|
1814
|
+
spawned = spawn('powershell.exe', [
|
|
1815
|
+
'-NoProfile',
|
|
1816
|
+
'-ExecutionPolicy', 'Bypass',
|
|
1817
|
+
'-Command',
|
|
1818
|
+
'$p=$env:SYNABUN_UPDATER_RUNNER; $w=$env:SYNABUN_UPDATER_CWD; $cmd=$env:ComSpec; if (-not $cmd) { $cmd="cmd.exe" }; Start-Process -FilePath $cmd -ArgumentList @("/k", "`"$p`"") -WorkingDirectory $w -WindowStyle Normal',
|
|
1819
|
+
], {
|
|
1776
1820
|
cwd: staged.stageDir,
|
|
1777
1821
|
shell: false,
|
|
1778
1822
|
detached: true,
|
|
@@ -10433,7 +10477,7 @@ app.post('/api/search/memories', async (req, res) => {
|
|
|
10433
10477
|
// when EITHER source is newer than the installed version (catches one-sided
|
|
10434
10478
|
// publishes — e.g. release tagged on GitHub before npm publish completes).
|
|
10435
10479
|
|
|
10436
|
-
let _synabunUpdateCache = {
|
|
10480
|
+
let _synabunUpdateCache = {
|
|
10437
10481
|
current: null,
|
|
10438
10482
|
installedChannel: 'stable', // 'stable' | 'prerelease'
|
|
10439
10483
|
npmLatestStable: null, // dist-tags.latest
|
|
@@ -10447,12 +10491,14 @@ let _synabunUpdateCache = {
|
|
|
10447
10491
|
gitUpdateAvailable: false,
|
|
10448
10492
|
updateAvailable: false, // npmUpdateAvailable || gitUpdateAvailable
|
|
10449
10493
|
source: null, // 'npm' | 'github' | 'both' | null
|
|
10450
|
-
installPlan: null, // server-owned npm
|
|
10494
|
+
installPlan: null, // server-owned npm/GitHub action target for click-to-update
|
|
10451
10495
|
checkedAt: null,
|
|
10452
10496
|
npmError: null,
|
|
10453
|
-
gitError: null,
|
|
10454
|
-
};
|
|
10455
|
-
|
|
10497
|
+
gitError: null,
|
|
10498
|
+
};
|
|
10499
|
+
|
|
10500
|
+
const SYNABUN_GITHUB_REPO_URL = 'https://github.com/danilokhury/Synabun';
|
|
10501
|
+
|
|
10456
10502
|
function isSynabunGitHubRemote(remoteText) {
|
|
10457
10503
|
return /github\.com[:/]danilokhury\/Synabun(?:\.git)?(?:[\s)]|$)/i.test(String(remoteText || ''));
|
|
10458
10504
|
}
|
|
@@ -10504,17 +10550,21 @@ function buildSynabunInstallPlan({ current, installedChannel, latest, updateAvai
|
|
|
10504
10550
|
if (!updateAvailable) return null;
|
|
10505
10551
|
|
|
10506
10552
|
const installSource = detectSynabunInstallSource();
|
|
10507
|
-
if (installSource.kind
|
|
10553
|
+
if (installSource.kind !== 'npm-global') {
|
|
10554
|
+
const clonedHint = installSource.kind === 'github-clone'
|
|
10555
|
+
? 'This SynaBun install is a GitHub checkout. Open the repository to pull or reinstall from the correct source.'
|
|
10556
|
+
: 'This SynaBun install was not detected as npm. Open the repository for the correct update path.';
|
|
10508
10557
|
return {
|
|
10509
|
-
source: 'github
|
|
10558
|
+
source: 'github',
|
|
10510
10559
|
installSource: installSource.kind,
|
|
10511
10560
|
canAutoUpdate: false,
|
|
10512
10561
|
current,
|
|
10513
10562
|
target: latest,
|
|
10514
10563
|
channel: installedChannel,
|
|
10515
|
-
displayCommand:
|
|
10516
|
-
|
|
10517
|
-
|
|
10564
|
+
displayCommand: SYNABUN_GITHUB_REPO_URL,
|
|
10565
|
+
openUrl: SYNABUN_GITHUB_REPO_URL,
|
|
10566
|
+
manualCommand: installSource.kind === 'github-clone' ? 'git pull --ff-only' : null,
|
|
10567
|
+
manualHint: clonedHint,
|
|
10518
10568
|
remote: installSource.remote,
|
|
10519
10569
|
};
|
|
10520
10570
|
}
|
package/package.json
CHANGED
package/updater.mjs
CHANGED
|
@@ -20,6 +20,7 @@ const legacyTag = sanitizeTag(args.tag || 'latest');
|
|
|
20
20
|
const installSpec = sanitizeInstallSpec(payload.installSpec || args['install-spec'] || `synabun@${legacyTag}`);
|
|
21
21
|
const displayCommand = payload.displayCommand || `npm i -g ${installSpec}`;
|
|
22
22
|
const serverPid = toPid(payload.serverPid ?? args.pid);
|
|
23
|
+
const cleanupPids = sanitizePidList(payload.cleanupPids).filter(pid => pid !== serverPid && pid !== process.pid);
|
|
23
24
|
const autoRestart = payload.autoRestart != null ? !!payload.autoRestart : !!args.restart;
|
|
24
25
|
const safeCwd = payload.cwd || process.cwd();
|
|
25
26
|
const noHold = !!args['no-hold'];
|
|
@@ -46,8 +47,8 @@ try {
|
|
|
46
47
|
if (exited) {
|
|
47
48
|
console.log(' Server exited.\n');
|
|
48
49
|
} else {
|
|
49
|
-
console.warn(' Server still alive after 30s - forcing kill.\n');
|
|
50
|
-
|
|
50
|
+
console.warn(' Server still alive after 30s - forcing server process kill.\n');
|
|
51
|
+
forceKillPid(serverPid);
|
|
51
52
|
await sleep(1500);
|
|
52
53
|
}
|
|
53
54
|
} else {
|
|
@@ -55,7 +56,15 @@ try {
|
|
|
55
56
|
}
|
|
56
57
|
|
|
57
58
|
await sleep(1500);
|
|
58
|
-
if (
|
|
59
|
+
if (cleanupPids.length) {
|
|
60
|
+
console.log(`Cleaning up ${cleanupPids.length} pre-existing child process${cleanupPids.length === 1 ? '' : 'es'}...`);
|
|
61
|
+
for (const pid of cleanupPids) {
|
|
62
|
+
if (!isPidAlive(pid)) continue;
|
|
63
|
+
forceKillTree(pid);
|
|
64
|
+
}
|
|
65
|
+
await sleep(1000);
|
|
66
|
+
console.log(' Cleanup complete.\n');
|
|
67
|
+
}
|
|
59
68
|
|
|
60
69
|
console.log(`Running: ${displayCommand}\n`);
|
|
61
70
|
console.log(HR);
|
|
@@ -143,6 +152,19 @@ function toPid(raw) {
|
|
|
143
152
|
return Number.isFinite(pid) && pid > 0 ? pid : null;
|
|
144
153
|
}
|
|
145
154
|
|
|
155
|
+
function sanitizePidList(raw) {
|
|
156
|
+
if (!Array.isArray(raw)) return [];
|
|
157
|
+
const out = [];
|
|
158
|
+
const seen = new Set();
|
|
159
|
+
for (const item of raw) {
|
|
160
|
+
const pid = toPid(item);
|
|
161
|
+
if (!pid || seen.has(pid)) continue;
|
|
162
|
+
seen.add(pid);
|
|
163
|
+
out.push(pid);
|
|
164
|
+
}
|
|
165
|
+
return out;
|
|
166
|
+
}
|
|
167
|
+
|
|
146
168
|
async function waitForExit(pid, timeoutMs) {
|
|
147
169
|
const deadline = Date.now() + timeoutMs;
|
|
148
170
|
while (Date.now() < deadline) {
|
|
@@ -176,6 +198,20 @@ function forceKillTree(pid) {
|
|
|
176
198
|
} catch {}
|
|
177
199
|
}
|
|
178
200
|
|
|
201
|
+
function forceKillPid(pid) {
|
|
202
|
+
try {
|
|
203
|
+
if (isWin) {
|
|
204
|
+
spawnSync('taskkill', ['/pid', String(pid), '/F'], {
|
|
205
|
+
shell: true,
|
|
206
|
+
windowsHide: true,
|
|
207
|
+
stdio: 'ignore',
|
|
208
|
+
});
|
|
209
|
+
} else {
|
|
210
|
+
try { process.kill(pid, 'SIGKILL'); } catch {}
|
|
211
|
+
}
|
|
212
|
+
} catch {}
|
|
213
|
+
}
|
|
214
|
+
|
|
179
215
|
function printFixTips(command) {
|
|
180
216
|
console.error('Common fixes:');
|
|
181
217
|
if (isWin) {
|