@www.hyperlinks.space/program-kit 1.2.3

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.
Files changed (87) hide show
  1. package/README.md +53 -0
  2. package/api/ai.ts +111 -0
  3. package/api/base.ts +117 -0
  4. package/api/blockchain.ts +58 -0
  5. package/api/bot.ts +19 -0
  6. package/api/ping.ts +41 -0
  7. package/api/releases.ts +162 -0
  8. package/api/telegram.ts +65 -0
  9. package/api/tsconfig.json +17 -0
  10. package/app/_layout.tsx +135 -0
  11. package/app/ai.tsx +39 -0
  12. package/app/components/GlobalBottomBar.tsx +447 -0
  13. package/app/components/GlobalBottomBarWeb.tsx +362 -0
  14. package/app/components/GlobalLogoBar.tsx +108 -0
  15. package/app/components/GlobalLogoBarFallback.tsx +66 -0
  16. package/app/components/GlobalLogoBarWithFallback.tsx +24 -0
  17. package/app/components/HyperlinksSpaceLogo.tsx +29 -0
  18. package/app/components/Telegram.tsx +648 -0
  19. package/app/components/telegramWebApp.ts +359 -0
  20. package/app/fonts.ts +12 -0
  21. package/app/index.tsx +102 -0
  22. package/app/theme.ts +117 -0
  23. package/app.json +60 -0
  24. package/assets/icon.ico +0 -0
  25. package/assets/images/favicon.png +0 -0
  26. package/blockchain/coffee.ts +217 -0
  27. package/blockchain/router.ts +44 -0
  28. package/bot/format.ts +143 -0
  29. package/bot/grammy.ts +52 -0
  30. package/bot/responder.ts +620 -0
  31. package/bot/webhook.ts +262 -0
  32. package/database/messages.ts +128 -0
  33. package/database/start.ts +133 -0
  34. package/database/users.ts +46 -0
  35. package/docs/ai_and_search_bar_input.md +94 -0
  36. package/docs/ai_bot_messages.md +124 -0
  37. package/docs/backlogs/medium_term_backlog.md +26 -0
  38. package/docs/backlogs/short_term_backlog.md +39 -0
  39. package/docs/blue_bar_tackling.md +143 -0
  40. package/docs/bot_async_streaming.md +174 -0
  41. package/docs/build_and_install.md +129 -0
  42. package/docs/database_messages.md +34 -0
  43. package/docs/fonts.md +18 -0
  44. package/docs/releases.md +201 -0
  45. package/docs/releases_github_actions.md +188 -0
  46. package/docs/scalability.md +34 -0
  47. package/docs/security_plan_raw.md +244 -0
  48. package/docs/security_raw.md +345 -0
  49. package/docs/timing_raw.md +63 -0
  50. package/docs/tma_logo_bar_jump_investigation.md +69 -0
  51. package/docs/update.md +205 -0
  52. package/docs/wallets_hosting_architecture.md +257 -0
  53. package/eas.json +47 -0
  54. package/eslint.config.js +10 -0
  55. package/fullREADME.md +159 -0
  56. package/global.css +67 -0
  57. package/npmReadMe.md +53 -0
  58. package/package.json +214 -0
  59. package/scripts/load-env.ts +17 -0
  60. package/scripts/migrate-db.ts +16 -0
  61. package/scripts/program-kit-init.cjs +58 -0
  62. package/scripts/run-bot-local.ts +30 -0
  63. package/scripts/set-webhook.ts +67 -0
  64. package/scripts/test-api-base.ts +12 -0
  65. package/telegram/post.ts +328 -0
  66. package/tsconfig.json +17 -0
  67. package/vercel.json +7 -0
  68. package/windows/after-sign-windows-icon.cjs +13 -0
  69. package/windows/build-layout.cjs +72 -0
  70. package/windows/build-with-progress.cjs +88 -0
  71. package/windows/build.cjs +2247 -0
  72. package/windows/cleanup-legacy-appdata-installs.ps1 +91 -0
  73. package/windows/cleanup-legacy-windows-shortcuts.ps1 +46 -0
  74. package/windows/cleanup.cjs +200 -0
  75. package/windows/embed-windows-exe-icon.cjs +55 -0
  76. package/windows/extractAppPackage.nsh +150 -0
  77. package/windows/forge/README.md +41 -0
  78. package/windows/forge/forge.config.js +138 -0
  79. package/windows/forge/make-with-stamp.cjs +65 -0
  80. package/windows/forge-cleanup.cjs +255 -0
  81. package/windows/hsp-app-process.ps1 +63 -0
  82. package/windows/installer-hooks.nsi +373 -0
  83. package/windows/product-brand.cjs +42 -0
  84. package/windows/remove-orphan-uninstall-registry.ps1 +67 -0
  85. package/windows/run-installed-with-icon-debug.cmd +20 -0
  86. package/windows/run-win-electron-builder.cjs +46 -0
  87. package/windows/updater-dialog.html +143 -0
