Imposta app desktop Electron/Vue con backend FastAPI
- Aggiunge scaffold frontend Vue con UI base e build verso renderer - Introduce backend FastAPI con endpoint generazione e salvataggio JSON - Sposta i generatori in backend e aggiorna CLI main.py - Aggiorna README, requirements e .gitignore per artefatti e config - Configura Electron (main/preload) e script npm per dev/build
This commit is contained in:
15
.gitignore
vendored
15
.gitignore
vendored
@@ -8,3 +8,18 @@ venv/
|
||||
|
||||
# JSON files (default ignore)
|
||||
*.json
|
||||
|
||||
# Keep essential config JSON tracked
|
||||
!package.json
|
||||
!package-lock.json
|
||||
|
||||
# Python caches/build
|
||||
__pycache__/
|
||||
*.pyc
|
||||
backend/build/
|
||||
backend/dist/
|
||||
|
||||
# Node/Electron outputs
|
||||
node_modules/
|
||||
desktop/renderer/
|
||||
release/
|
||||
|
||||
@@ -40,7 +40,7 @@ Dopo aver selezionato un'opzione, lo script dedicato verrà eseguito e guida l'u
|
||||
- Eventuale utilizzo di chiavi compresse/non compresse
|
||||
- Visualizzazione e salvataggio dei dati in un file `.json`
|
||||
|
||||
Ogni script è indipendente (`p2pk.py`, `p2pkh.py`, `p2sh.py`, `p2wpkh.py`, `p2tr.py`) e implementa le regole specifiche del relativo standard Bitcoin.
|
||||
Ogni script è indipendente (`backend/p2pk.py`, `backend/p2pkh.py`, `backend/p2sh.py`, `backend/p2wpkh.py`, `backend/p2tr.py`) e implementa le regole specifiche del relativo standard Bitcoin.
|
||||
|
||||
---
|
||||
|
||||
@@ -139,4 +139,4 @@ I dati saranno salvati in un file `.json` leggibile e riutilizzabile.
|
||||
---
|
||||
|
||||
## LICENZA
|
||||
Questo progetto è rilasciato sotto licenza MIT
|
||||
Questo progetto è rilasciato sotto licenza MIT
|
||||
|
||||
0
backend/__init__.py
Normal file
0
backend/__init__.py
Normal file
177
backend/app.py
Normal file
177
backend/app.py
Normal file
@@ -0,0 +1,177 @@
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
|
||||
from fastapi import FastAPI, HTTPException
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from pydantic import BaseModel
|
||||
|
||||
ROOT_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
|
||||
if ROOT_DIR not in sys.path:
|
||||
sys.path.insert(0, ROOT_DIR)
|
||||
|
||||
import p2pk
|
||||
import p2pkh
|
||||
import p2sh
|
||||
import p2tr
|
||||
import p2wpkh
|
||||
|
||||
app = FastAPI(title="AddressGen API", version="0.1.0")
|
||||
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=["*"],
|
||||
allow_methods=["*"],
|
||||
allow_headers=["*"],
|
||||
)
|
||||
|
||||
|
||||
class P2PKRequest(BaseModel):
|
||||
network: str = "mainnet"
|
||||
compressed: bool = False
|
||||
save_to_file: bool = False
|
||||
filename: Optional[str] = None
|
||||
|
||||
|
||||
class P2PKHRequest(BaseModel):
|
||||
network: str = "mainnet"
|
||||
compressed: bool = True
|
||||
save_to_file: bool = False
|
||||
filename: Optional[str] = None
|
||||
|
||||
|
||||
class P2WPKHRequest(BaseModel):
|
||||
network: str = "mainnet"
|
||||
compressed: bool = True
|
||||
save_to_file: bool = False
|
||||
filename: Optional[str] = None
|
||||
|
||||
|
||||
class P2TRRequest(BaseModel):
|
||||
network: str = "mainnet"
|
||||
save_to_file: bool = False
|
||||
filename: Optional[str] = None
|
||||
|
||||
|
||||
class P2SHRequest(BaseModel):
|
||||
network: str = "mainnet"
|
||||
m: int = 2
|
||||
n: int = 3
|
||||
compressed: bool = True
|
||||
sort_pubkeys: bool = True
|
||||
save_to_file: bool = False
|
||||
filename: Optional[str] = None
|
||||
|
||||
|
||||
class SaveRequest(BaseModel):
|
||||
data: dict
|
||||
filename: Optional[str] = None
|
||||
|
||||
|
||||
def _save_json(data: dict, filename: Optional[str] = None) -> str:
|
||||
wallets_dir = os.path.join(ROOT_DIR, "wallets")
|
||||
os.makedirs(wallets_dir, exist_ok=True)
|
||||
|
||||
if filename:
|
||||
safe = os.path.basename(filename)
|
||||
if not safe.endswith(".json"):
|
||||
safe += ".json"
|
||||
out_name = safe
|
||||
else:
|
||||
script_type = data.get("script_type", "wallet")
|
||||
network = data.get("network", "net")
|
||||
stamp = datetime.utcnow().strftime("%Y%m%d_%H%M%S")
|
||||
out_name = f"{script_type}_{network}_{stamp}.json"
|
||||
|
||||
out_path = os.path.join(wallets_dir, out_name)
|
||||
with open(out_path, "w", encoding="utf-8") as f:
|
||||
json.dump(data, f, indent=4)
|
||||
return out_path
|
||||
|
||||
|
||||
@app.get("/health")
|
||||
def health():
|
||||
return {"status": "ok"}
|
||||
|
||||
|
||||
@app.post("/p2pk")
|
||||
def create_p2pk(req: P2PKRequest):
|
||||
try:
|
||||
result = p2pk.generate_p2pk(req.network, req.compressed)
|
||||
if req.save_to_file:
|
||||
result["saved_path"] = _save_json(result, req.filename)
|
||||
return result
|
||||
except Exception as exc:
|
||||
raise HTTPException(status_code=400, detail=str(exc))
|
||||
|
||||
|
||||
@app.post("/p2pkh")
|
||||
def create_p2pkh(req: P2PKHRequest):
|
||||
try:
|
||||
result = p2pkh.generate_legacy_address(req.network, req.compressed)
|
||||
if req.save_to_file:
|
||||
result["saved_path"] = _save_json(result, req.filename)
|
||||
return result
|
||||
except Exception as exc:
|
||||
raise HTTPException(status_code=400, detail=str(exc))
|
||||
|
||||
|
||||
@app.post("/p2wpkh")
|
||||
def create_p2wpkh(req: P2WPKHRequest):
|
||||
try:
|
||||
result = p2wpkh.generate_segwit_address(req.network, req.compressed)
|
||||
if req.save_to_file:
|
||||
result["saved_path"] = _save_json(result, req.filename)
|
||||
return result
|
||||
except Exception as exc:
|
||||
raise HTTPException(status_code=400, detail=str(exc))
|
||||
|
||||
|
||||
@app.post("/p2tr")
|
||||
def create_p2tr(req: P2TRRequest):
|
||||
try:
|
||||
result = p2tr.generate_p2tr_address(req.network)
|
||||
if req.save_to_file:
|
||||
result["saved_path"] = _save_json(result, req.filename)
|
||||
return result
|
||||
except Exception as exc:
|
||||
raise HTTPException(status_code=400, detail=str(exc))
|
||||
|
||||
|
||||
@app.post("/p2sh")
|
||||
def create_p2sh(req: P2SHRequest):
|
||||
try:
|
||||
result = p2sh.generate_p2sh_multisig(
|
||||
network=req.network,
|
||||
m=req.m,
|
||||
n=req.n,
|
||||
compressed=req.compressed,
|
||||
sort_pubkeys=req.sort_pubkeys,
|
||||
)
|
||||
if req.save_to_file:
|
||||
result["saved_path"] = _save_json(result, req.filename)
|
||||
return result
|
||||
except Exception as exc:
|
||||
raise HTTPException(status_code=400, detail=str(exc))
|
||||
|
||||
|
||||
@app.post("/save")
|
||||
def save_result(req: SaveRequest):
|
||||
try:
|
||||
saved_path = _save_json(req.data, req.filename)
|
||||
return {"saved_path": saved_path}
|
||||
except Exception as exc:
|
||||
raise HTTPException(status_code=400, detail=str(exc))
|
||||
|
||||
|
||||
def run():
|
||||
import uvicorn
|
||||
|
||||
port = int(os.getenv("ADDRESSGEN_PORT", "8732"))
|
||||
uvicorn.run("backend.app:app", host="127.0.0.1", port=port, log_level="info")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
run()
|
||||
2
backend/requirements.txt
Normal file
2
backend/requirements.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
fastapi
|
||||
uvicorn[standard]
|
||||
66
desktop/main.js
Normal file
66
desktop/main.js
Normal file
@@ -0,0 +1,66 @@
|
||||
const { app, BrowserWindow } = require("electron");
|
||||
const path = require("path");
|
||||
const { spawn } = require("child_process");
|
||||
|
||||
const BACKEND_PORT = process.env.ADDRESSGEN_PORT || "8732";
|
||||
const API_BASE = `http://127.0.0.1:${BACKEND_PORT}`;
|
||||
|
||||
let backendProcess = null;
|
||||
|
||||
function startBackend() {
|
||||
const env = {
|
||||
...process.env,
|
||||
ADDRESSGEN_PORT: BACKEND_PORT,
|
||||
};
|
||||
|
||||
if (process.env.BACKEND_BIN) {
|
||||
backendProcess = spawn(process.env.BACKEND_BIN, [], { env, stdio: "inherit" });
|
||||
return;
|
||||
}
|
||||
|
||||
if (app.isPackaged) {
|
||||
const binPath = path.join(process.resourcesPath, "backend", "addressgen-backend");
|
||||
backendProcess = spawn(binPath, [], { env, stdio: "inherit" });
|
||||
} else {
|
||||
const python = process.env.PYTHON || "python3";
|
||||
const script = path.join(__dirname, "..", "backend", "app.py");
|
||||
backendProcess = spawn(python, [script], { env, stdio: "inherit" });
|
||||
}
|
||||
}
|
||||
|
||||
function createWindow() {
|
||||
process.env.ADDRESSGEN_API_BASE = API_BASE;
|
||||
|
||||
const win = new BrowserWindow({
|
||||
width: 1100,
|
||||
height: 720,
|
||||
webPreferences: {
|
||||
preload: path.join(__dirname, "preload.js"),
|
||||
contextIsolation: true,
|
||||
nodeIntegration: false,
|
||||
},
|
||||
});
|
||||
|
||||
if (app.isPackaged) {
|
||||
win.loadFile(path.join(__dirname, "renderer", "index.html"));
|
||||
} else {
|
||||
win.loadURL("http://localhost:5173");
|
||||
}
|
||||
}
|
||||
|
||||
app.whenReady().then(() => {
|
||||
startBackend();
|
||||
createWindow();
|
||||
|
||||
app.on("activate", () => {
|
||||
if (BrowserWindow.getAllWindows().length === 0) {
|
||||
createWindow();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
app.on("before-quit", () => {
|
||||
if (backendProcess) {
|
||||
backendProcess.kill();
|
||||
}
|
||||
});
|
||||
5
desktop/preload.js
Normal file
5
desktop/preload.js
Normal file
@@ -0,0 +1,5 @@
|
||||
const { contextBridge } = require("electron");
|
||||
|
||||
contextBridge.exposeInMainWorld("addressgen", {
|
||||
apiBase: process.env.ADDRESSGEN_API_BASE || "http://127.0.0.1:8732",
|
||||
});
|
||||
12
frontend/index.html
Normal file
12
frontend/index.html
Normal file
@@ -0,0 +1,12 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>AddressGen</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
265
frontend/src/App.vue
Normal file
265
frontend/src/App.vue
Normal file
@@ -0,0 +1,265 @@
|
||||
<template>
|
||||
<div class="page">
|
||||
<header class="hero">
|
||||
<h1>AddressGen</h1>
|
||||
<p>Genera indirizzi Bitcoin localmente usando il backend Python.</p>
|
||||
</header>
|
||||
|
||||
<section class="card">
|
||||
<div class="row">
|
||||
<label>Tipo</label>
|
||||
<select v-model="form.type">
|
||||
<option value="p2pk">P2PK</option>
|
||||
<option value="p2pkh">P2PKH</option>
|
||||
<option value="p2wpkh">P2WPKH</option>
|
||||
<option value="p2tr">P2TR</option>
|
||||
<option value="p2sh">P2SH Multisig</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<label>Network</label>
|
||||
<select v-model="form.network">
|
||||
<option value="mainnet">mainnet</option>
|
||||
<option value="testnet">testnet</option>
|
||||
<option value="regtest">regtest</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="row" v-if="form.type !== 'p2tr'">
|
||||
<label>Chiavi compresse</label>
|
||||
<input type="checkbox" v-model="form.compressed" />
|
||||
</div>
|
||||
|
||||
<div class="row" v-if="form.type === 'p2sh'">
|
||||
<label>m-of-n</label>
|
||||
<div class="inline">
|
||||
<input type="number" min="1" max="16" v-model.number="form.m" />
|
||||
<span>/</span>
|
||||
<input type="number" min="1" max="16" v-model.number="form.n" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<label>Nome file</label>
|
||||
<input type="text" v-model="form.filename" placeholder="wallet.json (opzionale)" />
|
||||
</div>
|
||||
|
||||
<div class="actions">
|
||||
<button class="primary" @click="generate" :disabled="loading">
|
||||
{{ loading ? "Genero..." : "Genera" }}
|
||||
</button>
|
||||
<button class="secondary" @click="save" :disabled="loading || !result">
|
||||
Salva JSON
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="card" v-if="result">
|
||||
<h2>Risultato</h2>
|
||||
<pre>{{ result }}</pre>
|
||||
</section>
|
||||
|
||||
<section class="card" v-if="savedPath">
|
||||
<h2>Salvato</h2>
|
||||
<pre>{{ savedPath }}</pre>
|
||||
</section>
|
||||
|
||||
<section class="card" v-if="error">
|
||||
<h2>Errore</h2>
|
||||
<pre>{{ error }}</pre>
|
||||
</section>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from "vue";
|
||||
|
||||
const apiBase = window?.addressgen?.apiBase || "http://127.0.0.1:8732";
|
||||
|
||||
const form = ref({
|
||||
type: "p2pkh",
|
||||
network: "mainnet",
|
||||
compressed: true,
|
||||
m: 2,
|
||||
n: 3,
|
||||
filename: "",
|
||||
});
|
||||
|
||||
const result = ref("");
|
||||
const error = ref("");
|
||||
const loading = ref(false);
|
||||
const savedPath = ref("");
|
||||
|
||||
const generate = async () => {
|
||||
loading.value = true;
|
||||
result.value = "";
|
||||
error.value = "";
|
||||
savedPath.value = "";
|
||||
|
||||
try {
|
||||
const endpoint = `${apiBase}/${form.value.type}`;
|
||||
const payload = {
|
||||
network: form.value.network,
|
||||
};
|
||||
|
||||
if (form.value.type !== "p2tr") {
|
||||
payload.compressed = form.value.compressed;
|
||||
}
|
||||
|
||||
if (form.value.type === "p2sh") {
|
||||
payload.m = form.value.m;
|
||||
payload.n = form.value.n;
|
||||
}
|
||||
|
||||
const res = await fetch(endpoint, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
|
||||
const data = await res.json();
|
||||
if (!res.ok) {
|
||||
throw new Error(data?.detail || "Errore sconosciuto");
|
||||
}
|
||||
|
||||
result.value = JSON.stringify(data, null, 2);
|
||||
} catch (err) {
|
||||
error.value = err?.message || String(err);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const save = async () => {
|
||||
if (!result.value) return;
|
||||
loading.value = true;
|
||||
error.value = "";
|
||||
savedPath.value = "";
|
||||
|
||||
try {
|
||||
const payload = {
|
||||
data: JSON.parse(result.value),
|
||||
};
|
||||
if (form.value.filename?.trim()) {
|
||||
payload.filename = form.value.filename.trim();
|
||||
}
|
||||
|
||||
const res = await fetch(`${apiBase}/save`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
const data = await res.json();
|
||||
if (!res.ok) {
|
||||
throw new Error(data?.detail || "Errore sconosciuto");
|
||||
}
|
||||
|
||||
savedPath.value = data.saved_path || "";
|
||||
} catch (err) {
|
||||
error.value = err?.message || String(err);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
:root {
|
||||
font-family: "Fira Sans", sans-serif;
|
||||
}
|
||||
|
||||
.page {
|
||||
min-height: 100vh;
|
||||
background: linear-gradient(135deg, #0f172a, #1e293b);
|
||||
color: #e2e8f0;
|
||||
padding: 32px;
|
||||
}
|
||||
|
||||
.hero {
|
||||
max-width: 720px;
|
||||
margin: 0 auto 24px;
|
||||
}
|
||||
|
||||
.hero h1 {
|
||||
font-size: 40px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.card {
|
||||
background: rgba(15, 23, 42, 0.7);
|
||||
border: 1px solid rgba(148, 163, 184, 0.2);
|
||||
border-radius: 16px;
|
||||
padding: 20px;
|
||||
max-width: 720px;
|
||||
margin: 0 auto 20px;
|
||||
backdrop-filter: blur(6px);
|
||||
}
|
||||
|
||||
.row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.inline {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
input,
|
||||
select {
|
||||
padding: 8px 10px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid rgba(148, 163, 184, 0.3);
|
||||
background: #0f172a;
|
||||
color: #e2e8f0;
|
||||
}
|
||||
|
||||
.actions {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.primary {
|
||||
background: #38bdf8;
|
||||
color: #0f172a;
|
||||
border: none;
|
||||
padding: 10px 16px;
|
||||
border-radius: 10px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.primary:disabled {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.secondary {
|
||||
background: transparent;
|
||||
color: #e2e8f0;
|
||||
border: 1px solid rgba(148, 163, 184, 0.5);
|
||||
padding: 10px 16px;
|
||||
border-radius: 10px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.secondary:disabled {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
pre {
|
||||
white-space: pre-wrap;
|
||||
background: #0b1120;
|
||||
padding: 12px;
|
||||
border-radius: 10px;
|
||||
border: 1px solid rgba(148, 163, 184, 0.2);
|
||||
overflow-x: auto;
|
||||
}
|
||||
</style>
|
||||
4
frontend/src/main.js
Normal file
4
frontend/src/main.js
Normal file
@@ -0,0 +1,4 @@
|
||||
import { createApp } from "vue";
|
||||
import App from "./App.vue";
|
||||
|
||||
createApp(App).mount("#app");
|
||||
17
frontend/vite.config.js
Normal file
17
frontend/vite.config.js
Normal file
@@ -0,0 +1,17 @@
|
||||
import { defineConfig } from "vite";
|
||||
import vue from "@vitejs/plugin-vue";
|
||||
import path from "path";
|
||||
|
||||
export default defineConfig({
|
||||
root: path.resolve(__dirname),
|
||||
base: "./",
|
||||
plugins: [vue()],
|
||||
build: {
|
||||
outDir: path.resolve(__dirname, "../desktop/renderer"),
|
||||
emptyOutDir: true,
|
||||
},
|
||||
server: {
|
||||
port: 5173,
|
||||
strictPort: true,
|
||||
},
|
||||
});
|
||||
39
main.py
39
main.py
@@ -1,5 +1,8 @@
|
||||
import subprocess
|
||||
import sys
|
||||
import backend.p2pk as p2pk
|
||||
import backend.p2pkh as p2pkh
|
||||
import backend.p2sh as p2sh
|
||||
import backend.p2wpkh as p2wpkh
|
||||
import backend.p2tr as p2tr
|
||||
|
||||
def main():
|
||||
print("=== GENERATORE INDIRIZZI BITCOIN ===")
|
||||
@@ -12,23 +15,23 @@ def main():
|
||||
|
||||
choice = input("Inserisci la tua scelta: ").strip()
|
||||
|
||||
scripts = {
|
||||
'1': 'p2pk.py',
|
||||
'2': 'p2pkh.py',
|
||||
'3': 'p2sh.py',
|
||||
'4': 'p2wpkh.py',
|
||||
'5': 'p2tr.py'
|
||||
actions = {
|
||||
'1': p2pk.main,
|
||||
'2': p2pkh.main,
|
||||
'3': p2sh.main,
|
||||
'4': p2wpkh.main,
|
||||
'5': p2tr.main,
|
||||
}
|
||||
|
||||
if choice in scripts:
|
||||
try:
|
||||
subprocess.run([sys.executable, scripts[choice]], check=True)
|
||||
except subprocess.CalledProcessError as e:
|
||||
print(f"Errore nell'esecuzione dello script: {e}")
|
||||
except KeyboardInterrupt:
|
||||
print("\nOperazione interrotta.")
|
||||
else:
|
||||
|
||||
action = actions.get(choice)
|
||||
if not action:
|
||||
print("Scelta non valida.")
|
||||
return
|
||||
|
||||
try:
|
||||
action()
|
||||
except KeyboardInterrupt:
|
||||
print("\nOperazione interrotta.")
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
main()
|
||||
|
||||
5125
package-lock.json
generated
Normal file
5125
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
45
package.json
Normal file
45
package.json
Normal file
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"name": "addressgen",
|
||||
"version": "0.1.0",
|
||||
"description": "Desktop app for generating Bitcoin addresses",
|
||||
"main": "desktop/main.js",
|
||||
"scripts": {
|
||||
"setup:py": "python3 -m pip install -r requirements.txt",
|
||||
"dev:backend": "python3 backend/app.py",
|
||||
"dev:frontend": "vite --config frontend/vite.config.js",
|
||||
"dev:electron": "wait-on http://localhost:5173 && electron .",
|
||||
"dev": "concurrently -k \"npm:dev:backend\" \"npm:dev:frontend\" \"npm:dev:electron\"",
|
||||
"build:frontend": "vite build --config frontend/vite.config.js",
|
||||
"build:backend": "pyinstaller --name addressgen-backend --onefile backend/app.py --distpath backend/dist --workpath backend/build --specpath backend",
|
||||
"build": "npm run build:frontend && npm run build:backend && electron-builder --linux"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-vue": "^5.0.0",
|
||||
"concurrently": "^8.2.2",
|
||||
"electron": "^30.0.0",
|
||||
"electron-builder": "^24.13.3",
|
||||
"vite": "^5.4.0",
|
||||
"vue": "^3.4.0",
|
||||
"wait-on": "^7.2.0"
|
||||
},
|
||||
"build": {
|
||||
"appId": "com.addressgen.app",
|
||||
"productName": "AddressGen",
|
||||
"directories": {
|
||||
"output": "release"
|
||||
},
|
||||
"files": [
|
||||
"desktop/**",
|
||||
"frontend/**"
|
||||
],
|
||||
"extraResources": [
|
||||
{
|
||||
"from": "backend/dist/addressgen-backend",
|
||||
"to": "backend/addressgen-backend"
|
||||
}
|
||||
],
|
||||
"linux": {
|
||||
"target": ["AppImage"]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,3 +2,5 @@ base58==2.1.1
|
||||
bech32==1.2.0
|
||||
ecdsa==0.19.0
|
||||
six==1.17.0
|
||||
fastapi==0.115.0
|
||||
uvicorn[standard]==0.30.6
|
||||
|
||||
Reference in New Issue
Block a user