2026-03-16 10:31:59 +01:00
|
|
|
|
# SOP – CadRoute MVP
|
2026-03-05 14:45:06 +01:00
|
|
|
|
|
|
|
|
|
|
## Obiettivo
|
|
|
|
|
|
Realizzare un'app desktop (.exe) che:
|
|
|
|
|
|
- accetti una cartella o uno ZIP
|
2026-03-05 16:11:01 +01:00
|
|
|
|
- legga file CAD (.prt .asm .dwr)
|
2026-03-05 14:45:06 +01:00
|
|
|
|
- analizzi nome file ed estensione
|
|
|
|
|
|
- applichi regole di routing
|
|
|
|
|
|
- copi i file verso destinazioni configurate (anche share di rete)
|
|
|
|
|
|
|
|
|
|
|
|
Tecnologie:
|
|
|
|
|
|
- Node.js
|
|
|
|
|
|
- Electron
|
|
|
|
|
|
- unzipper
|
|
|
|
|
|
- fs-extra
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 1. Prerequisiti
|
|
|
|
|
|
|
|
|
|
|
|
Installare:
|
|
|
|
|
|
- Node.js LTS
|
|
|
|
|
|
- Git
|
|
|
|
|
|
- Visual Studio Code / VSCodium
|
|
|
|
|
|
|
|
|
|
|
|
Verifica:
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
node -v
|
|
|
|
|
|
npm -v
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 2. Creazione progetto
|
|
|
|
|
|
|
|
|
|
|
|
```
|
2026-03-16 10:31:59 +01:00
|
|
|
|
mkdir cadroute
|
|
|
|
|
|
cd cadroute
|
2026-03-05 14:45:06 +01:00
|
|
|
|
npm init -y
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
Installare dipendenze:
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
npm install electron unzipper fs-extra
|
|
|
|
|
|
npm install electron-builder --save-dev
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 3. Struttura progetto
|
|
|
|
|
|
|
|
|
|
|
|
```
|
2026-03-16 10:31:59 +01:00
|
|
|
|
cadroute
|
2026-03-05 14:45:06 +01:00
|
|
|
|
│
|
|
|
|
|
|
├── package.json
|
|
|
|
|
|
├── main.js
|
|
|
|
|
|
├── preload.js
|
|
|
|
|
|
│
|
|
|
|
|
|
├── renderer
|
|
|
|
|
|
│ ├── index.html
|
|
|
|
|
|
│ └── renderer.js
|
|
|
|
|
|
│
|
|
|
|
|
|
├── services
|
|
|
|
|
|
│ ├── router.js
|
|
|
|
|
|
│ ├── zipProcessor.js
|
2026-03-05 16:11:01 +01:00
|
|
|
|
│ └── folderProcessor.js
|
2026-03-05 14:45:06 +01:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
2026-03-05 16:11:01 +01:00
|
|
|
|
## 4. Configurazione runtime
|
2026-03-05 14:45:06 +01:00
|
|
|
|
|
2026-03-05 16:11:01 +01:00
|
|
|
|
La destinazione viene gestita in memoria in `main.js` e modificata dalla UI durante l'esecuzione.
|
2026-03-05 14:45:06 +01:00
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
2026-03-05 16:11:01 +01:00
|
|
|
|
## 5. Motore di routing
|
2026-03-05 14:45:06 +01:00
|
|
|
|
|
|
|
|
|
|
`services/router.js`
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
const path = require("path")
|
|
|
|
|
|
|
|
|
|
|
|
function findDestination(filename, config){
|
|
|
|
|
|
|
|
|
|
|
|
const ext = path.extname(filename).slice(1)
|
|
|
|
|
|
|
|
|
|
|
|
for(const rule of config.rules){
|
|
|
|
|
|
|
|
|
|
|
|
if(rule.ext !== ext)
|
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
|
|
if(!rule.pattern)
|
|
|
|
|
|
return rule.destination
|
|
|
|
|
|
|
|
|
|
|
|
const regex = new RegExp(rule.pattern)
|
|
|
|
|
|
|
|
|
|
|
|
if(regex.test(filename))
|
|
|
|
|
|
return rule.destination
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return null
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
module.exports = { findDestination }
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 7. Processare una cartella
|
|
|
|
|
|
|
|
|
|
|
|
`services/folderProcessor.js`
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
const fs = require("fs-extra")
|
|
|
|
|
|
const path = require("path")
|
|
|
|
|
|
const { findDestination } = require("./router")
|
|
|
|
|
|
|
|
|
|
|
|
async function processFolder(folder, config){
|
|
|
|
|
|
|
|
|
|
|
|
const files = await fs.readdir(folder)
|
|
|
|
|
|
|
|
|
|
|
|
for(const file of files){
|
|
|
|
|
|
|
|
|
|
|
|
const src = path.join(folder,file)
|
|
|
|
|
|
|
|
|
|
|
|
const destDir = findDestination(file, config)
|
|
|
|
|
|
|
|
|
|
|
|
if(!destDir) continue
|
|
|
|
|
|
|
|
|
|
|
|
const dest = path.join(destDir,file)
|
|
|
|
|
|
|
|
|
|
|
|
await fs.copy(src,dest)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
module.exports = { processFolder }
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 8. Processare ZIP
|
|
|
|
|
|
|
|
|
|
|
|
`services/zipProcessor.js`
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
const unzipper = require("unzipper")
|
|
|
|
|
|
const fs = require("fs-extra")
|
|
|
|
|
|
const path = require("path")
|
|
|
|
|
|
const { findDestination } = require("./router")
|
|
|
|
|
|
|
|
|
|
|
|
async function processZip(zipPath, config){
|
|
|
|
|
|
|
|
|
|
|
|
const stream = fs.createReadStream(zipPath)
|
|
|
|
|
|
.pipe(unzipper.Parse())
|
|
|
|
|
|
|
|
|
|
|
|
for await (const entry of stream){
|
|
|
|
|
|
|
|
|
|
|
|
const file = entry.path
|
|
|
|
|
|
|
|
|
|
|
|
const destDir = findDestination(file, config)
|
|
|
|
|
|
|
|
|
|
|
|
if(!destDir){
|
|
|
|
|
|
entry.autodrain()
|
|
|
|
|
|
continue
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const dest = path.join(destDir,path.basename(file))
|
|
|
|
|
|
|
|
|
|
|
|
entry.pipe(fs.createWriteStream(dest))
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
module.exports = { processZip }
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 9. Main Electron
|
|
|
|
|
|
|
|
|
|
|
|
`main.js`
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
const { app, BrowserWindow, dialog, ipcMain } = require("electron")
|
|
|
|
|
|
const path = require("path")
|
|
|
|
|
|
|
|
|
|
|
|
const { processFolder } = require("./services/folderProcessor")
|
|
|
|
|
|
const { processZip } = require("./services/zipProcessor")
|
2026-03-05 16:11:01 +01:00
|
|
|
|
const CAD_EXTENSIONS = ["prt", "asm", "dwr"]
|
|
|
|
|
|
const DEFAULT_DESTINATION = "./output/cad"
|
2026-03-05 14:45:06 +01:00
|
|
|
|
|
2026-03-05 16:11:01 +01:00
|
|
|
|
let config = {
|
|
|
|
|
|
destination: DEFAULT_DESTINATION,
|
|
|
|
|
|
rules: CAD_EXTENSIONS.map((ext) => ({ ext, destination: DEFAULT_DESTINATION }))
|
|
|
|
|
|
}
|
2026-03-05 14:45:06 +01:00
|
|
|
|
|
|
|
|
|
|
function createWindow(){
|
|
|
|
|
|
|
|
|
|
|
|
const win = new BrowserWindow({
|
|
|
|
|
|
width:800,
|
|
|
|
|
|
height:600,
|
|
|
|
|
|
webPreferences:{
|
|
|
|
|
|
preload: path.join(__dirname,"preload.js")
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
win.loadFile("renderer/index.html")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
app.whenReady().then(createWindow)
|
|
|
|
|
|
|
|
|
|
|
|
ipcMain.handle("select-folder", async () => {
|
|
|
|
|
|
|
|
|
|
|
|
const result = await dialog.showOpenDialog({
|
|
|
|
|
|
properties:["openDirectory"]
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
if(result.canceled) return
|
|
|
|
|
|
|
|
|
|
|
|
await processFolder(result.filePaths[0], config)
|
|
|
|
|
|
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
ipcMain.handle("select-zip", async () => {
|
|
|
|
|
|
|
|
|
|
|
|
const result = await dialog.showOpenDialog({
|
|
|
|
|
|
filters:[{ name:"Zip", extensions:["zip"] }]
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
if(result.canceled) return
|
|
|
|
|
|
|
|
|
|
|
|
await processZip(result.filePaths[0], config)
|
|
|
|
|
|
|
|
|
|
|
|
})
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 10. Preload
|
|
|
|
|
|
|
|
|
|
|
|
`preload.js`
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
const { contextBridge, ipcRenderer } = require("electron")
|
|
|
|
|
|
|
|
|
|
|
|
contextBridge.exposeInMainWorld("api", {
|
|
|
|
|
|
|
|
|
|
|
|
selectFolder: () => ipcRenderer.invoke("select-folder"),
|
|
|
|
|
|
|
|
|
|
|
|
selectZip: () => ipcRenderer.invoke("select-zip")
|
|
|
|
|
|
|
|
|
|
|
|
})
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 11. UI minima
|
|
|
|
|
|
|
|
|
|
|
|
`renderer/index.html`
|
|
|
|
|
|
|
|
|
|
|
|
```
|
2026-03-16 10:31:59 +01:00
|
|
|
|
<h2>CadRoute</h2>
|
2026-03-05 14:45:06 +01:00
|
|
|
|
|
|
|
|
|
|
<button onclick="window.api.selectFolder()">
|
|
|
|
|
|
Process Folder
|
|
|
|
|
|
</button>
|
|
|
|
|
|
|
|
|
|
|
|
<button onclick="window.api.selectZip()">
|
|
|
|
|
|
Process ZIP
|
|
|
|
|
|
</button>
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 12. Avvio applicazione
|
|
|
|
|
|
|
|
|
|
|
|
Nel package.json aggiungere:
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
"scripts": {
|
|
|
|
|
|
"start": "electron ."
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
Avviare:
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
npm start
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 13. Build .exe
|
|
|
|
|
|
|
|
|
|
|
|
Aggiungere nel package.json:
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
"build": {
|
2026-03-16 10:31:59 +01:00
|
|
|
|
"appId": "com.cadroute",
|
2026-03-05 14:45:06 +01:00
|
|
|
|
"win": {
|
|
|
|
|
|
"target": "nsis"
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
Build:
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
npx electron-builder
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
Output:
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
dist/
|
2026-03-16 10:31:59 +01:00
|
|
|
|
CadRoute Setup.exe
|
2026-03-05 14:45:06 +01:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## MVP completato quando
|
|
|
|
|
|
|
|
|
|
|
|
- l'app si avvia
|
|
|
|
|
|
- si seleziona ZIP o cartella
|
2026-03-05 16:11:01 +01:00
|
|
|
|
- i file .prt .asm .dwr vengono analizzati
|
2026-03-05 14:45:06 +01:00
|
|
|
|
- i file vengono copiati nelle destinazioni configurate
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## Migliorie future
|
|
|
|
|
|
|
|
|
|
|
|
- progress bar
|
|
|
|
|
|
- drag and drop ZIP
|
2026-03-05 16:11:01 +01:00
|
|
|
|
- modifica regole avanzate da UI
|
2026-03-05 14:45:06 +01:00
|
|
|
|
- logging operazioni
|
|
|
|
|
|
- parallelismo file
|
|
|
|
|
|
- watch folder automatico
|