fantasta 0.3.1 → 0.3.2
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 +17 -8
- package/bin/fantasta.js +133 -10
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -13,12 +13,17 @@ pacchetto è firmato da GitHub, quindi non c'è Gatekeeper da aggirare.
|
|
|
13
13
|
|
|
14
14
|
## Cosa succede
|
|
15
15
|
|
|
16
|
-
-
|
|
16
|
+
- Al **primo avvio** su macOS viene preparata una vera **Fantasta.app** (nome,
|
|
17
|
+
icona e identificatore corretti) dentro `~/.fantasta/` — pochi secondi, una
|
|
18
|
+
volta sola;
|
|
19
|
+
- si apre la **finestra dell'app desktop** (regia) con l'icona e il nome di
|
|
20
|
+
Fantasta nel Dock;
|
|
17
21
|
- si avvia il **server SQLite/LAN** locale (porta `47821`);
|
|
18
22
|
- si avvia il **renderer** della regia (porta `47822`), raggiungibile anche dal
|
|
19
23
|
browser dei partecipanti;
|
|
20
|
-
-
|
|
21
|
-
per fermare tutto e disconnettere i partecipanti (
|
|
24
|
+
- il **terminale torna subito libero**: l'app gira da sola, chiudi la **finestra**
|
|
25
|
+
per fermare tutto e disconnettere i partecipanti (i log finiscono in
|
|
26
|
+
`~/.fantasta/desktop.log`).
|
|
22
27
|
|
|
23
28
|
I partecipanti si collegano con il **QR code** mostrato durante la creazione
|
|
24
29
|
della lega (o col link) e seguono l'asta dal telefono. Serve una sola
|
|
@@ -39,8 +44,9 @@ npx fantasta --browser
|
|
|
39
44
|
|
|
40
45
|
## Dove vengono salvati i dati
|
|
41
46
|
|
|
42
|
-
-
|
|
43
|
-
|
|
47
|
+
- Tutti i dati (database, sessione admin, log e l'app `Fantasta.app` su macOS)
|
|
48
|
+
stanno in `~/.fantasta/` (`FANTASTA_DATA_DIR` per cambiare cartella).
|
|
49
|
+
- Finestra desktop: `fantasta.db` in `~/.fantasta/` (o `FANTASTA_DATABASE_PATH`).
|
|
44
50
|
- Modalità browser / sessioni: `~/.fantasta/` (`fantasta.db`, `admin-session.txt`).
|
|
45
51
|
|
|
46
52
|
## Opzioni (variabili d'ambiente)
|
|
@@ -67,9 +73,12 @@ L'"app desktop" di Fantasta è in realtà **due processi Node** (server LAN e
|
|
|
67
73
|
renderer) avvolti da **Electron**. Il pacchetto npm include tutto e lancia il
|
|
68
74
|
binario Electron ufficiale, firmato da GitHub: per questo non c'è alcun
|
|
69
75
|
**avviso di sicurezza** all'apertura su macOS, Windows o Linux (niente
|
|
70
|
-
Gatekeeper da aggirare).
|
|
71
|
-
|
|
72
|
-
|
|
76
|
+
Gatekeeper da aggirare). Su macOS al primo avvio il pacchetto assembla una
|
|
77
|
+
vera `Fantasta.app` (copia del dist Electron con nome e icona corretti): nel
|
|
78
|
+
Dock compare **solo Fantasta**, senza l'icona "exec" né quella generica di
|
|
79
|
+
Electron. Se preferisci una finestra "installata" a sistema, la distribuzione
|
|
80
|
+
installer (`.dmg`/`.exe`) resta scaricabile dalle **GitHub Releases** del
|
|
81
|
+
progetto.
|
|
73
82
|
|
|
74
83
|
## Sviluppo in questo repo
|
|
75
84
|
|
package/bin/fantasta.js
CHANGED
|
@@ -1,18 +1,27 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// Launcher "fantasta".
|
|
3
3
|
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
4
|
+
// Su macOS assembla al primo avvio una vera Fantasta.app (copia del dist
|
|
5
|
+
// Electron con plist, nome e icona corretti, rifirmata adhoc) e la lancia
|
|
6
|
+
// in modalità detached: nel Dock compare solo Fantasta con la sua icona,
|
|
7
|
+
// senza il processo "exec" (che su macOS 26 appare quando un processo node
|
|
8
|
+
// resta vivo accanto a Electron) e senza l'icona atomo di Electron.
|
|
8
9
|
//
|
|
9
|
-
// Con `--browser` (o FANTASTA_BROWSER=1) usa la modalità legacy senza
|
|
10
|
-
// avvia i due processi Node (local-server.cjs + renderer standalone)
|
|
11
|
-
// regia nel browser di default.
|
|
10
|
+
// Con `--browser` (o FANTASTA_BROWSER=1) usa la modalità legacy senza
|
|
11
|
+
// Electron: avvia i due processi Node (local-server.cjs + renderer standalone)
|
|
12
|
+
// e apre la regia nel browser di default.
|
|
12
13
|
"use strict";
|
|
13
14
|
|
|
14
|
-
const { spawn } = require("node:child_process");
|
|
15
|
-
const {
|
|
15
|
+
const { spawn, execFileSync } = require("node:child_process");
|
|
16
|
+
const {
|
|
17
|
+
access,
|
|
18
|
+
copyFile,
|
|
19
|
+
mkdir,
|
|
20
|
+
open,
|
|
21
|
+
readFile,
|
|
22
|
+
rm,
|
|
23
|
+
writeFile,
|
|
24
|
+
} = require("node:fs/promises");
|
|
16
25
|
const { homedir } = require("node:os");
|
|
17
26
|
const path = require("node:path");
|
|
18
27
|
|
|
@@ -43,6 +52,15 @@ function error(message) {
|
|
|
43
52
|
console.error(`[fantasta] Errore: ${message}`);
|
|
44
53
|
}
|
|
45
54
|
|
|
55
|
+
async function fileExists(filePath) {
|
|
56
|
+
try {
|
|
57
|
+
await access(filePath);
|
|
58
|
+
return true;
|
|
59
|
+
} catch {
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
46
64
|
async function waitForHealth(url, label, healthPath = "/api/health", timeoutMs = 30_000) {
|
|
47
65
|
const deadline = Date.now() + timeoutMs;
|
|
48
66
|
while (Date.now() < deadline) {
|
|
@@ -127,6 +145,73 @@ function electronBinary() {
|
|
|
127
145
|
}
|
|
128
146
|
}
|
|
129
147
|
|
|
148
|
+
function cliVersion() {
|
|
149
|
+
try {
|
|
150
|
+
return require(path.join(__dirname, "..", "package.json")).version;
|
|
151
|
+
} catch {
|
|
152
|
+
return "0.0.0";
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Assemblea una vera Fantasta.app (macOS) dentro DATA_DIR copiando il dist
|
|
157
|
+
// Electron e patchando plist, icona e identificatore, poi rifirma adhoc.
|
|
158
|
+
// Ritorna il percorso del bundle. Se il bundle esiste già e la versione di
|
|
159
|
+
// Electron/risorse non è cambiata, lo riusa.
|
|
160
|
+
async function ensureMacAppBundle(binary) {
|
|
161
|
+
const distApp = path.resolve(path.dirname(binary), "../.."); // .../dist/Electron.app
|
|
162
|
+
const electronVersion = await readFile(path.join(path.dirname(distApp), "version"), "utf8").then((v) => v.trim()).catch(() => "unknown");
|
|
163
|
+
const marker = `${electronVersion}|${cliVersion()}`;
|
|
164
|
+
const bundlePath = path.join(DATA_DIR, "Fantasta.app");
|
|
165
|
+
const markerPath = path.join(DATA_DIR, ".bundle-version");
|
|
166
|
+
|
|
167
|
+
try {
|
|
168
|
+
const current = (await readFile(markerPath, "utf8")).trim();
|
|
169
|
+
if (current === marker && (await fileExists(bundlePath))) return bundlePath;
|
|
170
|
+
} catch {
|
|
171
|
+
// nessun marker: si assembla
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
log("Preparazione app Fantasta (solo la prima volta, pochi secondi)...");
|
|
175
|
+
await rm(bundlePath, { recursive: true, force: true });
|
|
176
|
+
|
|
177
|
+
// 1. Copia il dist Electron come base del bundle. Su macOS si usa `ditto`:
|
|
178
|
+
// fs.cp risolverebbe i symlink relativi dei framework in percorsi
|
|
179
|
+
// assoluti, rendendo il bundle non autonomo (icudtl.dat non trovato).
|
|
180
|
+
execFileSync("ditto", [distApp, bundlePath], { stdio: "ignore" });
|
|
181
|
+
|
|
182
|
+
// 2. Patch dell'Info.plist: nome, icona, identificatore e versione.
|
|
183
|
+
const plist = path.join(bundlePath, "Contents", "Info.plist");
|
|
184
|
+
const replacements = [
|
|
185
|
+
["CFBundleName", "Fantasta"],
|
|
186
|
+
["CFBundleDisplayName", "Fantasta"],
|
|
187
|
+
["CFBundleIdentifier", "com.fantasta.desktop"],
|
|
188
|
+
["CFBundleIconFile", "icon.icns"],
|
|
189
|
+
["CFBundleShortVersionString", cliVersion()],
|
|
190
|
+
["CFBundleVersion", cliVersion()],
|
|
191
|
+
];
|
|
192
|
+
for (const [key, value] of replacements) {
|
|
193
|
+
execFileSync("plutil", ["-replace", key, "-string", value, plist]);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// 3. Sostituisci l'icona di default (electron.icns) con quella di Fantasta.
|
|
197
|
+
await rm(path.join(bundlePath, "Contents", "Resources", "electron.icns"), { force: true });
|
|
198
|
+
await copyFile(path.join(appDir, "build", "icon.icns"), path.join(bundlePath, "Contents", "Resources", "icon.icns"));
|
|
199
|
+
|
|
200
|
+
// 4. Copia le risorse dell'app dentro il bundle: main.cjs/preload/icone in
|
|
201
|
+
// Contents/Resources/app, e server + renderer in Contents/Resources.
|
|
202
|
+
await rm(path.join(bundlePath, "Contents", "Resources", "app"), { recursive: true, force: true });
|
|
203
|
+
execFileSync("ditto", [appDir, path.join(bundlePath, "Contents", "Resources", "app")], { stdio: "ignore" });
|
|
204
|
+
await copyFile(serverEntry, path.join(bundlePath, "Contents", "Resources", "local-server.cjs"));
|
|
205
|
+
await rm(path.join(bundlePath, "Contents", "Resources", "web"), { recursive: true, force: true });
|
|
206
|
+
execFileSync("ditto", [path.join(resourcesRoot, "web"), path.join(bundlePath, "Contents", "Resources", "web")], { stdio: "ignore" });
|
|
207
|
+
|
|
208
|
+
// 5. Rifirma adhoc (la modifica del plist invalida la firma originale).
|
|
209
|
+
execFileSync("codesign", ["--force", "--deep", "--sign", "-", bundlePath], { stdio: "ignore" });
|
|
210
|
+
|
|
211
|
+
await writeFile(markerPath, marker);
|
|
212
|
+
return bundlePath;
|
|
213
|
+
}
|
|
214
|
+
|
|
130
215
|
async function runBrowserMode() {
|
|
131
216
|
log("Modalità browser (senza finestra).");
|
|
132
217
|
startServer();
|
|
@@ -188,6 +273,38 @@ async function runBrowserMode() {
|
|
|
188
273
|
}
|
|
189
274
|
}
|
|
190
275
|
|
|
276
|
+
async function runMacDesktopMode(binary) {
|
|
277
|
+
try {
|
|
278
|
+
const bundlePath = await ensureMacAppBundle(binary);
|
|
279
|
+
const executable = path.join(bundlePath, "Contents", "MacOS", "Electron");
|
|
280
|
+
const logPath = path.join(DATA_DIR, "desktop.log");
|
|
281
|
+
const logFd = await open(logPath, "a");
|
|
282
|
+
log(`Avvio app desktop da ${bundlePath}`);
|
|
283
|
+
log("App desktop avviata: chiudi la finestra per fermarla.");
|
|
284
|
+
log(`Log: ${logPath}`);
|
|
285
|
+
const child = spawn(executable, [`--user-data-dir=${DATA_DIR}`], {
|
|
286
|
+
env: {
|
|
287
|
+
...process.env,
|
|
288
|
+
FANTASTA_NPM_MODE: "1",
|
|
289
|
+
FANTASTA_HOST: HOST,
|
|
290
|
+
FANTASTA_PORT: LOCAL_PORT,
|
|
291
|
+
FANTASTA_RENDERER_PORT: RENDERER_PORT,
|
|
292
|
+
FANTASTA_DATABASE_PATH: DB_PATH,
|
|
293
|
+
FANTASTA_LOCAL_SERVER_URL: LOCAL_URL,
|
|
294
|
+
},
|
|
295
|
+
stdio: ["ignore", logFd, logFd],
|
|
296
|
+
detached: true,
|
|
297
|
+
});
|
|
298
|
+
// Il launcher esce subito: se restasse vivo, macOS mostrerebbe un'icona
|
|
299
|
+
// "exec" nel Dock accanto all'app.
|
|
300
|
+
child.unref();
|
|
301
|
+
} catch (cause) {
|
|
302
|
+
error(cause instanceof Error ? cause.message : String(cause));
|
|
303
|
+
log("Ripiego sulla modalità browser.");
|
|
304
|
+
return runBrowserMode();
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
191
308
|
async function runDesktopMode() {
|
|
192
309
|
const binary = electronBinary();
|
|
193
310
|
if (!binary) {
|
|
@@ -196,6 +313,12 @@ async function runDesktopMode() {
|
|
|
196
313
|
}
|
|
197
314
|
// Crea la directory dei dati prima che Electron apra il database.
|
|
198
315
|
await mkdir(DATA_DIR, { recursive: true });
|
|
316
|
+
if (process.platform === "darwin") {
|
|
317
|
+
await runMacDesktopMode(binary);
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
// Windows/Linux: lancio del binario Electron con le risorse del pacchetto.
|
|
321
|
+
// (Su queste piattaforme non esiste il problema dell'icona "exec" nel Dock.)
|
|
199
322
|
log(`Avvio app desktop (Electron) da ${appDir}`);
|
|
200
323
|
const child = spawn(binary, [appDir], {
|
|
201
324
|
env: {
|
|
@@ -233,4 +356,4 @@ async function main() {
|
|
|
233
356
|
}
|
|
234
357
|
}
|
|
235
358
|
|
|
236
|
-
void main();
|
|
359
|
+
void main();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fantasta",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.2",
|
|
4
4
|
"description": "Asta di Fantacalcio in LAN senza server: avvia la regia come vera app desktop e i partecipanti si collegano dal telefono. Nessuna installazione richiesta.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "commonjs",
|