@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,138 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { createRequire } from "node:module";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
|
|
6
|
+
const requireCjs = createRequire(import.meta.url);
|
|
7
|
+
|
|
8
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
9
|
+
// This file lives at app/windows/forge/ — app root is two levels up.
|
|
10
|
+
const appDir = path.resolve(__dirname, "..", "..");
|
|
11
|
+
const packageJson = JSON.parse(fs.readFileSync(path.join(appDir, "package.json"), "utf8"));
|
|
12
|
+
const forgeProductName = packageJson.build?.productName ?? "Hyperlinks Space Program";
|
|
13
|
+
|
|
14
|
+
const ICON_PATH = path.join(appDir, "assets", "icon.ico");
|
|
15
|
+
// `@electron/packager` expects `main` relative to `packagerConfig.dir`.
|
|
16
|
+
const MAIN_PROCESS_FILE_REL = path.join("windows", "build.cjs");
|
|
17
|
+
|
|
18
|
+
function forgeLog(step, details = "") {
|
|
19
|
+
const ts = new Date().toISOString();
|
|
20
|
+
const suffix = details ? ` ${details}` : "";
|
|
21
|
+
console.log(`[forge:${ts}] ${step}${suffix}`);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Forge config focused on Windows NSIS installer output.
|
|
25
|
+
// Note: your current electron-builder setup includes heavy custom NSIS hook files;
|
|
26
|
+
// Forge's stock NSIS maker may not replicate those exact installer pages out of the box.
|
|
27
|
+
export default {
|
|
28
|
+
outDir: path.join(appDir, "releases", "forge", "artifacts"),
|
|
29
|
+
buildIdentifier: "hsp-forge",
|
|
30
|
+
hooks: {
|
|
31
|
+
generateAssets: async () => {
|
|
32
|
+
forgeLog("generateAssets");
|
|
33
|
+
},
|
|
34
|
+
prePackage: async (_forgeConfig, platform, arch) => {
|
|
35
|
+
forgeLog("prePackage", `platform=${platform} arch=${arch}`);
|
|
36
|
+
if (!fs.existsSync(ICON_PATH)) {
|
|
37
|
+
throw new Error(`[forge] packager icon missing: ${ICON_PATH}`);
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
packageAfterCopy: async (buildPath, electronVersion, platform, arch) => {
|
|
41
|
+
forgeLog(
|
|
42
|
+
"packageAfterCopy",
|
|
43
|
+
`platform=${platform} arch=${arch} electron=${electronVersion} buildPath=${buildPath}`,
|
|
44
|
+
);
|
|
45
|
+
},
|
|
46
|
+
packageAfterPrune: async (buildPath, electronVersion, platform, arch) => {
|
|
47
|
+
forgeLog(
|
|
48
|
+
"packageAfterPrune",
|
|
49
|
+
`platform=${platform} arch=${arch} electron=${electronVersion} buildPath=${buildPath}`,
|
|
50
|
+
);
|
|
51
|
+
},
|
|
52
|
+
packageAfterExtract: async (buildPath, electronVersion, platform, arch) => {
|
|
53
|
+
forgeLog(
|
|
54
|
+
"packageAfterExtract",
|
|
55
|
+
`platform=${platform} arch=${arch} electron=${electronVersion} buildPath=${buildPath}`,
|
|
56
|
+
);
|
|
57
|
+
},
|
|
58
|
+
postPackage: async (_forgeConfig, packageResult) => {
|
|
59
|
+
const outputs = Array.isArray(packageResult?.outputPaths)
|
|
60
|
+
? packageResult.outputPaths.join(", ")
|
|
61
|
+
: "";
|
|
62
|
+
forgeLog(
|
|
63
|
+
"postPackage",
|
|
64
|
+
`platform=${packageResult?.platform} arch=${packageResult?.arch} outputs=${outputs}`,
|
|
65
|
+
);
|
|
66
|
+
if (packageResult?.platform === "win32" && Array.isArray(packageResult.outputPaths)) {
|
|
67
|
+
const { embedWindowsExeIcon } = requireCjs("../embed-windows-exe-icon.cjs");
|
|
68
|
+
for (const out of packageResult.outputPaths) {
|
|
69
|
+
await embedWindowsExeIcon({ appOutDir: out, projectDir: appDir });
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
preMake: async () => {
|
|
74
|
+
forgeLog("preMake");
|
|
75
|
+
},
|
|
76
|
+
postMake: async (_forgeConfig, makeResults) => {
|
|
77
|
+
const parts = Array.isArray(makeResults)
|
|
78
|
+
? makeResults
|
|
79
|
+
.map((r) => `${r.platform}/${r.arch}:${(r.artifacts || []).length}`)
|
|
80
|
+
.join(", ")
|
|
81
|
+
: "";
|
|
82
|
+
forgeLog("postMake", `results=${parts}`);
|
|
83
|
+
},
|
|
84
|
+
// `@electron/packager` validates the Electron main entry using `package.json.main`
|
|
85
|
+
// from the directory being packaged. Your app uses Expo Router, so we temporarily
|
|
86
|
+
// switch main to the Electron main entry.
|
|
87
|
+
//
|
|
88
|
+
// This hook is only used inside Forge's packaging step.
|
|
89
|
+
readPackageJson: async (_forgeConfig, packageJSON) => {
|
|
90
|
+
packageJSON.main = MAIN_PROCESS_FILE_REL;
|
|
91
|
+
return packageJSON;
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
packagerConfig: {
|
|
95
|
+
dir: appDir,
|
|
96
|
+
name: forgeProductName,
|
|
97
|
+
appBundleId: "com.sraibaby.app",
|
|
98
|
+
icon: ICON_PATH,
|
|
99
|
+
// electron-packager reads this as the main process entry.
|
|
100
|
+
main: MAIN_PROCESS_FILE_REL,
|
|
101
|
+
// Improves exe identity in Properties / some SmartScreen heuristics (does not replace Authenticode signing).
|
|
102
|
+
// Do not set OriginalFilename here: electron-packager sets it to the real sanitized exe name
|
|
103
|
+
// (e.g. Hyperlinks-Space-Program.exe). Overriding with spaces breaks metadata vs on-disk name.
|
|
104
|
+
win32metadata: {
|
|
105
|
+
CompanyName: typeof packageJson.author === "string" ? packageJson.author : "sraibaby",
|
|
106
|
+
FileDescription: forgeProductName,
|
|
107
|
+
ProductName: forgeProductName,
|
|
108
|
+
},
|
|
109
|
+
|
|
110
|
+
// Match your electron-builder asar strategy.
|
|
111
|
+
asar: {
|
|
112
|
+
smartUnpack: true,
|
|
113
|
+
},
|
|
114
|
+
asarUnpack: ["**/*.node", "**/*.dll", "**/*.exe", "assets/icon.ico"],
|
|
115
|
+
// Plain file next to app.asar (Program Files\...\resources\icon.ico) — shell APIs cannot read paths inside app.asar.
|
|
116
|
+
extraResource: [ICON_PATH],
|
|
117
|
+
},
|
|
118
|
+
makers: [
|
|
119
|
+
{
|
|
120
|
+
name: "@felixrieseberg/electron-forge-maker-nsis",
|
|
121
|
+
platforms: ["win32"],
|
|
122
|
+
config: {
|
|
123
|
+
// Pass NSIS options through maker's electron-builder bridge.
|
|
124
|
+
// `runAfterFinish` at the top level is not part of this maker's documented schema.
|
|
125
|
+
getAdditionalConfig: () => ({
|
|
126
|
+
oneClick: false,
|
|
127
|
+
runAfterFinish: true,
|
|
128
|
+
perMachine: true,
|
|
129
|
+
}),
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
name: "@electron-forge/maker-zip",
|
|
134
|
+
platforms: ["win32"],
|
|
135
|
+
config: {},
|
|
136
|
+
},
|
|
137
|
+
],
|
|
138
|
+
};
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
const { spawn } = require("child_process");
|
|
2
|
+
const path = require("path");
|
|
3
|
+
|
|
4
|
+
function pad2(n) {
|
|
5
|
+
return String(n).padStart(2, "0");
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function makeBuildStamp(d = new Date()) {
|
|
9
|
+
return `${pad2(d.getMonth() + 1)}${pad2(d.getDate())}${d.getFullYear()}_${pad2(d.getHours())}${pad2(d.getMinutes())}`;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function computeBuildStamp() {
|
|
13
|
+
if (process.env.BUILD_STAMP?.trim()) return process.env.BUILD_STAMP.trim();
|
|
14
|
+
const rid = process.env.RELEASE_BUILD_ID?.trim();
|
|
15
|
+
if (rid) {
|
|
16
|
+
const m = rid.match(/^build_(\d{8}_\d{4})_forge$/);
|
|
17
|
+
if (m) return m[1];
|
|
18
|
+
}
|
|
19
|
+
return makeBuildStamp();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function run() {
|
|
23
|
+
const appDir = path.resolve(__dirname, "..", "..");
|
|
24
|
+
const isVerbose = process.argv.includes("--verbose");
|
|
25
|
+
const cliPath = path.join(appDir, "node_modules", "@electron-forge", "cli", "dist", "electron-forge.js");
|
|
26
|
+
|
|
27
|
+
const BUILD_STAMP = computeBuildStamp();
|
|
28
|
+
|
|
29
|
+
const env = {
|
|
30
|
+
...process.env,
|
|
31
|
+
BUILD_STAMP,
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const args = [cliPath, "make", "--platform", "win32"];
|
|
35
|
+
if (isVerbose) args.push("--verbose");
|
|
36
|
+
|
|
37
|
+
console.log(`[forge] BUILD_STAMP=${env.BUILD_STAMP}`);
|
|
38
|
+
console.log(`[forge] node ${args.join(" ")}`);
|
|
39
|
+
|
|
40
|
+
const child = spawn(process.execPath, args, {
|
|
41
|
+
cwd: appDir,
|
|
42
|
+
env,
|
|
43
|
+
stdio: "inherit",
|
|
44
|
+
shell: false,
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
child.on("close", (code) => {
|
|
48
|
+
if (code !== 0) {
|
|
49
|
+
process.exit(code || 1);
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
const cleanupPath = path.join(appDir, "windows", "forge-cleanup.cjs");
|
|
53
|
+
const c = spawn(process.execPath, [cleanupPath], {
|
|
54
|
+
cwd: appDir,
|
|
55
|
+
env: { ...process.env, BUILD_STAMP },
|
|
56
|
+
stdio: "inherit",
|
|
57
|
+
shell: false,
|
|
58
|
+
});
|
|
59
|
+
c.on("close", (c2) => process.exit(c2 || 0));
|
|
60
|
+
c.on("error", () => process.exit(1));
|
|
61
|
+
});
|
|
62
|
+
child.on("error", () => process.exit(1));
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
run();
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* After Electron Forge `make`, mirrors builder cleanup layout under
|
|
3
|
+
* `releases/forge/build_MMDDYYYY_HHMM_forge/` — installer only at root; zip, yml, unpacked, etc. in dev/.
|
|
4
|
+
* CI sets RELEASE_BUILD_ID=build_<date>_forge; locally the id defaults to the same pattern.
|
|
5
|
+
*/
|
|
6
|
+
const fs = require("fs");
|
|
7
|
+
const path = require("path");
|
|
8
|
+
const crypto = require("crypto");
|
|
9
|
+
const { RELEASE_BUILD_DEV_DIRNAME } = require("./build-layout.cjs");
|
|
10
|
+
const { productSlug } = require("./product-brand.cjs");
|
|
11
|
+
|
|
12
|
+
const appDir = path.join(__dirname, "..");
|
|
13
|
+
const releasesDir = path.join(appDir, "releases");
|
|
14
|
+
const forgeArtifactsRoot = path.join(releasesDir, "forge", "artifacts");
|
|
15
|
+
|
|
16
|
+
const pad = (n) => String(n).padStart(2, "0");
|
|
17
|
+
const now = new Date();
|
|
18
|
+
|
|
19
|
+
const defaultBuildNameFromClock =
|
|
20
|
+
"build_" +
|
|
21
|
+
pad(now.getMonth() + 1) +
|
|
22
|
+
pad(now.getDate()) +
|
|
23
|
+
now.getFullYear() +
|
|
24
|
+
"_" +
|
|
25
|
+
pad(now.getHours()) +
|
|
26
|
+
pad(now.getMinutes()) +
|
|
27
|
+
"_forge";
|
|
28
|
+
|
|
29
|
+
const envBuildId = process.env.RELEASE_BUILD_ID?.trim();
|
|
30
|
+
// Parent `make-with-stamp.cjs` passes BUILD_STAMP so folder name matches the installer stamp.
|
|
31
|
+
const stampFromEnv = process.env.BUILD_STAMP?.trim();
|
|
32
|
+
const buildName =
|
|
33
|
+
envBuildId && /^build_\d{8}_\d{4}_forge$/.test(envBuildId)
|
|
34
|
+
? envBuildId
|
|
35
|
+
: stampFromEnv
|
|
36
|
+
? `build_${stampFromEnv}_forge`
|
|
37
|
+
: defaultBuildNameFromClock;
|
|
38
|
+
|
|
39
|
+
const buildDir = path.join(releasesDir, "forge", buildName);
|
|
40
|
+
const devDir = path.join(buildDir, RELEASE_BUILD_DEV_DIRNAME);
|
|
41
|
+
|
|
42
|
+
const latestYmlName = "latest.yml";
|
|
43
|
+
const zipLatestYmlName = "zip-latest.yml";
|
|
44
|
+
|
|
45
|
+
function walkDirs(dir, fn) {
|
|
46
|
+
if (!fs.existsSync(dir)) return;
|
|
47
|
+
const st = fs.statSync(dir);
|
|
48
|
+
if (!st.isDirectory()) return;
|
|
49
|
+
fn(dir);
|
|
50
|
+
for (const name of fs.readdirSync(dir)) {
|
|
51
|
+
walkDirs(path.join(dir, name), fn);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function collectFiles(root, predicate) {
|
|
56
|
+
const out = [];
|
|
57
|
+
function walk(d) {
|
|
58
|
+
if (!fs.existsSync(d)) return;
|
|
59
|
+
for (const name of fs.readdirSync(d)) {
|
|
60
|
+
const p = path.join(d, name);
|
|
61
|
+
const st = fs.statSync(p);
|
|
62
|
+
if (st.isDirectory()) walk(p);
|
|
63
|
+
else if (predicate(p, name)) out.push(p);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
walk(root);
|
|
67
|
+
return out;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function pickInstallerExe() {
|
|
71
|
+
const exes = collectFiles(
|
|
72
|
+
forgeArtifactsRoot,
|
|
73
|
+
(p, name) =>
|
|
74
|
+
p.replace(/\\/g, "/").toLowerCase().includes("/make/") &&
|
|
75
|
+
name.toLowerCase().endsWith(".exe"),
|
|
76
|
+
);
|
|
77
|
+
if (exes.length === 0) return null;
|
|
78
|
+
for (const f of exes) {
|
|
79
|
+
const b = path.basename(f).toLowerCase();
|
|
80
|
+
if (b.includes("setup") || b.includes("installer")) return f;
|
|
81
|
+
}
|
|
82
|
+
return exes[0];
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function pickZip() {
|
|
86
|
+
const zips = collectFiles(
|
|
87
|
+
forgeArtifactsRoot,
|
|
88
|
+
(p, name) =>
|
|
89
|
+
p.replace(/\\/g, "/").toLowerCase().includes("/make/") &&
|
|
90
|
+
name.toLowerCase().endsWith(".zip"),
|
|
91
|
+
);
|
|
92
|
+
if (zips.length === 0) return null;
|
|
93
|
+
return zips[0];
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function pickYml(name) {
|
|
97
|
+
const ymls = collectFiles(
|
|
98
|
+
forgeArtifactsRoot,
|
|
99
|
+
(p, n) =>
|
|
100
|
+
p.replace(/\\/g, "/").toLowerCase().includes("/make/") &&
|
|
101
|
+
n.toLowerCase() === name,
|
|
102
|
+
);
|
|
103
|
+
return ymls[0] || null;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function findUnpackedDir() {
|
|
107
|
+
let found = null;
|
|
108
|
+
walkDirs(forgeArtifactsRoot, (d) => {
|
|
109
|
+
if (found) return;
|
|
110
|
+
const base = path.basename(d);
|
|
111
|
+
if (!base.endsWith("-win32-x64")) return;
|
|
112
|
+
const exeCandidates = fs.readdirSync(d).filter((n) => n.toLowerCase().endsWith(".exe"));
|
|
113
|
+
if (exeCandidates.length > 0) found = d;
|
|
114
|
+
});
|
|
115
|
+
return found;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function moveIfExists(src, dest) {
|
|
119
|
+
if (!fs.existsSync(src)) return false;
|
|
120
|
+
try {
|
|
121
|
+
fs.renameSync(src, dest);
|
|
122
|
+
console.log("Moved:", path.relative(appDir, dest));
|
|
123
|
+
return true;
|
|
124
|
+
} catch (e) {
|
|
125
|
+
try {
|
|
126
|
+
const stat = fs.statSync(src);
|
|
127
|
+
if (stat.isDirectory()) {
|
|
128
|
+
fs.cpSync(src, dest, { recursive: true });
|
|
129
|
+
fs.rmSync(src, { recursive: true, force: true });
|
|
130
|
+
} else {
|
|
131
|
+
fs.copyFileSync(src, dest);
|
|
132
|
+
fs.unlinkSync(src);
|
|
133
|
+
}
|
|
134
|
+
console.log("Moved (copy+delete):", path.relative(appDir, dest));
|
|
135
|
+
return true;
|
|
136
|
+
} catch (e2) {
|
|
137
|
+
console.warn("Could not move", src, e2.message);
|
|
138
|
+
return false;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function stampFromBuildId(name) {
|
|
144
|
+
const m = name.match(/^build_(\d{8}_\d{4})_forge$/);
|
|
145
|
+
return m ? m[1] : null;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function writeLatestYmlForExe(exePath, ymlPath) {
|
|
149
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(appDir, "package.json"), "utf8"));
|
|
150
|
+
const version = String(pkg.version ?? "0.0.0");
|
|
151
|
+
const exeFileName = path.basename(exePath);
|
|
152
|
+
const buf = fs.readFileSync(exePath);
|
|
153
|
+
const sha512 = crypto.createHash("sha512").update(buf).digest("base64");
|
|
154
|
+
const size = buf.length;
|
|
155
|
+
const releaseDate = new Date().toISOString();
|
|
156
|
+
const yml =
|
|
157
|
+
`version: ${version}\n` +
|
|
158
|
+
`files:\n` +
|
|
159
|
+
` - url: ${exeFileName}\n` +
|
|
160
|
+
` sha512: ${sha512}\n` +
|
|
161
|
+
` size: ${size}\n` +
|
|
162
|
+
`path: ${exeFileName}\n` +
|
|
163
|
+
`sha512: ${sha512}\n` +
|
|
164
|
+
`releaseDate: '${releaseDate}'\n`;
|
|
165
|
+
fs.writeFileSync(ymlPath, yml, "utf8");
|
|
166
|
+
console.log("Wrote:", path.relative(appDir, ymlPath));
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function main() {
|
|
170
|
+
if (!fs.existsSync(forgeArtifactsRoot)) {
|
|
171
|
+
console.warn("No Forge output at releases/forge/artifacts/. Run forge make first.");
|
|
172
|
+
process.exit(1);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const stamp =
|
|
176
|
+
process.env.BUILD_STAMP?.trim() || stampFromBuildId(buildName);
|
|
177
|
+
|
|
178
|
+
if (!stamp) {
|
|
179
|
+
console.warn("Could not determine BUILD_STAMP (set env or use build_*_forge id).");
|
|
180
|
+
process.exit(1);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(appDir, "package.json"), "utf8"));
|
|
184
|
+
const version = String(pkg.version ?? "0.0.0");
|
|
185
|
+
const targetExeName = `${productSlug}Installer_${stamp}.exe`;
|
|
186
|
+
const targetZipName = `${productSlug}_${version}.zip`;
|
|
187
|
+
|
|
188
|
+
const exeSrc = pickInstallerExe();
|
|
189
|
+
if (!exeSrc) {
|
|
190
|
+
console.warn("No installer .exe under releases/forge/artifacts/**/make/**");
|
|
191
|
+
process.exit(1);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
fs.mkdirSync(devDir, { recursive: true });
|
|
195
|
+
|
|
196
|
+
const exeDest = path.join(buildDir, targetExeName);
|
|
197
|
+
moveIfExists(exeSrc, exeDest);
|
|
198
|
+
|
|
199
|
+
const latestSrc = pickYml(latestYmlName);
|
|
200
|
+
const latestDest = path.join(devDir, latestYmlName);
|
|
201
|
+
if (latestSrc) {
|
|
202
|
+
moveIfExists(latestSrc, latestDest);
|
|
203
|
+
}
|
|
204
|
+
if (fs.existsSync(exeDest) && !fs.existsSync(latestDest)) {
|
|
205
|
+
console.warn("No latest.yml from Forge — generating for electron-updater.");
|
|
206
|
+
writeLatestYmlForExe(exeDest, latestDest);
|
|
207
|
+
} else if (fs.existsSync(exeDest) && fs.existsSync(latestDest)) {
|
|
208
|
+
// ensure consistent
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const zipSrc = pickZip();
|
|
212
|
+
if (!zipSrc) {
|
|
213
|
+
console.error(
|
|
214
|
+
"No portable .zip under releases/forge/artifacts/**/make/** — ensure @electron-forge/maker-zip is enabled.",
|
|
215
|
+
);
|
|
216
|
+
process.exit(1);
|
|
217
|
+
}
|
|
218
|
+
const zipDest = path.join(devDir, targetZipName);
|
|
219
|
+
moveIfExists(zipSrc, zipDest);
|
|
220
|
+
|
|
221
|
+
const zipLatestDest = path.join(devDir, zipLatestYmlName);
|
|
222
|
+
const zipLatestSrc = pickYml(zipLatestYmlName);
|
|
223
|
+
if (zipLatestSrc) {
|
|
224
|
+
moveIfExists(zipLatestSrc, zipLatestDest);
|
|
225
|
+
} else if (fs.existsSync(zipDest)) {
|
|
226
|
+
writeLatestYmlForExe(zipDest, zipLatestDest);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const unpacked = findUnpackedDir();
|
|
230
|
+
if (unpacked) {
|
|
231
|
+
const destUnpacked = path.join(devDir, "win-unpacked");
|
|
232
|
+
fs.mkdirSync(devDir, { recursive: true });
|
|
233
|
+
if (fs.existsSync(destUnpacked)) fs.rmSync(destUnpacked, { recursive: true, force: true });
|
|
234
|
+
fs.cpSync(unpacked, destUnpacked, { recursive: true });
|
|
235
|
+
console.log("Copied unpacked →", path.relative(appDir, destUnpacked));
|
|
236
|
+
fs.rmSync(unpacked, { recursive: true, force: true });
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const blockmaps = collectFiles(forgeArtifactsRoot, (_, n) => n.toLowerCase().endsWith(".blockmap"));
|
|
240
|
+
for (const bm of blockmaps) {
|
|
241
|
+
moveIfExists(bm, path.join(devDir, path.basename(bm)));
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
try {
|
|
245
|
+
const forgeStaging = path.join(releasesDir, "forge", "artifacts");
|
|
246
|
+
if (fs.existsSync(forgeStaging)) {
|
|
247
|
+
fs.rmSync(forgeStaging, { recursive: true, force: true });
|
|
248
|
+
console.log("Removed releases/forge/artifacts/");
|
|
249
|
+
}
|
|
250
|
+
} catch (_) {}
|
|
251
|
+
|
|
252
|
+
console.log("Output:", path.join("releases", "forge", buildName));
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
main();
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# Used by installer-hooks.nsi (nsExec::Exec, no console). Sync process names with package.json:
|
|
2
|
+
# build.productName, name (npm), product-brand portable slug, legacy "Hyperlinks Space App".
|
|
3
|
+
param(
|
|
4
|
+
[Parameter(Mandatory = $true)]
|
|
5
|
+
[ValidateSet("Kill", "Test")]
|
|
6
|
+
[string]$Action
|
|
7
|
+
)
|
|
8
|
+
$ErrorActionPreference = "SilentlyContinue"
|
|
9
|
+
$InstDir = $env:HSP_INSTDIR
|
|
10
|
+
if ([string]::IsNullOrWhiteSpace($InstDir)) {
|
|
11
|
+
if ($Action -eq "Test") { exit 1 }
|
|
12
|
+
exit 0
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
$names = @(
|
|
16
|
+
"Hyperlinks Space Program",
|
|
17
|
+
"Hyperlinks Space Program Helper",
|
|
18
|
+
"Hyperlinks Space Program Helper (GPU)",
|
|
19
|
+
"Hyperlinks Space Program Helper (Renderer)",
|
|
20
|
+
"Hyperlinks Space Program Helper (Plugin)",
|
|
21
|
+
"expo-template-default",
|
|
22
|
+
"HyperlinksSpaceProgram",
|
|
23
|
+
"Hyperlinks Space App"
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
function Get-Root([string]$p) {
|
|
27
|
+
if ([string]::IsNullOrWhiteSpace($p)) { return "" }
|
|
28
|
+
return $p.TrimEnd("\").ToLower()
|
|
29
|
+
}
|
|
30
|
+
$root = Get-Root $InstDir
|
|
31
|
+
$rootContains = if ($root) { $root.ToLower() } else { "" }
|
|
32
|
+
|
|
33
|
+
function Test-AnyRunning {
|
|
34
|
+
foreach ($n in $names) {
|
|
35
|
+
if (Get-Process -Name $n -ErrorAction SilentlyContinue) { return $true }
|
|
36
|
+
}
|
|
37
|
+
if (-not $root) { return $false }
|
|
38
|
+
$hit = Get-CimInstance Win32_Process -ErrorAction SilentlyContinue | Where-Object {
|
|
39
|
+
($_.ExecutablePath -and $_.ExecutablePath.ToLower().StartsWith($root)) -or
|
|
40
|
+
($_.CommandLine -and $rootContains -and $_.CommandLine.ToLower().Contains($rootContains))
|
|
41
|
+
} | Select-Object -First 1
|
|
42
|
+
return [bool]$hit
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function Stop-All {
|
|
46
|
+
foreach ($n in $names) {
|
|
47
|
+
Get-Process -Name $n -ErrorAction SilentlyContinue | Stop-Process -Force -ErrorAction SilentlyContinue
|
|
48
|
+
}
|
|
49
|
+
if (-not $root) { return }
|
|
50
|
+
Get-CimInstance Win32_Process -ErrorAction SilentlyContinue | Where-Object {
|
|
51
|
+
($_.ExecutablePath -and $_.ExecutablePath.ToLower().StartsWith($root)) -or
|
|
52
|
+
($_.CommandLine -and $rootContains -and $_.CommandLine.ToLower().Contains($rootContains))
|
|
53
|
+
} | ForEach-Object {
|
|
54
|
+
Stop-Process -Id $_.ProcessId -Force -ErrorAction SilentlyContinue
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if ($Action -eq "Test") {
|
|
59
|
+
if (Test-AnyRunning) { exit 0 } else { exit 1 }
|
|
60
|
+
}
|
|
61
|
+
Stop-All
|
|
62
|
+
Start-Sleep -Milliseconds 2000
|
|
63
|
+
exit 0
|