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 +16 -0
- package/.env.example +23 -0
- package/.eslintrc.cjs +26 -0
- package/.gitignore +39 -0
- package/.nvmrc +1 -0
- package/.prettierignore +14 -0
- package/.prettierrc +10 -0
- package/LICENSE +21 -0
- package/README.md +193 -0
- package/bin/create-electron-vite-react-ts.js +129 -0
- package/dist/main/main.js +478 -0
- package/electron/dotenv.ts +83 -0
- package/electron/ipc.ts +29 -0
- package/electron/logger.ts +117 -0
- package/electron/main.ts +422 -0
- package/electron/preload.ts +28 -0
- package/electron.vite.config.ts +46 -0
- package/package.json +68 -0
- package/public/icons/vite.svg +1 -0
- package/public/logo/electron-vite.animate.svg +34 -0
- package/public/logo/electron-vite.svg +26 -0
- package/public/logo/react.svg +1 -0
- package/src/renderer/App.tsx +40 -0
- package/src/renderer/index.html +13 -0
- package/src/renderer/main.tsx +15 -0
- package/src/styles/App.css +42 -0
- package/src/styles/index.css +68 -0
- package/src/types/renderer.d.ts +12 -0
- package/tsconfig.app.json +26 -0
- package/tsconfig.electron.json +17 -0
- package/tsconfig.json +4 -0
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
|
package/.prettierignore
ADDED
package/.prettierrc
ADDED
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()
|