@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.
- package/README.md +53 -0
- package/api/ai.ts +111 -0
- package/api/base.ts +117 -0
- package/api/blockchain.ts +58 -0
- package/api/bot.ts +19 -0
- package/api/ping.ts +41 -0
- package/api/releases.ts +162 -0
- package/api/telegram.ts +65 -0
- package/api/tsconfig.json +17 -0
- package/app/_layout.tsx +135 -0
- package/app/ai.tsx +39 -0
- package/app/components/GlobalBottomBar.tsx +447 -0
- package/app/components/GlobalBottomBarWeb.tsx +362 -0
- package/app/components/GlobalLogoBar.tsx +108 -0
- package/app/components/GlobalLogoBarFallback.tsx +66 -0
- package/app/components/GlobalLogoBarWithFallback.tsx +24 -0
- package/app/components/HyperlinksSpaceLogo.tsx +29 -0
- package/app/components/Telegram.tsx +648 -0
- package/app/components/telegramWebApp.ts +359 -0
- package/app/fonts.ts +12 -0
- package/app/index.tsx +102 -0
- package/app/theme.ts +117 -0
- package/app.json +60 -0
- package/assets/icon.ico +0 -0
- package/assets/images/favicon.png +0 -0
- package/blockchain/coffee.ts +217 -0
- package/blockchain/router.ts +44 -0
- package/bot/format.ts +143 -0
- package/bot/grammy.ts +52 -0
- package/bot/responder.ts +620 -0
- package/bot/webhook.ts +262 -0
- package/database/messages.ts +128 -0
- package/database/start.ts +133 -0
- package/database/users.ts +46 -0
- package/docs/ai_and_search_bar_input.md +94 -0
- package/docs/ai_bot_messages.md +124 -0
- package/docs/backlogs/medium_term_backlog.md +26 -0
- package/docs/backlogs/short_term_backlog.md +39 -0
- package/docs/blue_bar_tackling.md +143 -0
- package/docs/bot_async_streaming.md +174 -0
- package/docs/build_and_install.md +129 -0
- package/docs/database_messages.md +34 -0
- package/docs/fonts.md +18 -0
- package/docs/releases.md +201 -0
- package/docs/releases_github_actions.md +188 -0
- package/docs/scalability.md +34 -0
- package/docs/security_plan_raw.md +244 -0
- package/docs/security_raw.md +345 -0
- package/docs/timing_raw.md +63 -0
- package/docs/tma_logo_bar_jump_investigation.md +69 -0
- package/docs/update.md +205 -0
- package/docs/wallets_hosting_architecture.md +257 -0
- package/eas.json +47 -0
- package/eslint.config.js +10 -0
- package/fullREADME.md +159 -0
- package/global.css +67 -0
- package/npmReadMe.md +53 -0
- package/package.json +214 -0
- package/scripts/load-env.ts +17 -0
- package/scripts/migrate-db.ts +16 -0
- package/scripts/program-kit-init.cjs +58 -0
- package/scripts/run-bot-local.ts +30 -0
- package/scripts/set-webhook.ts +67 -0
- package/scripts/test-api-base.ts +12 -0
- package/telegram/post.ts +328 -0
- package/tsconfig.json +17 -0
- package/vercel.json +7 -0
- package/windows/after-sign-windows-icon.cjs +13 -0
- package/windows/build-layout.cjs +72 -0
- package/windows/build-with-progress.cjs +88 -0
- package/windows/build.cjs +2247 -0
- package/windows/cleanup-legacy-appdata-installs.ps1 +91 -0
- package/windows/cleanup-legacy-windows-shortcuts.ps1 +46 -0
- package/windows/cleanup.cjs +200 -0
- package/windows/embed-windows-exe-icon.cjs +55 -0
- package/windows/extractAppPackage.nsh +150 -0
- package/windows/forge/README.md +41 -0
- package/windows/forge/forge.config.js +138 -0
- package/windows/forge/make-with-stamp.cjs +65 -0
- package/windows/forge-cleanup.cjs +255 -0
- package/windows/hsp-app-process.ps1 +63 -0
- package/windows/installer-hooks.nsi +373 -0
- package/windows/product-brand.cjs +42 -0
- package/windows/remove-orphan-uninstall-registry.ps1 +67 -0
- package/windows/run-installed-with-icon-debug.cmd +20 -0
- package/windows/run-win-electron-builder.cjs +46 -0
- 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.
|