@thoughtpivot/flight 2.0.2 → 3.0.1
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 +58 -0
- package/LICENSE +202 -0
- package/NOTICE +1 -0
- package/README.md +226 -19
- package/dist/backend-discovery.js +61 -0
- package/dist/flight.js +8 -47
- package/dist/spa-pipeline.js +22 -4
- package/package.json +23 -11
- package/src/bun/app.test.ts +465 -0
- package/src/bun/app.ts +159 -0
- package/src/bun/backends.ts +93 -0
- package/src/bun/cache.ts +57 -0
- package/src/bun/config.ts +182 -0
- package/src/bun/koa-compat.ts +381 -0
- package/src/bun/middleware.ts +256 -0
- package/src/bun/server.ts +16 -0
- package/src/bun/session.ts +123 -0
- package/src/bun/static.ts +72 -0
- package/src/bun/store.ts +128 -0
- package/src/flight.ts +6 -47
- package/LICENSE.md +0 -19
package/dist/flight.js
CHANGED
|
@@ -22,44 +22,8 @@ const path_1 = __importDefault(require("path"));
|
|
|
22
22
|
const koa_ratelimit_1 = __importDefault(require("koa-ratelimit"));
|
|
23
23
|
const koa_static_1 = __importDefault(require("koa-static"));
|
|
24
24
|
const koa_session_1 = __importDefault(require("koa-session"));
|
|
25
|
+
const backend_discovery_js_1 = require("./backend-discovery.js");
|
|
25
26
|
const spa_pipeline_js_1 = require("./spa-pipeline.js");
|
|
26
|
-
function normalizeExcludePaths(value) {
|
|
27
|
-
if (value == null || value === '')
|
|
28
|
-
return [];
|
|
29
|
-
const parts = Array.isArray(value) ? value : [value];
|
|
30
|
-
const out = [];
|
|
31
|
-
for (const p of parts) {
|
|
32
|
-
const s = String(p).trim();
|
|
33
|
-
if (!s)
|
|
34
|
-
continue;
|
|
35
|
-
out.push(...s
|
|
36
|
-
.split(',')
|
|
37
|
-
.map((x) => x.trim())
|
|
38
|
-
.filter(Boolean));
|
|
39
|
-
}
|
|
40
|
-
return out;
|
|
41
|
-
}
|
|
42
|
-
function dedupeStrings(items) {
|
|
43
|
-
return [...new Set(items)];
|
|
44
|
-
}
|
|
45
|
-
/** Build fast-glob ignore globs for trees rooted under `appRootAbs`. */
|
|
46
|
-
function backendDiscoveryIgnorePatterns(appRootAbs, excludeRelativeDirs) {
|
|
47
|
-
const patterns = [];
|
|
48
|
-
for (const raw of excludeRelativeDirs) {
|
|
49
|
-
const trimmed = raw.trim();
|
|
50
|
-
if (!trimmed)
|
|
51
|
-
continue;
|
|
52
|
-
const resolved = path_1.default.resolve(appRootAbs, trimmed);
|
|
53
|
-
const rel = path_1.default.relative(appRootAbs, resolved);
|
|
54
|
-
const relPosix = rel.replace(/\\/g, '/');
|
|
55
|
-
if (!relPosix || relPosix.startsWith('..') || path_1.default.isAbsolute(rel)) {
|
|
56
|
-
console.warn(`Flight: exclude_paths entry skipped (outside app_home): ${trimmed}`);
|
|
57
|
-
continue;
|
|
58
|
-
}
|
|
59
|
-
patterns.push(`${relPosix}/**`);
|
|
60
|
-
}
|
|
61
|
-
return patterns;
|
|
62
|
-
}
|
|
63
27
|
const _yargs = require('yargs/yargs');
|
|
64
28
|
const yargsEntry = typeof _yargs === 'function' ? _yargs : _yargs.default;
|
|
65
29
|
const argv = yargsEntry(process.argv.slice(2))
|
|
@@ -72,7 +36,7 @@ const argv = yargsEntry(process.argv.slice(2))
|
|
|
72
36
|
})
|
|
73
37
|
.option('mode', {
|
|
74
38
|
type: 'string',
|
|
75
|
-
describe: 'development (Vite HMR) or production'
|
|
39
|
+
describe: 'development (Vite HMR for Vue/React per vite.config) or production'
|
|
76
40
|
})
|
|
77
41
|
.option('port', {
|
|
78
42
|
type: 'number',
|
|
@@ -145,11 +109,8 @@ if (argv.disable_vite === undefined) {
|
|
|
145
109
|
// Ensure the value is a boolean
|
|
146
110
|
argv.disable_vite = Boolean(argv.disable_vite);
|
|
147
111
|
const appHomePath = path_1.default.resolve(argv.app_home);
|
|
148
|
-
const excludePathsConfigured =
|
|
149
|
-
|
|
150
|
-
...normalizeExcludePaths(process.env.FLIGHT_EXCLUDE_PATHS)
|
|
151
|
-
]);
|
|
152
|
-
const backendDiscoveryIgnores = backendDiscoveryIgnorePatterns(appHomePath, excludePathsConfigured);
|
|
112
|
+
const excludePathsConfigured = (0, backend_discovery_js_1.mergeExcludePaths)(argv.exclude_paths, process.env.FLIGHT_EXCLUDE_PATHS);
|
|
113
|
+
const backendDiscoveryIgnores = (0, backend_discovery_js_1.backendDiscoveryIgnorePatterns)(appHomePath, excludePathsConfigured);
|
|
153
114
|
process.chdir(appHomePath);
|
|
154
115
|
console.log(appHomePath);
|
|
155
116
|
if (backendDiscoveryIgnores.length > 0) {
|
|
@@ -230,7 +191,7 @@ else {
|
|
|
230
191
|
const distRoot = (0, spa_pipeline_js_1.resolveDistRoot)(process.cwd());
|
|
231
192
|
const useSpaPipeline = (0, spa_pipeline_js_1.productionSpaPipelineActive)(mode, Boolean(argv.disable_vite));
|
|
232
193
|
const staticPrefixes = (0, spa_pipeline_js_1.parseCommaPrefixes)(process.env.FLIGHT_STATIC_PREFIXES, '/assets,/fonts');
|
|
233
|
-
const rateLimitSkipPrefixes = dedupeStrings([
|
|
194
|
+
const rateLimitSkipPrefixes = (0, backend_discovery_js_1.dedupeStrings)([
|
|
234
195
|
...staticPrefixes,
|
|
235
196
|
...(0, spa_pipeline_js_1.parseCommaPrefixes)(process.env.FLIGHT_RATE_LIMIT_EXCLUDE_PREFIXES, '')
|
|
236
197
|
]);
|
|
@@ -286,17 +247,17 @@ else {
|
|
|
286
247
|
shell: true
|
|
287
248
|
});
|
|
288
249
|
viteProcess.on('error', (error) => {
|
|
289
|
-
console.error('Failed to start
|
|
250
|
+
console.error('Failed to start Vite dev server:', error);
|
|
290
251
|
});
|
|
291
252
|
viteProcess.on('exit', (code) => {
|
|
292
253
|
if (code !== 0) {
|
|
293
|
-
console.error(`Vite server exited with code ${code}`);
|
|
254
|
+
console.error(`Vite dev server exited with code ${code}`);
|
|
294
255
|
}
|
|
295
256
|
});
|
|
296
257
|
process.on('SIGINT', () => {
|
|
297
258
|
viteProcess.kill('SIGINT');
|
|
298
259
|
process.exit(0);
|
|
299
260
|
});
|
|
300
|
-
console.log(`Vite
|
|
261
|
+
console.log(`Vite dev server (HMR for Vue/React per your vite.config) for worker ${process.pid} on port 3001`);
|
|
301
262
|
}
|
|
302
263
|
}
|
package/dist/spa-pipeline.js
CHANGED
|
@@ -30,6 +30,7 @@ function applyTrustProxy(app, env = process.env) {
|
|
|
30
30
|
const t = env.FLIGHT_TRUST_PROXY;
|
|
31
31
|
app.proxy = t === '1' || t === 'true' || t === 'yes';
|
|
32
32
|
}
|
|
33
|
+
/** Use `fallback` only when `value` is missing. Add a leading `/` to entries that lack one. */
|
|
33
34
|
function parseCommaPrefixes(value, fallback) {
|
|
34
35
|
const raw = (value ?? fallback).trim();
|
|
35
36
|
if (!raw)
|
|
@@ -41,11 +42,18 @@ function parseCommaPrefixes(value, fallback) {
|
|
|
41
42
|
.map((p) => (p.startsWith('/') ? p : `/${p}`));
|
|
42
43
|
}
|
|
43
44
|
function resolveDistRoot(cwd, env = process.env) {
|
|
44
|
-
|
|
45
|
+
const configured = env.FLIGHT_DIST_PATH?.trim();
|
|
46
|
+
return path_1.default.resolve(cwd, configured || '../dist');
|
|
45
47
|
}
|
|
48
|
+
/** SPA shell path inside the dist root. `..` segments and blank values fall back to `index.html`. */
|
|
46
49
|
function spaIndexRelative(env = process.env) {
|
|
47
|
-
const
|
|
48
|
-
|
|
50
|
+
const configured = env.FLIGHT_SPA_INDEX?.trim().replace(/^\/+/, '');
|
|
51
|
+
if (!configured)
|
|
52
|
+
return 'index.html';
|
|
53
|
+
const normalized = configured.replace(/\\/g, '/');
|
|
54
|
+
if (normalized.split('/').includes('..'))
|
|
55
|
+
return 'index.html';
|
|
56
|
+
return normalized;
|
|
49
57
|
}
|
|
50
58
|
const DEFAULT_DENY_PREFIXES = ['/api', '/health'];
|
|
51
59
|
/** Match connect-history-api-fallback default: do not rewrite paths whose last segment looks like a file name. */
|
|
@@ -56,8 +64,16 @@ function lastPathSegmentLooksLikeFile(urlPath) {
|
|
|
56
64
|
/**
|
|
57
65
|
* After koa-static: serve index.html for document navigations that are not API, health, or static-like paths.
|
|
58
66
|
*/
|
|
67
|
+
function indexInsideDist(distRoot, indexRel) {
|
|
68
|
+
const indexAbs = path_1.default.resolve(distRoot, indexRel);
|
|
69
|
+
const rel = path_1.default.relative(distRoot, indexAbs);
|
|
70
|
+
const relPosix = rel.replace(/\\/g, '/');
|
|
71
|
+
if (!relPosix || relPosix === '..' || relPosix.startsWith('../') || path_1.default.isAbsolute(rel))
|
|
72
|
+
return undefined;
|
|
73
|
+
return indexAbs;
|
|
74
|
+
}
|
|
59
75
|
function spaIndexHtmlFallback(distRoot, indexRel, extraDenyPrefixes = []) {
|
|
60
|
-
const indexAbs =
|
|
76
|
+
const indexAbs = indexInsideDist(distRoot, indexRel);
|
|
61
77
|
const deny = dedupePathList([...DEFAULT_DENY_PREFIXES, ...extraDenyPrefixes]);
|
|
62
78
|
return async (ctx, next) => {
|
|
63
79
|
if (ctx.method !== 'GET' && ctx.method !== 'HEAD')
|
|
@@ -75,6 +91,8 @@ function spaIndexHtmlFallback(distRoot, indexRel, extraDenyPrefixes = []) {
|
|
|
75
91
|
return next();
|
|
76
92
|
}
|
|
77
93
|
}
|
|
94
|
+
if (!indexAbs)
|
|
95
|
+
return next();
|
|
78
96
|
try {
|
|
79
97
|
await fs_1.default.promises.access(indexAbs, fs_1.default.constants.R_OK);
|
|
80
98
|
}
|
package/package.json
CHANGED
|
@@ -1,18 +1,25 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@thoughtpivot/flight",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.1",
|
|
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": {
|
|
7
7
|
"access": "public"
|
|
8
8
|
},
|
|
9
|
+
"engines": {
|
|
10
|
+
"node": "^20.19.0 || >=22.12.0"
|
|
11
|
+
},
|
|
9
12
|
"bin": {
|
|
10
|
-
"flight": "dist/flight.js"
|
|
13
|
+
"flight": "dist/flight.js",
|
|
14
|
+
"flight-bun": "src/bun/server.ts"
|
|
11
15
|
},
|
|
12
16
|
"scripts": {
|
|
13
17
|
"lint": "eslint . --fix && prettier -w .",
|
|
18
|
+
"lint:check": "eslint . && prettier -c .",
|
|
14
19
|
"build": "rm -rf dist; node_modules/.bin/tsc --project .",
|
|
15
|
-
"test": "npm run build && node --test dist
|
|
20
|
+
"test": "npm run build && node --test dist/*.test.js",
|
|
21
|
+
"test:bun": "bun test src/bun/app.test.ts",
|
|
22
|
+
"start:bun": "bun src/bun/server.ts",
|
|
16
23
|
"prepublishOnly": "npm run build"
|
|
17
24
|
},
|
|
18
25
|
"repository": {
|
|
@@ -20,7 +27,7 @@
|
|
|
20
27
|
"url": "git+https://github.com/thoughtpivot/flight.git"
|
|
21
28
|
},
|
|
22
29
|
"author": "",
|
|
23
|
-
"license": "
|
|
30
|
+
"license": "Apache-2.0",
|
|
24
31
|
"bugs": {
|
|
25
32
|
"url": "https://github.com/thoughtpivot/flight/issues"
|
|
26
33
|
},
|
|
@@ -28,35 +35,40 @@
|
|
|
28
35
|
"files": [
|
|
29
36
|
"dist/flight.js",
|
|
30
37
|
"dist/spa-pipeline.js",
|
|
38
|
+
"dist/backend-discovery.js",
|
|
39
|
+
"src/bun",
|
|
31
40
|
"README.md",
|
|
32
41
|
"CHANGELOG.md",
|
|
42
|
+
"LICENSE",
|
|
43
|
+
"NOTICE",
|
|
33
44
|
"readme-assets"
|
|
34
45
|
],
|
|
35
46
|
"dependencies": {
|
|
36
47
|
"@koa/cors": "^5.0.0",
|
|
37
48
|
"@koa/router": "^13.1.0",
|
|
38
|
-
"@vitejs/plugin-
|
|
49
|
+
"@vitejs/plugin-react": "^6.1.1",
|
|
50
|
+
"@vitejs/plugin-vue": "^6.0.9",
|
|
39
51
|
"@vue/language-plugin-pug": "^2.2.0",
|
|
40
52
|
"core-js": "^3.40.0",
|
|
41
53
|
"dotenv": "^17.4.2",
|
|
42
54
|
"esbuild": "^0.28.0",
|
|
43
55
|
"fast-glob": "^3.3.3",
|
|
44
|
-
"ioredis": "^
|
|
56
|
+
"ioredis": "^6.0.0",
|
|
45
57
|
"koa": "^2.15.3",
|
|
46
58
|
"koa-bodyparser": "^4.4.1",
|
|
47
59
|
"koa-cash": "^5.0.0",
|
|
48
60
|
"koa-compress": "^5.1.1",
|
|
49
61
|
"koa-connect-history-api-fallback": "^0.3.1",
|
|
50
|
-
"koa-helmet": "^
|
|
51
|
-
"koa-logger": "^
|
|
52
|
-
"koa-ratelimit": "^
|
|
62
|
+
"koa-helmet": "^9.0.0",
|
|
63
|
+
"koa-logger": "^4.0.0",
|
|
64
|
+
"koa-ratelimit": "^6.0.0",
|
|
53
65
|
"koa-redis": "^4.0.1",
|
|
54
66
|
"koa-send": "^5.0.1",
|
|
55
67
|
"koa-session": "^6.4.0",
|
|
56
68
|
"koa-static": "^5.0.0",
|
|
57
69
|
"unplugin-fonts": "^2.0.0",
|
|
58
|
-
"vite": "^
|
|
59
|
-
"vite-plugin-vuetify": "^2.
|
|
70
|
+
"vite": "^8.3.2",
|
|
71
|
+
"vite-plugin-vuetify": "^2.1.3",
|
|
60
72
|
"yargs": "^18.0.0"
|
|
61
73
|
},
|
|
62
74
|
"devDependencies": {
|