stikfix 1.6.1 → 1.7.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.
- package/dist/host/src/index.js +15 -1
- package/dist/host/src/server.js +4 -0
- package/dist/host/src/tray.js +119 -7
- package/dist/host/src/update-check.js +134 -0
- package/package.json +2 -1
package/dist/host/src/index.js
CHANGED
|
@@ -13,11 +13,12 @@ import { writeFileSync, readFileSync, existsSync } from 'node:fs';
|
|
|
13
13
|
import { join } from 'node:path';
|
|
14
14
|
import { parseArgs } from 'node:util';
|
|
15
15
|
import { pathToFileURL } from 'node:url';
|
|
16
|
-
import { resolveConfig, resolveConfigValues, ensureNotesDir, writeTokenFile } from './config.js';
|
|
16
|
+
import { resolveConfig, resolveConfigValues, ensureNotesDir, writeTokenFile, VERSION } from './config.js';
|
|
17
17
|
import { createHostServer } from './server.js';
|
|
18
18
|
import { bindServer, BIND_HOST } from './bind.js';
|
|
19
19
|
import { probeExistingHost } from './probe.js';
|
|
20
20
|
import { startTray } from './tray.js';
|
|
21
|
+
import { runUpdateCheck } from './update-check.js';
|
|
21
22
|
/**
|
|
22
23
|
* Start the HTTP host. Extracted from the former top-level module body so the
|
|
23
24
|
* single-executable dispatcher (exe-main.ts) can call it as `serve`, while
|
|
@@ -132,6 +133,15 @@ export async function startHttpHost(argv = process.argv.slice(2)) {
|
|
|
132
133
|
notesDir: cfg.notesDir,
|
|
133
134
|
hostPid: process.pid,
|
|
134
135
|
});
|
|
136
|
+
// Host auto-update: check GitHub for a newer release on startup and every 6h.
|
|
137
|
+
// Fire-and-forget + fully swallowed — a failed check must never crash or block
|
|
138
|
+
// the host. runUpdateCheck updates the module state surfaced on GET /status.
|
|
139
|
+
void runUpdateCheck(VERSION).catch(() => { });
|
|
140
|
+
const updateTimer = setInterval(() => {
|
|
141
|
+
void runUpdateCheck(VERSION).catch(() => { });
|
|
142
|
+
}, 6 * 60 * 60 * 1000);
|
|
143
|
+
// .unref() so the interval never keeps the process alive on its own.
|
|
144
|
+
updateTimer.unref();
|
|
135
145
|
// Kill the tray child when the host shuts down so a dead host has no tray.
|
|
136
146
|
// (No prior signal handling existed here; these are additive.)
|
|
137
147
|
let shuttingDown = false;
|
|
@@ -139,6 +149,10 @@ export async function startHttpHost(argv = process.argv.slice(2)) {
|
|
|
139
149
|
if (shuttingDown)
|
|
140
150
|
return;
|
|
141
151
|
shuttingDown = true;
|
|
152
|
+
try {
|
|
153
|
+
clearInterval(updateTimer);
|
|
154
|
+
}
|
|
155
|
+
catch { /* best-effort */ }
|
|
142
156
|
try {
|
|
143
157
|
tray?.kill();
|
|
144
158
|
}
|
package/dist/host/src/server.js
CHANGED
|
@@ -17,6 +17,7 @@ import { writeNote } from './write-note.js';
|
|
|
17
17
|
import { listAnnotations, editNote, deleteNote } from './read-note.js';
|
|
18
18
|
import { validateChosenFolder } from './validate-folder.js';
|
|
19
19
|
import { isGitRepo, getLastGitSyncStatus, gitSyncNote } from './git-sync.js';
|
|
20
|
+
import { getUpdateState } from './update-check.js';
|
|
20
21
|
// ---------------------------------------------------------------------------
|
|
21
22
|
// Per-request notes-dir resolution (D-04 — targetDir support)
|
|
22
23
|
// ---------------------------------------------------------------------------
|
|
@@ -95,6 +96,9 @@ async function handleStatus(req, res, cfg) {
|
|
|
95
96
|
// result of the most recent git-sync attempt (null if none yet).
|
|
96
97
|
gitRepo: await isGitRepo(cfg.root),
|
|
97
98
|
gitSyncStatus: getLastGitSyncStatus(),
|
|
99
|
+
// Host auto-update surface (public, non-secret): whether a newer release is
|
|
100
|
+
// available and where to get it. Consumed by the tray for 1-click apply.
|
|
101
|
+
update: getUpdateState(),
|
|
98
102
|
});
|
|
99
103
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
100
104
|
res.end(body);
|
package/dist/host/src/tray.js
CHANGED
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
*/
|
|
21
21
|
import { spawn } from 'node:child_process';
|
|
22
22
|
import { writeFileSync, mkdirSync } from 'node:fs';
|
|
23
|
-
import { join } from 'node:path';
|
|
23
|
+
import { dirname, join } from 'node:path';
|
|
24
24
|
import { homedir } from 'node:os';
|
|
25
25
|
/**
|
|
26
26
|
* Directory + path where the embedded tray script is written on each start.
|
|
@@ -48,6 +48,7 @@ export function trayScriptPath() {
|
|
|
48
48
|
*/
|
|
49
49
|
export const TRAY_PS1 = String.raw `param(
|
|
50
50
|
[int]$Port,
|
|
51
|
+
[string]$IconPath,
|
|
51
52
|
[string]$Root,
|
|
52
53
|
[string]$Name,
|
|
53
54
|
[string]$NotesDir,
|
|
@@ -56,6 +57,13 @@ export const TRAY_PS1 = String.raw `param(
|
|
|
56
57
|
|
|
57
58
|
$ErrorActionPreference = 'SilentlyContinue'
|
|
58
59
|
|
|
60
|
+
# --- Update state (script scope) --------------------------------------------
|
|
61
|
+
# Set when GET /status reports an available update; consumed by the update menu
|
|
62
|
+
# item's click handler. UpdateShown gates the one-time "update available" balloon.
|
|
63
|
+
$script:UpdateShown = $false
|
|
64
|
+
$script:PendingUrl = ''
|
|
65
|
+
$script:PendingSha = ''
|
|
66
|
+
|
|
59
67
|
try {
|
|
60
68
|
Add-Type -AssemblyName System.Windows.Forms
|
|
61
69
|
Add-Type -AssemblyName System.Drawing
|
|
@@ -67,6 +75,7 @@ try {
|
|
|
67
75
|
# --- Resolve a custom app icon, else fall back to a stock icon -------------
|
|
68
76
|
function Get-BaseIcon {
|
|
69
77
|
$candidates = @(
|
|
78
|
+
$IconPath,
|
|
70
79
|
(Join-Path $PSScriptRoot 'stikfix.ico'),
|
|
71
80
|
(Join-Path $Root '.output\chrome-mv3\icon\stikfix.ico'),
|
|
72
81
|
(Join-Path $Root 'public\icon\stikfix.ico')
|
|
@@ -98,6 +107,45 @@ Set-SafeText "stikfix - $Name - starting on :$Port"
|
|
|
98
107
|
# --- Context menu -----------------------------------------------------------
|
|
99
108
|
$menu = New-Object System.Windows.Forms.ContextMenuStrip
|
|
100
109
|
|
|
110
|
+
# Update item — first in the menu, hidden until an update is detected. Clicking
|
|
111
|
+
# it downloads the new installer, verifies its SHA-256, and runs it (one UAC
|
|
112
|
+
# prompt). The elevated installer stops the old host, swaps files, and restarts
|
|
113
|
+
# it; this tray self-disposes when the old host process dies.
|
|
114
|
+
$miUpdate = New-Object System.Windows.Forms.ToolStripMenuItem
|
|
115
|
+
$miUpdate.Text = 'Update Stikfix'
|
|
116
|
+
$miUpdate.Add_Click({
|
|
117
|
+
$url = $script:PendingUrl
|
|
118
|
+
$sha = $script:PendingSha
|
|
119
|
+
if ([string]::IsNullOrEmpty($url) -or [string]::IsNullOrEmpty($sha)) {
|
|
120
|
+
try { $notify.ShowBalloonTip(5000, 'Stikfix', 'Update details unavailable', [System.Windows.Forms.ToolTipIcon]::Warning) } catch { }
|
|
121
|
+
return
|
|
122
|
+
}
|
|
123
|
+
try { $notify.ShowBalloonTip(5000, 'Stikfix', 'Downloading Stikfix update...', [System.Windows.Forms.ToolTipIcon]::Info) } catch { }
|
|
124
|
+
$tmp = Join-Path $env:TEMP 'stikfix-update-setup.exe'
|
|
125
|
+
try {
|
|
126
|
+
$wc = New-Object System.Net.WebClient
|
|
127
|
+
$wc.DownloadFile($url, $tmp)
|
|
128
|
+
$wc.Dispose()
|
|
129
|
+
} catch {
|
|
130
|
+
try { $notify.ShowBalloonTip(5000, 'Stikfix', 'Download failed', [System.Windows.Forms.ToolTipIcon]::Error) } catch { }
|
|
131
|
+
return
|
|
132
|
+
}
|
|
133
|
+
# Security gate: verify SHA-256 before executing the installer.
|
|
134
|
+
$actual = ''
|
|
135
|
+
try { $actual = (Get-FileHash -Algorithm SHA256 -LiteralPath $tmp).Hash } catch { }
|
|
136
|
+
if ([string]::IsNullOrEmpty($actual) -or ($actual -ine $sha)) {
|
|
137
|
+
try { Remove-Item -LiteralPath $tmp -Force -ErrorAction SilentlyContinue } catch { }
|
|
138
|
+
try { $notify.ShowBalloonTip(5000, 'Stikfix', 'Update verification failed - not installing.', [System.Windows.Forms.ToolTipIcon]::Error) } catch { }
|
|
139
|
+
return
|
|
140
|
+
}
|
|
141
|
+
try {
|
|
142
|
+
$notify.ShowBalloonTip(5000, 'Stikfix', "Installing update - you'll see a permission prompt.", [System.Windows.Forms.ToolTipIcon]::Info)
|
|
143
|
+
Start-Process -FilePath $tmp -ArgumentList '/SILENT','/SUPPRESSMSGBOXES','/NORESTART'
|
|
144
|
+
} catch {
|
|
145
|
+
try { $notify.ShowBalloonTip(5000, 'Stikfix', 'Could not launch the update installer.', [System.Windows.Forms.ToolTipIcon]::Error) } catch { }
|
|
146
|
+
}
|
|
147
|
+
})
|
|
148
|
+
|
|
101
149
|
$miOpen = New-Object System.Windows.Forms.ToolStripMenuItem
|
|
102
150
|
$miOpen.Text = 'Open notes folder'
|
|
103
151
|
$miOpen.Add_Click({
|
|
@@ -123,6 +171,14 @@ $miQuit.Add_Click({
|
|
|
123
171
|
[void]$menu.Items.Add((New-Object System.Windows.Forms.ToolStripSeparator))
|
|
124
172
|
[void]$menu.Items.Add($miStop)
|
|
125
173
|
[void]$menu.Items.Add($miQuit)
|
|
174
|
+
|
|
175
|
+
# Update item + its separator go FIRST (index 0/1), hidden until an update lands.
|
|
176
|
+
$sepUpdate = New-Object System.Windows.Forms.ToolStripSeparator
|
|
177
|
+
$menu.Items.Insert(0, $sepUpdate)
|
|
178
|
+
$menu.Items.Insert(0, $miUpdate)
|
|
179
|
+
$miUpdate.Visible = $false
|
|
180
|
+
$sepUpdate.Visible = $false
|
|
181
|
+
|
|
126
182
|
$notify.ContextMenuStrip = $menu
|
|
127
183
|
|
|
128
184
|
# --- Status polling ---------------------------------------------------------
|
|
@@ -132,17 +188,35 @@ function Test-HostAlive {
|
|
|
132
188
|
return [bool]$proc
|
|
133
189
|
}
|
|
134
190
|
|
|
135
|
-
|
|
191
|
+
# Fetch /status, read + parse the JSON body, and return a hashtable:
|
|
192
|
+
# @{ Ok = <bool health>; Update = <the .update object or $null> }
|
|
193
|
+
# Any failure (throw, non-200, unparseable body) => @{ Ok = $false; Update = $null }.
|
|
194
|
+
function Get-HostStatus {
|
|
136
195
|
try {
|
|
137
196
|
$req = [System.Net.WebRequest]::Create("http://127.0.0.1:$Port/status")
|
|
138
197
|
$req.Method = 'GET'
|
|
139
198
|
$req.Timeout = 1500
|
|
140
199
|
$resp = $req.GetResponse()
|
|
141
200
|
$code = [int]$resp.StatusCode
|
|
201
|
+
$body = ''
|
|
202
|
+
try {
|
|
203
|
+
$stream = $resp.GetResponseStream()
|
|
204
|
+
$reader = New-Object System.IO.StreamReader($stream)
|
|
205
|
+
$body = $reader.ReadToEnd()
|
|
206
|
+
$reader.Close()
|
|
207
|
+
} catch { }
|
|
142
208
|
$resp.Close()
|
|
143
|
-
|
|
209
|
+
if ($code -ne 200) { return @{ Ok = $false; Update = $null } }
|
|
210
|
+
$update = $null
|
|
211
|
+
try {
|
|
212
|
+
$json = $body | ConvertFrom-Json
|
|
213
|
+
if ($json -and ($json.PSObject.Properties.Name -contains 'update')) {
|
|
214
|
+
$update = $json.update
|
|
215
|
+
}
|
|
216
|
+
} catch { }
|
|
217
|
+
return @{ Ok = $true; Update = $update }
|
|
144
218
|
} catch {
|
|
145
|
-
return $false
|
|
219
|
+
return @{ Ok = $false; Update = $null }
|
|
146
220
|
}
|
|
147
221
|
}
|
|
148
222
|
|
|
@@ -154,13 +228,47 @@ function Update-State {
|
|
|
154
228
|
return
|
|
155
229
|
}
|
|
156
230
|
|
|
157
|
-
|
|
231
|
+
$status = Get-HostStatus
|
|
232
|
+
$ok = $status.Ok
|
|
233
|
+
$update = $status.Update
|
|
234
|
+
|
|
235
|
+
# Compute the base running/stopped tooltip; the update suffix is appended below.
|
|
236
|
+
if ($ok) {
|
|
158
237
|
$notify.Icon = $iconRunning
|
|
159
|
-
|
|
238
|
+
$tip = "stikfix - $Name - running on :$Port"
|
|
160
239
|
} else {
|
|
161
240
|
$notify.Icon = $iconStopped
|
|
162
|
-
|
|
241
|
+
$tip = "stikfix - $Name - not responding"
|
|
163
242
|
}
|
|
243
|
+
|
|
244
|
+
# Surface an available update (guarded — $update is $null on old hosts).
|
|
245
|
+
$hasUpdate = $false
|
|
246
|
+
try { $hasUpdate = ($update -ne $null) -and ($update.available -eq $true) } catch { $hasUpdate = $false }
|
|
247
|
+
|
|
248
|
+
if ($hasUpdate) {
|
|
249
|
+
$latest = $update.latestVersion
|
|
250
|
+
$script:PendingUrl = [string]$update.url
|
|
251
|
+
$script:PendingSha = [string]$update.sha256
|
|
252
|
+
try {
|
|
253
|
+
$miUpdate.Text = "Update Stikfix (v$latest)"
|
|
254
|
+
$miUpdate.Visible = $true
|
|
255
|
+
$sepUpdate.Visible = $true
|
|
256
|
+
} catch { }
|
|
257
|
+
$tip = "$tip - update v$latest available"
|
|
258
|
+
if ($script:UpdateShown -eq $false) {
|
|
259
|
+
try {
|
|
260
|
+
$notify.ShowBalloonTip(5000, 'Stikfix update available', "Version $latest is ready. Right-click the tray icon then Update Stikfix.", [System.Windows.Forms.ToolTipIcon]::Info)
|
|
261
|
+
} catch { }
|
|
262
|
+
$script:UpdateShown = $true
|
|
263
|
+
}
|
|
264
|
+
} else {
|
|
265
|
+
try {
|
|
266
|
+
$miUpdate.Visible = $false
|
|
267
|
+
$sepUpdate.Visible = $false
|
|
268
|
+
} catch { }
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
Set-SafeText $tip
|
|
164
272
|
}
|
|
165
273
|
|
|
166
274
|
$timer = New-Object System.Windows.Forms.Timer
|
|
@@ -193,6 +301,8 @@ export function startTray(opts) {
|
|
|
193
301
|
const ps1Path = trayScriptPath();
|
|
194
302
|
mkdirSync(join(homedir(), '.local', 'share', 'stikfix'), { recursive: true });
|
|
195
303
|
writeFileSync(ps1Path, TRAY_PS1, { encoding: 'utf8' });
|
|
304
|
+
// Installed host ships stikfix.ico next to the exe ({app}\stikfix.ico). For npx/dev this points at node's dir and simply won't resolve — Get-BaseIcon falls back to a stock icon.
|
|
305
|
+
const iconPath = join(dirname(process.execPath), 'stikfix.ico');
|
|
196
306
|
const child = spawn('powershell.exe', [
|
|
197
307
|
'-NoProfile',
|
|
198
308
|
'-ExecutionPolicy',
|
|
@@ -203,6 +313,8 @@ export function startTray(opts) {
|
|
|
203
313
|
ps1Path,
|
|
204
314
|
'-Port',
|
|
205
315
|
String(opts.port),
|
|
316
|
+
'-IconPath',
|
|
317
|
+
iconPath,
|
|
206
318
|
'-Root',
|
|
207
319
|
opts.root,
|
|
208
320
|
'-Name',
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Host auto-update check for stikfix-host.
|
|
3
|
+
*
|
|
4
|
+
* The host periodically asks GitHub for the latest published release manifest
|
|
5
|
+
* (`latest.json`) and, if a newer version is available, advertises it on
|
|
6
|
+
* GET /status (`update.available` / `latestVersion` / `url` / `sha256`) so the
|
|
7
|
+
* system-tray helper can offer a one-click apply.
|
|
8
|
+
*
|
|
9
|
+
* Design mirrors the git-sync module singleton pattern (getLastGitSyncStatus):
|
|
10
|
+
* a module-level UpdateState is mutated by runUpdateCheck and read by
|
|
11
|
+
* getUpdateState. All network I/O is fully defensive — fetchLatestManifest and
|
|
12
|
+
* runUpdateCheck NEVER throw (a failed check must never crash the host).
|
|
13
|
+
*
|
|
14
|
+
* Node builtins only — uses the Node 20 global `fetch` (follows redirects),
|
|
15
|
+
* consistent with lib/discovery.ts. No new npm dependency.
|
|
16
|
+
*/
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
// Semver compare (plain MAJOR.MINOR.PATCH, no external dep)
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
/**
|
|
21
|
+
* Compare two dotted-integer version strings.
|
|
22
|
+
*
|
|
23
|
+
* Each segment is parsed with parseInt so a non-numeric suffix (e.g. "1.7.0-rc1")
|
|
24
|
+
* contributes only its leading integer. Missing segments count as 0, so
|
|
25
|
+
* "1.7" === "1.7.0". A malformed/empty string parses every segment as 0.
|
|
26
|
+
*
|
|
27
|
+
* @returns -1 if a < b, 0 if equal, 1 if a > b.
|
|
28
|
+
*/
|
|
29
|
+
export function compareSemver(a, b) {
|
|
30
|
+
const parse = (v) => String(v ?? '')
|
|
31
|
+
.split('.')
|
|
32
|
+
.map((seg) => {
|
|
33
|
+
const n = parseInt(seg, 10);
|
|
34
|
+
return Number.isNaN(n) ? 0 : n;
|
|
35
|
+
});
|
|
36
|
+
const pa = parse(a);
|
|
37
|
+
const pb = parse(b);
|
|
38
|
+
const len = Math.max(pa.length, pb.length);
|
|
39
|
+
for (let i = 0; i < len; i++) {
|
|
40
|
+
const x = pa[i] ?? 0;
|
|
41
|
+
const y = pb[i] ?? 0;
|
|
42
|
+
if (x < y)
|
|
43
|
+
return -1;
|
|
44
|
+
if (x > y)
|
|
45
|
+
return 1;
|
|
46
|
+
}
|
|
47
|
+
return 0;
|
|
48
|
+
}
|
|
49
|
+
let updateState = {
|
|
50
|
+
available: false,
|
|
51
|
+
latestVersion: null,
|
|
52
|
+
url: null,
|
|
53
|
+
sha256: null,
|
|
54
|
+
checkedAt: 0,
|
|
55
|
+
error: null,
|
|
56
|
+
};
|
|
57
|
+
/** Read the most recent update-check result (for GET /status). */
|
|
58
|
+
export function getUpdateState() {
|
|
59
|
+
return updateState;
|
|
60
|
+
}
|
|
61
|
+
/** Test-only: reset the module singleton back to its initial shape. */
|
|
62
|
+
export function __resetUpdateStateForTests() {
|
|
63
|
+
updateState = {
|
|
64
|
+
available: false,
|
|
65
|
+
latestVersion: null,
|
|
66
|
+
url: null,
|
|
67
|
+
sha256: null,
|
|
68
|
+
checkedAt: 0,
|
|
69
|
+
error: null,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
export const UPDATE_MANIFEST_URL = process.env.STIKFIX_UPDATE_MANIFEST_URL ||
|
|
73
|
+
'https://github.com/omernesh/stikfix/releases/latest/download/latest.json';
|
|
74
|
+
/**
|
|
75
|
+
* GET the latest-release manifest and validate its shape.
|
|
76
|
+
*
|
|
77
|
+
* Returns null on any non-2xx, network error, JSON-parse error, or an invalid
|
|
78
|
+
* shape (missing/blank version|url, or a sha256 that is not 64 hex chars).
|
|
79
|
+
* NEVER throws — the caller treats null as "no usable manifest this time".
|
|
80
|
+
*/
|
|
81
|
+
export async function fetchLatestManifest(fetchImpl = fetch, url = UPDATE_MANIFEST_URL) {
|
|
82
|
+
try {
|
|
83
|
+
const resp = await fetchImpl(url, { signal: AbortSignal.timeout(8000) });
|
|
84
|
+
if (!resp.ok)
|
|
85
|
+
return null;
|
|
86
|
+
const data = (await resp.json());
|
|
87
|
+
if (!data || typeof data !== 'object')
|
|
88
|
+
return null;
|
|
89
|
+
const { version, url: dlUrl, sha256 } = data;
|
|
90
|
+
if (typeof version !== 'string' || version.length === 0 ||
|
|
91
|
+
typeof dlUrl !== 'string' || dlUrl.length === 0 ||
|
|
92
|
+
typeof sha256 !== 'string' || !/^[0-9a-f]{64}$/i.test(sha256)) {
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
return { version, url: dlUrl, sha256 };
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
// ---------------------------------------------------------------------------
|
|
102
|
+
// runUpdateCheck — refresh the module state
|
|
103
|
+
// ---------------------------------------------------------------------------
|
|
104
|
+
/**
|
|
105
|
+
* Fetch the manifest, compare against the running version, and update the
|
|
106
|
+
* module-level UpdateState.
|
|
107
|
+
*
|
|
108
|
+
* On a failed fetch/parse: keep any prior successful result's
|
|
109
|
+
* available/latestVersion/url/sha256 fields, set error = 'update check failed',
|
|
110
|
+
* and do NOT bump checkedAt.
|
|
111
|
+
*
|
|
112
|
+
* On success: available = remote > current; latestVersion always set to the
|
|
113
|
+
* remote version; url/sha256 set only when an update is available; error null;
|
|
114
|
+
* checkedAt = now.
|
|
115
|
+
*
|
|
116
|
+
* Safe to call with no network (inject fetchImpl in tests). NEVER throws.
|
|
117
|
+
*/
|
|
118
|
+
export async function runUpdateCheck(currentVersion, fetchImpl = fetch) {
|
|
119
|
+
const manifest = await fetchLatestManifest(fetchImpl);
|
|
120
|
+
if (manifest === null) {
|
|
121
|
+
updateState = { ...updateState, error: 'update check failed' };
|
|
122
|
+
return updateState;
|
|
123
|
+
}
|
|
124
|
+
const avail = compareSemver(manifest.version, currentVersion) > 0;
|
|
125
|
+
updateState = {
|
|
126
|
+
available: avail,
|
|
127
|
+
latestVersion: manifest.version,
|
|
128
|
+
url: avail ? manifest.url : null,
|
|
129
|
+
sha256: avail ? manifest.sha256 : null,
|
|
130
|
+
checkedAt: Date.now(),
|
|
131
|
+
error: null,
|
|
132
|
+
};
|
|
133
|
+
return updateState;
|
|
134
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "stikfix",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.7.0",
|
|
4
4
|
"description": "Pin sticky notes on any page — your AI reads them.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Omer Nesher <omernesher@gmail.com>",
|
|
@@ -53,6 +53,7 @@
|
|
|
53
53
|
"check": "tsc --noEmit && tsc --noEmit -p tsconfig.host.json && node scripts/clean-room-check.mjs && node scripts/host-smoke-test.mjs && npm run test:lib && npm test",
|
|
54
54
|
"pack:crx": "node scripts/pack-crx.mjs",
|
|
55
55
|
"gen:update-xml": "node scripts/gen-update-xml.mjs",
|
|
56
|
+
"gen:latest-json": "node scripts/gen-latest-json.mjs",
|
|
56
57
|
"build:sea": "node scripts/build-sea.mjs",
|
|
57
58
|
"build:installer": "node scripts/build-installer.mjs",
|
|
58
59
|
"postinstall": "node -e \"try{require.resolve('wxt');require('node:child_process').execSync('wxt prepare',{stdio:'inherit'})}catch(e){}\""
|