Add Electron desktop shell
This commit is contained in:
1 parent
42c5f68182
commit
c646f66e8f
9 files changed
+4228
-5
No files matched your search
@@ -1,7 +1,9 @@
|
|||||||
node_modules
|
node_modules
|
||||||
dist
|
dist
|
||||||
|
dist-electron
|
||||||
dist-ssr
|
dist-ssr
|
||||||
dist-archive
|
dist-archive
|
||||||
|
release
|
||||||
*.local
|
*.local
|
||||||
.vite
|
.vite
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,228 @@
|
|||||||
|
# Electron Desktop Plan
|
||||||
|
|
||||||
|
## Goal
|
||||||
|
|
||||||
|
Package RUWIO as a desktop app while keeping the existing Vue/Vite renderer as the primary application. The Electron layer should start small: host the built app, expose native filesystem dialogs, and later absorb the current server-side MP4 render workflow so desktop users do not need a separate render server.
|
||||||
|
|
||||||
|
## Current App Shape
|
||||||
|
|
||||||
|
- The UI is a Vue 3 + Vite single-page app in `src/`.
|
||||||
|
- The production web build is produced by `npm run build` into `dist/`.
|
||||||
|
- Project save/load is currently browser-driven:
|
||||||
|
- JSON project export/import lives in `src/lib/projectFile.ts`.
|
||||||
|
- `.ruw` bundle export/import lives in `src/lib/ruwFile.ts`.
|
||||||
|
- User-triggered file downloads use `Blob`, object URLs, and anchor clicks.
|
||||||
|
- Still/GIF/APNG style exports are implemented client-side.
|
||||||
|
- MP4 rendering is currently split out into `server/server.js`, using Express, Puppeteer Core, Chromium, and ffmpeg.
|
||||||
|
- The app already has PWA assets and static-host assumptions, which are compatible with an Electron renderer if paths are handled carefully.
|
||||||
|
|
||||||
|
## Recommended Architecture
|
||||||
|
|
||||||
|
Use a standard Electron split:
|
||||||
|
|
||||||
|
- `electron/main.ts`
|
||||||
|
- Creates the main `BrowserWindow`.
|
||||||
|
- Loads the Vite dev server during development.
|
||||||
|
- Loads `dist/index.html` in packaged builds.
|
||||||
|
- Owns native dialogs, app menu, recent files, and privileged filesystem/process work.
|
||||||
|
- `electron/preload.ts`
|
||||||
|
- Exposes a small typed API on `window.ruwioDesktop`.
|
||||||
|
- Uses `contextBridge` and IPC.
|
||||||
|
- Does not expose Node primitives directly.
|
||||||
|
- Vue renderer
|
||||||
|
- Remains browser-compatible.
|
||||||
|
- Detects the desktop API when available.
|
||||||
|
- Falls back to current browser file picker/download behavior on the web.
|
||||||
|
|
||||||
|
Keep Electron as an optional target rather than replacing the browser build. The same app should continue to run with `npm run dev`, `npm run build`, and static hosting.
|
||||||
|
|
||||||
|
## Package Additions
|
||||||
|
|
||||||
|
Add these development dependencies:
|
||||||
|
|
||||||
|
- `electron`
|
||||||
|
- `electron-builder` or `electron-forge`
|
||||||
|
- `concurrently` for local dev convenience
|
||||||
|
- `wait-on` if the Electron dev command waits for Vite
|
||||||
|
|
||||||
|
Recommended scripts:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"dev": "vite",
|
||||||
|
"desktop:dev": "concurrently \"vite\" \"wait-on http://localhost:5200 && electron .\"",
|
||||||
|
"desktop:build": "npm run build && electron-builder",
|
||||||
|
"desktop:pack": "npm run build && electron-builder --dir"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The exact script shape can be adjusted once the Electron entry files exist.
|
||||||
|
|
||||||
|
## Phase 1: Minimal Desktop Shell
|
||||||
|
|
||||||
|
1. Add Electron source files under `electron/`.
|
||||||
|
2. Configure TypeScript output for the Electron main/preload files.
|
||||||
|
3. Create a secure `BrowserWindow`:
|
||||||
|
- `contextIsolation: true`
|
||||||
|
- `nodeIntegration: false`
|
||||||
|
- `sandbox: true` if preload needs stay small enough to support it
|
||||||
|
4. Load `http://localhost:5200` in development.
|
||||||
|
5. Load the built `dist/index.html` in production.
|
||||||
|
6. Set app name, icon, minimum window size, and basic lifecycle handling for macOS/Windows/Linux.
|
||||||
|
7. Add a basic app menu with New/Open/Save/Save As/Export/Quit placeholders.
|
||||||
|
|
||||||
|
Acceptance check:
|
||||||
|
|
||||||
|
- `npm run desktop:dev` opens the existing app.
|
||||||
|
- `npm run desktop:pack` creates an unpacked app.
|
||||||
|
- The normal browser dev/build flows still work.
|
||||||
|
|
||||||
|
## Phase 2: Desktop File API
|
||||||
|
|
||||||
|
Add IPC-backed file operations instead of immediately rewriting the app's save/load model.
|
||||||
|
|
||||||
|
Expose a preload API like:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
window.ruwioDesktop = {
|
||||||
|
openProject(): Promise<{ name: string; bytes: Uint8Array } | null>,
|
||||||
|
saveProject(options: { suggestedName: string; bytes: Uint8Array }): Promise<{ path: string } | null>,
|
||||||
|
saveJson(options: { suggestedName: string; text: string }): Promise<{ path: string } | null>,
|
||||||
|
exportBlob(options: { suggestedName: string; bytes: Uint8Array }): Promise<{ path: string } | null>
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Renderer changes:
|
||||||
|
|
||||||
|
- Add a small desktop adapter module, for example `src/lib/desktopBridge.ts`.
|
||||||
|
- In `downloadProject` and `downloadRuwFile`, use the desktop bridge when present and fall back to browser downloads otherwise.
|
||||||
|
- For loading, keep the current `<input type="file">` path as the web fallback and add desktop open dialog support for Electron.
|
||||||
|
- Preserve existing `.ruw` bundle logic. The desktop API should move bytes to and from disk, not duplicate parsing/packing rules.
|
||||||
|
|
||||||
|
Acceptance check:
|
||||||
|
|
||||||
|
- Open `.ruw` and legacy `.json` projects from a native dialog.
|
||||||
|
- Save `.ruw` projects through a native save dialog.
|
||||||
|
- Browser import/export behavior remains unchanged.
|
||||||
|
|
||||||
|
## Phase 3: Native Menus And App State
|
||||||
|
|
||||||
|
Wire menu actions to renderer commands through IPC.
|
||||||
|
|
||||||
|
Planned menu commands:
|
||||||
|
|
||||||
|
- New Project
|
||||||
|
- Open
|
||||||
|
- Save
|
||||||
|
- Save As
|
||||||
|
- Export HTML/JS bundle
|
||||||
|
- Export PNG/GIF/APNG/MP4 as available
|
||||||
|
- Undo/Redo if the existing history store can expose commands
|
||||||
|
- Toggle fullscreen/preview/test overlay where appropriate
|
||||||
|
|
||||||
|
Implementation notes:
|
||||||
|
|
||||||
|
- The main process should not mutate project state directly.
|
||||||
|
- The renderer should own project state and respond to menu events.
|
||||||
|
- Track the current file path in the desktop bridge or a small Pinia/composable integration.
|
||||||
|
- Add a dirty-state signal so the main process can confirm before closing a window with unsaved work.
|
||||||
|
|
||||||
|
Acceptance check:
|
||||||
|
|
||||||
|
- Menu shortcuts trigger the same code paths as toolbar buttons.
|
||||||
|
- Closing the app warns when there are unsaved changes.
|
||||||
|
- Recent files can be added after the open/save path is stable.
|
||||||
|
|
||||||
|
## Phase 4: Desktop MP4 Rendering
|
||||||
|
|
||||||
|
Replace the external render server requirement for desktop builds.
|
||||||
|
|
||||||
|
Preferred approach:
|
||||||
|
|
||||||
|
1. Move the core render job orchestration out of `server/server.js` into a reusable Node module.
|
||||||
|
2. Keep the existing Express server as a thin wrapper around that module for web/server deployments.
|
||||||
|
3. Call the same render module from Electron main via IPC.
|
||||||
|
4. Stream progress back to the renderer.
|
||||||
|
5. Save the resulting MP4 through a native save dialog.
|
||||||
|
|
||||||
|
Rendering dependencies:
|
||||||
|
|
||||||
|
- Bundle or locate Chromium. Electron already ships Chromium, but Puppeteer against Electron's browser runtime needs careful validation. The safer first version is to keep Puppeteer or Playwright with an explicit executable path.
|
||||||
|
- Bundle or locate ffmpeg. For a consumer desktop app, prefer an embedded ffmpeg binary per platform or document a clear install requirement until bundling is solved.
|
||||||
|
- Keep temporary frames/pipes in the OS temp directory, as the current server does.
|
||||||
|
|
||||||
|
Acceptance check:
|
||||||
|
|
||||||
|
- MP4 export works offline in the packaged desktop app.
|
||||||
|
- Progress, cancel, and failure states match the current server export UX.
|
||||||
|
- The existing `server/` deployment still runs.
|
||||||
|
|
||||||
|
## Phase 5: Packaging And Release
|
||||||
|
|
||||||
|
Configure packaging metadata:
|
||||||
|
|
||||||
|
- App id, product name, copyright.
|
||||||
|
- Icons for macOS, Windows, and Linux.
|
||||||
|
- File associations for `.ruw`.
|
||||||
|
- Build targets:
|
||||||
|
- macOS: `.dmg` or `.zip`
|
||||||
|
- Windows: `nsis`
|
||||||
|
- Linux: `AppImage` first, optionally `.deb`
|
||||||
|
- Include `dist/`, Electron compiled files, static assets, and any render binaries.
|
||||||
|
- Exclude source maps and development-only files unless needed for debugging.
|
||||||
|
|
||||||
|
Later release tasks:
|
||||||
|
|
||||||
|
- Code signing and notarization for macOS.
|
||||||
|
- Windows signing.
|
||||||
|
- Auto-update channel if RUWIO will ship outside app stores.
|
||||||
|
- CI builds for all target platforms.
|
||||||
|
|
||||||
|
Acceptance check:
|
||||||
|
|
||||||
|
- A clean checkout can produce a packaged desktop artifact.
|
||||||
|
- The artifact launches without a local dev server.
|
||||||
|
- File associations open `.ruw` projects when double-clicked.
|
||||||
|
|
||||||
|
## Security Rules
|
||||||
|
|
||||||
|
- Keep Node access out of the Vue renderer.
|
||||||
|
- Use `contextBridge` with a narrow allowlist.
|
||||||
|
- Validate IPC payloads in the main process.
|
||||||
|
- Do not pass arbitrary file paths from renderer to main without checks.
|
||||||
|
- Restrict navigation to the app origin in development and local files in production.
|
||||||
|
- Disable or guard `window.open`.
|
||||||
|
- Keep the render process isolated from arbitrary project content where possible, especially if imported SVG/HTML assets can contain script-like payloads.
|
||||||
|
|
||||||
|
## Risk Areas
|
||||||
|
|
||||||
|
- Asset paths under `file://` may behave differently than Vite dev paths. Test fonts, icons, manifest assets, imported images, audio, and exported runtime files in packaged builds.
|
||||||
|
- Browser download code is synchronous from the user's perspective, while native save dialogs are async and cancellable. UI states should handle cancellation cleanly.
|
||||||
|
- MP4 export is the largest unknown because Chromium and ffmpeg packaging varies by platform.
|
||||||
|
- Large `.ruw` files may put pressure on IPC if sent as huge arrays. If this becomes a problem, switch to main-process file handles or temporary files.
|
||||||
|
- Existing in-browser tests need an Electron smoke layer because packaged-app failures may not appear in Vite.
|
||||||
|
|
||||||
|
## Suggested Implementation Order
|
||||||
|
|
||||||
|
1. Add Electron shell and dev/build scripts.
|
||||||
|
2. Package the current app without native integrations.
|
||||||
|
3. Add preload typings and a tiny `desktopBridge`.
|
||||||
|
4. Move project open/save to native dialogs.
|
||||||
|
5. Add native export save paths for existing browser-generated blobs.
|
||||||
|
6. Add app menu and unsaved-change handling.
|
||||||
|
7. Refactor `server/server.js` render logic into a reusable Node module.
|
||||||
|
8. Add desktop MP4 export through Electron main.
|
||||||
|
9. Configure packaging icons, file associations, and platform targets.
|
||||||
|
10. Add CI/package smoke checks.
|
||||||
|
|
||||||
|
## First Pull Request Scope
|
||||||
|
|
||||||
|
Keep the first PR intentionally small:
|
||||||
|
|
||||||
|
- Electron main/preload files.
|
||||||
|
- Desktop dev and pack scripts.
|
||||||
|
- Production loading of `dist/index.html`.
|
||||||
|
- Basic secure window configuration.
|
||||||
|
- No changes to project save/load or rendering behavior yet.
|
||||||
|
|
||||||
|
That gives RUWIO a launchable desktop shell before any app behavior changes, making the rest of the migration easier to test in small increments.
|
||||||
@@ -0,0 +1,137 @@
|
|||||||
|
import { app, BrowserWindow, Menu, shell } from 'electron'
|
||||||
|
import type { BrowserWindow as BrowserWindowInstance, Event, MenuItemConstructorOptions } from 'electron'
|
||||||
|
import path from 'node:path'
|
||||||
|
|
||||||
|
const isDev = !app.isPackaged
|
||||||
|
const devServerUrl = process.env.VITE_DEV_SERVER_URL ?? 'http://localhost:5200'
|
||||||
|
|
||||||
|
let mainWindow: BrowserWindowInstance | null = null
|
||||||
|
|
||||||
|
function createMainWindow(): void {
|
||||||
|
mainWindow = new BrowserWindow({
|
||||||
|
width: 1280,
|
||||||
|
height: 860,
|
||||||
|
minWidth: 1024,
|
||||||
|
minHeight: 700,
|
||||||
|
title: 'RUWIO',
|
||||||
|
show: false,
|
||||||
|
backgroundColor: '#14171f',
|
||||||
|
webPreferences: {
|
||||||
|
preload: path.join(__dirname, 'preload.cjs'),
|
||||||
|
contextIsolation: true,
|
||||||
|
nodeIntegration: false,
|
||||||
|
sandbox: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
mainWindow.once('ready-to-show', () => {
|
||||||
|
mainWindow?.show()
|
||||||
|
})
|
||||||
|
|
||||||
|
mainWindow.webContents.setWindowOpenHandler(({ url }: { url: string }) => {
|
||||||
|
if (url.startsWith('https://') || url.startsWith('http://')) {
|
||||||
|
void shell.openExternal(url)
|
||||||
|
}
|
||||||
|
return { action: 'deny' }
|
||||||
|
})
|
||||||
|
|
||||||
|
mainWindow.webContents.on('will-navigate', (event: Event, url: string) => {
|
||||||
|
const allowedUrl = isDev ? devServerUrl : `file://${path.join(app.getAppPath(), 'dist', 'index.html')}`
|
||||||
|
if (!url.startsWith(allowedUrl)) {
|
||||||
|
event.preventDefault()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if (isDev) {
|
||||||
|
void mainWindow.loadURL(devServerUrl)
|
||||||
|
mainWindow.webContents.openDevTools({ mode: 'detach' })
|
||||||
|
} else {
|
||||||
|
void mainWindow.loadFile(path.join(app.getAppPath(), 'dist', 'index.html'))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function createMenu(): void {
|
||||||
|
const template: MenuItemConstructorOptions[] = [
|
||||||
|
{
|
||||||
|
label: 'File',
|
||||||
|
submenu: [
|
||||||
|
{ label: 'New Project', accelerator: 'CmdOrCtrl+N', enabled: false },
|
||||||
|
{ label: 'Open...', accelerator: 'CmdOrCtrl+O', enabled: false },
|
||||||
|
{ type: 'separator' },
|
||||||
|
{ label: 'Save', accelerator: 'CmdOrCtrl+S', enabled: false },
|
||||||
|
{ label: 'Save As...', accelerator: 'CmdOrCtrl+Shift+S', enabled: false },
|
||||||
|
{ type: 'separator' },
|
||||||
|
{ label: 'Export...', accelerator: 'CmdOrCtrl+E', enabled: false },
|
||||||
|
{ type: 'separator' },
|
||||||
|
process.platform === 'darwin' ? { role: 'close' } : { role: 'quit' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Edit',
|
||||||
|
submenu: [
|
||||||
|
{ role: 'undo' },
|
||||||
|
{ role: 'redo' },
|
||||||
|
{ type: 'separator' },
|
||||||
|
{ role: 'cut' },
|
||||||
|
{ role: 'copy' },
|
||||||
|
{ role: 'paste' },
|
||||||
|
{ role: 'selectAll' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'View',
|
||||||
|
submenu: [
|
||||||
|
{ role: 'reload' },
|
||||||
|
{ role: 'forceReload' },
|
||||||
|
{ role: 'toggleDevTools' },
|
||||||
|
{ type: 'separator' },
|
||||||
|
{ role: 'resetZoom' },
|
||||||
|
{ role: 'zoomIn' },
|
||||||
|
{ role: 'zoomOut' },
|
||||||
|
{ type: 'separator' },
|
||||||
|
{ role: 'togglefullscreen' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Window',
|
||||||
|
submenu: [
|
||||||
|
{ role: 'minimize' },
|
||||||
|
{ role: 'zoom' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
if (process.platform === 'darwin') {
|
||||||
|
template.unshift({
|
||||||
|
label: app.name,
|
||||||
|
submenu: [
|
||||||
|
{ role: 'about' },
|
||||||
|
{ type: 'separator' },
|
||||||
|
{ role: 'services' },
|
||||||
|
{ type: 'separator' },
|
||||||
|
{ role: 'hide' },
|
||||||
|
{ role: 'hideOthers' },
|
||||||
|
{ role: 'unhide' },
|
||||||
|
{ type: 'separator' },
|
||||||
|
{ role: 'quit' },
|
||||||
|
],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
Menu.setApplicationMenu(Menu.buildFromTemplate(template))
|
||||||
|
}
|
||||||
|
|
||||||
|
app.setName('RUWIO')
|
||||||
|
|
||||||
|
app.whenReady().then(() => {
|
||||||
|
createMenu()
|
||||||
|
createMainWindow()
|
||||||
|
|
||||||
|
app.on('activate', () => {
|
||||||
|
if (BrowserWindow.getAllWindows().length === 0) createMainWindow()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
app.on('window-all-closed', () => {
|
||||||
|
if (process.platform !== 'darwin') app.quit()
|
||||||
|
})
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
import { contextBridge } from 'electron'
|
||||||
|
|
||||||
|
contextBridge.exposeInMainWorld('ruwioDesktop', {
|
||||||
|
platform: process.platform,
|
||||||
|
versions: {
|
||||||
|
electron: process.versions.electron,
|
||||||
|
chrome: process.versions.chrome,
|
||||||
|
node: process.versions.node,
|
||||||
|
},
|
||||||
|
})
|
||||||
Generated
+3782
-3
File diff suppressed because it is too large.
Load diff
+39
-1
@@ -3,9 +3,14 @@
|
|||||||
"private": true,
|
"private": true,
|
||||||
"version": "1.2.13",
|
"version": "1.2.13",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
"main": "dist-electron/main.cjs",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"build": "vue-tsc --noEmit && vite build",
|
"build": "vue-tsc --noEmit && vite build",
|
||||||
|
"desktop:dev": "concurrently -k \"vite\" \"wait-on http://localhost:5200 && npm run desktop:start\"",
|
||||||
|
"desktop:start": "tsc -p tsconfig.electron.json && electron .",
|
||||||
|
"desktop:build": "npm run build && tsc -p tsconfig.electron.json && electron-builder",
|
||||||
|
"desktop:pack": "npm run build && tsc -p tsconfig.electron.json && electron-builder --dir",
|
||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
"type-check": "vue-tsc --noEmit",
|
"type-check": "vue-tsc --noEmit",
|
||||||
"pack": "bash scripts/pack.sh"
|
"pack": "bash scripts/pack.sh"
|
||||||
@@ -22,8 +27,41 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^22.10.2",
|
"@types/node": "^22.10.2",
|
||||||
"@vitejs/plugin-vue": "^5.2.1",
|
"@vitejs/plugin-vue": "^5.2.1",
|
||||||
|
"concurrently": "^9.2.1",
|
||||||
|
"electron": "^42.3.3",
|
||||||
|
"electron-builder": "^26.8.1",
|
||||||
"typescript": "^5.6.3",
|
"typescript": "^5.6.3",
|
||||||
"vite": "^6.0.3",
|
"vite": "^6.0.3",
|
||||||
"vue-tsc": "^2.1.10"
|
"vue-tsc": "^2.1.10",
|
||||||
|
"wait-on": "^8.0.5"
|
||||||
|
},
|
||||||
|
"build": {
|
||||||
|
"appId": "app.ruwio.desktop",
|
||||||
|
"productName": "RUWIO",
|
||||||
|
"directories": {
|
||||||
|
"output": "release"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"dist/**/*",
|
||||||
|
"dist-electron/**/*",
|
||||||
|
"package.json"
|
||||||
|
],
|
||||||
|
"linux": {
|
||||||
|
"target": [
|
||||||
|
"AppImage"
|
||||||
|
],
|
||||||
|
"category": "Graphics"
|
||||||
|
},
|
||||||
|
"win": {
|
||||||
|
"target": [
|
||||||
|
"nsis"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"mac": {
|
||||||
|
"target": [
|
||||||
|
"dmg"
|
||||||
|
],
|
||||||
|
"category": "public.app-category.graphics-design"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+1
-1
@@ -22,7 +22,7 @@ app.mount('#app')
|
|||||||
// In Chrome/Edge, an installed PWA runs in standalone display mode,
|
// In Chrome/Edge, an installed PWA runs in standalone display mode,
|
||||||
// which removes the top-edge "exit fullscreen" indicator that tabs
|
// which removes the top-edge "exit fullscreen" indicator that tabs
|
||||||
// show when the Fullscreen API is active.
|
// show when the Fullscreen API is active.
|
||||||
if ('serviceWorker' in navigator) {
|
if ('serviceWorker' in navigator && !window.ruwioDesktop) {
|
||||||
window.addEventListener('load', () => {
|
window.addEventListener('load', () => {
|
||||||
navigator.serviceWorker.register('/sw.js').catch(() => {
|
navigator.serviceWorker.register('/sw.js').catch(() => {
|
||||||
/* registration failed — app still works without PWA install */
|
/* registration failed — app still works without PWA install */
|
||||||
|
|||||||
Vendored
+14
@@ -0,0 +1,14 @@
|
|||||||
|
export {}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface Window {
|
||||||
|
ruwioDesktop?: {
|
||||||
|
platform: 'aix' | 'android' | 'darwin' | 'freebsd' | 'haiku' | 'linux' | 'openbsd' | 'sunos' | 'win32' | 'cygwin' | 'netbsd'
|
||||||
|
versions: {
|
||||||
|
electron?: string
|
||||||
|
chrome?: string
|
||||||
|
node?: string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2022",
|
||||||
|
"module": "NodeNext",
|
||||||
|
"moduleResolution": "NodeNext",
|
||||||
|
"strict": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"types": ["node", "electron"],
|
||||||
|
"outDir": "dist-electron",
|
||||||
|
"rootDir": "electron",
|
||||||
|
"sourceMap": false
|
||||||
|
},
|
||||||
|
"include": ["electron/**/*.cts"]
|
||||||
|
}
|
||||||
Reference in new issue
Block a user