tina4-nodejs 3.10.50 → 3.10.55

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tina4-nodejs",
3
- "version": "3.10.50",
3
+ "version": "3.10.55",
4
4
  "type": "module",
5
5
  "description": "Tina4 for Node.js/TypeScript — 54 built-in features, zero dependencies",
6
6
  "keywords": ["tina4", "framework", "web", "api", "orm", "graphql", "websocket", "typescript"],
@@ -46,6 +46,7 @@ const HELP = `
46
46
  Options:
47
47
  --port <number> Server port (default: 7148)
48
48
  --no-browser Don't open the browser on serve
49
+ --no-reload Disable file watcher / live-reload on serve
49
50
  --all Install AI context for all tools (with ai command)
50
51
  --force Overwrite existing AI context files (with ai command)
51
52
  --help Show this help message
@@ -85,11 +86,12 @@ async function main(): Promise<void> {
85
86
  const portIndex = args.indexOf("--port");
86
87
  const port = portIndex !== -1 ? parseInt(args[portIndex + 1], 10) : 7148;
87
88
  const noBrowser = args.includes("--no-browser");
89
+ const noReload = args.includes("--no-reload");
88
90
 
89
91
  // Kill any existing process on the port
90
92
  killProcessOnPort(port);
91
93
 
92
- await serveProject({ port, noBrowser });
94
+ await serveProject({ port, noBrowser, noReload });
93
95
  break;
94
96
  }
95
97
  case "migrate": {
@@ -4,9 +4,14 @@ import { existsSync } from "node:fs";
4
4
  export interface ServeOptions {
5
5
  port?: number;
6
6
  noBrowser?: boolean;
7
+ noReload?: boolean;
7
8
  }
8
9
 
9
10
  export async function serveProject(options: ServeOptions): Promise<void> {
11
+ if (options.noReload) {
12
+ process.env.TINA4_NO_RELOAD = "true";
13
+ }
14
+
10
15
  const port = options.port ?? 7148;
11
16
  const cwd = process.cwd();
12
17
 
@@ -33,26 +38,30 @@ export async function serveProject(options: ServeOptions): Promise<void> {
33
38
  });
34
39
 
35
40
  // Watch for file changes and reload routes
41
+ const noReload = ["true", "1", "yes"].includes((process.env.TINA4_NO_RELOAD ?? "").toLowerCase());
36
42
  const watchDirs = [routesDir, ormDir, modelsDir, templatesDir].filter((d) => existsSync(d));
37
- const watcher = watchForChanges(watchDirs, async () => {
38
- try {
39
- const { discoverRoutes } = await import("../../../core/src/index.js");
40
- // Clear routes BEFORE re-discovery to avoid stale duplicates
41
- server.router.clear();
42
- const routes = await discoverRoutes(routesDir);
43
- for (const route of routes) {
44
- server.router.addRoute(route);
43
+ let watcher: { close: () => void } | null = null;
44
+ if (!noReload) {
45
+ watcher = watchForChanges(watchDirs, async () => {
46
+ try {
47
+ const { discoverRoutes } = await import("../../../core/src/index.js");
48
+ // Clear routes BEFORE re-discovery to avoid stale duplicates
49
+ server.router.clear();
50
+ const routes = await discoverRoutes(routesDir);
51
+ for (const route of routes) {
52
+ server.router.addRoute(route);
53
+ }
54
+ console.log(` Reloaded ${routes.length} route(s)`);
55
+ } catch (err) {
56
+ console.error(" Error reloading routes:", err);
45
57
  }
46
- console.log(` Reloaded ${routes.length} route(s)`);
47
- } catch (err) {
48
- console.error(" Error reloading routes:", err);
49
- }
50
- });
58
+ });
59
+ }
51
60
 
52
61
  // Graceful shutdown
53
62
  const shutdown = () => {
54
63
  console.log("\n Shutting down...");
55
- watcher.close();
64
+ watcher?.close();
56
65
  server.close();
57
66
  process.exit(0);
58
67
  };
@@ -2467,7 +2467,7 @@ function renderBubbleChart(files,depGraph,scanMode){
2467
2467
  bubbles.forEach(function(b,i){if(b.f.path===src)srcIdx=i;});
2468
2468
  if(srcIdx===null)return;
2469
2469
  (depGraph[src]||[]).forEach(function(tgt){
2470
- var tgtName=tgt.split('.').pop().toLowerCase();
2470
+ var tgtName=basename(tgt);
2471
2471
  var tgtIdx=nameIdx[tgtName];
2472
2472
  if(tgtIdx!==undefined&&srcIdx!==tgtIdx)edges.push([srcIdx,tgtIdx]);
2473
2473
  });
@@ -673,7 +673,17 @@ ${reset}
673
673
  const requestId = Date.now().toString(36);
674
674
 
675
675
  // Wrap res.raw.end to inject dev toolbar and capture requests
676
- if (isDevMode() && !pathname.startsWith("/__dev")) {
676
+ // Skip toolbar injection on the AI port (no-reload behaviour)
677
+ const isAiPortRequest = !!(rawReq as any)._tina4AiPort;
678
+
679
+ // AI port: block /__dev_reload so AI tools never trigger a browser reload
680
+ if (isAiPortRequest && pathname === "/__dev_reload") {
681
+ res.raw.writeHead(404, { "Content-Type": "application/json" });
682
+ res.raw.end(JSON.stringify({ error: "Not available on AI port" }));
683
+ return;
684
+ }
685
+
686
+ if (isDevMode() && !pathname.startsWith("/__dev") && !isAiPortRequest) {
677
687
  const originalEnd = res.raw.end.bind(res.raw);
678
688
 
679
689
  const wrappedEnd: typeof res.raw.end = function (
@@ -873,7 +883,35 @@ ${reset}
873
883
  // Determine server mode label
874
884
  const serverMode = isDebug ? "single" : (cluster.isWorker ? "cluster-worker" : "single");
875
885
 
886
+ // AI dual-port: start a second HTTP server on port+1 when in debug mode
887
+ // and TINA4_NO_AI_PORT is not set. This port serves requests without
888
+ // dev-reload or toolbar injection so AI coding tools get stable responses.
889
+ const noAiPort = isTruthy(process.env.TINA4_NO_AI_PORT ?? "");
890
+ let aiServer: ReturnType<typeof createServer> | null = null;
891
+ let aiPort = port + 1;
892
+
893
+ if (isDebug && !noAiPort) {
894
+ aiServer = createServer(async (req, res) => {
895
+ // Tag the request so dispatch knows it came from the AI port
896
+ (req as any)._tina4AiPort = true;
897
+ await dispatch(req, res);
898
+ });
899
+
900
+ aiServer.on("error", (err: any) => {
901
+ if (err.code === "EADDRINUSE") {
902
+ Log.warn(`AI port ${aiPort} in use — skipping`);
903
+ aiServer = null;
904
+ }
905
+ });
906
+
907
+ aiServer.listen(aiPort, host);
908
+ }
909
+
876
910
  // Banner goes to stdout via console.log — NOT through the framework logger
911
+ const aiPortLine = (isDebug && !noAiPort)
912
+ ? `\n AI Port: http://localhost:${aiPort} (no-reload)`
913
+ : "";
914
+
877
915
  console.log(`${color}
878
916
  ______ _ __ __
879
917
  /_ __/(_)___ ____ _/ // /
@@ -886,7 +924,7 @@ ${reset}
886
924
  Server: http://${displayHost}:${port} (${serverMode})
887
925
  Swagger: http://localhost:${port}/swagger
888
926
  Dashboard: http://localhost:${port}/__dev
889
- Debug: ${isDebug ? "ON" : "OFF"} (Log level: ${logLevel})
927
+ Debug: ${isDebug ? "ON" : "OFF"} (Log level: ${logLevel})${aiPortLine}
890
928
  `);
891
929
  const noBrowser = isTruthy(process.env.TINA4_NO_BROWSER);
892
930
  if (!noBrowser) {
@@ -894,6 +932,7 @@ ${reset}
894
932
  }
895
933
  resolvePromise({
896
934
  close: () => {
935
+ if (aiServer) aiServer.close();
897
936
  server.close();
898
937
  // Close database if ORM was initialized
899
938
  import("../../orm/src/index.js").then((orm) => orm.closeDatabase()).catch(() => {});