@@ -0,0 +1,373 @@
1
+ ; Installer hooks for debug-friendly installs:
2
+ ; - real-time DetailPrint + mirrored log file in %TEMP%
3
+ ; - finish page shows full log in selectable read-only text area
4
+ ;
5
+ ; InstFiles page: the one-line status above the list is separate from the details list. SetDetailsPrint
6
+ ; both duplicates DetailPrint into both; we use textonly for HspInstallStepStatus, then listonly so
7
+ ; HspInstallDetailPrint lines only appear in the list (not repeated on the status line).
8
+ ; Steps 1–N: see !define HSP_INSTALL_STEP_TOTAL and !insertmacro HspInstallStepStatus in HspInstFilesShow,
9
+ ; customCheckAppRunning, extractAppPackage (decompress), customInstall.
10
+ ;
11
+ ; HSP_INSTALLER_AUTO_FINISH — two finish-page setups (see commits 4f25a5c vs 160595ef):
12
+ ; • Defined → auto-dismiss wizard after install (4f25a5c: no MUI_FINISHPAGE_NOAUTOCLOSE, Finish
13
+ ; button, then WM_CLOSE / Quit in HspFinishPage*).
14
+ ; • Commented → installer stays open for logs (160595ef: MUI_FINISHPAGE_NOAUTOCLOSE, no forced close).
15
+ ; Uncomment the next line to enable auto-close:
16
+ !define HSP_INSTALLER_AUTO_FINISH
17
+
18
+ ; Extra exe name for older builds (do not use APP_EXECUTABLE_FILENAME here — not always defined by NSIS / CI).
19
+ ; Legacy main exe from older "Hyperlinks Space App" installs (taskkill / relaunch).
20
+ !define HSP_ALT_MAIN_EXE "Hyperlinks Space App.exe"
21
+ ; Hiding the bar: MUI2 only allows MUI_INSTFILESPAGE_PROGRESSBAR = "" | colored | smooth — "disable" is invalid and breaks InstProgressFlags (NSIS 3 CI). Hide msctls_progress32 at runtime in HspInstFilesShow instead.
22
+
23
+ !include "FileFunc.nsh"
24
+ !include "WinMessages.nsh"
25
+
26
+ !define HSP_INSTALL_STEP_TOTAL 6
27
+ ; Status line only (textonly). Keep in sync with step inserts in customCheckAppRunning, extractAppPackage, customInstall.
28
+ !macro HspInstallStepStatus STEP
29
+ !ifndef BUILD_UNINSTALLER
30
+ SetDetailsView show
31
+ SetDetailsPrint textonly
32
+ DetailPrint "Installation: Step ${STEP} of ${HSP_INSTALL_STEP_TOTAL}"
33
+ SetDetailsPrint listonly
34
+ !endif
35
+ !macroend
36
+
37
+ !ifdef BUILD_UNINSTALLER
38
+ !macro HspAppendInstallerLog TEXT
39
+ !macroend
40
+ !macro HspInstallDetailPrint MSG
41
+ !macroend
42
+ !endif
43
+
44
+ !ifndef BUILD_UNINSTALLER
45
+ Var HspLogFile
46
+ Var HspLogHandle
47
+ Var HspFinishLogEdit
48
+ Var HspDidLaunchApp
49
+
50
+ Function HspEnsureInstallerLogPath
51
+ StrCmp $HspLogFile "" hspSetLogPath hspLogPathDone
52
+ hspSetLogPath:
53
+ StrCpy $HspLogFile "$TEMP\HyperlinksSpaceInstall.log"
54
+ Delete "$HspLogFile"
55
+ hspLogPathDone:
56
+ FunctionEnd
57
+
58
+ !macro HspAppendInstallerLog TEXT
59
+ Call HspEnsureInstallerLogPath
60
+ ${GetTime} "" "L" $R0 $R1 $R2 $R3 $R4 $R5 $R6
61
+ StrCpy $R7 "[$R2-$R1-$R0 $R4:$R5:$R6] "
62
+ FileOpen $HspLogHandle "$HspLogFile" a
63
+ FileWrite $HspLogHandle $R7
64
+ FileWrite $HspLogHandle "${TEXT}"
65
+ FileWrite $HspLogHandle "$\r$\n"
66
+ FileClose $HspLogHandle
67
+ !macroend
68
+
69
+ !macro HspInstallDetailPrint MSG
70
+ SetDetailsView show
71
+ SetDetailsPrint listonly
72
+ DetailPrint "${MSG}"
73
+ !insertmacro HspAppendInstallerLog "${MSG}"
74
+ !macroend
75
+
76
+ Function .onInstSuccess
77
+ ; Primary launch trigger for all installer modes (including one-click/silent paths).
78
+ ; Use one-shot guard so Finish-page fallback does not launch twice.
79
+ StrCmp $HspDidLaunchApp "1" hspInstSuccessAfterLaunch
80
+ StrCpy $HspDidLaunchApp "1"
81
+ Sleep 500
82
+ Call HspLaunchInstalledApp
83
+ hspInstSuccessAfterLaunch:
84
+ !insertmacro HspAppendInstallerLog "INSTALL_SUCCESS"
85
+ FunctionEnd
86
+
87
+ Function HspLaunchInstalledApp
88
+ ; Keep Forge/electron-builder compatibility: avoid $launchLink (not always defined).
89
+ IfFileExists "$INSTDIR\current\${PRODUCT_FILENAME}.exe" hspLaunchCurrent hspLaunchLegacy
90
+ hspLaunchCurrent:
91
+ ExecShell "open" "$INSTDIR\current\${PRODUCT_FILENAME}.exe"
92
+ !insertmacro HspAppendInstallerLog "APP_LAUNCH_TRIGGERED(current)"
93
+ Return
94
+ hspLaunchLegacy:
95
+ IfFileExists "$INSTDIR\${PRODUCT_FILENAME}.exe" hspLaunchLegacyDo hspLaunchFailed
96
+ hspLaunchLegacyDo:
97
+ ExecShell "open" "$INSTDIR\${PRODUCT_FILENAME}.exe"
98
+ !insertmacro HspAppendInstallerLog "APP_LAUNCH_TRIGGERED"
99
+ Return
100
+ hspLaunchFailed:
101
+ !insertmacro HspAppendInstallerLog "APP_LAUNCH_FAILED"
102
+ FunctionEnd
103
+
104
+ Function .onInstFailed
105
+ !insertmacro HspAppendInstallerLog "INSTALL_FAILED"
106
+ FunctionEnd
107
+
108
+ Function HspInstFilesShow
109
+ !insertmacro HspInstallStepStatus 1
110
+ FindWindow $0 "#32770" "" $HWNDPARENT
111
+ FindWindow $1 "msctls_progress32" "" $0
112
+ IntCmp $1 0 hspInstFilesBarDone
113
+ ShowWindow $1 ${SW_HIDE}
114
+ hspInstFilesBarDone:
115
+ FunctionEnd
116
+
117
+ ; $0 = 1 if any known packaged exe is still running (see hsp-app-process.ps1 -Action Test).
118
+ Function HspResolvePowerShellExe
119
+ IfFileExists "$WINDIR\Sysnative\WindowsPowerShell\v1.0\powershell.exe" 0 hspPsSys32
120
+ StrCpy $R5 "$WINDIR\Sysnative\WindowsPowerShell\v1.0\powershell.exe"
121
+ Return
122
+ hspPsSys32:
123
+ StrCpy $R5 "$WINDIR\System32\WindowsPowerShell\v1.0\powershell.exe"
124
+ FunctionEnd
125
+
126
+ Function HspSetEnvInstDir
127
+ System::Call 'kernel32::SetEnvironmentVariable(t, t) ("HSP_INSTDIR", "$INSTDIR")'
128
+ FunctionEnd
129
+
130
+ Function HspClearEnvInstDir
131
+ System::Call 'kernel32::SetEnvironmentVariable(t, i) ("HSP_INSTDIR", 0)'
132
+ FunctionEnd
133
+
134
+ ; PowerShell: use nsExec::Exec (not ExecWait). ExecWait always attaches a console to powershell.exe,
135
+ ; which flashes on screen even with -WindowStyle Hidden; nsExec runs the process with no console window.
136
+ Function HspAnyPackagedExeRunning
137
+ Call HspResolvePowerShellExe
138
+ Call HspSetEnvInstDir
139
+ IfFileExists "$PLUGINSDIR\hsp-app-process.ps1" 0 hspAnyExeNoScript
140
+ nsExec::Exec `"$R5" -NoProfile -NonInteractive -ExecutionPolicy Bypass -File "$PLUGINSDIR\hsp-app-process.ps1" -Action Test`
141
+ Pop $R4
142
+ Call HspClearEnvInstDir
143
+ StrCmp $R4 "error" hspAnyExeNsFail
144
+ StrCmp $R4 "timeout" hspAnyExeNsFail
145
+ IntCmp $R4 0 hspAnyExeYes
146
+ StrCpy $0 0
147
+ Return
148
+ hspAnyExeNsFail:
149
+ StrCpy $0 0
150
+ Return
151
+ hspAnyExeNoScript:
152
+ Call HspClearEnvInstDir
153
+ StrCpy $0 0
154
+ Return
155
+ hspAnyExeYes:
156
+ StrCpy $0 1
157
+ Return
158
+ FunctionEnd
159
+
160
+ Function HspWaitUntilPackagedProcessesGone
161
+ StrCpy $R8 0
162
+ hspWaitPackagedPoll:
163
+ Call HspAnyPackagedExeRunning
164
+ IntCmp $0 0 hspWaitPackagedDone
165
+ IntOp $R8 $R8 + 1
166
+ IntCmp $R8 600 0 0 hspWaitPackagedDone
167
+ Sleep 50
168
+ Goto hspWaitPackagedPoll
169
+ hspWaitPackagedDone:
170
+ ; Extra settle after Test script reports processes gone (handles on DLLs).
171
+ Sleep 500
172
+ FunctionEnd
173
+
174
+ Function HspKillPackagedAppProcesses
175
+ Call HspResolvePowerShellExe
176
+ Call HspSetEnvInstDir
177
+ IfFileExists "$PLUGINSDIR\hsp-app-process.ps1" 0 hspKillNoScript
178
+ nsExec::Exec `"$R5" -NoProfile -NonInteractive -ExecutionPolicy Bypass -File "$PLUGINSDIR\hsp-app-process.ps1" -Action Kill`
179
+ Pop $R4
180
+ hspKillNoScript:
181
+ Call HspClearEnvInstDir
182
+ FunctionEnd
183
+
184
+ ; Called from windows/extractAppPackage.nsh before each CopyFiles (and each retry).
185
+ Function HspKillBeforeCopy
186
+ SetDetailsView show
187
+ SetDetailsPrint listonly
188
+ DetailPrint "[installer] unlock install dir before copy (attempt $R1)"
189
+ Call HspKillPackagedAppProcesses
190
+ Call HspWaitUntilPackagedProcessesGone
191
+ ; Second pass: elevation can miss user processes on first taskkill; retry once.
192
+ Call HspKillPackagedAppProcesses
193
+ Call HspWaitUntilPackagedProcessesGone
194
+ ; Clear read-only on existing tree (helps overwrite in Program Files).
195
+ IfFileExists "$INSTDIR" 0 hspKillBeforeCopyNoAttrib
196
+ nsExec::Exec `%COMSPEC% /c attrib -R "$INSTDIR\*.*" /S /D`
197
+ Pop $R9
198
+ hspKillBeforeCopyNoAttrib:
199
+ FunctionEnd
200
+
201
+ Function HspFinishPageShow
202
+ !ifndef HSP_INSTALLER_AUTO_FINISH
203
+ SetAutoClose false
204
+ !endif
205
+ Call HspEnsureInstallerLogPath
206
+ ; Launch app automatically when install reaches finish page; keep installer open for logs.
207
+ StrCmp $HspDidLaunchApp "1" hspSkipAutoLaunch
208
+ StrCpy $HspDidLaunchApp "1"
209
+ Call HspLaunchInstalledApp
210
+ hspSkipAutoLaunch:
211
+ StrCpy $HspFinishLogEdit ""
212
+ System::Call "user32::CreateWindowExW(i 0, w \"Edit\", w \"\", i 0x50201844, i 128, i 128, i 360, i 220, i $HWNDPARENT, i 0, i 0, i 0) i.r0"
213
+ IntCmp $0 0 hspFinishShowDone
214
+ StrCpy $HspFinishLogEdit $0
215
+ StrCpy $9 $0
216
+ System::Call "user32::SendMessageW(i r9, i 0xC5, i 16777216, i 0)"
217
+ IfFileExists "$HspLogFile" hspFinishFillFile
218
+ System::Call "user32::SetWindowTextW(i r9, w \"No installation log file was found.\")"
219
+ Goto hspFinishShowDone
220
+ hspFinishFillFile:
221
+ FileOpen $R0 "$HspLogFile" r
222
+ hspFinishReadLoop:
223
+ FileRead $R0 $1
224
+ IfErrors hspFinishFileDone
225
+ System::Call "user32::SendMessageW(i r9, i 0x000E, i 0, i 0) i.r4"
226
+ System::Call "user32::SendMessageW(i r9, i 0xB1, i r4, i r4)"
227
+ StrCpy $2 "$1$\r$\n"
228
+ System::Call "user32::SendMessageW(i r9, i 0xC2, i 1, w r2)"
229
+ Goto hspFinishReadLoop
230
+ hspFinishFileDone:
231
+ FileClose $R0
232
+ hspFinishShowDone:
233
+ !ifdef HSP_INSTALLER_AUTO_FINISH
234
+ ; Quit in SHOW alone is unreliable (runs before nsDialogs::Show message loop).
235
+ SendMessage $HWNDPARENT ${WM_CLOSE} 0 0
236
+ System::Call "user32::PostQuitMessage(i 0)"
237
+ !endif
238
+ FunctionEnd
239
+
240
+ Function HspFinishPageLeave
241
+ StrCmp $HspFinishLogEdit "" +3
242
+ StrCpy $0 $HspFinishLogEdit
243
+ System::Call "user32::DestroyWindow(i r0)"
244
+ StrCpy $HspFinishLogEdit ""
245
+ !ifdef HSP_INSTALLER_AUTO_FINISH
246
+ Quit
247
+ !endif
248
+ FunctionEnd
249
+ !endif
250
+
251
+ !macro customHeader
252
+ Caption "${PRODUCT_NAME}"
253
+ ShowInstDetails show
254
+ !ifdef BUILD_UNINSTALLER
255
+ ShowUninstDetails show
256
+ !endif
257
+ !macroend
258
+
259
+ !macro customPageAfterChangeDir
260
+ ShowInstDetails show
261
+ !define MUI_PAGE_CUSTOMFUNCTION_SHOW HspInstFilesShow
262
+ !macroend
263
+
264
+ !macro customInstallMode
265
+ ; Leave install mode to electron-builder (perMachine in package.json → Program Files).
266
+ ; Forcing $isForceCurrentInstall breaks INSTALL_MODE_PER_ALL_USERS_REQUIRED / per-machine builds.
267
+ !macroend
268
+
269
+ ; Installer only. Uninstaller defines BUILD_UNINSTALLER — Call must use un.* there; use stock _CHECK_APP_RUNNING.
270
+ !ifndef BUILD_UNINSTALLER
271
+ !macro customCheckAppRunning
272
+ !insertmacro HspInstallDetailPrint "[installer] stop running app processes (tree kill + wait, all exe names)"
273
+ Call HspKillPackagedAppProcesses
274
+ Call HspWaitUntilPackagedProcessesGone
275
+ !insertmacro HspInstallStepStatus 2
276
+ !macroend
277
+ !endif
278
+
279
+ !macro customInit
280
+ !insertmacro HspInstallDetailPrint "[installer] customInit start"
281
+ ; Remove stale Start Menu / Desktop shortcuts from older "Hyperlinks Space App" installs (new product: ${PRODUCT_NAME}).
282
+ Delete "$SMPROGRAMS\Hyperlinks Space App.lnk"
283
+ Delete "$COMMONPROGRAMS\Hyperlinks Space App.lnk"
284
+ Delete "$DESKTOP\Hyperlinks Space App.lnk"
285
+ Delete "$COMMONDESKTOP\Hyperlinks Space App.lnk"
286
+ RMDir /r /REBOOTOK "$SMPROGRAMS\Hyperlinks Space App"
287
+ RMDir /r /REBOOTOK "$COMMONPROGRAMS\Hyperlinks Space App"
288
+ ; Old per-user installs (AppData\Local\Programs\...) when current build uses perMachine → Program Files.
289
+ ReadEnvStr $R8 "LOCALAPPDATA"
290
+ RMDir /r /REBOOTOK "$R8\Programs\Hyperlinks Space App"
291
+ RMDir /r /REBOOTOK "$R8\Programs\HyperlinksSpaceApp"
292
+ !insertmacro HspInstallDetailPrint "[installer] removed legacy Hyperlinks Space App shortcuts (if present)"
293
+ ; Do NOT delete UninstallString / QuietUninstallString here — that breaks Windows Settings "Uninstall"
294
+ ; for this product and can leave a grayed-out Apps entry with no uninstall command.
295
+ !insertmacro HspInstallDetailPrint "[installer] customInit complete"
296
+ !macroend
297
+
298
+ !macro customInstall
299
+ !insertmacro HspInstallStepStatus 5
300
+ !insertmacro HspInstallDetailPrint "[installer] customInstall start"
301
+ !insertmacro HspInstallDetailPrint "[installer] files copied, waiting for Finish page"
302
+ ; Trigger launch as soon as install work is complete.
303
+ StrCmp $HspDidLaunchApp "1" hspCustomInstallAfterLaunch
304
+ StrCpy $HspDidLaunchApp "1"
305
+ Call HspLaunchInstalledApp
306
+ hspCustomInstallAfterLaunch:
307
+ !insertmacro HspInstallDetailPrint "[installer] customInstall complete"
308
+ !insertmacro HspInstallStepStatus 6
309
+ !macroend
310
+
311
+ !macro customFinishPage
312
+ !ifndef BUILD_UNINSTALLER
313
+ !ifdef HSP_INSTALLER_AUTO_FINISH
314
+ ; 4f25a5c: omit NOAUTOCLOSE (single-step to Finish; then we force-close in HspFinishPageShow/Leave).
315
+ !define MUI_FINISHPAGE_BUTTON "Finish"
316
+ !define MUI_PAGE_CUSTOMFUNCTION_SHOW HspFinishPageShow
317
+ !define MUI_PAGE_CUSTOMFUNCTION_LEAVE HspFinishPageLeave
318
+ !insertmacro MUI_PAGE_FINISH
319
+ !else
320
+ ; 160595ef: NOAUTOCLOSE keeps the wizard open until the user is done (log copy / manual close).
321
+ !define MUI_FINISHPAGE_NOAUTOCLOSE
322
+ !define MUI_PAGE_CUSTOMFUNCTION_SHOW HspFinishPageShow
323
+ !define MUI_PAGE_CUSTOMFUNCTION_LEAVE HspFinishPageLeave
324
+ !insertmacro MUI_PAGE_FINISH
325
+ !endif
326
+ !endif
327
+ !macroend
328
+
329
+ !macro customUnInstall
330
+ !insertmacro HspAppendInstallerLog "[uninstaller] start"
331
+ ; Per-machine installs only remove SHELL_CONTEXT=HKLM keys. Legacy per-user installs also wrote
332
+ ; HKCU Uninstall + Software\{APP_GUID}; that leaves a duplicate "Installed apps" row. Clean HKCU when
333
+ ; uninstalling for all users. (Do not remove HKCU InstallLocation when uninstalling per-user —
334
+ ; InstFiles still reads MenuDirectory from HKCU in that mode.)
335
+ DetailPrint "[uninstaller] legacy HKCU registry cleanup (if any)"
336
+ DeleteRegKey HKCU "${UNINSTALL_REGISTRY_KEY}"
337
+ !ifdef UNINSTALL_REGISTRY_KEY_2
338
+ DeleteRegKey HKCU "${UNINSTALL_REGISTRY_KEY_2}"
339
+ !endif
340
+ ${if} $installMode == "all"
341
+ DeleteRegKey HKCU "${INSTALL_REGISTRY_KEY}"
342
+ ${endif}
343
+
344
+ ; Defensive cleanup for older 32-bit-view uninstall entries (WOW6432Node) that can remain visible
345
+ ; in Windows "Installed apps" if an old uninstall executable was removed manually.
346
+ DetailPrint "[uninstaller] legacy WOW6432Node uninstall key cleanup (if any)"
347
+ DeleteRegKey HKLM "Software\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\${UNINSTALL_APP_KEY}"
348
+ DeleteRegKey HKLM "Software\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\${APP_GUID}"
349
+
350
+ ; Remove legacy all-users install folders only (do not target current PRODUCT folder here).
351
+ ; Try immediate delete first; schedule reboot cleanup only if path is locked.
352
+ ClearErrors
353
+ RMDir /r "$PROGRAMFILES\Hyperlinks Space App"
354
+ IfErrors 0 +2
355
+ RMDir /r /REBOOTOK "$PROGRAMFILES\Hyperlinks Space App"
356
+
357
+ ClearErrors
358
+ RMDir /r "$PROGRAMFILES64\Hyperlinks Space App"
359
+ IfErrors 0 +2
360
+ RMDir /r /REBOOTOK "$PROGRAMFILES64\Hyperlinks Space App"
361
+
362
+ !insertmacro HspAppendInstallerLog "[uninstaller] complete"
363
+ !macroend
364
+
365
+ ; Runs before Section install (electron-builder prepends this include). Drops helper script for nsExec::Exec -File.
366
+ ; Use BUILD_RESOURCES_DIR so makensis finds the script when cwd is the NSIS cache dir (Forge/CI).
367
+ !ifndef BUILD_UNINSTALLER
368
+ Section "-hsp_app_process_ps1"
369
+ InitPluginsDir
370
+ SetOutPath $PLUGINSDIR
371
+ File "${BUILD_RESOURCES_DIR}\hsp-app-process.ps1"
372
+ SectionEnd
373
+ !endif
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Single source of truth for Windows/Electron product naming.
3
+ * Display name and artifact slug come from app/package.json → build.productName.
4
+ */
5
+ const fs = require("fs");
6
+ const path = require("path");
7
+
8
+ const pkgPath = path.join(__dirname, "..", "package.json");
9
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
10
+ const productDisplayName = pkg.build?.productName ?? "Hyperlinks Space Program";
11
+ /** No spaces; matches electron-builder artifactName patterns (e.g. HyperlinksSpaceProgram_1.0.0.zip). */
12
+ const productSlug = productDisplayName.replace(/\s+/g, "");
13
+
14
+ /** Previous public name — keep for updater zip matching, staged layouts, and NSIS taskkill of old installs. */
15
+ const legacyDisplayNames = ["Hyperlinks Space App"];
16
+ const legacySlugs = ["HyperlinksSpaceApp"];
17
+
18
+ function allKnownExeBaseNames() {
19
+ const set = new Set();
20
+ set.add(`${productDisplayName}.exe`);
21
+ set.add(`${productSlug}.exe`);
22
+ for (const d of legacyDisplayNames) set.add(`${d}.exe`);
23
+ for (const s of legacySlugs) set.add(`${s}.exe`);
24
+ return [...set];
25
+ }
26
+
27
+ /** Regex: release zip assets starting with current or legacy slug (e.g. HyperlinksSpaceProgram_ or HyperlinksSpaceApp_). */
28
+ function portableZipAssetPattern() {
29
+ const slugs = [productSlug, ...legacySlugs].join("|");
30
+ return new RegExp(`^(?:${slugs})[_-]`, "i");
31
+ }
32
+
33
+ module.exports = {
34
+ productDisplayName,
35
+ productSlug,
36
+ /** Same as build.win.artifactName portable zip prefix `${slug}_`. */
37
+ portableZipPrefix: `${productSlug}_`,
38
+ legacyDisplayNames,
39
+ legacySlugs,
40
+ allKnownExeBaseNames,
41
+ portableZipAssetPattern,
42
+ };
@@ -0,0 +1,67 @@
1
+ # Remove broken "Hyperlinks Space App" entries from Uninstall registry so Settings no longer shows a ghost app
2
+ # with grayed-out Uninstall (usually UninstallString missing or points to deleted files).
3
+ #
4
+ # Run from elevated PowerShell if HKLM keys need removal: Right-click PowerShell -> Run as administrator
5
+ #
6
+ # Usage:
7
+ # powershell -ExecutionPolicy Bypass -File .\windows\remove-orphan-uninstall-registry.ps1 # list only
8
+ # powershell -ExecutionPolicy Bypass -File .\windows\remove-orphan-uninstall-registry.ps1 -Remove # delete matching keys
9
+ #
10
+ param(
11
+ [switch]$Remove
12
+ )
13
+
14
+ $ErrorActionPreference = "Continue"
15
+ $matchDisplay = "Hyperlinks Space App"
16
+
17
+ $roots = @(
18
+ "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
19
+ "HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
20
+ "HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
21
+ )
22
+
23
+ $found = @()
24
+ foreach ($root in $roots) {
25
+ if (-not (Test-Path -LiteralPath $root)) { continue }
26
+ Get-ChildItem -LiteralPath $root -ErrorAction SilentlyContinue | ForEach-Object {
27
+ try {
28
+ $p = Get-ItemProperty -LiteralPath $_.PSPath -ErrorAction Stop
29
+ $name = $p.DisplayName
30
+ if (-not $name) { return }
31
+ if ($name -notlike "*$matchDisplay*") { return }
32
+ $un = $p.UninstallString
33
+ $quiet = $p.QuietUninstallString
34
+ $found += [pscustomobject]@{
35
+ PsPath = $_.PSPath
36
+ DisplayName = $name
37
+ UninstallString = $un
38
+ QuietUninstall = $quiet
39
+ }
40
+ } catch {}
41
+ }
42
+ }
43
+
44
+ if ($found.Count -eq 0) {
45
+ Write-Host "No Uninstall registry entries with DisplayName matching '$matchDisplay' were found."
46
+ exit 0
47
+ }
48
+
49
+ Write-Host "Matching registry keys:"
50
+ $found | Select-Object DisplayName, UninstallString, QuietUninstall, PsPath | Format-List
51
+
52
+ if (-not $Remove) {
53
+ Write-Host ""
54
+ Write-Host "To remove these keys (so the ghost app disappears from Settings), re-run with -Remove"
55
+ exit 0
56
+ }
57
+
58
+ foreach ($row in $found) {
59
+ try {
60
+ Remove-Item -LiteralPath $row.PsPath -Recurse -Force
61
+ Write-Host "Removed: $($row.PsPath)"
62
+ } catch {
63
+ Write-Warning "Could not remove $($row.PsPath) (try Administrator PowerShell for HKLM): $_"
64
+ }
65
+ }
66
+
67
+ Write-Host "Done. If Settings still shows the app, restart it or sign out and back in."
@@ -0,0 +1,20 @@
1
+ @echo off
2
+ setlocal EnableExtensions
3
+ rem Launches the per-machine NSIS install with HSP_DEBUG_ICON=1 so main.log gets [icon:debug] lines.
4
+ rem Log file: %%APPDATA%%\expo-template-default\main.log (see package.json "name").
5
+ set "HSP_DEBUG_ICON=1"
6
+ set "ROOT=%ProgramFiles%\Hyperlinks Space Program"
7
+ if not exist "%ROOT%\" (
8
+ echo [run-installed-with-icon-debug] Not found: "%ROOT%"
9
+ exit /b 1
10
+ )
11
+ for %%N in ("Hyperlinks Space Program.exe" "Hyperlinks-Space-Program.exe" "HyperlinksSpaceProgram.exe") do (
12
+ if exist "%ROOT%\%%~N" (
13
+ echo [run-installed-with-icon-debug] Starting "%ROOT%\%%~N"
14
+ start "" "%ROOT%\%%~N"
15
+ exit /b 0
16
+ )
17
+ )
18
+ echo [run-installed-with-icon-debug] No known exe in "%ROOT%":
19
+ dir /b "%ROOT%\*.exe" 2>nul
20
+ exit /b 1
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Runs electron-builder --win with BUILD_STAMP, per-build output under releases/builder/<id>/eb-output/,
3
+ * then cleanup.cjs in the same process env (so HSP_EB_OUTPUT is not lost between shells).
4
+ */
5
+ const { spawnSync } = require("child_process");
6
+ const path = require("path");
7
+ const { RELEASE_BUILD_DEV_DIRNAME, resolveBuildLayout, ensureCleanEbOutput } = require("./build-layout.cjs");
8
+
9
+ const appDir = path.join(__dirname, "..");
10
+ const ebCli = require.resolve("electron-builder/cli.js");
11
+
12
+ function relForConfig(p) {
13
+ return path.relative(appDir, p).split(path.sep).join("/");
14
+ }
15
+
16
+ const layout = resolveBuildLayout(appDir);
17
+ ensureCleanEbOutput(layout.ebOutputDir);
18
+
19
+ const outArg = `--config.directories.output=${relForConfig(layout.ebOutputDir)}`;
20
+ const env = {
21
+ ...process.env,
22
+ BUILD_STAMP: layout.buildStamp,
23
+ RELEASE_BUILD_ID: layout.buildName,
24
+ HSP_EB_OUTPUT: relForConfig(layout.ebOutputDir),
25
+ };
26
+
27
+ console.log(`[win-eb] build=${layout.buildName} stamp=${layout.buildStamp}`);
28
+ console.log(`[win-eb] staging → ${relForConfig(layout.ebOutputDir)} (removed after cleanup)`);
29
+ console.log(`[win-eb] final → releases/builder/${layout.buildName}/<installer>.exe + ${RELEASE_BUILD_DEV_DIRNAME}/`);
30
+
31
+ const r = spawnSync(process.execPath, [ebCli, "--win", "--publish", "never", outArg], {
32
+ cwd: appDir,
33
+ env,
34
+ stdio: "inherit",
35
+ });
36
+
37
+ if (r.status !== 0) {
38
+ process.exit(r.status === null ? 1 : r.status);
39
+ }
40
+
41
+ const c = spawnSync(process.execPath, [path.join(__dirname, "cleanup.cjs")], {
42
+ cwd: appDir,
43
+ env,
44
+ stdio: "inherit",
45
+ });
46
+ process.exit(c.status === null ? 1 : c.status);
@@ -0,0 +1,143 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <title>Updater</title>
6
+ <style>
7
+ #log {
8
+ scrollbar-width: thin;
9
+ scrollbar-color: #3d3d3d #141414;
10
+ }
11
+ #log::-webkit-scrollbar {
12
+ width: 8px;
13
+ height: 8px;
14
+ }
15
+ #log::-webkit-scrollbar-track {
16
+ background: #141414;
17
+ border-radius: 4px;
18
+ }
19
+ #log::-webkit-scrollbar-thumb {
20
+ background: #3d3d3d;
21
+ border-radius: 4px;
22
+ border: 1px solid #1f1f1f;
23
+ }
24
+ #log::-webkit-scrollbar-thumb:hover {
25
+ background: #505050;
26
+ }
27
+ #log::-webkit-scrollbar-corner {
28
+ background: #0d0d0d;
29
+ }
30
+ #install:disabled {
31
+ opacity: 0.45;
32
+ cursor: not-allowed;
33
+ }
34
+ </style>
35
+ </head>
36
+ <body
37
+ style="
38
+ font-family: Segoe UI, Arial, sans-serif;
39
+ box-sizing: border-box;
40
+ padding: 12px 14px 10px;
41
+ background: #111;
42
+ color: #eee;
43
+ margin: 0;
44
+ "
45
+ >
46
+ <div id="cv" style="font-size: 12px; color: #aaa; margin-bottom: 6px"></div>
47
+ <div id="t" style="font-size: 14px; margin-bottom: 8px; line-height: 1.35">Checking for updates...</div>
48
+ <div id="progressWrap" style="display: none; margin-bottom: 8px">
49
+ <div style="height: 14px; background: #333; border-radius: 7px; overflow: hidden">
50
+ <div
51
+ id="b"
52
+ style="height: 100%; width: 0%; min-width: 0; background: #2ea043; transition: width 0.12s ease-out"
53
+ ></div>
54
+ </div>
55
+ </div>
56
+ <div id="logWrap" style="margin-bottom: 8px">
57
+ <div style="font-size: 10px; color: #888; margin-bottom: 4px">Activity log</div>
58
+ <pre
59
+ id="log"
60
+ style="
61
+ margin: 0;
62
+ max-height: 90px;
63
+ overflow-y: auto;
64
+ overflow-x: auto;
65
+ background: #0d0d0d;
66
+ border: 1px solid #333;
67
+ border-radius: 4px;
68
+ padding: 6px 8px;
69
+ font: 11px/1.35 Consolas, 'Cascadia Mono', monospace;
70
+ color: #c9d1d9;
71
+ white-space: pre-wrap;
72
+ word-break: break-all;
73
+ "
74
+ ></pre>
75
+ </div>
76
+ <div id="actionsWrap" style="display: none; flex-direction: row; justify-content: flex-end">
77
+ <button id="install" disabled style="padding: 5px 10px">Update with reload</button>
78
+ </div>
79
+ <script>
80
+ (function () {
81
+ try {
82
+ const { ipcRenderer } = require("electron");
83
+ function applyUpdaterUi(data) {
84
+ const t = document.getElementById("t");
85
+ const progressWrap = document.getElementById("progressWrap");
86
+ const actionsWrap = document.getElementById("actionsWrap");
87
+ const b = document.getElementById("b");
88
+ const i = document.getElementById("install");
89
+ if (t) t.textContent = data.text;
90
+ if (progressWrap) progressWrap.style.display = data.showProgress ? "block" : "none";
91
+ if (actionsWrap) actionsWrap.style.display = data.showActions ? "flex" : "none";
92
+ if (b) {
93
+ const w = Math.max(0, Math.min(100, Math.round(Number(data.percent) || 0)));
94
+ b.style.width = w + "%";
95
+ b.style.minWidth = w > 0 ? "2px" : "0";
96
+ }
97
+ if (i) i.disabled = !data.installEnabled;
98
+ }
99
+ function appendLogLine(line) {
100
+ const el = document.getElementById("log");
101
+ if (!el) return;
102
+ el.textContent += (el.textContent ? "\n" : "") + line;
103
+ const parts = el.textContent.split("\n");
104
+ if (parts.length > 220) el.textContent = parts.slice(-220).join("\n");
105
+ el.scrollTop = el.scrollHeight;
106
+ }
107
+ ipcRenderer.on("updater-ui", (_e, data) => applyUpdaterUi(data));
108
+ ipcRenderer.on("updater-log-init", (_e, lines) => {
109
+ const el = document.getElementById("log");
110
+ if (!el) return;
111
+ el.textContent = Array.isArray(lines) ? lines.join("\n") : "";
112
+ el.scrollTop = el.scrollHeight;
113
+ });
114
+ ipcRenderer.on("updater-log", (_e, line) =>
115
+ appendLogLine(typeof line === "string" ? line : String(line)),
116
+ );
117
+ const installBtn = document.getElementById("install");
118
+ if (installBtn) {
119
+ installBtn.addEventListener("click", async () => {
120
+ appendLogLine("[ui] Update with reload clicked — sending to main process…");
121
+ try {
122
+ const r = await ipcRenderer.invoke("updater-install-now");
123
+ if (r && r.ok === false) {
124
+ appendLogLine("[ui] Main refused: " + (r.err || "unknown"));
125
+ }
126
+ } catch (e) {
127
+ const msg = e && e.message ? e.message : String(e);
128
+ appendLogLine("[ui] IPC invoke failed: " + msg + " (trying legacy send)");
129
+ ipcRenderer.send("updater-install-click");
130
+ }
131
+ });
132
+ } else {
133
+ ipcRenderer.send("updater-renderer-error", "install button #install missing");
134
+ }
135
+ } catch (err) {
136
+ try {
137
+ require("electron").ipcRenderer.send("updater-renderer-error", String(err && err.message ? err.message : err));
138
+ } catch (_) {}
139
+ }
140
+ })();
141
+ </script>
142
+ </body>
143
+ </html>