create-electron-vite-react-ts 0.1.0

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/.editorconfig ADDED
@@ -0,0 +1,16 @@
1
+ root = true
2
+
3
+ [*]
4
+ indent_style = tab
5
+ indent_size = 4
6
+ end_of_line = lf
7
+ charset = utf-8
8
+ trim_trailing_whitespace = true
9
+ insert_final_newline = true
10
+
11
+ [*.{js,jsx,ts,tsx,json}]
12
+ indent_size = 4
13
+
14
+ [*.md]
15
+ trim_trailing_whitespace = false
16
+
package/.env.example ADDED
@@ -0,0 +1,23 @@
1
+ # Copy this file to ".env" at the project root. Main process reads it at startup.
2
+ # Precedence for each key: process environment overrides values below.
3
+
4
+ # LOG_ENABLED — Master switch for AppLogger. If false, no log file and no terminal output from the logger.
5
+ # Accepted: 1 / true / yes (on) or 0 / false / no (off). Default when unset: true.
6
+ LOG_ENABLED=true
7
+
8
+ # LOG_FILE — Append log lines to logs/system.YYYYMMDD.log under the project root (when logger is enabled).
9
+ # Default when unset: true for non-packaged runs, false when the app is packaged.
10
+ LOG_FILE=true
11
+
12
+ # LOG_CONSOLE — Mirror logger lines to the terminal (Node console) when the logger is enabled.
13
+ # Default when unset: true in development, false otherwise.
14
+ LOG_CONSOLE=true
15
+
16
+ # SCREEN_CENTER — When true: if the window is not maximized and its size is smaller than the target
17
+ # monitor work area, position the window at the horizontal and vertical center of that work area on show.
18
+ # No effect for maximized or full-screen windows. Default when unset: false.
19
+ SCREEN_CENTER=false
20
+
21
+ # ELECTRON_MENU_ENABLED — If false, removes the application menu bar (Menu.setApplicationMenu(null)).
22
+ # Default when unset: true (menu shown).
23
+ ELECTRON_MENU_ENABLED=true
package/.eslintrc.cjs ADDED
@@ -0,0 +1,26 @@
1
+ module.exports = {
2
+ root: true,
3
+ env: {
4
+ browser: true,
5
+ es2020: true,
6
+ node: true,
7
+ },
8
+ parser: '@typescript-eslint/parser',
9
+ plugins: ['@typescript-eslint', 'react-hooks', 'react-refresh'],
10
+ extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended'],
11
+ ignorePatterns: ['dist'],
12
+ overrides: [
13
+ {
14
+ files: ['**/*.{ts,tsx}'],
15
+ rules: {
16
+ 'react-refresh/only-export-components': 'warn',
17
+ },
18
+ },
19
+ {
20
+ files: ['electron/dotenv.ts'],
21
+ rules: {
22
+ '@typescript-eslint/no-namespace': 'off',
23
+ },
24
+ },
25
+ ],
26
+ }
package/.gitignore ADDED
@@ -0,0 +1,39 @@
1
+ # Logs
2
+ logs
3
+ *.log
4
+ npm-debug.log*
5
+ yarn-debug.log*
6
+ yarn-error.log*
7
+ pnpm-debug.log*
8
+ lerna-debug.log*
9
+
10
+ node_modules
11
+ dist
12
+ dist-electron
13
+ release
14
+ dist-ssr
15
+ *.local
16
+
17
+ # Editor directories and files
18
+ .vscode/*
19
+ !.vscode/extensions.json
20
+ .idea
21
+ .DS_Store
22
+ *.suo
23
+ *.ntvs*
24
+ *.njsproj
25
+ *.sln
26
+ *.sw?
27
+
28
+ .cursor
29
+ .github
30
+ !.github/
31
+ !.github/workflows/
32
+ !.github/workflows/*.yml
33
+ .env
34
+
35
+ .flows
36
+ flows
37
+ bin
38
+ .nvmrc
39
+ package-lock.json
package/.nvmrc ADDED
@@ -0,0 +1 @@
1
+ 22.21.1
@@ -0,0 +1,14 @@
1
+ node_modules
2
+ assets
3
+ app
4
+ dist
5
+ build
6
+ *.log
7
+ log
8
+ coverage
9
+ .nyc_output
10
+ *.min.js
11
+ *.min.css
12
+ out
13
+ migration
14
+ resources
package/.prettierrc ADDED
@@ -0,0 +1,10 @@
1
+ {
2
+ "semi": false,
3
+ "singleQuote": true,
4
+ "tabWidth": 4,
5
+ "useTabs": true,
6
+ "trailingComma": "es5",
7
+ "printWidth": 10000,
8
+ "arrowParens": "avoid",
9
+ "endOfLine": "lf"
10
+ }
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Chris Ham
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,193 @@
1
+ # electron-vite-react-ts
2
+
3
+ An Electron desktop application built with **React 18** and **TypeScript**.
4
+ It uses **[electron-vite](https://electron-vite.org/)** to build the main, preload, and renderer processes together.
5
+ The baseline includes essential cross-platform desktop capabilities for **Windows, macOS, and Linux** (window lifecycle/state restore, safe IPC bridge, external-link handling, environment-driven startup behavior, and logging).
6
+ It also presents a production-oriented architecture that helps teams design and extend the exact product they want by separating Electron main, preload, and renderer responsibilities clearly.
7
+
8
+
9
+ ## Create CLI Usage
10
+
11
+ Publish this package to npm as `create-electron-vite-react-ts`, then bootstrap a new app:
12
+
13
+ ```bash
14
+ npx create-electron-vite-react-ts my-desktop-app
15
+ cd my-desktop-app
16
+ npm install
17
+ npm run dev
18
+ ```
19
+
20
+ ## Requirements
21
+
22
+ - **Node.js** — LTS recommended (for example, Node 20+)
23
+ - **npm** — package manager
24
+
25
+ ## Getting Started
26
+
27
+ Install dependencies after cloning the repository:
28
+
29
+ ```bash
30
+ npm install
31
+ ```
32
+
33
+ ## npm Scripts
34
+
35
+ | Command | Description |
36
+ |------|------|
37
+ | `npm run dev` | Development mode — runs Vite dev server and Electron |
38
+ | `npm run build` | Runs TypeScript project reference build, then `electron-vite build` |
39
+ | `npm run start` | Preview built output with `electron-vite preview` |
40
+ | `npm run lint` | Run ESLint |
41
+ | `npm run format` | Run Prettier formatting |
42
+ | `npm run clean` | Clean build outputs and cache (`dist`, `release`, etc.) |
43
+ | `npm run release` | Run `npm run build`, then package with **electron-builder** |
44
+ | `npm run lang` | Switch Windows console code page to UTF-8 (65001) |
45
+
46
+ > **Note:** Default dev server port is 5173. If occupied, another port (for example 5174) may be selected automatically.
47
+
48
+ ## Project Structure
49
+
50
+ ```text
51
+ .
52
+ ├─ electron/
53
+ │ ├─ main.ts # Main process bootstrap and window lifecycle
54
+ │ ├─ preload.ts # ContextBridge APIs exposed to renderer
55
+ │ ├─ ipc.ts # IPC channel constants and handlers
56
+ │ ├─ logger.ts # Main-process logger (file/console policy)
57
+ │ └─ dotenv.ts # .env parser + shared global typings
58
+ ├─ src/
59
+ │ ├─ renderer/
60
+ │ │ ├─ index.html # Renderer HTML entry (Vite root)
61
+ │ │ ├─ main.tsx # Renderer React bootstrap
62
+ │ │ └─ App.tsx # Root React component
63
+ │ ├─ components/ # Recommended shared UI components folder (create when scaling)
64
+ │ ├─ stores/ # Recommended state stores folder (create when scaling)
65
+ │ ├─ styles/
66
+ │ │ ├─ index.css # Global styles
67
+ │ │ └─ App.css # App-level styles
68
+ │ ├─ assets/
69
+ │ │ └─ react.svg
70
+ │ └─ types/
71
+ │ └─ renderer.d.ts # Window global type declarations
72
+ ├─ public/ # Static assets copied by Vite/electron-vite
73
+ ├─ logs/ # Runtime logs (dev/non-packaged)
74
+ ├─ dist/ # Build output (main/preload/renderer)
75
+ ├─ release/ # electron-builder artifacts
76
+ ├─ electron.vite.config.ts
77
+ ├─ tsconfig.app.json
78
+ ├─ tsconfig.electron.json
79
+ └─ package.json
80
+ ```
81
+
82
+ - All build outputs are placed under **`dist/`**: `dist/main/`, `dist/preload/`, and renderer files (`index.html`, `assets/`).
83
+ - `package.json` `main` points to Electron entry at **`dist/main/main.js`**.
84
+
85
+ ## Extension Concepts
86
+
87
+ This project is organized to scale by separating concerns between Electron main, preload bridge, and renderer UI.
88
+
89
+ - **Renderer feature growth**
90
+ - Add feature folders under `src/renderer/` (for example, `src/renderer/features/<feature-name>/`).
91
+ - Create `src/components/` for reusable presentation/UI components shared across screens.
92
+ - Create `src/stores/` for app-wide state management (for example, auth/session/app settings).
93
+ - Keep visual styles in `src/styles/` or feature-local style files.
94
+ - Keep `main.tsx` as pure bootstrap; avoid placing business logic there.
95
+
96
+ - **IPC-first desktop capabilities**
97
+ - Define channel names and handlers in `electron/ipc.ts`.
98
+ - Expose minimal, safe APIs in `electron/preload.ts` via `contextBridge`.
99
+ - Consume those APIs in renderer through `window.electronAPI` with typed contracts in `src/types/renderer.d.ts`.
100
+ - Prefer `invoke/handle` for request-response flows; reserve event channels for streaming/push use cases.
101
+
102
+ - **Main-process service layering**
103
+ - As logic grows, split `electron/main.ts` into domain modules (for example, `electron/services/window-state.ts`, `electron/services/menu.ts`).
104
+ - Keep `main.ts` as orchestration/composition layer.
105
+
106
+ - **Configuration and environment strategy**
107
+ - Centralize env parsing/defaults in `electron/dotenv.ts`.
108
+ - Add new flags in `.env.example` with clear comments and production-safe defaults.
109
+ - Keep renderer-safe env usage prefixed and explicit.
110
+
111
+ - **Build and packaging evolution**
112
+ - Renderer root is `src/renderer`, while output is unified under `dist/`.
113
+ - Packaging outputs are isolated in `release/` via `build.directories.output`.
114
+ - Add platform-specific packaging options in `package.json > build` as distribution needs increase.
115
+
116
+ ## Feature Expansion Checklist
117
+
118
+ When adding a new desktop capability:
119
+
120
+ 1. Add/extend IPC channel and runtime guard in `electron/ipc.ts`.
121
+ 2. Expose a narrow preload API in `electron/preload.ts`.
122
+ 3. Add/update renderer global typings in `src/types/renderer.d.ts`.
123
+ 4. Implement UI flow under `src/renderer/` and styles in `src/styles/`.
124
+ 5. Validate with `npm run build` and run-time smoke test in `npm run dev`.
125
+
126
+ ## Development Notes
127
+
128
+ - In development mode, electron-vite passes the renderer URL through **`ELECTRON_RENDERER_URL`** (kept with fallback support for legacy `VITE_DEV_SERVER_URL`).
129
+ - If Unicode/Korean output is broken in Windows terminal, run `npm run ko` first and execute commands in the same session.
130
+ - Renderer clicks on external `http(s)` links are handled via IPC (`app:open-external`) and opened in the **system default browser**, not inside the app.
131
+
132
+ ## Window State Restore Policy
133
+
134
+ - **Saved by run mode:** development (with dev server) and production (loading built files) use separate state files.
135
+ - `window-state.development.json`
136
+ - `window-state.production.json`
137
+ - These files are stored in Electron's **`userData`** directory.
138
+ - **Save timing:** state is saved **once right before `close`**, not on every move/resize.
139
+ - **Saved fields:** absolute position (`x`, `y`), size (`width`, `height`), maximized state, **display ID (`displayId`)**, display-relative offsets (`offsetX`, `offsetY`), and work-area size (`workAreaWidth`, `workAreaHeight`).
140
+ - **Restore order:** the window starts with **`show: false`**, then applies saved bounds (or maximize) on `ready-to-show`, and finally calls **`show()`** to reduce flicker and first-paint size jumps.
141
+ - **Multi-monitor behavior:** if the saved monitor is unavailable, it can fall back to primary display. If the same display ID is still valid, saved bounds are prioritized.
142
+ - **Minimized state is not restored** to avoid launching into an invisible window.
143
+
144
+ ## SCREEN_CENTER by Run Mode
145
+
146
+ - `SCREEN_CENTER` is read from `.env` (or `process.env`) and applies in both development and production.
147
+ - The centering decision runs during startup before the first `show()` and only when all conditions are met:
148
+ - `SCREEN_CENTER=true`
149
+ - window is not maximized
150
+ - window size is smaller than the target display work area
151
+ - Because window-state is separated by run mode, centering can lead to different visible results:
152
+ - **Development mode:** uses `window-state.development.json`
153
+ - **Production mode:** uses `window-state.production.json`
154
+ - If the two mode files store different size/position history, startup placement can differ by mode even with the same `SCREEN_CENTER` value.
155
+
156
+ ## Electron Menu Visibility
157
+
158
+ - Menu visibility is controlled by `ELECTRON_MENU_ENABLED`.
159
+ - Default behavior is `true` (menu is visible).
160
+ - Set `ELECTRON_MENU_ENABLED=false` to hide the application menu via `Menu.setApplicationMenu(null)` at app startup.
161
+ - This setting is applied in the main process during `app.whenReady()`.
162
+ - Note: hiding the top menu mostly affects Windows/Linux UX; on macOS, global menu-bar conventions still apply at OS level.
163
+
164
+ ## Logging (`logs/`)
165
+
166
+ - Main process logging (`logger.ts`) can write daily logs to project root as **`logs/system.YYYYMMDD.log`**.
167
+ - **Development mode:** writes to both console and file.
168
+ - **Production mode (non-packaged):** writes to file only.
169
+ - **Packaged app (`app.isPackaged`):** file logging is disabled.
170
+
171
+ ## DevTools in Development
172
+
173
+ - In development mode (when loading renderer from Vite dev server), the main process opens **DevTools** automatically.
174
+ - In production build mode (loading from `dist`), DevTools is not opened automatically.
175
+
176
+ ## Release Packaging
177
+
178
+ ```bash
179
+ npm run release
180
+ ```
181
+
182
+ - This project sets `build.directories.output` to `release` in `package.json`.
183
+ - Packaging artifacts are generated under root **`release/`**.
184
+ - For platform-specific options, extend the `build` field using the [electron-builder docs](https://www.electron.build/).
185
+
186
+ ---
187
+
188
+ ## Tech Stack
189
+
190
+ - **Runtime:** Electron 30
191
+ - **Bundler / Dev:** Vite 6, electron-vite 5
192
+ - **UI:** React 18, TypeScript 5
193
+ - **Quality:** ESLint, Prettier
@@ -0,0 +1,129 @@
1
+ #!/usr/bin/env node
2
+ import fs from 'node:fs'
3
+ import path from 'node:path'
4
+ import { fileURLToPath } from 'node:url'
5
+
6
+ const __filename = fileURLToPath(import.meta.url)
7
+ const __dirname = path.dirname(__filename)
8
+ const packageRoot = path.resolve(__dirname, '..')
9
+
10
+ const TEMPLATE_DIRS = ['electron', 'public', 'src']
11
+ const TEMPLATE_FILES = [
12
+ '.editorconfig',
13
+ '.env.example',
14
+ '.eslintrc.cjs',
15
+ '.gitignore',
16
+ '.nvmrc',
17
+ '.prettierignore',
18
+ '.prettierrc',
19
+ 'electron.vite.config.ts',
20
+ 'tsconfig.app.json',
21
+ 'tsconfig.electron.json',
22
+ 'tsconfig.json',
23
+ 'README.md',
24
+ ]
25
+
26
+ const SPECIAL_FILE_SOURCES = {
27
+ '.gitignore': ['.gitignore', '.npmignore'],
28
+ }
29
+
30
+ function printUsage() {
31
+ console.log('Usage: npx create-electron-vite-react-ts <project-name>')
32
+ }
33
+
34
+ function isValidProjectName(name) {
35
+ return /^[a-zA-Z0-9._-]+$/.test(name)
36
+ }
37
+
38
+ function ensureNotExists(targetPath) {
39
+ if (fs.existsSync(targetPath)) {
40
+ throw new Error(`Target already exists: ${targetPath}`)
41
+ }
42
+ }
43
+
44
+ function copyTemplateFiles(targetRoot) {
45
+ for (const dir of TEMPLATE_DIRS) {
46
+ fs.cpSync(path.join(packageRoot, dir), path.join(targetRoot, dir), {
47
+ recursive: true,
48
+ })
49
+ }
50
+
51
+ for (const file of TEMPLATE_FILES) {
52
+ const candidates = SPECIAL_FILE_SOURCES[file] ?? [file]
53
+ const sourceCandidate = candidates.find(candidate =>
54
+ fs.existsSync(path.join(packageRoot, candidate))
55
+ )
56
+
57
+ if (!sourceCandidate) {
58
+ throw new Error(`Template file not found: ${file}`)
59
+ }
60
+
61
+ fs.copyFileSync(path.join(packageRoot, sourceCandidate), path.join(targetRoot, file))
62
+ }
63
+ }
64
+
65
+ function createTargetPackageJson(targetRoot, projectName) {
66
+ const templatePackageJsonPath = path.join(packageRoot, 'package.json')
67
+ const source = JSON.parse(fs.readFileSync(templatePackageJsonPath, 'utf8'))
68
+
69
+ const appPackageJson = {
70
+ name: projectName.toLowerCase(),
71
+ private: true,
72
+ version: '0.1.0',
73
+ type: 'module',
74
+ scripts: {
75
+ dev: source.scripts.dev,
76
+ build: source.scripts.build,
77
+ lint: source.scripts.lint,
78
+ start: source.scripts.start,
79
+ release: source.scripts.release,
80
+ format: source.scripts.format,
81
+ clean: source.scripts.clean,
82
+ lang: source.scripts.lang,
83
+ },
84
+ dependencies: source.dependencies,
85
+ devDependencies: source.devDependencies,
86
+ build: source.build,
87
+ main: source.main,
88
+ }
89
+
90
+ const output = `${JSON.stringify(appPackageJson, null, '\t')}\n`
91
+ fs.writeFileSync(path.join(targetRoot, 'package.json'), output, 'utf8')
92
+ }
93
+
94
+ function main() {
95
+ const projectName = process.argv[2]
96
+
97
+ if (!projectName) {
98
+ printUsage()
99
+ process.exit(1)
100
+ }
101
+
102
+ if (!isValidProjectName(projectName)) {
103
+ console.error(`Invalid project name: "${projectName}"`)
104
+ console.error('Allowed characters: a-z, A-Z, 0-9, dot(.), underscore(_), hyphen(-)')
105
+ process.exit(1)
106
+ }
107
+
108
+ const targetRoot = path.resolve(process.cwd(), projectName)
109
+
110
+ try {
111
+ ensureNotExists(targetRoot)
112
+ fs.mkdirSync(targetRoot, { recursive: true })
113
+ copyTemplateFiles(targetRoot)
114
+ createTargetPackageJson(targetRoot, projectName)
115
+
116
+ console.log('\nProject created successfully.')
117
+ console.log(`Location: ${targetRoot}\n`)
118
+ console.log('Next steps:')
119
+ console.log(` cd ${projectName}`)
120
+ console.log(' npm install')
121
+ console.log(' npm run dev\n')
122
+ } catch (error) {
123
+ console.error('\nFailed to create project.')
124
+ console.error(error instanceof Error ? error.message : String(error))
125
+ process.exit(1)
126
+ }
127
+ }
128
+
129
+ main()