@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/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 = dedupeStrings([
149
- ...normalizeExcludePaths(argv.exclude_paths),
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 vite server:', error);
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 development server with hot module reload ${process.pid} started on 3001`);
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
  }
@@ -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
- return path_1.default.resolve(cwd, env.FLIGHT_DIST_PATH || '../dist');
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 v = (env.FLIGHT_SPA_INDEX || 'index.html').trim();
48
- return v.replace(/^\/+/, '');
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 = path_1.default.join(distRoot, indexRel);
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": "2.0.2",
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/spa-pipeline.test.js",
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": "MIT",
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-vue": "^5.2.1",
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": "^5.4.2",
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": "^8.0.1",
51
- "koa-logger": "^3.2.1",
52
- "koa-ratelimit": "^5.1.0",
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": "^6.1.7",
59
- "vite-plugin-vuetify": "^2.0.4",
70
+ "vite": "^8.3.2",
71
+ "vite-plugin-vuetify": "^2.1.3",
60
72
  "yargs": "^18.0.0"
61
73
  },
62
74
  "devDependencies": {