@thoughtpivot/flight 1.0.7
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/.circleci/config.yml +83 -0
- package/.editorconfig +5 -0
- package/.github/dependabot.yml +11 -0
- package/.nvmrc +1 -0
- package/.prettierrc +11 -0
- package/.vscode/extensions.json +3 -0
- package/.vscode/settings.json +10 -0
- package/LICENSE.md +19 -0
- package/README.md +266 -0
- package/dist/flight.js +187 -0
- package/eslint.config.mjs +32 -0
- package/package.json +75 -0
- package/src/flight.ts +226 -0
- package/tsconfig.json +10 -0
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
filter-run-always: &filter-run-always
|
|
2
|
+
filters:
|
|
3
|
+
tags:
|
|
4
|
+
only: /.*/
|
|
5
|
+
|
|
6
|
+
filter-run-on-master-and-version-tag-only: &filter-run-on-master-and-version-tag-only
|
|
7
|
+
filters:
|
|
8
|
+
tags:
|
|
9
|
+
only: /^v.*/
|
|
10
|
+
branches:
|
|
11
|
+
only: main
|
|
12
|
+
|
|
13
|
+
aliases:
|
|
14
|
+
- &step-checkout checkout
|
|
15
|
+
- &step-restore-cache
|
|
16
|
+
restore_cache:
|
|
17
|
+
keys:
|
|
18
|
+
- v1-dependencies-{{ checksum "package.json" }}
|
|
19
|
+
- v1-dependencies-
|
|
20
|
+
- &step-install
|
|
21
|
+
run: npm install
|
|
22
|
+
- &step-save-cache
|
|
23
|
+
save_cache:
|
|
24
|
+
paths:
|
|
25
|
+
- node_modules
|
|
26
|
+
key: v1-dependencies-{{ checksum "package.json" }}
|
|
27
|
+
- &step-build
|
|
28
|
+
run: npm run build
|
|
29
|
+
|
|
30
|
+
build-node-common: &common-build
|
|
31
|
+
working_directory: ~/repo
|
|
32
|
+
steps:
|
|
33
|
+
- *step-checkout
|
|
34
|
+
- *step-restore-cache
|
|
35
|
+
- *step-install
|
|
36
|
+
- *step-save-cache
|
|
37
|
+
- *step-build
|
|
38
|
+
- run:
|
|
39
|
+
name: Testing
|
|
40
|
+
command: npm run test
|
|
41
|
+
|
|
42
|
+
version: 2
|
|
43
|
+
jobs:
|
|
44
|
+
deploy:
|
|
45
|
+
working_directory: ~/repo
|
|
46
|
+
docker:
|
|
47
|
+
- image: cimg/node:20.12
|
|
48
|
+
steps:
|
|
49
|
+
- *step-checkout
|
|
50
|
+
- *step-restore-cache
|
|
51
|
+
- *step-install
|
|
52
|
+
- *step-save-cache
|
|
53
|
+
- *step-build
|
|
54
|
+
- run:
|
|
55
|
+
name: Authenticate with registry
|
|
56
|
+
command: echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" > ~/repo/.npmrc
|
|
57
|
+
- run:
|
|
58
|
+
name: Publish Package
|
|
59
|
+
command: npm publish
|
|
60
|
+
|
|
61
|
+
build-node18:
|
|
62
|
+
<<: *common-build
|
|
63
|
+
docker:
|
|
64
|
+
- image: cimg/node:18.20
|
|
65
|
+
|
|
66
|
+
build-node20:
|
|
67
|
+
<<: *common-build
|
|
68
|
+
docker:
|
|
69
|
+
- image: cimg/node:20.11
|
|
70
|
+
|
|
71
|
+
workflows:
|
|
72
|
+
version: 2
|
|
73
|
+
build_all:
|
|
74
|
+
jobs:
|
|
75
|
+
- build-node18:
|
|
76
|
+
<<: *filter-run-always
|
|
77
|
+
- build-node20:
|
|
78
|
+
<<: *filter-run-always
|
|
79
|
+
- deploy:
|
|
80
|
+
requires:
|
|
81
|
+
- build-node18
|
|
82
|
+
- build-node20
|
|
83
|
+
<<: *filter-run-on-master-and-version-tag-only
|
package/.editorconfig
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
[*.{js,jsx,ts,tsx,cs, json, md, yml, yaml, html, css, scss, less, graphql, mdx, vue, svelte, php, py, rb, rs, toml, go, java, sh, fish, bash, zsh, ksh, sql, tex, r, rmd, dart, xml, cfml, cfc, cfm, tcl, liquid, lisp, el, vue, svelte, swift, scala, groovy, kt, kts, clj, cljs, edn, hs, lhs, lua, moon, ml, mli, mll, mly, pascal, pas, pl, pm, t, pod, php, php3, php4, php5, php6, php7, php8, phtml, ps1, psm1, py, py3, pyi, pyx, r, rkt, rpy, rpy2, rb, rs, rli}]
|
|
2
|
+
indent_style = space
|
|
3
|
+
indent_size = 4
|
|
4
|
+
trim_trailing_whitespace = true
|
|
5
|
+
insert_final_newline = true
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# To get started with Dependabot version updates, you'll need to specify which
|
|
2
|
+
# package ecosystems to update and where the package manifests are located.
|
|
3
|
+
# Please see the documentation for all configuration options:
|
|
4
|
+
# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
|
|
5
|
+
|
|
6
|
+
version: 2
|
|
7
|
+
updates:
|
|
8
|
+
- package-ecosystem: 'npm'
|
|
9
|
+
directory: '/' # Location of package manifests
|
|
10
|
+
schedule:
|
|
11
|
+
interval: 'weekly'
|
package/.nvmrc
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
20.11
|
package/.prettierrc
ADDED
package/LICENSE.md
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# MIT License
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
5
|
+
in the Software without restriction, including without limitation the rights
|
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
8
|
+
furnished to do so, subject to the following conditions:
|
|
9
|
+
|
|
10
|
+
The above copyright notice and this permission notice shall be included in all
|
|
11
|
+
copies or substantial portions of the Software.
|
|
12
|
+
|
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
19
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
# Flight
|
|
2
|
+
|
|
3
|
+
Flight is a modern, high-performance web application server built on Node.js, designed for building scalable, component-driven applications. It represents the evolution of the [Avian](https://github.com/ispyhumanfly/avian) component application server, incorporating modern best practices and improved developer experience.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
Flight takes the best concepts from Avian and enhances them with modern tooling and practices. It provides a robust foundation for building enterprise-grade applications while maintaining simplicity and developer productivity.
|
|
8
|
+
|
|
9
|
+
### Key Features
|
|
10
|
+
|
|
11
|
+
- **Modern Stack**: Built on Koa.js with TypeScript support
|
|
12
|
+
- **Component-Based Architecture**: Organize your application into reusable components
|
|
13
|
+
- **Built-in Development Server**: Powered by Vite for lightning-fast development
|
|
14
|
+
- **Production-Ready**: Includes rate limiting, compression, and caching out of the box
|
|
15
|
+
- **Cluster Mode**: Automatic load balancing across CPU cores
|
|
16
|
+
- **Redis Integration**: Built-in support for session management and caching
|
|
17
|
+
- **TypeScript First**: Native TypeScript support throughout the framework
|
|
18
|
+
- **Hot Module Replacement**: Fast development with instant updates
|
|
19
|
+
- **CORS Enabled**: Ready for modern web applications
|
|
20
|
+
- **Security Features**: Rate limiting, secure session handling, and more
|
|
21
|
+
|
|
22
|
+
## Installation
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
npm install @thoughtpivot/flight
|
|
26
|
+
# or
|
|
27
|
+
yarn add @thoughtpivot/flight
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Previously published on npm as `@spytech/flight`; switch your dependency to `@thoughtpivot/flight`.
|
|
31
|
+
|
|
32
|
+
## Quick Start
|
|
33
|
+
|
|
34
|
+
1. Create a new project directory and initialize:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
mkdir my-flight-app
|
|
38
|
+
cd my-flight-app
|
|
39
|
+
npm init -y
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
2. Install Flight and its dependencies:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
npm install @thoughtpivot/flight ioredis
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
3. Ensure Redis is running locally or set environment variables:
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
# Default values shown below
|
|
52
|
+
export FLIGHT_REDIS_HOST=localhost
|
|
53
|
+
export FLIGHT_REDIS_PORT=6379
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
4. Create a component with a backend route:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
mkdir -p components/hello
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Create `components/hello/hello.backend.ts`:
|
|
63
|
+
|
|
64
|
+
```typescript
|
|
65
|
+
import Router from '@koa/router'
|
|
66
|
+
|
|
67
|
+
const router = new Router()
|
|
68
|
+
|
|
69
|
+
router.get('/hello', async (ctx) => {
|
|
70
|
+
ctx.body = { message: 'Hello from Flight!' }
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
export default router.routes()
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
5. Start the server:
|
|
77
|
+
|
|
78
|
+
Development mode:
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
node flight.js --mode development --app_home .
|
|
82
|
+
# Starts development server on port 3001 with HMR
|
|
83
|
+
# Backend API available on port 3000
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Production mode:
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
node flight.js --mode production --app_home .
|
|
90
|
+
# Builds and serves application on port 3000
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Available CLI options:
|
|
94
|
+
|
|
95
|
+
- `--app_home`: Application root directory (default: current directory)
|
|
96
|
+
- `--app_key`: Application key for sessions (default: 'flightApp')
|
|
97
|
+
- `--app_secret`: Secret key for session encryption (default: 'the best secret key in the world')
|
|
98
|
+
- `--mode`: 'development' or 'production' (default: 'production')
|
|
99
|
+
|
|
100
|
+
## Project Structure
|
|
101
|
+
|
|
102
|
+
```
|
|
103
|
+
my-app/
|
|
104
|
+
├── components/ # Application components
|
|
105
|
+
│ ├── Hello/ # Component directory
|
|
106
|
+
│ │ ├── Hello.vue # Vue component view
|
|
107
|
+
│ │ └── Hello.backend.ts # Backend routes and logic
|
|
108
|
+
├── assets/ # Static assets
|
|
109
|
+
├── dist/ # Production build output
|
|
110
|
+
└── package.json
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Each component follows a simple structure:
|
|
114
|
+
|
|
115
|
+
- `Index.vue`: Contains the Vue component template, script, and styles
|
|
116
|
+
- `Index.backend.ts`: Contains the backend routes and logic for the component
|
|
117
|
+
|
|
118
|
+
Example component files:
|
|
119
|
+
|
|
120
|
+
`components/hello/Index.vue`:
|
|
121
|
+
|
|
122
|
+
```vue
|
|
123
|
+
<template>
|
|
124
|
+
<div>
|
|
125
|
+
<h1>{{ message }}</h1>
|
|
126
|
+
</div>
|
|
127
|
+
</template>
|
|
128
|
+
|
|
129
|
+
<script setup lang="ts">
|
|
130
|
+
import { ref } from 'vue'
|
|
131
|
+
|
|
132
|
+
const message = ref('Hello from Flight!')
|
|
133
|
+
</script>
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
`components/hello/Index.backend.ts`:
|
|
137
|
+
|
|
138
|
+
```typescript
|
|
139
|
+
import Router from '@koa/router'
|
|
140
|
+
|
|
141
|
+
const router = new Router()
|
|
142
|
+
|
|
143
|
+
router.get('/hello', async (ctx) => {
|
|
144
|
+
ctx.body = { message: 'Hello from Flight!' }
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
export default router.routes()
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
## Configuration
|
|
151
|
+
|
|
152
|
+
Flight can be configured through environment variables or command-line arguments:
|
|
153
|
+
|
|
154
|
+
```bash
|
|
155
|
+
FLIGHT_MODE=development
|
|
156
|
+
FLIGHT_REDIS_HOST=localhost
|
|
157
|
+
FLIGHT_REDIS_PORT=6379
|
|
158
|
+
FLIGHT_MAX_WORKERS=4
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
## Development Mode
|
|
162
|
+
|
|
163
|
+
In development mode, Flight provides:
|
|
164
|
+
|
|
165
|
+
- Hot Module Replacement (HMR)
|
|
166
|
+
- Fast refresh for React components
|
|
167
|
+
- Detailed error messages
|
|
168
|
+
- Development server on port 3001
|
|
169
|
+
|
|
170
|
+
## Production Mode
|
|
171
|
+
|
|
172
|
+
Production mode includes:
|
|
173
|
+
|
|
174
|
+
- Optimized builds
|
|
175
|
+
- Rate limiting
|
|
176
|
+
- Response compression
|
|
177
|
+
- Redis caching
|
|
178
|
+
- Cluster mode for load balancing
|
|
179
|
+
- Production server on port 3000
|
|
180
|
+
|
|
181
|
+
## Comparison with Avian
|
|
182
|
+
|
|
183
|
+
Flight is a modern reimagining of the Avian framework, making several architectural improvements while maintaining the core philosophy of component-driven applications. Here are the key differences:
|
|
184
|
+
|
|
185
|
+
### Framework Evolution
|
|
186
|
+
|
|
187
|
+
- **Koa Instead of Express**: Flight uses Koa.js as its foundation instead of Express, providing better async/await support and a more modern middleware architecture
|
|
188
|
+
- **Vite Instead of Webpack**: Replaced Webpack bundling with Vite for significantly faster development experience and simpler configuration
|
|
189
|
+
- **TypeScript First**: While Avian supported TypeScript, Flight is built with TypeScript from the ground up
|
|
190
|
+
|
|
191
|
+
### Architectural Improvements
|
|
192
|
+
|
|
193
|
+
1. **Simplified Component Structure**
|
|
194
|
+
- Avian: Complex component hierarchy with multiple file types (.client, .server, .view, .config)
|
|
195
|
+
- Flight: Streamlined with `.backend.ts` files and modern frontend frameworks
|
|
196
|
+
2. **Development Experience**
|
|
197
|
+
- Avian: Webpack-based bundling with slower rebuild times
|
|
198
|
+
- Flight: Vite-powered development with instant HMR and no bundle step in development
|
|
199
|
+
|
|
200
|
+
3. **Session Management**
|
|
201
|
+
- Avian: Express-session with Redis store
|
|
202
|
+
- Flight: Koa-session with Redis store, improved security defaults
|
|
203
|
+
|
|
204
|
+
4. **Performance Features**
|
|
205
|
+
- Built-in rate limiting
|
|
206
|
+
- Redis-based caching
|
|
207
|
+
- Automatic compression in production
|
|
208
|
+
- Cluster mode for CPU utilization
|
|
209
|
+
|
|
210
|
+
5. **Configuration**
|
|
211
|
+
- Avian: Complex webpack configuration and multiple build modes
|
|
212
|
+
- Flight: Simplified configuration with sensible defaults and Vite's zero-config approach
|
|
213
|
+
|
|
214
|
+
### What's Different
|
|
215
|
+
|
|
216
|
+
1. **Removed Features**
|
|
217
|
+
- Removed Webpack-specific configurations
|
|
218
|
+
- Removed legacy view engine support (EJS, Twig, Pug)
|
|
219
|
+
- Removed Sentry integration (can be added as middleware if needed)
|
|
220
|
+
- Removed built-in cron job scheduler (better handled by dedicated services)
|
|
221
|
+
|
|
222
|
+
2. **New Features**
|
|
223
|
+
- Native ESM support
|
|
224
|
+
- Built-in CORS support
|
|
225
|
+
- Improved Redis integration
|
|
226
|
+
- Better security defaults
|
|
227
|
+
- Simpler API for backend routes
|
|
228
|
+
- Modern frontend tooling support
|
|
229
|
+
|
|
230
|
+
3. **Simplified Architecture**
|
|
231
|
+
- Reduced configuration complexity
|
|
232
|
+
- More intuitive component organization
|
|
233
|
+
- Better separation of concerns
|
|
234
|
+
- Modern middleware approach
|
|
235
|
+
|
|
236
|
+
### Migration from Avian
|
|
237
|
+
|
|
238
|
+
If you're migrating from Avian, the main changes you'll need to make are:
|
|
239
|
+
|
|
240
|
+
1. Update your component structure to use `.backend.ts` files
|
|
241
|
+
2. Move from Express middleware to Koa middleware
|
|
242
|
+
3. Update your frontend bundling to use Vite
|
|
243
|
+
4. Adapt to the new session management system
|
|
244
|
+
5. Update your static file serving configuration
|
|
245
|
+
|
|
246
|
+
## Requirements
|
|
247
|
+
|
|
248
|
+
- Node.js 16.x or higher
|
|
249
|
+
- Redis server
|
|
250
|
+
- TypeScript 4.x or higher
|
|
251
|
+
|
|
252
|
+
## License
|
|
253
|
+
|
|
254
|
+
MIT
|
|
255
|
+
|
|
256
|
+
## Contributing
|
|
257
|
+
|
|
258
|
+
Contributions are welcome! Please feel free to submit a Pull Request.
|
|
259
|
+
|
|
260
|
+
## Acknowledgments
|
|
261
|
+
|
|
262
|
+
Flight is built upon the solid foundation laid by the [Avian](https://github.com/ispyhumanfly/avian) framework, created by FlyPaper Technologies, LLC. We're grateful for their pioneering work in component-driven architecture.
|
|
263
|
+
|
|
264
|
+
## ThoughtPivot
|
|
265
|
+
|
|
266
|
+
Flight is a [ThoughtPivot](https://github.com/thoughtpivot) technology: developed and maintained as part of the ThoughtPivot organization and ecosystem.
|
package/dist/flight.js
ADDED
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
#!ts-node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
|
+
};
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
const child_process_1 = require("child_process");
|
|
8
|
+
const koa_1 = __importDefault(require("koa"));
|
|
9
|
+
const ioredis_1 = __importDefault(require("ioredis"));
|
|
10
|
+
const koa_redis_1 = __importDefault(require("koa-redis"));
|
|
11
|
+
const router_1 = __importDefault(require("@koa/router"));
|
|
12
|
+
const koa_bodyparser_1 = __importDefault(require("koa-bodyparser"));
|
|
13
|
+
const cluster_1 = __importDefault(require("cluster"));
|
|
14
|
+
const koa_compress_1 = __importDefault(require("koa-compress"));
|
|
15
|
+
const cors_1 = __importDefault(require("@koa/cors"));
|
|
16
|
+
const fast_glob_1 = __importDefault(require("fast-glob"));
|
|
17
|
+
const koa_cash_1 = __importDefault(require("koa-cash"));
|
|
18
|
+
const koa_logger_1 = __importDefault(require("koa-logger"));
|
|
19
|
+
const os_1 = __importDefault(require("os"));
|
|
20
|
+
const path_1 = __importDefault(require("path"));
|
|
21
|
+
const koa_ratelimit_1 = __importDefault(require("koa-ratelimit"));
|
|
22
|
+
const koa_static_1 = __importDefault(require("koa-static"));
|
|
23
|
+
const koa_session_1 = __importDefault(require("koa-session"));
|
|
24
|
+
const argv = require('yargs/yargs')(process.argv.slice(2)).argv;
|
|
25
|
+
// Set default session duration (24 hours in milliseconds)
|
|
26
|
+
const DEFAULT_SESSION_DURATION = 86400000; // 24 hours in milliseconds
|
|
27
|
+
// Get session duration from environment variable or command line argument
|
|
28
|
+
argv.session_duration = Number(process.env.FLIGHT_SESSION_DURATION_MS) || argv.session_duration;
|
|
29
|
+
// Validate session duration
|
|
30
|
+
if (isNaN(argv.session_duration) || argv.session_duration < 0) {
|
|
31
|
+
console.error('Invalid session duration specified. Using default of 24 hours (86400000ms).');
|
|
32
|
+
argv.session_duration = DEFAULT_SESSION_DURATION;
|
|
33
|
+
}
|
|
34
|
+
if (!argv.app_home) {
|
|
35
|
+
argv.app_home = '.';
|
|
36
|
+
}
|
|
37
|
+
if (!argv.app_key) {
|
|
38
|
+
argv.app_key = 'flightApp';
|
|
39
|
+
}
|
|
40
|
+
if (!argv.app_secret) {
|
|
41
|
+
argv.app_secret = 'the best secret key in the world';
|
|
42
|
+
}
|
|
43
|
+
// Set default port values
|
|
44
|
+
if (!argv.port) {
|
|
45
|
+
// Check for environment variable first, then use default
|
|
46
|
+
argv.port = process.env.FLIGHT_PORT || 3000;
|
|
47
|
+
}
|
|
48
|
+
if (!argv.payload_limit) {
|
|
49
|
+
argv.payload_limit = process.env.FLIGHT_PAYLOAD_LIMIT || '1mb';
|
|
50
|
+
}
|
|
51
|
+
// Convert port to number
|
|
52
|
+
argv.port = Number(argv.port);
|
|
53
|
+
// Validate port is a valid number
|
|
54
|
+
if (isNaN(argv.port) || argv.port < 1 || argv.port > 65535) {
|
|
55
|
+
console.error('Invalid port specified. Using default port 3000.');
|
|
56
|
+
argv.port = 3000;
|
|
57
|
+
}
|
|
58
|
+
// Set default value for disable_vite flag
|
|
59
|
+
if (argv.disable_vite === undefined) {
|
|
60
|
+
// Check for environment variable first, then default to false
|
|
61
|
+
argv.disable_vite = process.env.FLIGHT_DISABLE_VITE === 'true' ? true : false;
|
|
62
|
+
}
|
|
63
|
+
// Ensure the value is a boolean
|
|
64
|
+
argv.disable_vite = Boolean(argv.disable_vite);
|
|
65
|
+
const appHomePath = path_1.default.resolve(argv.app_home);
|
|
66
|
+
process.chdir(appHomePath);
|
|
67
|
+
console.log(appHomePath);
|
|
68
|
+
const mode = process.env.FLIGHT_MODE || argv.mode || 'production';
|
|
69
|
+
/** Prefix without breaking printf-style logs (koa-logger uses `%s` as the first argument). */
|
|
70
|
+
const flightModeLabel = `Flight (${mode}):`;
|
|
71
|
+
const origConsoleLog = console.log.bind(console);
|
|
72
|
+
console.log = (first, ...rest) => {
|
|
73
|
+
if (typeof first === 'string' && /%[sdjifoO%]/.test(first)) {
|
|
74
|
+
origConsoleLog(`${flightModeLabel} ${first}`, ...rest);
|
|
75
|
+
}
|
|
76
|
+
else if (first !== undefined) {
|
|
77
|
+
origConsoleLog(flightModeLabel, first, ...rest);
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
origConsoleLog(flightModeLabel);
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
const redis = new ioredis_1.default({
|
|
84
|
+
host: process.env.FLIGHT_REDIS_HOST || 'localhost',
|
|
85
|
+
port: Number(process.env.FLIGHT_REDIS_PORT) || 6379
|
|
86
|
+
});
|
|
87
|
+
if (cluster_1.default.isPrimary) {
|
|
88
|
+
const numCPUs = os_1.default.cpus().length;
|
|
89
|
+
const maxWorkers = Number(process.env.FLIGHT_MAX_WORKERS) || numCPUs;
|
|
90
|
+
const workersCount = Math.min(maxWorkers, numCPUs);
|
|
91
|
+
for (let i = 0; i < workersCount; i++) {
|
|
92
|
+
cluster_1.default.fork();
|
|
93
|
+
}
|
|
94
|
+
cluster_1.default.on('exit', () => {
|
|
95
|
+
cluster_1.default.fork();
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
const app = new koa_1.default();
|
|
100
|
+
app.use((0, koa_logger_1.default)());
|
|
101
|
+
app.keys = argv.app_secret.split(',');
|
|
102
|
+
const SESSION_CONFIG = {
|
|
103
|
+
key: argv.app_key,
|
|
104
|
+
maxAge: argv.session_duration,
|
|
105
|
+
sameSite: true,
|
|
106
|
+
path: '/',
|
|
107
|
+
store: (0, koa_redis_1.default)({
|
|
108
|
+
client: redis
|
|
109
|
+
})
|
|
110
|
+
};
|
|
111
|
+
app.use((0, koa_session_1.default)(SESSION_CONFIG, app));
|
|
112
|
+
const router = new router_1.default();
|
|
113
|
+
app.use((0, cors_1.default)()).use((0, koa_bodyparser_1.default)({
|
|
114
|
+
jsonLimit: argv.payload_limit
|
|
115
|
+
}));
|
|
116
|
+
const backEndFiles = fast_glob_1.default.sync('**/*.backend.ts');
|
|
117
|
+
backEndFiles.forEach((file) => {
|
|
118
|
+
const serverRoutes = require(path_1.default.resolve(file));
|
|
119
|
+
console.log('Found component backend file: ' + path_1.default.resolve(file));
|
|
120
|
+
if (serverRoutes && serverRoutes.default) {
|
|
121
|
+
router.use(serverRoutes.default);
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
app.use(router.routes()).use(router.allowedMethods());
|
|
125
|
+
if (mode === 'production') {
|
|
126
|
+
console.log('Starting flight in production mode');
|
|
127
|
+
if (!argv.disable_vite) {
|
|
128
|
+
(0, child_process_1.exec)('npx vite build', (error, stdout, stderr) => {
|
|
129
|
+
if (error) {
|
|
130
|
+
console.error(`exec error: ${error}`);
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
console.log(`stdout: ${stdout}`);
|
|
134
|
+
console.error(`stderr: ${stderr}`);
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
app.use((0, koa_compress_1.default)());
|
|
138
|
+
app.use((0, koa_ratelimit_1.default)({
|
|
139
|
+
driver: 'redis',
|
|
140
|
+
db: redis,
|
|
141
|
+
duration: 60000,
|
|
142
|
+
errorMessage: 'Sometimes You Just Have to Slow Down.',
|
|
143
|
+
id: (ctx) => ctx.get('x-forwarded-for') || ctx.ip,
|
|
144
|
+
headers: {
|
|
145
|
+
remaining: 'Rate-Limit-Remaining',
|
|
146
|
+
reset: 'Rate-Limit-Reset',
|
|
147
|
+
total: 'Rate-Limit-Total'
|
|
148
|
+
},
|
|
149
|
+
max: 1200,
|
|
150
|
+
disableHeader: false
|
|
151
|
+
}));
|
|
152
|
+
app.use((0, koa_cash_1.default)({
|
|
153
|
+
get: (key) => redis.get(key),
|
|
154
|
+
set: (key, value) => redis.set(key, value, 'EX', 30)
|
|
155
|
+
}));
|
|
156
|
+
app.use((0, koa_static_1.default)(process.env.FLIGHT_DIST_PATH || '../dist'));
|
|
157
|
+
if (!argv.disable_vite) {
|
|
158
|
+
console.log(`App served out of dist/ and available on port ${argv.port}`);
|
|
159
|
+
}
|
|
160
|
+
else {
|
|
161
|
+
console.log(`App served out of ${appHomePath} and available on port ${argv.port}`);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
app.listen(argv.port, () => {
|
|
165
|
+
console.log(`Server worker ${process.pid} started, All backend services are running on port ${argv.port}`);
|
|
166
|
+
});
|
|
167
|
+
if (mode === 'development') {
|
|
168
|
+
console.log('Starting flight in development mode');
|
|
169
|
+
const viteProcess = (0, child_process_1.spawn)('npx', ['vite', '--port', '3001', '--host', '0.0.0.0'], {
|
|
170
|
+
stdio: 'inherit',
|
|
171
|
+
shell: true
|
|
172
|
+
});
|
|
173
|
+
viteProcess.on('error', (error) => {
|
|
174
|
+
console.error('Failed to start vite server:', error);
|
|
175
|
+
});
|
|
176
|
+
viteProcess.on('exit', (code) => {
|
|
177
|
+
if (code !== 0) {
|
|
178
|
+
console.error(`Vite server exited with code ${code}`);
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
process.on('SIGINT', () => {
|
|
182
|
+
viteProcess.kill('SIGINT');
|
|
183
|
+
process.exit(0);
|
|
184
|
+
});
|
|
185
|
+
console.log(`Vite development server with hot module reload ${process.pid} started on 3001`);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import jsdoc from 'eslint-plugin-jsdoc'
|
|
2
|
+
import tsParser from '@typescript-eslint/parser'
|
|
3
|
+
|
|
4
|
+
export default [
|
|
5
|
+
{
|
|
6
|
+
files: ['**/*.js'],
|
|
7
|
+
plugins: {
|
|
8
|
+
jsdoc: jsdoc
|
|
9
|
+
},
|
|
10
|
+
rules: {
|
|
11
|
+
'jsdoc/require-description': 'error',
|
|
12
|
+
'jsdoc/check-values': 'error'
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
files: ['**/*.ts'],
|
|
17
|
+
languageOptions: {
|
|
18
|
+
parser: tsParser,
|
|
19
|
+
parserOptions: {
|
|
20
|
+
ecmaVersion: 'latest',
|
|
21
|
+
sourceType: 'module'
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
plugins: {
|
|
25
|
+
jsdoc: jsdoc
|
|
26
|
+
},
|
|
27
|
+
rules: {
|
|
28
|
+
'jsdoc/require-description': 'error',
|
|
29
|
+
'jsdoc/check-values': 'error'
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
]
|
package/package.json
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@thoughtpivot/flight",
|
|
3
|
+
"version": "1.0.7",
|
|
4
|
+
"description": "A web server designed for running full stack, component based applications at enterprise scale.",
|
|
5
|
+
"main": "src/flight.ts",
|
|
6
|
+
"publishConfig": {
|
|
7
|
+
"access": "public"
|
|
8
|
+
},
|
|
9
|
+
"bin": {
|
|
10
|
+
"flight": "dist/flight.js"
|
|
11
|
+
},
|
|
12
|
+
"scripts": {
|
|
13
|
+
"lint": "eslint . --fix && prettier -w .",
|
|
14
|
+
"build": "rm -rf dist; node_modules/.bin/tsc --project .",
|
|
15
|
+
"test": "echo \"Error: no test specified\" && exit 0"
|
|
16
|
+
},
|
|
17
|
+
"repository": {
|
|
18
|
+
"type": "git",
|
|
19
|
+
"url": "git+https://github.com/thoughtpivot/flight.git"
|
|
20
|
+
},
|
|
21
|
+
"author": "",
|
|
22
|
+
"license": "MIT",
|
|
23
|
+
"bugs": {
|
|
24
|
+
"url": "https://github.com/thoughtpivot/flight/issues"
|
|
25
|
+
},
|
|
26
|
+
"homepage": "https://github.com/thoughtpivot/flight#readme",
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"@koa/cors": "^5.0.0",
|
|
29
|
+
"@koa/router": "^13.1.0",
|
|
30
|
+
"@vitejs/plugin-vue": "^5.2.1",
|
|
31
|
+
"@vue/language-plugin-pug": "^2.2.0",
|
|
32
|
+
"core-js": "^3.40.0",
|
|
33
|
+
"dotenv": "^17.4.2",
|
|
34
|
+
"esbuild": "^0.28.0",
|
|
35
|
+
"fast-glob": "^3.3.3",
|
|
36
|
+
"ioredis": "^5.4.2",
|
|
37
|
+
"koa": "^2.15.3",
|
|
38
|
+
"koa-bodyparser": "^4.4.1",
|
|
39
|
+
"koa-cash": "^5.0.0",
|
|
40
|
+
"koa-compress": "^5.1.1",
|
|
41
|
+
"koa-connect-history-api-fallback": "^0.3.1",
|
|
42
|
+
"koa-helmet": "^8.0.1",
|
|
43
|
+
"koa-logger": "^3.2.1",
|
|
44
|
+
"koa-ratelimit": "^5.1.0",
|
|
45
|
+
"koa-redis": "^4.0.1",
|
|
46
|
+
"koa-send": "^5.0.1",
|
|
47
|
+
"koa-session": "^6.4.0",
|
|
48
|
+
"koa-static": "^5.0.0",
|
|
49
|
+
"unplugin-fonts": "^2.0.0",
|
|
50
|
+
"vite": "^6.1.7",
|
|
51
|
+
"vite-plugin-vuetify": "^2.0.4",
|
|
52
|
+
"yargs": "^18.0.0"
|
|
53
|
+
},
|
|
54
|
+
"devDependencies": {
|
|
55
|
+
"@types/koa": "^2.15.0",
|
|
56
|
+
"@types/koa__cors": "^5.0.0",
|
|
57
|
+
"@types/koa__router": "^12.0.4",
|
|
58
|
+
"@types/koa-bodyparser": "^4.3.12",
|
|
59
|
+
"@types/koa-compress": "^4.0.6",
|
|
60
|
+
"@types/koa-logger": "^3.1.5",
|
|
61
|
+
"@types/koa-ratelimit": "^5.0.5",
|
|
62
|
+
"@types/koa-redis": "^4.0.5",
|
|
63
|
+
"@types/koa-redis-cache": "^3.0.4",
|
|
64
|
+
"@types/koa-session": "^6.4.5",
|
|
65
|
+
"@types/koa-static": "^4.0.4",
|
|
66
|
+
"@types/node": "^22.19.17",
|
|
67
|
+
"@typescript-eslint/parser": "^8.59.1",
|
|
68
|
+
"eslint": "^9.39.4",
|
|
69
|
+
"eslint-plugin-jsdoc": "^50.8.0",
|
|
70
|
+
"eslint-plugin-vue": "^9.33.0",
|
|
71
|
+
"prettier": "^3.8.3",
|
|
72
|
+
"ts-node": "^10.9.2",
|
|
73
|
+
"typescript": "^5.9.3"
|
|
74
|
+
}
|
|
75
|
+
}
|
package/src/flight.ts
ADDED
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
#!ts-node
|
|
2
|
+
|
|
3
|
+
import { exec, spawn } from 'child_process'
|
|
4
|
+
|
|
5
|
+
import Koa from 'koa'
|
|
6
|
+
import Redis from 'ioredis'
|
|
7
|
+
import RedisStore from 'koa-redis'
|
|
8
|
+
import Router from '@koa/router'
|
|
9
|
+
import bodyParser from 'koa-bodyparser'
|
|
10
|
+
import cluster from 'cluster'
|
|
11
|
+
import compress from 'koa-compress'
|
|
12
|
+
import cors from '@koa/cors'
|
|
13
|
+
import fg from 'fast-glob'
|
|
14
|
+
import koaCash from 'koa-cash'
|
|
15
|
+
import logger from 'koa-logger'
|
|
16
|
+
import os from 'os'
|
|
17
|
+
import path from 'path'
|
|
18
|
+
import ratelimit from 'koa-ratelimit'
|
|
19
|
+
import serve from 'koa-static'
|
|
20
|
+
import session from 'koa-session'
|
|
21
|
+
|
|
22
|
+
const argv = require('yargs/yargs')(process.argv.slice(2)).argv
|
|
23
|
+
|
|
24
|
+
// Set default session duration (24 hours in milliseconds)
|
|
25
|
+
const DEFAULT_SESSION_DURATION = 86400000 // 24 hours in milliseconds
|
|
26
|
+
|
|
27
|
+
// Get session duration from environment variable or command line argument
|
|
28
|
+
argv.session_duration = Number(process.env.FLIGHT_SESSION_DURATION_MS) || argv.session_duration
|
|
29
|
+
|
|
30
|
+
// Validate session duration
|
|
31
|
+
if (isNaN(argv.session_duration) || argv.session_duration < 0) {
|
|
32
|
+
console.error('Invalid session duration specified. Using default of 24 hours (86400000ms).')
|
|
33
|
+
argv.session_duration = DEFAULT_SESSION_DURATION
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (!argv.app_home) {
|
|
37
|
+
argv.app_home = '.'
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (!argv.app_key) {
|
|
41
|
+
argv.app_key = 'flightApp'
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (!argv.app_secret) {
|
|
45
|
+
argv.app_secret = 'the best secret key in the world'
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Set default port values
|
|
49
|
+
if (!argv.port) {
|
|
50
|
+
// Check for environment variable first, then use default
|
|
51
|
+
argv.port = process.env.FLIGHT_PORT || 3000
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (!argv.payload_limit) {
|
|
55
|
+
argv.payload_limit = process.env.FLIGHT_PAYLOAD_LIMIT || '1mb'
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Convert port to number
|
|
59
|
+
argv.port = Number(argv.port)
|
|
60
|
+
|
|
61
|
+
// Validate port is a valid number
|
|
62
|
+
if (isNaN(argv.port) || argv.port < 1 || argv.port > 65535) {
|
|
63
|
+
console.error('Invalid port specified. Using default port 3000.')
|
|
64
|
+
argv.port = 3000
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Set default value for disable_vite flag
|
|
68
|
+
if (argv.disable_vite === undefined) {
|
|
69
|
+
// Check for environment variable first, then default to false
|
|
70
|
+
argv.disable_vite = process.env.FLIGHT_DISABLE_VITE === 'true' ? true : false
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Ensure the value is a boolean
|
|
74
|
+
argv.disable_vite = Boolean(argv.disable_vite)
|
|
75
|
+
|
|
76
|
+
const appHomePath = path.resolve(argv.app_home)
|
|
77
|
+
process.chdir(appHomePath)
|
|
78
|
+
|
|
79
|
+
console.log(appHomePath)
|
|
80
|
+
|
|
81
|
+
const mode = process.env.FLIGHT_MODE || argv.mode || 'production'
|
|
82
|
+
|
|
83
|
+
/** Prefix without breaking printf-style logs (koa-logger uses `%s` as the first argument). */
|
|
84
|
+
const flightModeLabel = `Flight (${mode}):`
|
|
85
|
+
const origConsoleLog = console.log.bind(console)
|
|
86
|
+
console.log = (first?: unknown, ...rest: unknown[]): void => {
|
|
87
|
+
if (typeof first === 'string' && /%[sdjifoO%]/.test(first)) {
|
|
88
|
+
origConsoleLog(`${flightModeLabel} ${first}`, ...rest)
|
|
89
|
+
} else if (first !== undefined) {
|
|
90
|
+
origConsoleLog(flightModeLabel, first, ...rest)
|
|
91
|
+
} else {
|
|
92
|
+
origConsoleLog(flightModeLabel)
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const redis = new Redis({
|
|
97
|
+
host: process.env.FLIGHT_REDIS_HOST || 'localhost',
|
|
98
|
+
port: Number(process.env.FLIGHT_REDIS_PORT) || 6379
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
if (cluster.isPrimary) {
|
|
102
|
+
const numCPUs = os.cpus().length
|
|
103
|
+
const maxWorkers = Number(process.env.FLIGHT_MAX_WORKERS) || numCPUs
|
|
104
|
+
const workersCount = Math.min(maxWorkers, numCPUs)
|
|
105
|
+
|
|
106
|
+
for (let i = 0; i < workersCount; i++) {
|
|
107
|
+
cluster.fork()
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
cluster.on('exit', () => {
|
|
111
|
+
cluster.fork()
|
|
112
|
+
})
|
|
113
|
+
} else {
|
|
114
|
+
const app = new Koa()
|
|
115
|
+
app.use(logger())
|
|
116
|
+
|
|
117
|
+
app.keys = argv.app_secret.split(',')
|
|
118
|
+
|
|
119
|
+
const SESSION_CONFIG = {
|
|
120
|
+
key: argv.app_key,
|
|
121
|
+
maxAge: argv.session_duration,
|
|
122
|
+
sameSite: true,
|
|
123
|
+
path: '/',
|
|
124
|
+
store: RedisStore({
|
|
125
|
+
client: redis
|
|
126
|
+
})
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
app.use(session(SESSION_CONFIG, app))
|
|
130
|
+
|
|
131
|
+
const router = new Router()
|
|
132
|
+
|
|
133
|
+
app.use(cors()).use(
|
|
134
|
+
bodyParser({
|
|
135
|
+
jsonLimit: argv.payload_limit
|
|
136
|
+
})
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
const backEndFiles = fg.sync('**/*.backend.ts')
|
|
140
|
+
backEndFiles.forEach((file) => {
|
|
141
|
+
const serverRoutes = require(path.resolve(file))
|
|
142
|
+
|
|
143
|
+
console.log('Found component backend file: ' + path.resolve(file))
|
|
144
|
+
|
|
145
|
+
if (serverRoutes && serverRoutes.default) {
|
|
146
|
+
router.use(serverRoutes.default)
|
|
147
|
+
}
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
app.use(router.routes()).use(router.allowedMethods())
|
|
151
|
+
|
|
152
|
+
if (mode === 'production') {
|
|
153
|
+
console.log('Starting flight in production mode')
|
|
154
|
+
|
|
155
|
+
if (!argv.disable_vite) {
|
|
156
|
+
exec('npx vite build', (error, stdout, stderr) => {
|
|
157
|
+
if (error) {
|
|
158
|
+
console.error(`exec error: ${error}`)
|
|
159
|
+
return
|
|
160
|
+
}
|
|
161
|
+
console.log(`stdout: ${stdout}`)
|
|
162
|
+
console.error(`stderr: ${stderr}`)
|
|
163
|
+
})
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
app.use(compress())
|
|
167
|
+
app.use(
|
|
168
|
+
ratelimit({
|
|
169
|
+
driver: 'redis',
|
|
170
|
+
db: redis,
|
|
171
|
+
duration: 60000,
|
|
172
|
+
errorMessage: 'Sometimes You Just Have to Slow Down.',
|
|
173
|
+
id: (ctx) => ctx.get('x-forwarded-for') || ctx.ip,
|
|
174
|
+
headers: {
|
|
175
|
+
remaining: 'Rate-Limit-Remaining',
|
|
176
|
+
reset: 'Rate-Limit-Reset',
|
|
177
|
+
total: 'Rate-Limit-Total'
|
|
178
|
+
},
|
|
179
|
+
max: 1200,
|
|
180
|
+
disableHeader: false
|
|
181
|
+
})
|
|
182
|
+
)
|
|
183
|
+
app.use(
|
|
184
|
+
koaCash({
|
|
185
|
+
get: (key) => redis.get(key),
|
|
186
|
+
set: (key, value) => redis.set(key, value, 'EX', 30)
|
|
187
|
+
})
|
|
188
|
+
)
|
|
189
|
+
app.use(serve(process.env.FLIGHT_DIST_PATH || '../dist'))
|
|
190
|
+
|
|
191
|
+
if (!argv.disable_vite) {
|
|
192
|
+
console.log(`App served out of dist/ and available on port ${argv.port}`)
|
|
193
|
+
} else {
|
|
194
|
+
console.log(`App served out of ${appHomePath} and available on port ${argv.port}`)
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
app.listen(argv.port, () => {
|
|
199
|
+
console.log(`Server worker ${process.pid} started, All backend services are running on port ${argv.port}`)
|
|
200
|
+
})
|
|
201
|
+
|
|
202
|
+
if (mode === 'development') {
|
|
203
|
+
console.log('Starting flight in development mode')
|
|
204
|
+
const viteProcess = spawn('npx', ['vite', '--port', '3001', '--host', '0.0.0.0'], {
|
|
205
|
+
stdio: 'inherit',
|
|
206
|
+
shell: true
|
|
207
|
+
})
|
|
208
|
+
|
|
209
|
+
viteProcess.on('error', (error) => {
|
|
210
|
+
console.error('Failed to start vite server:', error)
|
|
211
|
+
})
|
|
212
|
+
|
|
213
|
+
viteProcess.on('exit', (code) => {
|
|
214
|
+
if (code !== 0) {
|
|
215
|
+
console.error(`Vite server exited with code ${code}`)
|
|
216
|
+
}
|
|
217
|
+
})
|
|
218
|
+
|
|
219
|
+
process.on('SIGINT', () => {
|
|
220
|
+
viteProcess.kill('SIGINT')
|
|
221
|
+
process.exit(0)
|
|
222
|
+
})
|
|
223
|
+
|
|
224
|
+
console.log(`Vite development server with hot module reload ${process.pid} started on 3001`)
|
|
225
|
+
}
|
|
226
|
+
}
|