@thoughtpivot/flight 1.1.0 → 2.0.2
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/CHANGELOG.md +24 -0
- package/README.md +169 -138
- package/dist/flight.js +70 -12
- package/dist/spa-pipeline.js +122 -0
- package/package.json +12 -2
- package/readme-assets/thoughtpivot-logo.svg +17 -0
- package/src/flight.ts +100 -30
- package/.circleci/config.yml +0 -83
- package/.editorconfig +0 -5
- package/.github/dependabot.yml +0 -11
- package/.nvmrc +0 -1
- package/.prettierrc +0 -11
- package/.vscode/extensions.json +0 -3
- package/.vscode/settings.json +0 -10
- package/eslint.config.mjs +0 -32
- package/tsconfig.json +0 -10
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 2.0.0
|
|
4
|
+
|
|
5
|
+
### Breaking
|
|
6
|
+
|
|
7
|
+
- **Production middleware order** when `FLIGHT_MODE` / `mode` is `production` **and** built-assets mode is on (`--disable_vite` or `FLIGHT_DISABLE_VITE` of `true` / `1` / `yes`): Flight now uses the **production SPA pipeline** by default. Static files from `FLIGHT_DIST_PATH` (default `../dist`) and the SPA `index.html` fallback run **immediately after** the Koa `router` (your `**/*.backend.ts` routes), **before** `koa-compress`, Redis-backed rate limiting, and optional response caching.
|
|
8
|
+
- **`koa-cash` is off by default** in that SPA pipeline (to avoid caching HTML or mixed `vary` surprises). Enable with **`FLIGHT_HTTP_CACHE=true`** (or `1` / `yes`) if you want the previous Redis-backed cache layer in that configuration.
|
|
9
|
+
- **Legacy stack** (previous order: `compress` → `ratelimit` → `koa-cash` → `koa-static` after the router) remains when **either** Vite is **not** disabled in production **or** you set **`FLIGHT_DISABLE_SPA_PIPELINE=true`** (or `1` / `yes`).
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- **Yargs CLI parity** for **`--mode`**, **`--port`**, **`--app_home`**, **`--app_key`**, **`--app_secret`**, **`--payload_limit`**, and **`--disable_vite`** (plus kebab-case aliases), so documented flags populate `argv` reliably instead of being dropped by the parser.
|
|
14
|
+
- **`FLIGHT_TRUST_PROXY`**: when `true` / `1` / `yes`, sets `app.proxy = true` so `ctx.ip` and rate-limit identity honor `X-Forwarded-For` behind a reverse proxy or load balancer.
|
|
15
|
+
- **`FLIGHT_STATIC_PREFIXES`**: comma-separated URL path prefixes (default `/assets,/fonts`) used to build the **rate-limit skip** list for `GET`/`HEAD` (hashed assets should not burn the API limiter).
|
|
16
|
+
- **`FLIGHT_RATE_LIMIT_EXCLUDE_PREFIXES`**: extra comma-separated prefixes merged into that skip list.
|
|
17
|
+
- **`FLIGHT_SPA_INDEX`**: path to the SPA shell inside the dist root (default `index.html`).
|
|
18
|
+
- **`FLIGHT_SPA_DENY_PREFIXES`**: extra comma-separated path prefixes that never receive the SPA HTML fallback (always merged with `/api` and `/health`).
|
|
19
|
+
- **`npm test`**: regression tests for the SPA pipeline (assets, deep links, API prefix, file-like paths).
|
|
20
|
+
- **`files` field** in `package.json` so publishes include only `dist/flight.js`, `dist/spa-pipeline.js`, and docs assets.
|
|
21
|
+
|
|
22
|
+
### Notes
|
|
23
|
+
|
|
24
|
+
- `FLIGHT_DISABLE_VITE` accepts **`true`**, **`1`**, or **`yes`** (case-sensitive values as implemented for the string checks).
|
package/README.md
CHANGED
|
@@ -1,23 +1,24 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="readme-assets/thoughtpivot-logo.svg" alt="ThoughtPivot" width="280" />
|
|
3
|
+
</p>
|
|
4
|
+
|
|
1
5
|
# Flight
|
|
2
6
|
|
|
3
|
-
Flight is a
|
|
7
|
+
**Flight** is a Node.js application server for teams who want something **fast**, **boring in the good way**, and **ready for serious traffic**. You bring your own hosting—there is no lock-in to a proprietary edge or a single vendor’s deployment story. It fits **twelve-factor** style workflows: configuration via environment variables, horizontal scaling, and state kept where it belongs (for Flight, that includes **Redis** for sessions and cache-friendly layers).
|
|
4
8
|
|
|
5
|
-
|
|
9
|
+
Think **platform-agnostic**: not framework-as-a-platform, but a clear runtime you can run wherever Node runs—VMs, Kubernetes, bare metal, your cloud of choice. Flight is aimed at **hyperscale-friendly** designs (cluster workers out of the box), **ephemeral** processes, and **component-shaped** backends so routes stay colocated with the features they serve. **Vue** and **Vite** are first-class today; **React** support is on the roadmap.
|
|
6
10
|
|
|
7
|
-
Flight
|
|
11
|
+
Flight is **open source** from **[ThoughtPivot](https://github.com/thoughtpivot)**.
|
|
8
12
|
|
|
9
|
-
|
|
13
|
+
## Highlights
|
|
10
14
|
|
|
11
|
-
- **
|
|
12
|
-
- **
|
|
13
|
-
- **
|
|
14
|
-
- **Production
|
|
15
|
-
- **
|
|
16
|
-
- **
|
|
17
|
-
- **
|
|
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
|
|
15
|
+
- **Performance-focused**: Cluster mode, compression, Redis-backed caching hooks, rate limiting in production
|
|
16
|
+
- **Developer velocity**: Vite-powered dev server with HMR for **Vue** (React roadmap)
|
|
17
|
+
- **Composable backends**: Discover `**/*.backend.ts` under your app root and mount Koa routes per component
|
|
18
|
+
- **Production SPA**: Built-in **`dist` + `index.html`** fallback (option B) when running **`production`** with **`disable_vite`**, with an explicit opt-out for API-only processes
|
|
19
|
+
- **Configurable discovery**: `--exclude_paths` / `FLIGHT_EXCLUDE_PATHS` to skip directories when scanning backends
|
|
20
|
+
- **TypeScript-native**: Written for TS projects; sensible defaults, minimal ceremony
|
|
21
|
+
- **Interop-friendly**: Correct handling of `yargs` when launched via **tsx** or similar loaders (no patch-package needed from **v1.0.8** onward)
|
|
21
22
|
|
|
22
23
|
## Installation
|
|
23
24
|
|
|
@@ -27,11 +28,11 @@ npm install @thoughtpivot/flight
|
|
|
27
28
|
yarn add @thoughtpivot/flight
|
|
28
29
|
```
|
|
29
30
|
|
|
30
|
-
|
|
31
|
+
Legacy npm scope: the package was previously published as `@spytech/flight`—use `@thoughtpivot/flight` going forward.
|
|
31
32
|
|
|
32
|
-
### Downstream apps (tsx
|
|
33
|
+
### Downstream apps (tsx)
|
|
33
34
|
|
|
34
|
-
From **v1.0.8** onward, Flight normalizes `require('yargs/yargs')`
|
|
35
|
+
From **v1.0.8** onward, Flight normalizes `require('yargs/yargs')` under loaders that expose `{ default: factory }`. If you added **patch-package** only for that issue, upgrade Flight and drop that patch.
|
|
35
36
|
|
|
36
37
|
## Quick Start
|
|
37
38
|
|
|
@@ -49,13 +50,7 @@ npm init -y
|
|
|
49
50
|
npm install @thoughtpivot/flight ioredis
|
|
50
51
|
```
|
|
51
52
|
|
|
52
|
-
3. Ensure Redis is running locally or set environment variables
|
|
53
|
-
|
|
54
|
-
```bash
|
|
55
|
-
# Default values shown below
|
|
56
|
-
export FLIGHT_REDIS_HOST=localhost
|
|
57
|
-
export FLIGHT_REDIS_PORT=6379
|
|
58
|
-
```
|
|
53
|
+
3. Ensure Redis is running locally or set environment variables (see table below).
|
|
59
54
|
|
|
60
55
|
4. Create a component with a backend route:
|
|
61
56
|
|
|
@@ -77,50 +72,130 @@ router.get('/hello', async (ctx) => {
|
|
|
77
72
|
export default router.routes()
|
|
78
73
|
```
|
|
79
74
|
|
|
80
|
-
5.
|
|
75
|
+
5. Add scripts to **`package.json`** (the `flight` binary comes from **`node_modules/.bin`** after install):
|
|
76
|
+
|
|
77
|
+
```json
|
|
78
|
+
{
|
|
79
|
+
"scripts": {
|
|
80
|
+
"dev": "flight --mode development",
|
|
81
|
+
"start": "flight --mode production"
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Use **`FLIGHT_DISABLE_VITE`**, **`FLIGHT_DIST_PATH`**, and related env vars in **`.env`** for production built-assets mode (see the configuration table below).
|
|
81
87
|
|
|
82
|
-
|
|
88
|
+
6. Start the server:
|
|
89
|
+
|
|
90
|
+
**Development (Vite on 3001, API on 3000):**
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
npx flight --mode development
|
|
94
|
+
# or: npm run dev
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
**Production:**
|
|
83
98
|
|
|
84
99
|
```bash
|
|
85
|
-
|
|
86
|
-
#
|
|
87
|
-
# Backend API available on port 3000
|
|
100
|
+
npx flight --mode production
|
|
101
|
+
# or: npm run start
|
|
88
102
|
```
|
|
89
103
|
|
|
90
|
-
|
|
104
|
+
**`--app_home`** defaults to **`.`** (the current working directory). Pass **`--app_home path/to/app`** only when your app root is not the directory you run the command from.
|
|
105
|
+
|
|
106
|
+
**Try without adding a dependency** (downloads the package for this invocation):
|
|
91
107
|
|
|
92
108
|
```bash
|
|
93
|
-
|
|
94
|
-
# Builds and serves application on port 3000
|
|
109
|
+
npx --yes --package @thoughtpivot/flight flight --mode development
|
|
95
110
|
```
|
|
96
111
|
|
|
97
|
-
|
|
112
|
+
**Global install** (optional): `npm install -g @thoughtpivot/flight`, then run **`flight`** from your PATH the same way as **`npx flight`**.
|
|
98
113
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
-
|
|
102
|
-
|
|
103
|
-
|
|
114
|
+
## Development vs production
|
|
115
|
+
|
|
116
|
+
Flight picks **`mode`** as: **`FLIGHT_MODE`** (if set and non-empty), else **`--mode`** from argv, else **`production`**. Everything below assumes Redis is reachable unless you only use routes that avoid session/ratelimit/cache.
|
|
117
|
+
|
|
118
|
+
| Topic | Development | Production |
|
|
119
|
+
| ---------------- | --------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
120
|
+
| **Vite** | Child process: `npx vite --port 3001 --host 0.0.0.0` (HMR). | If **`disable_vite` is false**: `exec('npx vite build')` is triggered once at worker startup. If **`disable_vite` is true**: no Vite build. |
|
|
121
|
+
| **Listen port** | API + sessions on **`--port`** / `FLIGHT_PORT` (default **3000**). UI dev server on **3001**. | Same **`--port`** / `FLIGHT_PORT` for the worker. |
|
|
122
|
+
| **Static / SPA** | The Vite dev server serves UI; Flight does **not** mount the production static/SPA stack. | See **Production SPA pipeline** below: when **`production`** + **`disable_vite`**, Flight uses **Option A** by default unless opted out. |
|
|
123
|
+
| **Typical use** | Local full-stack with HMR. | CI-built `dist` served by Flight (or behind a load balancer). |
|
|
124
|
+
|
|
125
|
+
**Edge cases**
|
|
126
|
+
|
|
127
|
+
- **`production` + `disable_vite` false**: Vite build still runs; **legacy** middleware order applies (`compress` → `ratelimit` → `koa-cash` → static), because built-assets-only mode is off.
|
|
128
|
+
- **`production` + `disable_vite` true + `FLIGHT_DISABLE_SPA_PIPELINE`**: **Legacy** stack only—no SPA HTML fallback for unknown GETs; tail `koa-static` only after compress/ratelimit/cache.
|
|
129
|
+
- **`development`**: unchanged—Redis session store and production-only static/SPA branches do not apply the SPA pipeline section (the dev Vite server owns UI URLs).
|
|
130
|
+
|
|
131
|
+
## Configuration: CLI, `.env`, and environment variables
|
|
132
|
+
|
|
133
|
+
Flight loads a **`.env`** file from the **current working directory** at startup (via **`dotenv`**). Use **`FLIGHT_*`** in real deployments.
|
|
134
|
+
|
|
135
|
+
**Precedence**
|
|
136
|
+
|
|
137
|
+
- **`mode`**: **`FLIGHT_MODE`** wins over **`--mode`** when the variable is set (non-empty).
|
|
138
|
+
- **`disable_vite`**: explicit CLI wins; if omitted, **`FLIGHT_DISABLE_VITE`** (`true` / `1` / `yes` → on); otherwise **`false`**.
|
|
139
|
+
- Other settings: argv value if your launcher passes it, else the **`FLIGHT_*`** column, then default.
|
|
140
|
+
|
|
141
|
+
**Registered CLI options:** Flight registers **`--exclude_paths`**, **`--mode`**, **`--port`**, **`--app_home`**, **`--app_key`**, **`--app_secret`**, **`--payload_limit`**, and **`--disable_vite`** (with kebab-case aliases where listed in `--help`). yargs is not in **strict** mode, so additional flags may still appear on argv if you extend the entrypoint.
|
|
142
|
+
|
|
143
|
+
| Setting (CLI / argv) | Environment variable | Default | Use case |
|
|
144
|
+
| ------------------------------------ | ------------------------------------ | ---------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
145
|
+
| `--exclude_paths`, `--exclude-paths` | `FLIGHT_EXCLUDE_PATHS` | _(empty)_ | Skip subtrees when globbing `**/*.backend.ts` (monorepo vendor trees, fixtures). |
|
|
146
|
+
| `--app_home` | `FLIGHT_APP_HOME` | `.` | Repository root; `process.chdir` target before discovery and static paths. |
|
|
147
|
+
| `--app_key` | `FLIGHT_APP_KEY` | `flightApp` | Session cookie name. |
|
|
148
|
+
| `--app_secret` | `FLIGHT_APP_SECRET` | _(see code)_ | Cookie signing secret(s); comma-separated for key rotation. |
|
|
149
|
+
| _(argv)_ | `FLIGHT_SESSION_DURATION_MS` | `86400000` | Session `maxAge` in ms. |
|
|
150
|
+
| `--port` | `FLIGHT_PORT` | `3000` | HTTP listen port (all modes). |
|
|
151
|
+
| `--payload_limit` | `FLIGHT_PAYLOAD_LIMIT` | `1mb` | `koa-bodyparser` JSON limit. |
|
|
152
|
+
| `--disable_vite` | `FLIGHT_DISABLE_VITE` | `false` | **`true` / `1` / `yes`**: skip `vite build` and treat the process as **built-assets** mode (triggers SPA pipeline rules in production). |
|
|
153
|
+
| `--mode` | `FLIGHT_MODE` | `production` | `development` spawns Vite; `production` enables production middleware. |
|
|
154
|
+
| _(n/a)_ | `FLIGHT_DIST_PATH` | `../dist` | Filesystem root for `koa-static` (relative to cwd **after** chdir to `app_home`). |
|
|
155
|
+
| _(n/a)_ | `FLIGHT_DISABLE_SPA_PIPELINE` | _(unset)_ | **`1` / `true` / `yes`**: force **legacy** production stack (no in-router SPA fallback) while keeping `production` + `disable_vite`. Use for **API-only** workers or until duplicate app middleware is removed. |
|
|
156
|
+
| _(n/a)_ | `FLIGHT_STATIC_PREFIXES` | `/assets,/fonts` | Baseline URL prefixes for **rate-limit skipping** on `GET`/`HEAD` (static hashed chunks). |
|
|
157
|
+
| _(n/a)_ | `FLIGHT_RATE_LIMIT_EXCLUDE_PREFIXES` | _(empty)_ | Extra prefixes merged with `FLIGHT_STATIC_PREFIXES` for the same skip list. |
|
|
158
|
+
| _(n/a)_ | `FLIGHT_SPA_INDEX` | `index.html` | SPA shell file inside the dist root for HTML fallback. |
|
|
159
|
+
| _(n/a)_ | `FLIGHT_SPA_DENY_PREFIXES` | _(empty)_ | Extra prefixes that never get SPA fallback (merged with `/api`, `/health`). |
|
|
160
|
+
| _(n/a)_ | `FLIGHT_TRUST_PROXY` | `false` | **`1` / `true` / `yes`**: set **`app.proxy = true`** so `ctx.ip` / rate-limit id use **`X-Forwarded-For`** behind an L7 LB. |
|
|
161
|
+
| _(n/a)_ | `FLIGHT_HTTP_CACHE` | `false` | **`1` / `true` / `yes`**: enable **`koa-cash`** in the **SPA pipeline** branch only (off by default there). Legacy branch always attaches `koa-cash`. |
|
|
162
|
+
| _(n/a)_ | `FLIGHT_REDIS_HOST` | `localhost` | Redis for sessions, rate limit, and cache adapter. |
|
|
163
|
+
| _(n/a)_ | `FLIGHT_REDIS_PORT` | `6379` | Redis port. |
|
|
164
|
+
| _(n/a)_ | `FLIGHT_MAX_WORKERS` | CPU count | Cap cluster worker count on small nodes. |
|
|
165
|
+
|
|
166
|
+
Example `.env` fragment:
|
|
167
|
+
|
|
168
|
+
```bash
|
|
169
|
+
FLIGHT_MODE=development
|
|
170
|
+
FLIGHT_APP_HOME=.
|
|
171
|
+
FLIGHT_REDIS_HOST=127.0.0.1
|
|
172
|
+
FLIGHT_REDIS_PORT=6379
|
|
173
|
+
FLIGHT_PORT=3000
|
|
174
|
+
FLIGHT_MAX_WORKERS=4
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
Production behind a proxy:
|
|
178
|
+
|
|
179
|
+
```bash
|
|
180
|
+
FLIGHT_MODE=production
|
|
181
|
+
FLIGHT_DISABLE_VITE=true
|
|
182
|
+
FLIGHT_TRUST_PROXY=1
|
|
183
|
+
```
|
|
104
184
|
|
|
105
185
|
## Project Structure
|
|
106
186
|
|
|
107
187
|
```
|
|
108
188
|
my-app/
|
|
109
|
-
├── components/
|
|
110
|
-
│
|
|
111
|
-
│
|
|
112
|
-
│
|
|
113
|
-
├── assets/
|
|
114
|
-
├── dist/
|
|
189
|
+
├── components/
|
|
190
|
+
│ └── Hello/
|
|
191
|
+
│ ├── Hello.vue # Vue UI (example)
|
|
192
|
+
│ └── Hello.backend.ts # Koa routes for this component
|
|
193
|
+
├── assets/
|
|
194
|
+
├── dist/ # Production build output
|
|
115
195
|
└── package.json
|
|
116
196
|
```
|
|
117
197
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
- `Index.vue`: Contains the Vue component template, script, and styles
|
|
121
|
-
- `Index.backend.ts`: Contains the backend routes and logic for the component
|
|
122
|
-
|
|
123
|
-
Example component files:
|
|
198
|
+
Example Vue + backend snippets:
|
|
124
199
|
|
|
125
200
|
`components/hello/Index.vue`:
|
|
126
201
|
|
|
@@ -152,107 +227,67 @@ router.get('/hello', async (ctx) => {
|
|
|
152
227
|
export default router.routes()
|
|
153
228
|
```
|
|
154
229
|
|
|
155
|
-
##
|
|
230
|
+
## Development mode
|
|
156
231
|
|
|
157
|
-
|
|
232
|
+
- **Vue** + **Vite** with HMR on port **3001**
|
|
233
|
+
- Backend worker on **`--port`** (default **3000**)
|
|
234
|
+
- Request logging via **`koa-logger`**
|
|
158
235
|
|
|
159
|
-
|
|
160
|
-
FLIGHT_MODE=development
|
|
161
|
-
FLIGHT_REDIS_HOST=localhost
|
|
162
|
-
FLIGHT_REDIS_PORT=6379
|
|
163
|
-
FLIGHT_MAX_WORKERS=4
|
|
164
|
-
```
|
|
165
|
-
|
|
166
|
-
## Development Mode
|
|
167
|
-
|
|
168
|
-
In development mode, Flight provides:
|
|
169
|
-
|
|
170
|
-
- Hot Module Replacement (HMR)
|
|
171
|
-
- Fast refresh for React components
|
|
172
|
-
- Detailed error messages
|
|
173
|
-
- Development server on port 3001
|
|
174
|
-
|
|
175
|
-
## Production Mode
|
|
236
|
+
## Production mode
|
|
176
237
|
|
|
177
|
-
|
|
238
|
+
- Optional **`npx vite build`** before serving static assets when **`disable_vite`** is **false**
|
|
239
|
+
- Cluster workers capped by **`FLIGHT_MAX_WORKERS`**
|
|
240
|
+
- Compression + Redis-backed rate limiting in all production configurations
|
|
241
|
+
- Static dist + SPA behavior depends on **built-assets mode**—see next section
|
|
178
242
|
|
|
179
|
-
|
|
180
|
-
- Rate limiting
|
|
181
|
-
- Response compression
|
|
182
|
-
- Redis caching
|
|
183
|
-
- Cluster mode for load balancing
|
|
184
|
-
- Production server on port 3000
|
|
243
|
+
## Production SPA pipeline (v2)
|
|
185
244
|
|
|
186
|
-
|
|
245
|
+
**When it is on (option B):** `mode === 'production'` **and** **`disable_vite === true`** **and** **`FLIGHT_DISABLE_SPA_PIPELINE` is not truthy.**
|
|
187
246
|
|
|
188
|
-
|
|
247
|
+
**What it does:** After your **`router`** (all `**/*.backend.ts` routes), Flight serves **`FLIGHT_DIST_PATH`** with **`koa-static`**, then applies an **`index.html`** fallback for typical browser navigations (see tests in `src/spa-pipeline.test.ts` for the behavioral contract). Only then runs **`koa-compress`**, Redis **`koa-ratelimit`** (with **`GET`/`HEAD` skipped** under `FLIGHT_STATIC_PREFIXES` ∪ `FLIGHT_RATE_LIMIT_EXCLUDE_PREFIXES`), and optionally **`koa-cash`** when **`FLIGHT_HTTP_CACHE`** is enabled.
|
|
189
248
|
|
|
190
|
-
|
|
249
|
+
**Path ownership**
|
|
191
250
|
|
|
192
|
-
- **
|
|
193
|
-
- **
|
|
194
|
-
- **
|
|
251
|
+
- **API / BFF:** Implement on your `Router` in `*.backend.ts` under prefixes you own (conventionally **`/api/...`**). Those paths are excluded from the SPA fallback, along with **`/health`** and any **`FLIGHT_SPA_DENY_PREFIXES`**.
|
|
252
|
+
- **Static:** Any file present under the dist root is served by **`koa-static`** first.
|
|
253
|
+
- **SPA:** Remaining **`GET`/`HEAD`** requests that look like document navigations and whose last path segment has **no dot** (so `file.ext` URLs are not rewritten) may receive **`FLIGHT_SPA_INDEX`**.
|
|
195
254
|
|
|
196
|
-
|
|
255
|
+
**Load balancers and `X-Forwarded-For`**
|
|
197
256
|
|
|
198
|
-
1. **
|
|
199
|
-
|
|
200
|
-
- Flight: Streamlined with `.backend.ts` files and modern frontend frameworks
|
|
201
|
-
2. **Development Experience**
|
|
202
|
-
- Avian: Webpack-based bundling with slower rebuild times
|
|
203
|
-
- Flight: Vite-powered development with instant HMR and no bundle step in development
|
|
257
|
+
- Set **`FLIGHT_TRUST_PROXY=1`** when Flight sits behind a trusted reverse proxy so **`ctx.ip`** and the rate limiter’s **`id`** reflect the client. Configure your proxy to append **one** well-formed **`X-Forwarded-For`** chain.
|
|
258
|
+
- Sessions use cookies (`sameSite: true` in code today); sticky sessions or shared Redis are operational choices outside Flight’s defaults.
|
|
204
259
|
|
|
205
|
-
|
|
206
|
-
- Avian: Express-session with Redis store
|
|
207
|
-
- Flight: Koa-session with Redis store, improved security defaults
|
|
260
|
+
### Middleware order (mermaid)
|
|
208
261
|
|
|
209
|
-
|
|
210
|
-
- Built-in rate limiting
|
|
211
|
-
- Redis-based caching
|
|
212
|
-
- Automatic compression in production
|
|
213
|
-
- Cluster mode for CPU utilization
|
|
262
|
+
**SPA pipeline (default when production + `disable_vite`):**
|
|
214
263
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
2. **New Features**
|
|
228
|
-
- Native ESM support
|
|
229
|
-
- Built-in CORS support
|
|
230
|
-
- Improved Redis integration
|
|
231
|
-
- Better security defaults
|
|
232
|
-
- Simpler API for backend routes
|
|
233
|
-
- Modern frontend tooling support
|
|
264
|
+
```mermaid
|
|
265
|
+
flowchart TD
|
|
266
|
+
A[cors + bodyparser + session] --> B[router + *.backend.ts]
|
|
267
|
+
B --> C[koa-static dist]
|
|
268
|
+
C --> D[SPA index.html fallback]
|
|
269
|
+
D --> E[koa-compress]
|
|
270
|
+
E --> F[koa-ratelimit with prefix skips]
|
|
271
|
+
F --> G{koa-cash}
|
|
272
|
+
G -->|FLIGHT_HTTP_CACHE on| H[Redis-backed cache]
|
|
273
|
+
G -->|default| I[no cache middleware]
|
|
274
|
+
```
|
|
234
275
|
|
|
235
|
-
|
|
236
|
-
- Reduced configuration complexity
|
|
237
|
-
- More intuitive component organization
|
|
238
|
-
- Better separation of concerns
|
|
239
|
-
- Modern middleware approach
|
|
276
|
+
**Legacy production stack** (`disable_vite` false, or **`FLIGHT_DISABLE_SPA_PIPELINE`** set): `router` → **`compress`** → **`ratelimit`** → **`koa-cash`** → **`koa-static`**.
|
|
240
277
|
|
|
241
|
-
###
|
|
278
|
+
### Migrating from Flight 1.x
|
|
242
279
|
|
|
243
|
-
If you
|
|
280
|
+
1. If you rely on the **old** order (everything after `router` saw compress/ratelimit/cache **before** static), set **`FLIGHT_DISABLE_SPA_PIPELINE=1`** until you validate the new behavior.
|
|
281
|
+
2. If your app already shipped **custom SPA / history fallback** middleware in `*.backend.ts`, remove the duplicate once you confirm Flight’s fallback matches your routes (especially non-`/api` prefixes—use **`FLIGHT_SPA_DENY_PREFIXES`**).
|
|
282
|
+
3. Expect **`koa-cash` off** in the SPA pipeline unless you opt in with **`FLIGHT_HTTP_CACHE`**.
|
|
244
283
|
|
|
245
|
-
|
|
246
|
-
2. Move from Express middleware to Koa middleware
|
|
247
|
-
3. Update your frontend bundling to use Vite
|
|
248
|
-
4. Adapt to the new session management system
|
|
249
|
-
5. Update your static file serving configuration
|
|
284
|
+
See **[CHANGELOG.md](CHANGELOG.md)** for the full **2.0.0** notes.
|
|
250
285
|
|
|
251
286
|
## Requirements
|
|
252
287
|
|
|
253
|
-
- Node.js 16.x or higher
|
|
254
|
-
- Redis
|
|
255
|
-
- TypeScript
|
|
288
|
+
- Node.js **16.x** or higher
|
|
289
|
+
- **Redis** (sessions / rate limit / cache integrations)
|
|
290
|
+
- **TypeScript** in your app if you author `.backend.ts` modules as TS
|
|
256
291
|
|
|
257
292
|
## License
|
|
258
293
|
|
|
@@ -260,14 +295,10 @@ MIT
|
|
|
260
295
|
|
|
261
296
|
## Contributing
|
|
262
297
|
|
|
263
|
-
|
|
298
|
+
Issues and pull requests are welcome. Flight improves fastest with real workloads—if you hit an edge case, open an issue with a minimal repro.
|
|
264
299
|
|
|
265
300
|
## Acknowledgments
|
|
266
301
|
|
|
267
|
-
Flight succeeds
|
|
268
|
-
|
|
269
|
-
We’re grateful to FlyPaper Technologies for stewarding Avian and advancing component-driven architecture.
|
|
270
|
-
|
|
271
|
-
## ThoughtPivot
|
|
302
|
+
**Flight** succeeds **[Avian](https://github.com/ispyhumanfly/avian)**—the component-oriented Node server that helped prove this programming model. **Dan Stevenson** created Avian and carried it into **FlyPaper Technologies**, where **Nick Fredericks**, Dan, and the FlyPaper team sharpened Avian’s component boundaries and pushed its operational story. Dan continued to maintain Avian while **Flight** took shape to embrace newer tooling and a cleaner baseline for the next decade.
|
|
272
303
|
|
|
273
|
-
Today Flight is
|
|
304
|
+
Today **Flight** is maintained by the **ThoughtPivot** engineering team and **contributors like you**—the same spirit of openness and iteration, with a hard focus on speed, clarity, and deployment flexibility.
|
package/dist/flight.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
|
|
1
|
+
#!/usr/bin/env node
|
|
2
2
|
"use strict";
|
|
3
3
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
4
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
5
|
};
|
|
6
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
require("dotenv/config");
|
|
7
8
|
const child_process_1 = require("child_process");
|
|
8
9
|
const koa_1 = __importDefault(require("koa"));
|
|
9
10
|
const ioredis_1 = __importDefault(require("ioredis"));
|
|
@@ -21,6 +22,7 @@ const path_1 = __importDefault(require("path"));
|
|
|
21
22
|
const koa_ratelimit_1 = __importDefault(require("koa-ratelimit"));
|
|
22
23
|
const koa_static_1 = __importDefault(require("koa-static"));
|
|
23
24
|
const koa_session_1 = __importDefault(require("koa-session"));
|
|
25
|
+
const spa_pipeline_js_1 = require("./spa-pipeline.js");
|
|
24
26
|
function normalizeExcludePaths(value) {
|
|
25
27
|
if (value == null || value === '')
|
|
26
28
|
return [];
|
|
@@ -67,6 +69,39 @@ const argv = yargsEntry(process.argv.slice(2))
|
|
|
67
69
|
string: true,
|
|
68
70
|
default: [],
|
|
69
71
|
describe: 'Directories under app_home to skip when discovering **/*.backend.ts (repeat flag or comma-separated)'
|
|
72
|
+
})
|
|
73
|
+
.option('mode', {
|
|
74
|
+
type: 'string',
|
|
75
|
+
describe: 'development (Vite HMR) or production'
|
|
76
|
+
})
|
|
77
|
+
.option('port', {
|
|
78
|
+
type: 'number',
|
|
79
|
+
describe: 'HTTP listen port'
|
|
80
|
+
})
|
|
81
|
+
.option('app_home', {
|
|
82
|
+
alias: 'app-home',
|
|
83
|
+
type: 'string',
|
|
84
|
+
describe: 'Application root directory'
|
|
85
|
+
})
|
|
86
|
+
.option('app_key', {
|
|
87
|
+
alias: 'app-key',
|
|
88
|
+
type: 'string',
|
|
89
|
+
describe: 'Session cookie name'
|
|
90
|
+
})
|
|
91
|
+
.option('app_secret', {
|
|
92
|
+
alias: 'app-secret',
|
|
93
|
+
type: 'string',
|
|
94
|
+
describe: 'Session signing secret(s), comma-separated for rotation'
|
|
95
|
+
})
|
|
96
|
+
.option('payload_limit', {
|
|
97
|
+
alias: 'payload-limit',
|
|
98
|
+
type: 'string',
|
|
99
|
+
describe: 'koa-bodyparser JSON body limit'
|
|
100
|
+
})
|
|
101
|
+
.option('disable_vite', {
|
|
102
|
+
alias: 'disable-vite',
|
|
103
|
+
type: 'boolean',
|
|
104
|
+
describe: 'Skip vite build in production; enables built-assets / SPA pipeline when mode is production'
|
|
70
105
|
})
|
|
71
106
|
.parseSync();
|
|
72
107
|
// Set default session duration (24 hours in milliseconds)
|
|
@@ -79,13 +114,13 @@ if (isNaN(argv.session_duration) || argv.session_duration < 0) {
|
|
|
79
114
|
argv.session_duration = DEFAULT_SESSION_DURATION;
|
|
80
115
|
}
|
|
81
116
|
if (!argv.app_home) {
|
|
82
|
-
argv.app_home = '.';
|
|
117
|
+
argv.app_home = process.env.FLIGHT_APP_HOME || '.';
|
|
83
118
|
}
|
|
84
119
|
if (!argv.app_key) {
|
|
85
|
-
argv.app_key = 'flightApp';
|
|
120
|
+
argv.app_key = process.env.FLIGHT_APP_KEY || 'flightApp';
|
|
86
121
|
}
|
|
87
122
|
if (!argv.app_secret) {
|
|
88
|
-
argv.app_secret = 'the best secret key in the world';
|
|
123
|
+
argv.app_secret = process.env.FLIGHT_APP_SECRET || 'the best secret key in the world';
|
|
89
124
|
}
|
|
90
125
|
// Set default port values
|
|
91
126
|
if (!argv.port) {
|
|
@@ -104,8 +139,8 @@ if (isNaN(argv.port) || argv.port < 1 || argv.port > 65535) {
|
|
|
104
139
|
}
|
|
105
140
|
// Set default value for disable_vite flag
|
|
106
141
|
if (argv.disable_vite === undefined) {
|
|
107
|
-
|
|
108
|
-
argv.disable_vite =
|
|
142
|
+
const dv = process.env.FLIGHT_DISABLE_VITE;
|
|
143
|
+
argv.disable_vite = dv === 'true' || dv === '1' || dv === 'yes';
|
|
109
144
|
}
|
|
110
145
|
// Ensure the value is a boolean
|
|
111
146
|
argv.disable_vite = Boolean(argv.disable_vite);
|
|
@@ -152,6 +187,7 @@ if (cluster_1.default.isPrimary) {
|
|
|
152
187
|
}
|
|
153
188
|
else {
|
|
154
189
|
const app = new koa_1.default();
|
|
190
|
+
(0, spa_pipeline_js_1.applyTrustProxy)(app);
|
|
155
191
|
app.use((0, koa_logger_1.default)());
|
|
156
192
|
app.keys = argv.app_secret.split(',');
|
|
157
193
|
const SESSION_CONFIG = {
|
|
@@ -191,8 +227,15 @@ else {
|
|
|
191
227
|
console.error(`stderr: ${stderr}`);
|
|
192
228
|
});
|
|
193
229
|
}
|
|
194
|
-
|
|
195
|
-
|
|
230
|
+
const distRoot = (0, spa_pipeline_js_1.resolveDistRoot)(process.cwd());
|
|
231
|
+
const useSpaPipeline = (0, spa_pipeline_js_1.productionSpaPipelineActive)(mode, Boolean(argv.disable_vite));
|
|
232
|
+
const staticPrefixes = (0, spa_pipeline_js_1.parseCommaPrefixes)(process.env.FLIGHT_STATIC_PREFIXES, '/assets,/fonts');
|
|
233
|
+
const rateLimitSkipPrefixes = dedupeStrings([
|
|
234
|
+
...staticPrefixes,
|
|
235
|
+
...(0, spa_pipeline_js_1.parseCommaPrefixes)(process.env.FLIGHT_RATE_LIMIT_EXCLUDE_PREFIXES, '')
|
|
236
|
+
]);
|
|
237
|
+
const spaDenyExtra = (0, spa_pipeline_js_1.parseCommaPrefixes)(process.env.FLIGHT_SPA_DENY_PREFIXES, '');
|
|
238
|
+
const productionRatelimit = (0, koa_ratelimit_1.default)({
|
|
196
239
|
driver: 'redis',
|
|
197
240
|
db: redis,
|
|
198
241
|
duration: 60000,
|
|
@@ -205,12 +248,27 @@ else {
|
|
|
205
248
|
},
|
|
206
249
|
max: 1200,
|
|
207
250
|
disableHeader: false
|
|
208
|
-
})
|
|
209
|
-
|
|
251
|
+
});
|
|
252
|
+
const productionKoaCash = (0, koa_cash_1.default)({
|
|
210
253
|
get: (key) => redis.get(key),
|
|
211
254
|
set: (key, value) => redis.set(key, value, 'EX', 30)
|
|
212
|
-
})
|
|
213
|
-
|
|
255
|
+
});
|
|
256
|
+
if (useSpaPipeline) {
|
|
257
|
+
console.log('Flight: production SPA pipeline (static + index.html fallback before compress / rate limit); opt out with FLIGHT_DISABLE_SPA_PIPELINE=1');
|
|
258
|
+
app.use((0, koa_static_1.default)(distRoot));
|
|
259
|
+
app.use((0, spa_pipeline_js_1.spaIndexHtmlFallback)(distRoot, (0, spa_pipeline_js_1.spaIndexRelative)(), spaDenyExtra));
|
|
260
|
+
app.use((0, koa_compress_1.default)());
|
|
261
|
+
app.use((0, spa_pipeline_js_1.ratelimitWithPrefixSkips)(redis, rateLimitSkipPrefixes));
|
|
262
|
+
if ((0, spa_pipeline_js_1.httpCacheEnabledInSpaPipeline)()) {
|
|
263
|
+
app.use(productionKoaCash);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
else {
|
|
267
|
+
app.use((0, koa_compress_1.default)());
|
|
268
|
+
app.use(productionRatelimit);
|
|
269
|
+
app.use(productionKoaCash);
|
|
270
|
+
app.use((0, koa_static_1.default)(distRoot));
|
|
271
|
+
}
|
|
214
272
|
if (!argv.disable_vite) {
|
|
215
273
|
console.log(`App served out of dist/ and available on port ${argv.port}`);
|
|
216
274
|
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.productionSpaPipelineActive = productionSpaPipelineActive;
|
|
7
|
+
exports.applyTrustProxy = applyTrustProxy;
|
|
8
|
+
exports.parseCommaPrefixes = parseCommaPrefixes;
|
|
9
|
+
exports.resolveDistRoot = resolveDistRoot;
|
|
10
|
+
exports.spaIndexRelative = spaIndexRelative;
|
|
11
|
+
exports.spaIndexHtmlFallback = spaIndexHtmlFallback;
|
|
12
|
+
exports.shouldSkipRateLimitForPath = shouldSkipRateLimitForPath;
|
|
13
|
+
exports.ratelimitWithPrefixSkips = ratelimitWithPrefixSkips;
|
|
14
|
+
exports.httpCacheEnabledInSpaPipeline = httpCacheEnabledInSpaPipeline;
|
|
15
|
+
const fs_1 = __importDefault(require("fs"));
|
|
16
|
+
const path_1 = __importDefault(require("path"));
|
|
17
|
+
const koa_ratelimit_1 = __importDefault(require("koa-ratelimit"));
|
|
18
|
+
/** Option B: production + built assets (disable_vite), unless explicitly opted out. */
|
|
19
|
+
function productionSpaPipelineActive(mode, disableVite, env = process.env) {
|
|
20
|
+
if (mode !== 'production')
|
|
21
|
+
return false;
|
|
22
|
+
if (!disableVite)
|
|
23
|
+
return false;
|
|
24
|
+
const d = env.FLIGHT_DISABLE_SPA_PIPELINE;
|
|
25
|
+
if (d === '1' || d === 'true' || d === 'yes')
|
|
26
|
+
return false;
|
|
27
|
+
return true;
|
|
28
|
+
}
|
|
29
|
+
function applyTrustProxy(app, env = process.env) {
|
|
30
|
+
const t = env.FLIGHT_TRUST_PROXY;
|
|
31
|
+
app.proxy = t === '1' || t === 'true' || t === 'yes';
|
|
32
|
+
}
|
|
33
|
+
function parseCommaPrefixes(value, fallback) {
|
|
34
|
+
const raw = (value ?? fallback).trim();
|
|
35
|
+
if (!raw)
|
|
36
|
+
return [];
|
|
37
|
+
return raw
|
|
38
|
+
.split(',')
|
|
39
|
+
.map((s) => s.trim())
|
|
40
|
+
.filter(Boolean)
|
|
41
|
+
.map((p) => (p.startsWith('/') ? p : `/${p}`));
|
|
42
|
+
}
|
|
43
|
+
function resolveDistRoot(cwd, env = process.env) {
|
|
44
|
+
return path_1.default.resolve(cwd, env.FLIGHT_DIST_PATH || '../dist');
|
|
45
|
+
}
|
|
46
|
+
function spaIndexRelative(env = process.env) {
|
|
47
|
+
const v = (env.FLIGHT_SPA_INDEX || 'index.html').trim();
|
|
48
|
+
return v.replace(/^\/+/, '');
|
|
49
|
+
}
|
|
50
|
+
const DEFAULT_DENY_PREFIXES = ['/api', '/health'];
|
|
51
|
+
/** Match connect-history-api-fallback default: do not rewrite paths whose last segment looks like a file name. */
|
|
52
|
+
function lastPathSegmentLooksLikeFile(urlPath) {
|
|
53
|
+
const base = urlPath.slice(urlPath.lastIndexOf('/') + 1);
|
|
54
|
+
return base.includes('.');
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* After koa-static: serve index.html for document navigations that are not API, health, or static-like paths.
|
|
58
|
+
*/
|
|
59
|
+
function spaIndexHtmlFallback(distRoot, indexRel, extraDenyPrefixes = []) {
|
|
60
|
+
const indexAbs = path_1.default.join(distRoot, indexRel);
|
|
61
|
+
const deny = dedupePathList([...DEFAULT_DENY_PREFIXES, ...extraDenyPrefixes]);
|
|
62
|
+
return async (ctx, next) => {
|
|
63
|
+
if (ctx.method !== 'GET' && ctx.method !== 'HEAD')
|
|
64
|
+
return next();
|
|
65
|
+
if (ctx.body != null)
|
|
66
|
+
return next();
|
|
67
|
+
const accept = ctx.get('accept') || '';
|
|
68
|
+
if (accept && !accept.includes('text/html') && !accept.includes('*/*'))
|
|
69
|
+
return next();
|
|
70
|
+
const urlPath = ctx.path.split('?')[0];
|
|
71
|
+
if (lastPathSegmentLooksLikeFile(urlPath))
|
|
72
|
+
return next();
|
|
73
|
+
for (const pre of deny) {
|
|
74
|
+
if (urlPath === pre || (pre !== '/' && urlPath.startsWith(`${pre}/`))) {
|
|
75
|
+
return next();
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
try {
|
|
79
|
+
await fs_1.default.promises.access(indexAbs, fs_1.default.constants.R_OK);
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
return next();
|
|
83
|
+
}
|
|
84
|
+
ctx.type = 'text/html; charset=utf-8';
|
|
85
|
+
ctx.body = fs_1.default.createReadStream(indexAbs);
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
function dedupePathList(paths) {
|
|
89
|
+
return [...new Set(paths)];
|
|
90
|
+
}
|
|
91
|
+
function shouldSkipRateLimitForPath(path, method, prefixes) {
|
|
92
|
+
if (method !== 'GET' && method !== 'HEAD')
|
|
93
|
+
return false;
|
|
94
|
+
return prefixes.some((pre) => path === pre || path.startsWith(`${pre}/`));
|
|
95
|
+
}
|
|
96
|
+
/** Same ratelimit as Flight legacy, but skips GET/HEAD under configured prefixes (e.g. hashed assets). */
|
|
97
|
+
function ratelimitWithPrefixSkips(redis, skipPrefixes) {
|
|
98
|
+
const inner = (0, koa_ratelimit_1.default)({
|
|
99
|
+
driver: 'redis',
|
|
100
|
+
db: redis,
|
|
101
|
+
duration: 60000,
|
|
102
|
+
errorMessage: 'Sometimes You Just Have to Slow Down.',
|
|
103
|
+
id: (ctx) => ctx.get('x-forwarded-for') || ctx.ip,
|
|
104
|
+
headers: {
|
|
105
|
+
remaining: 'Rate-Limit-Remaining',
|
|
106
|
+
reset: 'Rate-Limit-Reset',
|
|
107
|
+
total: 'Rate-Limit-Total'
|
|
108
|
+
},
|
|
109
|
+
max: 1200,
|
|
110
|
+
disableHeader: false
|
|
111
|
+
});
|
|
112
|
+
return async (ctx, next) => {
|
|
113
|
+
if (shouldSkipRateLimitForPath(ctx.path, ctx.method, skipPrefixes)) {
|
|
114
|
+
return next();
|
|
115
|
+
}
|
|
116
|
+
await inner(ctx, next);
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
function httpCacheEnabledInSpaPipeline(env = process.env) {
|
|
120
|
+
const v = env.FLIGHT_HTTP_CACHE;
|
|
121
|
+
return v === '1' || v === 'true' || v === 'yes';
|
|
122
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@thoughtpivot/flight",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.2",
|
|
4
4
|
"description": "A web server designed for running full stack, component based applications at enterprise scale.",
|
|
5
5
|
"main": "src/flight.ts",
|
|
6
6
|
"publishConfig": {
|
|
@@ -12,7 +12,8 @@
|
|
|
12
12
|
"scripts": {
|
|
13
13
|
"lint": "eslint . --fix && prettier -w .",
|
|
14
14
|
"build": "rm -rf dist; node_modules/.bin/tsc --project .",
|
|
15
|
-
"test": "
|
|
15
|
+
"test": "npm run build && node --test dist/spa-pipeline.test.js",
|
|
16
|
+
"prepublishOnly": "npm run build"
|
|
16
17
|
},
|
|
17
18
|
"repository": {
|
|
18
19
|
"type": "git",
|
|
@@ -24,6 +25,13 @@
|
|
|
24
25
|
"url": "https://github.com/thoughtpivot/flight/issues"
|
|
25
26
|
},
|
|
26
27
|
"homepage": "https://github.com/thoughtpivot/flight#readme",
|
|
28
|
+
"files": [
|
|
29
|
+
"dist/flight.js",
|
|
30
|
+
"dist/spa-pipeline.js",
|
|
31
|
+
"README.md",
|
|
32
|
+
"CHANGELOG.md",
|
|
33
|
+
"readme-assets"
|
|
34
|
+
],
|
|
27
35
|
"dependencies": {
|
|
28
36
|
"@koa/cors": "^5.0.0",
|
|
29
37
|
"@koa/router": "^13.1.0",
|
|
@@ -64,11 +72,13 @@
|
|
|
64
72
|
"@types/koa-session": "^6.4.5",
|
|
65
73
|
"@types/koa-static": "^4.0.4",
|
|
66
74
|
"@types/node": "^22.19.17",
|
|
75
|
+
"@types/supertest": "^6.0.2",
|
|
67
76
|
"@typescript-eslint/parser": "^8.59.1",
|
|
68
77
|
"eslint": "^9.39.4",
|
|
69
78
|
"eslint-plugin-jsdoc": "^50.8.0",
|
|
70
79
|
"eslint-plugin-vue": "^9.33.0",
|
|
71
80
|
"prettier": "^3.8.3",
|
|
81
|
+
"supertest": "^7.0.0",
|
|
72
82
|
"ts-node": "^10.9.2",
|
|
73
83
|
"typescript": "^5.9.3"
|
|
74
84
|
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<svg width="5343" height="865" viewBox="0 0 5343 865" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<path fill-rule="evenodd" clip-rule="evenodd" d="M642.666 865C533.606 865 431.066 865 322.006 865C393.594 596.025 462.176 327.554 533.606 58.4919C543.977 18.4241 561.039 0.0278033 599.015 0.0278033C743.037 0.0278033 887.058 0.0278033 1031.25 0.0278033C993.277 0.0278033 976.214 18.4241 965.844 58.4919C894.413 327.554 854.267 487.462 783.01 756.611C765.781 821.507 708.067 865 642.666 865Z" fill="black"/>
|
|
3
|
+
<path fill-rule="evenodd" clip-rule="evenodd" d="M0 187.175C9.20051 152.387 24.7565 93.4178 33.957 58.4641C44.3263 18.3964 61.3904 3.89425e-05 99.3628 3.89425e-05C243.386 3.89425e-05 387.581 3.89425e-05 531.602 3.89425e-05C493.791 3.89425e-05 476.737 18.3964 466.193 58.4641C456.494 95.2567 440.939 153.973 432.234 187.175H0Z" fill="black"/>
|
|
4
|
+
<path fill-rule="evenodd" clip-rule="evenodd" d="M1426.35 206.948C1459.81 76.2171 1380.18 0.0276436 1243.85 0.0276436H1098.49C1060.69 0.0276436 1043.63 18.424 1033.08 58.4917C961.661 327.554 970.193 302.623 898.77 571.599C1007.83 571.599 1110.2 571.599 1219.26 571.599C1284.83 571.599 1342.37 528.114 1359.61 463.123C1410.8 270.337 1406.27 284.227 1425.85 208.7V208.613C1426.02 208.029 1426.19 207.445 1426.35 206.948Z" fill="black"/>
|
|
5
|
+
<path d="M1890.76 188.564C1893.08 188.564 1894.93 189.492 1896.32 191.346C1898.18 192.737 1899.1 194.592 1899.1 196.911V264.377C1899.1 266.696 1898.18 268.782 1896.32 270.637C1894.93 272.028 1893.08 272.723 1890.76 272.723H1803.81C1801.49 272.723 1800.33 273.883 1800.33 276.201V667.089C1800.33 669.408 1799.41 671.494 1797.55 673.349C1796.16 674.74 1794.31 675.436 1791.99 675.436H1710.61C1708.29 675.436 1706.2 674.74 1704.35 673.349C1702.96 671.494 1702.26 669.408 1702.26 667.089V276.201C1702.26 273.883 1701.1 272.723 1698.78 272.723H1615.31C1613 272.723 1610.91 272.028 1609.05 270.637C1607.66 268.782 1606.97 266.696 1606.97 264.377V196.911C1606.97 194.592 1607.66 192.737 1609.05 191.346C1610.91 189.492 1613 188.564 1615.31 188.564H1890.76Z" fill="black"/>
|
|
6
|
+
<path d="M2120.83 196.911C2120.83 194.592 2121.53 192.737 2122.92 191.346C2124.77 189.492 2126.86 188.564 2129.18 188.564H2210.56C2212.88 188.564 2214.73 189.492 2216.12 191.346C2217.98 192.737 2218.91 194.592 2218.91 196.911V667.089C2218.91 669.408 2217.98 671.494 2216.12 673.349C2214.73 674.74 2212.88 675.436 2210.56 675.436H2129.18C2126.86 675.436 2124.77 674.74 2122.92 673.349C2121.53 671.494 2120.83 669.408 2120.83 667.089V477.209C2120.83 474.891 2119.67 473.732 2117.35 473.732H2047.8C2045.48 473.732 2044.32 474.891 2044.32 477.209V667.089C2044.32 669.408 2043.39 671.494 2041.54 673.349C2040.15 674.74 2038.29 675.436 2035.97 675.436H1954.59C1952.27 675.436 1950.19 674.74 1948.33 673.349C1946.94 671.494 1946.25 669.408 1946.25 667.089V196.911C1946.25 194.592 1946.94 192.737 1948.33 191.346C1950.19 189.492 1952.27 188.564 1954.59 188.564H2035.97C2038.29 188.564 2040.15 189.492 2041.54 191.346C2043.39 192.737 2044.32 194.592 2044.32 196.911V386.095C2044.32 388.413 2045.48 389.573 2047.8 389.573H2117.35C2119.67 389.573 2120.83 388.413 2120.83 386.095V196.911Z" fill="black"/>
|
|
7
|
+
<path d="M2413.99 681C2371.33 681 2337.25 668.48 2311.74 643.441C2286.24 618.402 2273.49 585.017 2273.49 543.285V320.715C2273.49 278.983 2286.24 245.598 2311.74 220.559C2337.25 195.52 2371.33 183 2413.99 183C2456.65 183 2490.74 195.52 2516.24 220.559C2542.21 245.598 2555.19 278.983 2555.19 320.715V543.285C2555.19 585.017 2542.21 618.402 2516.24 643.441C2490.74 668.48 2456.65 681 2413.99 681ZM2413.99 596.841C2426.98 596.841 2437.41 592.436 2445.29 583.626C2453.17 574.352 2457.12 562.296 2457.12 547.458V316.542C2457.12 301.704 2453.17 289.88 2445.29 281.07C2437.41 271.796 2426.98 267.159 2413.99 267.159C2401.01 267.159 2390.57 271.796 2382.69 281.07C2375.27 289.88 2371.56 301.704 2371.56 316.542V547.458C2371.56 562.296 2375.27 574.352 2382.69 583.626C2390.57 592.436 2401.01 596.841 2413.99 596.841Z" fill="black"/>
|
|
8
|
+
<path d="M2745.54 681C2703.8 681 2670.18 668.017 2644.68 642.05C2619.64 615.62 2607.12 580.612 2607.12 537.025V196.911C2607.12 194.592 2607.81 192.737 2609.21 191.346C2611.06 189.492 2613.15 188.564 2615.47 188.564H2696.85C2699.17 188.564 2701.02 189.492 2702.41 191.346C2704.27 192.737 2705.19 194.592 2705.19 196.911V546.763C2705.19 561.601 2708.9 573.656 2716.32 582.93C2723.74 592.204 2733.48 596.841 2745.54 596.841C2757.59 596.841 2767.1 592.204 2774.06 582.93C2781.47 573.656 2785.18 561.601 2785.18 546.763V196.911C2785.18 194.592 2785.88 192.737 2787.27 191.346C2789.13 189.492 2791.21 188.564 2793.53 188.564H2874.91C2877.23 188.564 2879.09 189.492 2880.48 191.346C2882.33 192.737 2883.26 194.592 2883.26 196.911V537.025C2883.26 580.612 2870.51 615.62 2845 642.05C2819.96 668.017 2786.81 681 2745.54 681Z" fill="black"/>
|
|
9
|
+
<path d="M3071.47 681C3029.74 681 2996.12 669.176 2970.61 645.528C2945.57 621.416 2933.05 589.422 2933.05 549.545V314.455C2933.05 274.578 2945.57 242.816 2970.61 219.168C2996.12 195.056 3029.74 183 3071.47 183C3113.2 183 3146.82 195.056 3172.33 219.168C3197.83 243.279 3210.58 275.274 3210.58 315.151V341.581C3210.58 343.899 3209.65 345.986 3207.8 347.841C3206.41 349.232 3204.55 349.927 3202.24 349.927H3120.85C3118.54 349.927 3116.45 349.232 3114.59 347.841C3113.2 345.986 3112.51 343.899 3112.51 341.581V313.76C3112.51 299.849 3108.8 288.721 3101.38 280.374C3093.96 271.564 3083.99 267.159 3071.47 267.159C3059.41 267.159 3049.67 271.564 3042.26 280.374C3034.84 288.721 3031.13 299.849 3031.13 313.76V550.24C3031.13 564.151 3034.84 575.511 3042.26 584.321C3049.67 592.668 3059.41 596.841 3071.47 596.841C3083.99 596.841 3093.96 592.668 3101.38 584.321C3108.8 575.511 3112.51 564.151 3112.51 550.24V494.598C3112.51 492.279 3111.35 491.12 3109.03 491.12H3078.42C3076.11 491.12 3074.02 490.425 3072.16 489.034C3070.77 487.179 3070.08 485.092 3070.08 482.774V420.871C3070.08 418.553 3070.77 416.698 3072.16 415.307C3074.02 413.452 3076.11 412.525 3078.42 412.525H3202.24C3204.55 412.525 3206.41 413.452 3207.8 415.307C3209.65 416.698 3210.58 418.553 3210.58 420.871V549.545C3210.58 589.422 3197.83 621.416 3172.33 645.528C3146.82 669.176 3113.2 681 3071.47 681Z" fill="black"/>
|
|
10
|
+
<path d="M3439.28 196.911C3439.28 194.592 3439.98 192.737 3441.37 191.346C3443.22 189.492 3445.31 188.564 3447.63 188.564H3529.01C3531.33 188.564 3533.18 189.492 3534.57 191.346C3536.43 192.737 3537.36 194.592 3537.36 196.911V667.089C3537.36 669.408 3536.43 671.494 3534.57 673.349C3533.18 674.74 3531.33 675.436 3529.01 675.436H3447.63C3445.31 675.436 3443.22 674.74 3441.37 673.349C3439.98 671.494 3439.28 669.408 3439.28 667.089V477.209C3439.28 474.891 3438.12 473.732 3435.8 473.732H3366.25C3363.93 473.732 3362.77 474.891 3362.77 477.209V667.089C3362.77 669.408 3361.84 671.494 3359.99 673.349C3358.6 674.74 3356.74 675.436 3354.42 675.436H3273.04C3270.72 675.436 3268.64 674.74 3266.78 673.349C3265.39 671.494 3264.69 669.408 3264.69 667.089V196.911C3264.69 194.592 3265.39 192.737 3266.78 191.346C3268.64 189.492 3270.72 188.564 3273.04 188.564H3354.42C3356.74 188.564 3358.6 189.492 3359.99 191.346C3361.84 192.737 3362.77 194.592 3362.77 196.911V386.095C3362.77 388.413 3363.93 389.573 3366.25 389.573H3435.8C3438.12 389.573 3439.28 388.413 3439.28 386.095V196.911Z" fill="black"/>
|
|
11
|
+
<path d="M3868.77 188.564C3871.09 188.564 3872.94 189.492 3874.34 191.346C3876.19 192.737 3877.12 194.592 3877.12 196.911V264.377C3877.12 266.696 3876.19 268.782 3874.34 270.637C3872.94 272.028 3871.09 272.723 3868.77 272.723H3781.83C3779.51 272.723 3778.35 273.883 3778.35 276.201V667.089C3778.35 669.408 3777.42 671.494 3775.57 673.349C3774.17 674.74 3772.32 675.436 3770 675.436H3688.62C3686.3 675.436 3684.21 674.74 3682.36 673.349C3680.97 671.494 3680.27 669.408 3680.27 667.089V276.201C3680.27 273.883 3679.11 272.723 3676.8 272.723H3593.33C3591.01 272.723 3588.92 272.028 3587.07 270.637C3585.68 268.782 3584.98 266.696 3584.98 264.377V196.911C3584.98 194.592 3585.68 192.737 3587.07 191.346C3588.92 189.492 3591.01 188.564 3593.33 188.564H3868.77Z" fill="black"/>
|
|
12
|
+
<path d="M4072.41 187.869C4097.92 187.869 4120.64 194.128 4140.58 206.648C4160.52 219.168 4175.82 236.788 4186.49 259.508C4197.62 281.765 4203.18 307.268 4203.18 336.017C4203.18 379.14 4191.59 413.684 4168.4 439.651C4145.22 465.617 4114.84 478.601 4077.28 478.601H4025.12C4022.8 478.601 4021.64 479.76 4021.64 482.078V667.089C4021.64 669.408 4020.71 671.494 4018.86 673.349C4017.46 674.74 4015.61 675.436 4013.29 675.436H3931.91C3929.59 675.436 3927.5 674.74 3925.65 673.349C3924.26 671.494 3923.56 669.408 3923.56 667.089V196.215C3923.56 193.897 3924.26 192.042 3925.65 190.651C3927.5 188.796 3929.59 187.869 3931.91 187.869H4072.41ZM4055.72 402.092C4070.56 402.092 4082.38 396.528 4091.19 385.399C4100.47 373.807 4105.11 358.042 4105.11 338.103C4105.11 317.701 4100.47 301.704 4091.19 290.112C4082.38 278.52 4070.56 272.723 4055.72 272.723H4025.12C4022.8 272.723 4021.64 273.883 4021.64 276.201V398.614C4021.64 400.933 4022.8 402.092 4025.12 402.092H4055.72Z" fill="#4B7CB5"/>
|
|
13
|
+
<path d="M4255.93 675.436C4253.62 675.436 4251.53 674.74 4249.67 673.349C4248.28 671.494 4247.59 669.408 4247.59 667.089V196.911C4247.59 194.592 4248.28 192.737 4249.67 191.346C4251.53 189.492 4253.62 188.564 4255.93 188.564H4337.32C4339.63 188.564 4341.49 189.492 4342.88 191.346C4344.74 192.737 4345.66 194.592 4345.66 196.911V667.089C4345.66 669.408 4344.74 671.494 4342.88 673.349C4341.49 674.74 4339.63 675.436 4337.32 675.436H4255.93Z" fill="#4B7CB5"/>
|
|
14
|
+
<path d="M4501.8 675.436C4496.69 675.436 4493.68 672.885 4492.75 667.785L4393.29 197.606L4392.59 194.824C4392.59 190.651 4395.14 188.564 4400.24 188.564H4489.97C4495.54 188.564 4498.55 191.115 4499.01 196.215L4547.01 502.249C4547.47 504.103 4548.17 505.031 4549.09 505.031C4550.02 505.031 4550.72 504.103 4551.18 502.249L4597.78 196.215C4598.25 191.115 4601.26 188.564 4606.83 188.564H4693.77C4699.8 188.564 4702.12 191.578 4700.73 197.606L4600.57 667.785C4599.64 672.885 4596.62 675.436 4591.52 675.436H4501.8Z" fill="#4B7CB5"/>
|
|
15
|
+
<path d="M4869.53 681C4826.87 681 4792.78 668.48 4767.28 643.441C4741.77 618.402 4729.02 585.017 4729.02 543.285V320.715C4729.02 278.983 4741.77 245.598 4767.28 220.559C4792.78 195.52 4826.87 183 4869.53 183C4912.19 183 4946.27 195.52 4971.77 220.559C4997.74 245.598 5010.73 278.983 5010.73 320.715V543.285C5010.73 585.017 4997.74 618.402 4971.77 643.441C4946.27 668.48 4912.19 681 4869.53 681ZM4869.53 596.841C4882.51 596.841 4892.94 592.436 4900.83 583.626C4908.71 574.352 4912.65 562.296 4912.65 547.458V316.542C4912.65 301.704 4908.71 289.88 4900.83 281.07C4892.94 271.796 4882.51 267.159 4869.53 267.159C4856.54 267.159 4846.11 271.796 4838.23 281.07C4830.81 289.88 4827.1 301.704 4827.1 316.542V547.458C4827.1 562.296 4830.81 574.352 4838.23 583.626C4846.11 592.436 4856.54 596.841 4869.53 596.841Z" fill="#4B7CB5"/>
|
|
16
|
+
<path d="M5334.62 188.564C5336.94 188.564 5338.79 189.492 5340.19 191.346C5342.04 192.737 5342.97 194.592 5342.97 196.911V264.377C5342.97 266.696 5342.04 268.782 5340.19 270.637C5338.79 272.028 5336.94 272.723 5334.62 272.723H5247.68C5245.36 272.723 5244.2 273.883 5244.2 276.201V667.089C5244.2 669.408 5243.27 671.494 5241.41 673.349C5240.02 674.74 5238.17 675.436 5235.85 675.436H5154.47C5152.15 675.436 5150.06 674.74 5148.21 673.349C5146.82 671.494 5146.12 669.408 5146.12 667.089V276.201C5146.12 273.883 5144.96 272.723 5142.64 272.723H5059.18C5056.86 272.723 5054.77 272.028 5052.92 270.637C5051.53 268.782 5050.83 266.696 5050.83 264.377V196.911C5050.83 194.592 5051.53 192.737 5052.92 191.346C5054.77 189.492 5056.86 188.564 5059.18 188.564H5334.62Z" fill="#4B7CB5"/>
|
|
17
|
+
</svg>
|
package/src/flight.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import 'dotenv/config'
|
|
2
4
|
|
|
3
5
|
import { exec, spawn } from 'child_process'
|
|
4
6
|
|
|
@@ -19,6 +21,17 @@ import ratelimit from 'koa-ratelimit'
|
|
|
19
21
|
import serve from 'koa-static'
|
|
20
22
|
import session from 'koa-session'
|
|
21
23
|
|
|
24
|
+
import {
|
|
25
|
+
applyTrustProxy,
|
|
26
|
+
httpCacheEnabledInSpaPipeline,
|
|
27
|
+
parseCommaPrefixes,
|
|
28
|
+
productionSpaPipelineActive,
|
|
29
|
+
ratelimitWithPrefixSkips,
|
|
30
|
+
resolveDistRoot,
|
|
31
|
+
spaIndexHtmlFallback,
|
|
32
|
+
spaIndexRelative
|
|
33
|
+
} from './spa-pipeline.js'
|
|
34
|
+
|
|
22
35
|
/** CLI argv shape after Flight applies defaults (see block below). */
|
|
23
36
|
interface FlightArgv {
|
|
24
37
|
session_duration?: number
|
|
@@ -87,6 +100,39 @@ const argv = yargsEntry(process.argv.slice(2))
|
|
|
87
100
|
default: [],
|
|
88
101
|
describe: 'Directories under app_home to skip when discovering **/*.backend.ts (repeat flag or comma-separated)'
|
|
89
102
|
})
|
|
103
|
+
.option('mode', {
|
|
104
|
+
type: 'string',
|
|
105
|
+
describe: 'development (Vite HMR) or production'
|
|
106
|
+
})
|
|
107
|
+
.option('port', {
|
|
108
|
+
type: 'number',
|
|
109
|
+
describe: 'HTTP listen port'
|
|
110
|
+
})
|
|
111
|
+
.option('app_home', {
|
|
112
|
+
alias: 'app-home',
|
|
113
|
+
type: 'string',
|
|
114
|
+
describe: 'Application root directory'
|
|
115
|
+
})
|
|
116
|
+
.option('app_key', {
|
|
117
|
+
alias: 'app-key',
|
|
118
|
+
type: 'string',
|
|
119
|
+
describe: 'Session cookie name'
|
|
120
|
+
})
|
|
121
|
+
.option('app_secret', {
|
|
122
|
+
alias: 'app-secret',
|
|
123
|
+
type: 'string',
|
|
124
|
+
describe: 'Session signing secret(s), comma-separated for rotation'
|
|
125
|
+
})
|
|
126
|
+
.option('payload_limit', {
|
|
127
|
+
alias: 'payload-limit',
|
|
128
|
+
type: 'string',
|
|
129
|
+
describe: 'koa-bodyparser JSON body limit'
|
|
130
|
+
})
|
|
131
|
+
.option('disable_vite', {
|
|
132
|
+
alias: 'disable-vite',
|
|
133
|
+
type: 'boolean',
|
|
134
|
+
describe: 'Skip vite build in production; enables built-assets / SPA pipeline when mode is production'
|
|
135
|
+
})
|
|
90
136
|
.parseSync() as FlightArgv
|
|
91
137
|
|
|
92
138
|
// Set default session duration (24 hours in milliseconds)
|
|
@@ -102,15 +148,15 @@ if (isNaN(argv.session_duration) || argv.session_duration < 0) {
|
|
|
102
148
|
}
|
|
103
149
|
|
|
104
150
|
if (!argv.app_home) {
|
|
105
|
-
argv.app_home = '.'
|
|
151
|
+
argv.app_home = process.env.FLIGHT_APP_HOME || '.'
|
|
106
152
|
}
|
|
107
153
|
|
|
108
154
|
if (!argv.app_key) {
|
|
109
|
-
argv.app_key = 'flightApp'
|
|
155
|
+
argv.app_key = process.env.FLIGHT_APP_KEY || 'flightApp'
|
|
110
156
|
}
|
|
111
157
|
|
|
112
158
|
if (!argv.app_secret) {
|
|
113
|
-
argv.app_secret = 'the best secret key in the world'
|
|
159
|
+
argv.app_secret = process.env.FLIGHT_APP_SECRET || 'the best secret key in the world'
|
|
114
160
|
}
|
|
115
161
|
|
|
116
162
|
// Set default port values
|
|
@@ -134,8 +180,8 @@ if (isNaN(argv.port) || argv.port < 1 || argv.port > 65535) {
|
|
|
134
180
|
|
|
135
181
|
// Set default value for disable_vite flag
|
|
136
182
|
if (argv.disable_vite === undefined) {
|
|
137
|
-
|
|
138
|
-
argv.disable_vite =
|
|
183
|
+
const dv = process.env.FLIGHT_DISABLE_VITE
|
|
184
|
+
argv.disable_vite = dv === 'true' || dv === '1' || dv === 'yes'
|
|
139
185
|
}
|
|
140
186
|
|
|
141
187
|
// Ensure the value is a boolean
|
|
@@ -192,6 +238,8 @@ if (cluster.isPrimary) {
|
|
|
192
238
|
})
|
|
193
239
|
} else {
|
|
194
240
|
const app = new Koa()
|
|
241
|
+
applyTrustProxy(app)
|
|
242
|
+
|
|
195
243
|
app.use(logger())
|
|
196
244
|
|
|
197
245
|
app.keys = argv.app_secret.split(',')
|
|
@@ -245,30 +293,52 @@ if (cluster.isPrimary) {
|
|
|
245
293
|
})
|
|
246
294
|
}
|
|
247
295
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
)
|
|
271
|
-
|
|
296
|
+
const distRoot = resolveDistRoot(process.cwd())
|
|
297
|
+
const useSpaPipeline = productionSpaPipelineActive(mode, Boolean(argv.disable_vite))
|
|
298
|
+
const staticPrefixes = parseCommaPrefixes(process.env.FLIGHT_STATIC_PREFIXES, '/assets,/fonts')
|
|
299
|
+
const rateLimitSkipPrefixes = dedupeStrings([
|
|
300
|
+
...staticPrefixes,
|
|
301
|
+
...parseCommaPrefixes(process.env.FLIGHT_RATE_LIMIT_EXCLUDE_PREFIXES, '')
|
|
302
|
+
])
|
|
303
|
+
const spaDenyExtra = parseCommaPrefixes(process.env.FLIGHT_SPA_DENY_PREFIXES, '')
|
|
304
|
+
|
|
305
|
+
const productionRatelimit = ratelimit({
|
|
306
|
+
driver: 'redis',
|
|
307
|
+
db: redis,
|
|
308
|
+
duration: 60000,
|
|
309
|
+
errorMessage: 'Sometimes You Just Have to Slow Down.',
|
|
310
|
+
id: (ctx) => ctx.get('x-forwarded-for') || ctx.ip,
|
|
311
|
+
headers: {
|
|
312
|
+
remaining: 'Rate-Limit-Remaining',
|
|
313
|
+
reset: 'Rate-Limit-Reset',
|
|
314
|
+
total: 'Rate-Limit-Total'
|
|
315
|
+
},
|
|
316
|
+
max: 1200,
|
|
317
|
+
disableHeader: false
|
|
318
|
+
})
|
|
319
|
+
|
|
320
|
+
const productionKoaCash = koaCash({
|
|
321
|
+
get: (key) => redis.get(key),
|
|
322
|
+
set: (key, value) => redis.set(key, value, 'EX', 30)
|
|
323
|
+
})
|
|
324
|
+
|
|
325
|
+
if (useSpaPipeline) {
|
|
326
|
+
console.log(
|
|
327
|
+
'Flight: production SPA pipeline (static + index.html fallback before compress / rate limit); opt out with FLIGHT_DISABLE_SPA_PIPELINE=1'
|
|
328
|
+
)
|
|
329
|
+
app.use(serve(distRoot))
|
|
330
|
+
app.use(spaIndexHtmlFallback(distRoot, spaIndexRelative(), spaDenyExtra))
|
|
331
|
+
app.use(compress())
|
|
332
|
+
app.use(ratelimitWithPrefixSkips(redis, rateLimitSkipPrefixes))
|
|
333
|
+
if (httpCacheEnabledInSpaPipeline()) {
|
|
334
|
+
app.use(productionKoaCash)
|
|
335
|
+
}
|
|
336
|
+
} else {
|
|
337
|
+
app.use(compress())
|
|
338
|
+
app.use(productionRatelimit)
|
|
339
|
+
app.use(productionKoaCash)
|
|
340
|
+
app.use(serve(distRoot))
|
|
341
|
+
}
|
|
272
342
|
|
|
273
343
|
if (!argv.disable_vite) {
|
|
274
344
|
console.log(`App served out of dist/ and available on port ${argv.port}`)
|
package/.circleci/config.yml
DELETED
|
@@ -1,83 +0,0 @@
|
|
|
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
DELETED
|
@@ -1,5 +0,0 @@
|
|
|
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
|
package/.github/dependabot.yml
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
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
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
20.11
|
package/.prettierrc
DELETED
package/.vscode/extensions.json
DELETED
package/.vscode/settings.json
DELETED
package/eslint.config.mjs
DELETED
|
@@ -1,32 +0,0 @@
|
|
|
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
|
-
]
|