@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,91 @@
1
+ # Remove leftover per-user installs under %LOCALAPPDATA%\Programs\ and updater sidecars.
2
+ # Older builds often installed to AppData while current NSIS uses perMachine -> Program Files.
3
+ #
4
+ # Usage (from app/):
5
+ # powershell -ExecutionPolicy Bypass -File .\windows\cleanup-legacy-appdata-installs.ps1 # list only
6
+ # powershell -ExecutionPolicy Bypass -File .\windows\cleanup-legacy-appdata-installs.ps1 -Force # delete
7
+ # If removal fails with "in use", close the app or run with -KillAppProcesses (stops exes under those folders).
8
+ #
9
+ param(
10
+ [switch]$Force,
11
+ [switch]$KillAppProcesses
12
+ )
13
+
14
+ $ErrorActionPreference = "Continue"
15
+
16
+ $legacyDirPatterns = @(
17
+ "Hyperlinks Space App"
18
+ "HyperlinksSpaceApp"
19
+ "hyperlinks-space-app"
20
+ "HyperlinksSpaceApp*"
21
+ )
22
+
23
+ function Test-LegacyDirName([string]$name) {
24
+ foreach ($p in $legacyDirPatterns) {
25
+ if ($p.EndsWith("*")) {
26
+ $prefix = $p.TrimEnd("*")
27
+ if ($name.StartsWith($prefix)) { return $true }
28
+ } elseif ($name -eq $p) { return $true }
29
+ }
30
+ return $false
31
+ }
32
+
33
+ $candidates = @()
34
+ $programsRoot = "$env:LOCALAPPDATA\Programs"
35
+ if (Test-Path -LiteralPath $programsRoot) {
36
+ Get-ChildItem -LiteralPath $programsRoot -Directory -ErrorAction SilentlyContinue | ForEach-Object {
37
+ if (Test-LegacyDirName $_.Name) { $candidates += $_.FullName }
38
+ }
39
+ }
40
+
41
+ $extra = @(
42
+ "$env:LOCALAPPDATA\expo-template-default-updater"
43
+ "$env:APPDATA\expo-template-default-updater"
44
+ ) | Where-Object { Test-Path -LiteralPath $_ }
45
+
46
+ if ($candidates.Count -eq 0 -and $extra.Count -eq 0) {
47
+ Write-Host "No legacy per-user folders under LOCALAPPDATA\Programs (or updater sidecars) found."
48
+ exit 0
49
+ }
50
+
51
+ Write-Host "Legacy / AppData artifacts:"
52
+ $candidates | ForEach-Object { Write-Host " [dir] $_" }
53
+ $extra | ForEach-Object { Write-Host " [extra] $_" }
54
+
55
+ if (-not $Force) {
56
+ Write-Host ""
57
+ Write-Host "Re-run with -Force to delete these paths."
58
+ exit 0
59
+ }
60
+
61
+ if ($KillAppProcesses -and $candidates.Count -gt 0) {
62
+ foreach ($dir in $candidates) {
63
+ $prefix = ($dir.TrimEnd("\")) + "\"
64
+ Get-Process -ErrorAction SilentlyContinue | Where-Object {
65
+ $_.Path -and $_.Path.StartsWith($prefix, [System.StringComparison]::OrdinalIgnoreCase)
66
+ } | ForEach-Object {
67
+ Write-Host "Stopping process: $($_.Name) ($($_.Path))"
68
+ Stop-Process -Id $_.Id -Force -ErrorAction SilentlyContinue
69
+ }
70
+ }
71
+ Start-Sleep -Milliseconds 500
72
+ }
73
+
74
+ foreach ($p in $candidates) {
75
+ try {
76
+ Remove-Item -LiteralPath $p -Recurse -Force -ErrorAction Stop
77
+ Write-Host "Removed: $p"
78
+ } catch {
79
+ Write-Warning "Failed to remove $p : $_"
80
+ }
81
+ }
82
+ foreach ($p in $extra) {
83
+ try {
84
+ Remove-Item -LiteralPath $p -Recurse -Force -ErrorAction Stop
85
+ Write-Host "Removed: $p"
86
+ } catch {
87
+ Write-Warning "Failed to remove $p : $_"
88
+ }
89
+ }
90
+
91
+ Write-Host "Done."
@@ -0,0 +1,46 @@
1
+ # One-time cleanup: orphaned "Hyperlinks Space App" shortcuts (old product name; current is "Hyperlinks Space Program").
2
+ # Run in PowerShell: powershell -ExecutionPolicy Bypass -File windows/cleanup-legacy-windows-shortcuts.ps1
3
+ # From repo app/: powershell -ExecutionPolicy Bypass -File .\windows\cleanup-legacy-windows-shortcuts.ps1
4
+
5
+ $ErrorActionPreference = "Continue"
6
+ $legacy = "Hyperlinks Space App"
7
+
8
+ $roots = @(
9
+ "$env:APPDATA\Microsoft\Windows\Start Menu\Programs",
10
+ "$env:ProgramData\Microsoft\Windows\Start Menu\Programs",
11
+ [Environment]::GetFolderPath("Desktop"),
12
+ "$env:PUBLIC\Desktop"
13
+ ) | Where-Object { $_ -and (Test-Path $_) }
14
+
15
+ $removed = @()
16
+ foreach ($root in $roots) {
17
+ Get-ChildItem -LiteralPath $root -Recurse -File -ErrorAction SilentlyContinue |
18
+ Where-Object {
19
+ $_.Extension -eq ".lnk" -and (
20
+ $_.Name -like "*$legacy*" -or
21
+ $_.DirectoryName -like "*$legacy*"
22
+ )
23
+ } |
24
+ ForEach-Object {
25
+ try {
26
+ Remove-Item -LiteralPath $_.FullName -Force
27
+ $removed += $_.FullName
28
+ } catch {
29
+ Write-Warning "Could not remove $($_.FullName): $_"
30
+ }
31
+ }
32
+ }
33
+
34
+ if ($removed.Count -eq 0) {
35
+ Write-Host "No legacy shortcuts matching '$legacy' were found under Start Menu / Desktop."
36
+ } else {
37
+ Write-Host "Removed $($removed.Count) item(s):"
38
+ $removed | ForEach-Object { Write-Host " $_" }
39
+ }
40
+
41
+ Write-Host ""
42
+ Write-Host "If 'Hyperlinks Space App' still appears under Settings -> Apps, uninstall it there,"
43
+ Write-Host "or run the current installer once (it also deletes legacy shortcuts in customInit)."
44
+ Write-Host ""
45
+ Write-Host "Old per-user installs may still exist under %LOCALAPPDATA%\Programs\. Run:"
46
+ Write-Host " powershell -ExecutionPolicy Bypass -File .\windows\cleanup-legacy-appdata-installs.ps1 -Force"
@@ -0,0 +1,200 @@
1
+ /**
2
+ * Final layout under releases/builder/<build_id>/:
3
+ * <productSlug>Installer_<stamp>.exe (root — only distributable at top level)
4
+ * dev/ (all other build artifacts)
5
+ * Staging (eb-output or releases/artifacts) is removed after this script runs.
6
+ */
7
+ const fs = require("fs");
8
+ const path = require("path");
9
+ const crypto = require("crypto");
10
+ const { RELEASE_BUILD_DEV_DIRNAME } = require("./build-layout.cjs");
11
+ const { productSlug } = require("./product-brand.cjs");
12
+
13
+ const appDir = path.join(__dirname, "..");
14
+ const releasesDir = path.join(appDir, "releases");
15
+ const legacyReleaseDir = path.join(appDir, "release");
16
+ /** Per-build electron-builder staging (set by run-win-electron-builder / build-with-progress); else legacy global. */
17
+ const artifactsDir = process.env.HSP_EB_OUTPUT?.trim()
18
+ ? path.resolve(appDir, process.env.HSP_EB_OUTPUT.trim())
19
+ : path.join(releasesDir, "artifacts");
20
+
21
+ // build_MMDDYYYY_HHMM — optional override for CI (must match build_* pattern)
22
+ const now = new Date();
23
+ const pad = (n) => String(n).padStart(2, "0");
24
+ const envBuildId = process.env.RELEASE_BUILD_ID?.trim();
25
+ const defaultBuildName =
26
+ "build_" +
27
+ pad(now.getMonth() + 1) +
28
+ pad(now.getDate()) +
29
+ now.getFullYear() +
30
+ "_" +
31
+ pad(now.getHours()) +
32
+ pad(now.getMinutes());
33
+ const buildName =
34
+ envBuildId && /^build_\d{8}_\d{4}$/.test(envBuildId) ? envBuildId : defaultBuildName;
35
+
36
+ // releases/builder/build_MMDDYYYY_HHMM — installer only at root; everything else under dev/
37
+ const buildDir = path.join(releasesDir, "builder", buildName);
38
+ const devDir = path.join(buildDir, RELEASE_BUILD_DEV_DIRNAME);
39
+
40
+ /** Required for electron-updater (GitHub) — must be uploaded next to the installer on each release. */
41
+ const latestYmlName = "latest.yml";
42
+ const zipLatestYmlName = "zip-latest.yml";
43
+ const devArtifacts = [
44
+ "win-unpacked",
45
+ "builder-debug.yml",
46
+ "builder-effective-config.yaml",
47
+ ];
48
+
49
+ function pickInstallerName() {
50
+ const files = fs.readdirSync(artifactsDir);
51
+ const installerRe = new RegExp(
52
+ `^${productSlug}Installer(?:_\\d{8}_\\d{4})?\\.exe$`,
53
+ "i",
54
+ );
55
+ const candidates = files.filter((f) => installerRe.test(f));
56
+ if (candidates.length === 0) return null;
57
+ // Prefer timestamped file if both legacy/static and stamped files exist.
58
+ candidates.sort((a, b) => b.length - a.length || a.localeCompare(b));
59
+ return candidates[0];
60
+ }
61
+
62
+ function pickZipName() {
63
+ const files = fs.readdirSync(artifactsDir);
64
+ const zipRe = new RegExp(`^${productSlug}_[\\d.]+\\.zip$`, "i");
65
+ const candidates = files.filter((f) => zipRe.test(f));
66
+ if (candidates.length === 0) return null;
67
+ candidates.sort((a, b) => b.localeCompare(a));
68
+ return candidates[0];
69
+ }
70
+
71
+ function moveIfExists(src, dest) {
72
+ if (!fs.existsSync(src)) return false;
73
+ try {
74
+ fs.renameSync(src, dest);
75
+ console.log("Moved:", path.relative(artifactsDir, src));
76
+ return true;
77
+ } catch (e) {
78
+ try {
79
+ const stat = fs.statSync(src);
80
+ if (stat.isDirectory()) {
81
+ fs.cpSync(src, dest, { recursive: true });
82
+ fs.rmSync(src, { recursive: true });
83
+ } else {
84
+ fs.copyFileSync(src, dest);
85
+ fs.unlinkSync(src);
86
+ }
87
+ console.log("Moved (copy+delete):", path.relative(artifactsDir, src));
88
+ return true;
89
+ } catch (e2) {
90
+ console.warn("Could not move", src, e2.message);
91
+ return false;
92
+ }
93
+ }
94
+ }
95
+
96
+ /** Anything electron-builder left behind (zip blockmaps, nsis temps, etc.) → dev/ */
97
+ function moveRemainingStagingToDev() {
98
+ if (!fs.existsSync(artifactsDir)) return;
99
+ let names;
100
+ try {
101
+ names = fs.readdirSync(artifactsDir);
102
+ } catch (_) {
103
+ return;
104
+ }
105
+ for (const name of names) {
106
+ const src = path.join(artifactsDir, name);
107
+ const dest = path.join(devDir, name);
108
+ if (!fs.existsSync(src)) continue;
109
+ try {
110
+ if (fs.existsSync(dest)) {
111
+ fs.rmSync(dest, { recursive: true, force: true });
112
+ }
113
+ } catch (e) {
114
+ console.warn("Could not clear dev target", dest, e?.message || e);
115
+ continue;
116
+ }
117
+ moveIfExists(src, dest);
118
+ }
119
+ }
120
+
121
+ const exeName = pickInstallerName();
122
+ if (!exeName) {
123
+ console.warn(`No installer in ${path.relative(appDir, artifactsDir)}/. Run electron-builder first.`);
124
+ process.exit(1);
125
+ }
126
+ const exeSrc = path.join(artifactsDir, exeName);
127
+
128
+ fs.mkdirSync(devDir, { recursive: true });
129
+
130
+ // Installer at build folder root only
131
+ const exeDest = path.join(buildDir, exeName);
132
+ moveIfExists(exeSrc, exeDest);
133
+
134
+ const latestSrc = path.join(artifactsDir, latestYmlName);
135
+ const latestDest = path.join(devDir, latestYmlName);
136
+ if (!moveIfExists(latestSrc, latestDest)) {
137
+ console.warn(`No latest.yml in ${path.relative(appDir, artifactsDir)}/ — generating one for electron-updater.`);
138
+ }
139
+
140
+ /** NSIS update metadata; electron-builder sometimes omits this unless publishing. */
141
+ function writeLatestYmlForExe(exePath, ymlPath) {
142
+ const pkg = JSON.parse(fs.readFileSync(path.join(appDir, "package.json"), "utf8"));
143
+ const version = String(pkg.version ?? "0.0.0");
144
+ const exeFileName = path.basename(exePath);
145
+ const buf = fs.readFileSync(exePath);
146
+ const sha512 = crypto.createHash("sha512").update(buf).digest("base64");
147
+ const size = buf.length;
148
+ const releaseDate = new Date().toISOString();
149
+ const yml =
150
+ `version: ${version}\n` +
151
+ `files:\n` +
152
+ ` - url: ${exeFileName}\n` +
153
+ ` sha512: ${sha512}\n` +
154
+ ` size: ${size}\n` +
155
+ `path: ${exeFileName}\n` +
156
+ `sha512: ${sha512}\n` +
157
+ `releaseDate: '${releaseDate}'\n`;
158
+ fs.writeFileSync(ymlPath, yml, "utf8");
159
+ console.log("Wrote:", path.relative(appDir, ymlPath));
160
+ }
161
+
162
+ if (fs.existsSync(exeDest) && !fs.existsSync(latestDest)) {
163
+ writeLatestYmlForExe(exeDest, latestDest);
164
+ }
165
+
166
+ const zipName = pickZipName();
167
+ const zipDest = zipName ? path.join(devDir, zipName) : null;
168
+ const zipLatestDest = path.join(devDir, zipLatestYmlName);
169
+ if (zipName) {
170
+ moveIfExists(path.join(artifactsDir, zipName), zipDest);
171
+ }
172
+ if (zipDest && fs.existsSync(zipDest)) {
173
+ writeLatestYmlForExe(zipDest, zipLatestDest);
174
+ }
175
+
176
+ // Move optional/debug artifacts into dev/
177
+ for (const name of devArtifacts) {
178
+ const src = path.join(artifactsDir, name);
179
+ const dest = path.join(devDir, name);
180
+ moveIfExists(src, dest);
181
+ }
182
+ const blockmapName = `${exeName}.blockmap`;
183
+ moveIfExists(path.join(artifactsDir, blockmapName), path.join(devDir, blockmapName));
184
+
185
+ moveRemainingStagingToDev();
186
+
187
+ // Remove electron-builder staging (eb-output or legacy releases/artifacts).
188
+ try {
189
+ if (fs.existsSync(legacyReleaseDir)) {
190
+ fs.rmSync(legacyReleaseDir, { recursive: true, force: true });
191
+ console.log("Removed release/ (legacy)");
192
+ }
193
+ if (fs.existsSync(artifactsDir)) {
194
+ fs.rmSync(artifactsDir, { recursive: true, force: true });
195
+ const label = process.env.HSP_EB_OUTPUT?.trim() ? "eb-output" : "releases/artifacts";
196
+ console.log(`Removed ${label}/`);
197
+ }
198
+ } catch (_) {}
199
+
200
+ console.log("Output:", path.join("releases", "builder", buildName));
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Embed assets/icon.ico into the Windows app .exe using electron-builder's app-builder `rcedit`
3
+ * (same stack as WinPackager.signAndEditResources). Do not use the `rcedit` npm wrapper here:
4
+ * it often lacks bin/rcedit.exe or uses an older binary that mishandles large PNG-based .ico files.
5
+ */
6
+ const fs = require("fs");
7
+ const path = require("path");
8
+ const { executeAppBuilder } = require("builder-util");
9
+
10
+ async function embedIconWithAppBuilder(exePath, iconPath) {
11
+ const rceditArgs = [exePath, "--set-icon", path.resolve(iconPath)];
12
+ await executeAppBuilder(["rcedit", "--args", JSON.stringify(rceditArgs)], undefined, {}, 3);
13
+ }
14
+
15
+ /**
16
+ * @param {{ appOutDir: string, projectDir: string, productFilename?: string }} opts
17
+ */
18
+ async function embedWindowsExeIcon(opts) {
19
+ if (process.platform !== "win32") return;
20
+ if (process.env.CSC_LINK || process.env.WIN_CSC_LINK) {
21
+ console.log("[embed-windows-exe-icon] skip: CSC_LINK / WIN_CSC_LINK set (would break signature)");
22
+ return;
23
+ }
24
+ const icon = path.resolve(opts.projectDir, "assets", "icon.ico");
25
+ if (!fs.existsSync(icon)) {
26
+ console.warn(`[embed-windows-exe-icon] skip: missing ${icon}`);
27
+ return;
28
+ }
29
+ let exe;
30
+ if (opts.productFilename) {
31
+ exe = path.join(opts.appOutDir, `${opts.productFilename}.exe`);
32
+ } else {
33
+ let names;
34
+ try {
35
+ names = fs.readdirSync(opts.appOutDir);
36
+ } catch (e) {
37
+ console.warn(`[embed-windows-exe-icon] skip: cannot read ${opts.appOutDir}: ${e?.message || e}`);
38
+ return;
39
+ }
40
+ const exes = names.filter((f) => f.endsWith(".exe") && !/uninstall/i.test(f));
41
+ if (exes.length !== 1) {
42
+ console.warn(`[embed-windows-exe-icon] skip: expected one app .exe in ${opts.appOutDir}, got: ${exes.join(", ")}`);
43
+ return;
44
+ }
45
+ exe = path.join(opts.appOutDir, exes[0]);
46
+ }
47
+ if (!fs.existsSync(exe)) {
48
+ console.warn(`[embed-windows-exe-icon] skip: exe not found ${exe}`);
49
+ return;
50
+ }
51
+ await embedIconWithAppBuilder(exe, icon);
52
+ console.log(`[embed-windows-exe-icon] app-builder rcedit: ${icon} -> ${exe}`);
53
+ }
54
+
55
+ module.exports = { embedWindowsExeIcon, embedIconWithAppBuilder };
@@ -0,0 +1,150 @@
1
+ ; Shadow of app-builder-lib templates/nsis/include/extractAppPackage.nsh
2
+ ; (buildResources is searched before the bundled template — see NSIS !addincludedir order.)
3
+ ;
4
+ ; Changes: Call HspKillBeforeCopy before every CopyFiles (and each retry) so taskkill + wait
5
+ ; runs after uninstall/old files, not only in customCheckAppRunning. More automatic retries
6
+ ; before the "cannot close app" dialog. Sync with upstream when upgrading electron-builder.
7
+ ; HspInstallStepStatus: step 3 = decompress start, step 4 = payload on disk (must match HSP_INSTALL_STEP_TOTAL in installer-hooks.nsi).
8
+
9
+ !macro extractEmbeddedAppPackage
10
+ !ifdef COMPRESS
11
+ SetCompress off
12
+ !endif
13
+
14
+ Var /GLOBAL packageArch
15
+
16
+ !insertmacro identify_package
17
+ !insertmacro compute_files_for_current_arch
18
+
19
+ !ifdef COMPRESS
20
+ SetCompress "${COMPRESS}"
21
+ !endif
22
+
23
+ !insertmacro decompress
24
+ !insertmacro custom_files_post_decompression
25
+ !macroend
26
+
27
+ !macro identify_package
28
+ !ifdef APP_32
29
+ StrCpy $packageArch "32"
30
+ !endif
31
+ !ifdef APP_64
32
+ ${if} ${RunningX64}
33
+ ${OrIf} ${IsNativeARM64}
34
+ StrCpy $packageArch "64"
35
+ ${endif}
36
+ !endif
37
+ !ifdef APP_ARM64
38
+ ${if} ${IsNativeARM64}
39
+ StrCpy $packageArch "ARM64"
40
+ ${endif}
41
+ !endif
42
+ !macroend
43
+
44
+ !macro compute_files_for_current_arch
45
+ ${if} $packageArch == "ARM64"
46
+ !ifdef APP_ARM64
47
+ !insertmacro arm64_app_files
48
+ !endif
49
+ ${elseif} $packageArch == "64"
50
+ !ifdef APP_64
51
+ !insertmacro x64_app_files
52
+ !endif
53
+ ${else}
54
+ !ifdef APP_32
55
+ !insertmacro ia32_app_files
56
+ !endif
57
+ ${endIf}
58
+ !macroend
59
+
60
+ !macro custom_files_post_decompression
61
+ ${if} $packageArch == "ARM64"
62
+ !ifmacrodef customFiles_arm64
63
+ !insertmacro customFiles_arm64
64
+ !endif
65
+ ${elseif} $packageArch == "64"
66
+ !ifmacrodef customFiles_x64
67
+ !insertmacro customFiles_x64
68
+ !endif
69
+ ${else}
70
+ !ifmacrodef customFiles_ia32
71
+ !insertmacro customFiles_ia32
72
+ !endif
73
+ ${endIf}
74
+ !macroend
75
+
76
+ !macro arm64_app_files
77
+ File /oname=$PLUGINSDIR\app-arm64.${COMPRESSION_METHOD} "${APP_ARM64}"
78
+ !macroend
79
+
80
+ !macro x64_app_files
81
+ File /oname=$PLUGINSDIR\app-64.${COMPRESSION_METHOD} "${APP_64}"
82
+ !macroend
83
+
84
+ !macro ia32_app_files
85
+ File /oname=$PLUGINSDIR\app-32.${COMPRESSION_METHOD} "${APP_32}"
86
+ !macroend
87
+
88
+ !macro decompress
89
+ !ifdef ZIP_COMPRESSION
90
+ !insertmacro HspInstallStepStatus 3
91
+ nsisunz::Unzip "$PLUGINSDIR\app-$packageArch.zip" "$INSTDIR"
92
+ Pop $R0
93
+ StrCmp $R0 "success" +3
94
+ MessageBox MB_OK|MB_ICONEXCLAMATION "$(decompressionFailed)$\n$R0"
95
+ Quit
96
+ !insertmacro HspInstallStepStatus 4
97
+ !else
98
+ !insertmacro extractUsing7za "$PLUGINSDIR\app-$packageArch.7z"
99
+ !endif
100
+ !macroend
101
+
102
+ !macro extractUsing7za FILE
103
+ !insertmacro HspInstallStepStatus 3
104
+ Push $OUTDIR
105
+ CreateDirectory "$PLUGINSDIR\7z-out"
106
+ ClearErrors
107
+ SetOutPath "$PLUGINSDIR\7z-out"
108
+ Nsis7z::Extract "${FILE}"
109
+ Pop $R0
110
+ SetOutPath $R0
111
+
112
+ # Retry counter
113
+ StrCpy $R1 0
114
+
115
+ LoopExtract7za:
116
+ IntOp $R1 $R1 + 1
117
+ Call HspKillBeforeCopy
118
+
119
+ # Attempt to copy files in atomic way
120
+ CopyFiles /SILENT "$PLUGINSDIR\7z-out\*" $OUTDIR
121
+ IfErrors 0 DoneExtract7za
122
+
123
+ DetailPrint `Can't modify "${PRODUCT_NAME}"'s files.`
124
+ ${if} $R1 < 15
125
+ # Automatic retries (same work as user clicking Retry: kill + wait + copy again).
126
+ Goto RetryExtract7za
127
+ ${else}
128
+ MessageBox MB_RETRYCANCEL|MB_ICONEXCLAMATION "$(appCannotBeClosed)" /SD IDRETRY IDCANCEL AbortExtract7za
129
+ ${endIf}
130
+
131
+ # As an absolutely last resort after a few automatic attempts and user
132
+ # intervention - we will just overwrite everything with `Nsis7z::Extract`
133
+ # even though it is not atomic and will ignore errors.
134
+
135
+ # Clear the temporary folder first to make sure we don't use twice as
136
+ # much disk space.
137
+ RMDir /r "$PLUGINSDIR\7z-out"
138
+
139
+ Nsis7z::Extract "${FILE}"
140
+ Goto DoneExtract7za
141
+
142
+ AbortExtract7za:
143
+ Quit
144
+
145
+ RetryExtract7za:
146
+ Goto LoopExtract7za
147
+
148
+ DoneExtract7za:
149
+ !insertmacro HspInstallStepStatus 4
150
+ !macroend
@@ -0,0 +1,41 @@
1
+ # Electron Forge (Windows)
2
+
3
+ Forge config and helpers live under `windows/forge/`.
4
+
5
+ This packages your existing Electron main process:
6
+
7
+ - `../build.cjs`
8
+
9
+ ## Configuration
10
+
11
+ Forge loads config via `package.json` → `config.forge` → `./windows/forge/forge.config.js` (no root `forge.config.js` wrapper).
12
+
13
+ ## Build
14
+
15
+ From `app/`, run:
16
+
17
+ ```bash
18
+ npm run make:win:forge
19
+ ```
20
+
21
+ This will:
22
+
23
+ 1. Build the Expo web `dist/`
24
+ 2. Run Electron Forge `make` (Windows NSIS + zip makers)
25
+ 3. Run `windows/forge-cleanup.cjs` into `releases/forge/build_<stamp>_forge/`
26
+
27
+ ## Release layout (mirrors builder; tag ends with `_forge`)
28
+
29
+ Each Forge build folder:
30
+
31
+ - `releases/forge/build_MMDDYYYY_HHMM_forge/HyperlinksSpaceProgramInstaller_<stamp>.exe` (root only)
32
+ - `releases/forge/build_MMDDYYYY_HHMM_forge/dev/latest.yml`
33
+ - `releases/forge/build_MMDDYYYY_HHMM_forge/dev/HyperlinksSpaceProgram_<version>.zip`
34
+ - `releases/forge/build_MMDDYYYY_HHMM_forge/dev/zip-latest.yml`
35
+ - `releases/forge/build_MMDDYYYY_HHMM_forge/dev/` — unpacked app, blockmaps, etc.
36
+
37
+ CI flattens the four release assets into `releases/electron/build_<timestamp>_forge/` for GitHub upload.
38
+
39
+ ## Notes
40
+
41
+ The Forge NSIS maker reads `package.json` `build` (app-builder-lib), so behavior can align with electron-builder.