@titanpl/packet 1.0.0 → 1.4.0

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/ts/titan/dev.js CHANGED
@@ -1,17 +1,12 @@
1
1
  import chokidar from "chokidar";
2
2
  import { spawn, execSync } from "child_process";
3
3
  import path from "path";
4
- import { fileURLToPath } from "url";
5
4
  import fs from "fs";
6
- import esbuild from "esbuild";
5
+ import { bundle } from "./bundle.js";
6
+ import { buildMetadata } from "./builder.js";
7
7
  import { createRequire } from "module";
8
+ import os from "os";
8
9
 
9
- // Required for __dirname in ES modules
10
- const __filename = fileURLToPath(import.meta.url);
11
- const __dirname = path.dirname(__filename);
12
-
13
-
14
- // Colors
15
10
  const cyan = (t) => `\x1b[36m${t}\x1b[0m`;
16
11
  const green = (t) => `\x1b[32m${t}\x1b[0m`;
17
12
  const yellow = (t) => `\x1b[33m${t}\x1b[0m`;
@@ -22,371 +17,102 @@ const bold = (t) => `\x1b[1m${t}\x1b[0m`;
22
17
  function getTitanVersion() {
23
18
  try {
24
19
  const require = createRequire(import.meta.url);
25
- const pkgPath = require.resolve("@ezetgalaxy/titan/package.json");
20
+ const pkgPath = require.resolve("@titanpl/cli/package.json");
26
21
  return JSON.parse(fs.readFileSync(pkgPath, "utf-8")).version;
27
22
  } catch (e) {
28
- try {
29
- // Check levels up to find the framework root
30
- let cur = __dirname;
31
- for (let i = 0; i < 5; i++) {
32
- const pkgPath = path.join(cur, "package.json");
33
- if (fs.existsSync(pkgPath)) {
34
- const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
35
- if (pkg.name === "@ezetgalaxy/titan") return pkg.version;
36
- }
37
- cur = path.join(cur, "..");
38
- }
39
- } catch (e2) { }
40
-
41
- try {
42
- // Fallback to calling tit --version
43
- const output = execSync("tit --version", { encoding: "utf-8" }).trim();
44
- const match = output.match(/v(\d+\.\d+\.\d+)/);
45
- if (match) return match[1];
46
- } catch (e3) { }
23
+ return "1.0.0";
47
24
  }
48
- return "0.1.0";
49
25
  }
50
26
 
51
- let serverProcess = null;
52
- let isKilling = false;
53
- let isFirstBoot = true;
54
-
55
- async function killServer() {
56
- if (!serverProcess) return;
57
-
58
- isKilling = true;
59
- const pid = serverProcess.pid;
60
- const killPromise = new Promise((resolve) => {
61
- if (serverProcess.exitCode !== null) return resolve();
62
- serverProcess.once("close", resolve);
63
- });
64
-
65
- if (process.platform === "win32") {
66
- try {
67
- execSync(`taskkill /pid ${pid} /f /t`, { stdio: 'ignore' });
68
- } catch (e) {
69
- // Ignore errors if process is already dead
70
- }
71
- } else {
72
- serverProcess.kill();
27
+ function getEngineBinaryPath(root) {
28
+ const platform = os.platform();
29
+ const arch = os.arch();
30
+ const binName = platform === 'win32' ? 'titan-server.exe' : 'titan-server';
31
+ const pkgName = `@titanpl/engine-${platform}-${arch}`;
32
+
33
+ // Monorepo search
34
+ let current = root;
35
+ for (let i = 0; i < 5; i++) {
36
+ const potential = path.join(current, 'engine', 'target', 'release', binName);
37
+ if (fs.existsSync(potential)) return potential;
38
+ current = path.dirname(current);
73
39
  }
74
40
 
41
+ // Node modules search
75
42
  try {
76
- await killPromise;
43
+ const require = createRequire(import.meta.url);
44
+ const pkgPath = require.resolve(`${pkgName}/package.json`);
45
+ const binPath = path.join(path.dirname(pkgPath), 'bin', binName);
46
+ if (fs.existsSync(binPath)) return binPath;
77
47
  } catch (e) { }
78
- serverProcess = null;
79
- isKilling = false;
80
- }
81
-
82
- const delay = (ms) => new Promise(res => setTimeout(res, ms));
83
-
84
- let spinnerTimer = null;
85
- const frames = ["⏣", "⟐", "⟡", "⟠", "⟡", "⟐"];
86
- let frameIdx = 0;
87
-
88
- function startSpinner(text) {
89
- if (spinnerTimer) clearInterval(spinnerTimer);
90
- process.stdout.write("\x1B[?25l"); // Hide cursor
91
- spinnerTimer = setInterval(() => {
92
- process.stdout.write(`\r ${cyan(frames[frameIdx])} ${gray(text)}`);
93
- frameIdx = (frameIdx + 1) % frames.length;
94
- }, 80);
95
- }
96
48
 
97
- function stopSpinner(success = true, text = "") {
98
- if (spinnerTimer) {
99
- clearInterval(spinnerTimer);
100
- spinnerTimer = null;
101
- }
102
- process.stdout.write("\r\x1B[K"); // Clear line
103
- process.stdout.write("\x1B[?25h"); // Show cursor
104
- if (text) {
105
- if (success) {
106
- console.log(` ${green("✔")} ${green(text)}`);
107
- } else {
108
- console.log(` ${red("✖")} ${red(text)}`);
109
- }
110
- }
49
+ return null;
111
50
  }
112
51
 
113
- async function startRustServer(retryCount = 0) {
114
- // If TS is broken, don't start
115
- if (isTs && !isTsHealthy) {
116
- stopSpinner(false, "Waiting for TypeScript errors to be fixed...");
117
- return;
118
- }
119
-
120
- const waitTime = retryCount > 0 ? 1000 : 500;
121
-
122
- await killServer();
123
- await delay(waitTime);
124
-
125
- const serverPath = path.join(process.cwd(), "server");
126
- const startTime = Date.now();
127
-
128
- startSpinner("Stabilizing your app on its orbit...");
129
-
130
- let isReady = false;
131
- let stdoutBuffer = "";
132
- let buildLogs = "";
133
- let stderrBuffer = "";
134
-
135
- // If it takes more than 30s, update the message
136
- const slowTimer = setTimeout(() => {
137
- if (!isReady && !isKilling) {
138
- startSpinner("Still stabilizing... (the first orbit takes longer)");
139
- }
140
- }, 30000);
141
-
142
- serverProcess = spawn("cargo", ["run", "--quiet"], {
143
- cwd: serverPath,
144
- stdio: ["ignore", "pipe", "pipe"],
145
- env: { ...process.env, CARGO_INCREMENTAL: "1" }
146
- });
147
-
148
- serverProcess.on("error", (err) => {
149
- stopSpinner(false, "Failed to start orbit");
150
- console.error(red(`[Titan] Error: ${err.message}`));
151
- });
52
+ let serverProcess = null;
152
53
 
153
- serverProcess.stderr.on("data", (data) => {
154
- const str = data.toString();
155
- if (isReady) {
156
- process.stderr.write(data);
157
- } else {
158
- buildLogs += str;
159
- }
160
- });
54
+ async function killServer() {
55
+ if (!serverProcess) return;
161
56
 
162
- serverProcess.stdout.on("data", (data) => {
163
- const out = data.toString();
57
+ return new Promise((resolve) => {
58
+ const onExit = () => {
59
+ serverProcess = null;
60
+ resolve();
61
+ };
164
62
 
165
- if (!isReady) {
166
- stdoutBuffer += out;
167
- if (stdoutBuffer.includes("Titan server running") || stdoutBuffer.includes("████████╗")) {
168
- isReady = true;
169
- clearTimeout(slowTimer);
170
- stopSpinner(true, "Your app is now orbiting Titan Planet");
63
+ serverProcess.on('exit', onExit);
171
64
 
172
- if (isFirstBoot) {
173
- process.stdout.write(stdoutBuffer);
174
- isFirstBoot = false;
175
- } else {
176
- // On subsequent reloads, only print non-banner lines from the buffer
177
- const lines = stdoutBuffer.split("\n");
178
- for (const line of lines) {
179
- const isBanner = line.includes("Titan server running") ||
180
- line.includes("████████╗") ||
181
- line.includes("╚══") ||
182
- line.includes(" ██║") ||
183
- line.includes(" ╚═╝");
184
- if (!isBanner && line.trim()) {
185
- process.stdout.write(line + "\n");
186
- }
187
- }
188
- }
189
- stdoutBuffer = "";
190
- }
65
+ if (os.platform() === 'win32') {
66
+ try {
67
+ execSync(`taskkill /pid ${serverProcess.pid} /f /t`, { stdio: 'ignore' });
68
+ } catch (e) { }
191
69
  } else {
192
- process.stdout.write(data);
70
+ serverProcess.kill('SIGKILL');
193
71
  }
194
- });
195
72
 
196
- // Monitor stderr for port binding errors
197
- serverProcess.stderr.on("data", (data) => {
198
- stderrBuffer += data.toString();
73
+ // Failsafe format
74
+ setTimeout(onExit, 500);
199
75
  });
200
-
201
- serverProcess.on("close", async (code) => {
202
- clearTimeout(slowTimer);
203
- if (isKilling) return;
204
- const runTime = Date.now() - startTime;
205
-
206
- if (code !== 0 && code !== null) {
207
- // Check for port binding errors
208
- const isPortError = stderrBuffer.includes("Address already in use") ||
209
- stderrBuffer.includes("address in use") ||
210
- stderrBuffer.includes("os error 10048") || // Windows
211
- stderrBuffer.includes("EADDRINUSE") ||
212
- stderrBuffer.includes("AddrInUse");
213
-
214
- if (isPortError) {
215
- if (retryCount < 3) {
216
- // It's likely the previous process hasn't fully released the port
217
- await delay(1000);
218
- await startRustServer(retryCount + 1);
219
- return;
220
- }
221
-
222
- stopSpinner(false, "Orbit stabilization failed");
223
-
224
- // Try to read intended port
225
- let port = 3000;
226
- try {
227
- const routesConfig = JSON.parse(fs.readFileSync(path.join(process.cwd(), "server", "routes.json"), "utf8"));
228
- if (routesConfig && routesConfig.__config && routesConfig.__config.port) {
229
- port = routesConfig.__config.port;
230
- }
231
- } catch (e) { }
232
-
233
- console.log("");
234
-
235
- console.log(red("⏣ Your application cannot enter this orbit"));
236
- console.log(red(`↳ Another application is already bound to port ${port}.`));
237
- console.log("");
238
-
239
- console.log(yellow("Recommended Actions:"));
240
- console.log(yellow(" 1.") + " Release the occupied orbit (stop the other service).");
241
- console.log(yellow(" 2.") + " Assign your application to a new orbit in " + cyan("app/app.js"));
242
- console.log(yellow(" Example: ") + cyan(`t.start(${port + 1}, "Titan Running!")`));
243
- console.log("");
244
-
245
- return;
246
- }
247
-
248
-
249
- stopSpinner(false, "Orbit stabilization failed");
250
-
251
- // Debug: Show stderr if it's not empty and not a port error
252
- if (stderrBuffer && stderrBuffer.trim()) {
253
- console.log(gray("\n[Debug] Cargo stderr:"));
254
- console.log(gray(stderrBuffer.substring(0, 500))); // Show first 500 chars
255
- }
256
-
257
- if (runTime < 15000 && retryCount < maxRetries) {
258
- await delay(2000);
259
- await startRustServer(retryCount + 1);
260
- } else if (retryCount >= maxRetries) {
261
- console.log(gray("\n[Titan] Waiting for changes to retry..."));
262
- }
263
- }
264
- });
265
-
266
- }
267
-
268
- async function rebuild() {
269
- if (isTs && !isTsHealthy) return; // Don't rebuild if TS is broken
270
-
271
- try {
272
- const root = process.cwd();
273
- const appTs = path.join(root, "app", "app.ts");
274
- const dotTitan = path.join(root, ".titan");
275
- const compiledApp = path.join(dotTitan, "app.js");
276
-
277
- if (fs.existsSync(appTs)) {
278
- if (!fs.existsSync(dotTitan)) fs.mkdirSync(dotTitan, { recursive: true });
279
-
280
- await esbuild.build({
281
- entryPoints: [appTs],
282
- outfile: compiledApp,
283
- bundle: true,
284
- platform: "node",
285
- format: "esm",
286
- external: ["fs", "path", "esbuild", "chokidar", "typescript"],
287
- logLevel: "silent"
288
- });
289
-
290
- execSync(`node "${compiledApp}"`, { stdio: "inherit" });
291
- } else {
292
- execSync("node app/app.js", { stdio: "ignore" });
293
- }
294
- } catch (e) {
295
- stopSpinner(false, "Failed to prepare runtime");
296
- console.log(red(`[Titan] Error: ${e.message}`));
297
- }
298
76
  }
299
77
 
300
- let tsProcess = null;
301
- let isTsHealthy = false; // STRICT: Assume unhealthy until checked
302
-
303
- function startTypeChecker() {
304
- const root = process.cwd();
305
- if (!fs.existsSync(path.join(root, "tsconfig.json"))) return;
306
-
307
- let tscPath;
308
- try {
309
- const require = createRequire(import.meta.url);
310
- tscPath = require.resolve("typescript/bin/tsc");
311
- } catch (e) {
312
- tscPath = path.join(root, "node_modules", "typescript", "bin", "tsc");
313
- }
314
-
315
- if (!fs.existsSync(tscPath)) {
78
+ function startServer(root, outDir) {
79
+ const binaryPath = getEngineBinaryPath(root);
80
+ if (!binaryPath) {
81
+ console.error(red("[TitanPL] Error: Could not find engine binary. Ensure you have installed the correct engine package."));
316
82
  return;
317
83
  }
318
84
 
319
- const args = [tscPath, "--noEmit", "--watch", "--preserveWatchOutput", "--pretty"];
85
+ const distPath = path.resolve(root, "dist");
320
86
 
321
- tsProcess = spawn(process.execPath, args, {
322
- cwd: root,
323
- stdio: "pipe",
324
- shell: false
325
- });
326
-
327
- tsProcess.stdout.on("data", (data) => {
328
- const lines = data.toString().split("\n");
329
- for (const line of lines) {
330
- if (line.trim().includes("File change detected") || line.trim().includes("Starting compilation")) {
331
- isTsHealthy = false;
332
- continue;
333
- }
334
- if (line.includes("Found 0 errors")) {
335
- isTsHealthy = true;
336
- // TS is happy, so we rebuild and restart (or start) the server
337
- rebuild().then(startRustServer);
338
-
339
- } else if (line.includes("error TS")) {
340
- isTsHealthy = false;
341
- if (serverProcess) {
342
- console.log(red(`[Titan] TypeScript error detected. Stopping server...`));
343
- killServer();
344
- }
345
- process.stdout.write(line + "\n");
346
- } else if (line.match(/Found [1-9]\d* error/)) {
347
- isTsHealthy = false;
348
- if (serverProcess) {
349
- console.log(red(`[Titan] TypeScript compilation failed. Stopping server...`));
350
- killServer();
351
- }
352
- process.stdout.write(line + "\n");
353
- } else if (line.trim()) {
354
- process.stdout.write(gray(`[TS] ${line}\n`));
355
- }
87
+ serverProcess = spawn(binaryPath, ['run', distPath, '--watch'], {
88
+ stdio: 'inherit',
89
+ env: {
90
+ ...process.env,
91
+ TITAN_ENV: 'development',
92
+ Titan_Dev: '1'
356
93
  }
357
94
  });
358
95
 
359
- tsProcess.stderr.on("data", (data) => {
360
- process.stdout.write(data);
96
+ serverProcess.on('error', (err) => {
97
+ if (err.code === 'ENOENT') {
98
+ console.error(red("[TitanPL] Failed to start engine: Binary not found."));
99
+ } else {
100
+ console.error(red(`[TitanPL] Engine error: ${err.message}`));
101
+ }
361
102
  });
362
103
  }
363
104
 
364
- let isTs = false;
105
+ export async function dev(options) {
106
+ const root = options.root || process.cwd();
107
+ const outDir = options.outDir || path.join(root, "dist");
365
108
 
366
- async function startDev() {
367
- const root = process.cwd();
368
- const actionsDir = path.join(root, "app", "actions");
369
- let hasRust = false;
370
- if (fs.existsSync(actionsDir)) {
371
- hasRust = fs.readdirSync(actionsDir).some(f => f.endsWith(".rs"));
372
- }
373
-
374
- isTs = fs.existsSync(path.join(root, "tsconfig.json")) ||
375
- fs.existsSync(path.join(root, "app", "app.ts"));
376
-
377
- let mode = "";
378
- if (hasRust) {
379
- mode = isTs ? "Rust + TS Actions" : "Rust + JS Actions";
380
- } else {
381
- mode = isTs ? "TS Actions" : "JS Actions";
382
- }
383
109
  const version = getTitanVersion();
384
110
 
385
111
  console.clear();
386
112
  console.log("");
387
113
  console.log(` ${bold(cyan("⏣ Titan Planet"))} ${gray("v" + version)} ${yellow("[ Dev Mode ]")}`);
388
114
  console.log("");
389
- console.log(` ${gray("Type: ")} ${mode}`);
115
+ console.log(` ${gray("Type: ")} TS Actions`);
390
116
  console.log(` ${gray("Hot Reload: ")} ${green("Enabled")}`);
391
117
 
392
118
  if (fs.existsSync(path.join(root, ".env"))) {
@@ -394,61 +120,56 @@ async function startDev() {
394
120
  }
395
121
  console.log("");
396
122
 
397
- if (isTs) {
398
- startTypeChecker();
399
- } else {
400
- // If no TS, start immediately
123
+ const runBuildCycle = async () => {
401
124
  try {
402
- await rebuild();
403
- await startRustServer();
404
- } catch (e) {
125
+ await killServer();
126
+
127
+ // Re-check TS health via bundle() which now calls checkTypes()
128
+ await buildMetadata(root, outDir);
129
+ await bundle({ root, outDir });
130
+
131
+ console.log(cyan(`\n[TitanPL] Starting Engine...`));
132
+ startServer(root, outDir);
133
+ } catch (err) {
134
+ if (err.message !== '__TITAN_BUNDLE_FAILED__') {
135
+ console.error(red(`[TitanPL] Build failed: ${err.message}`));
136
+ }
137
+ console.log(yellow("\n[TitanPL] Waiting for fixes before restarting orbit..."));
405
138
  }
406
- }
139
+ };
140
+
141
+ // Initial build
142
+ await runBuildCycle();
407
143
 
408
- const watcher = chokidar.watch(["app", ".env"], {
144
+ // Watch for changes inside app/
145
+ const appDir = path.join(root, "app");
146
+ const envFile = path.join(root, ".env");
147
+ const tsConfig = path.join(root, "tsconfig.json");
148
+
149
+ const watcher = chokidar.watch([appDir, envFile, tsConfig], {
409
150
  ignoreInitial: true,
410
- awaitWriteFinish: { stabilityThreshold: 500, pollInterval: 100 }
151
+ awaitWriteFinish: { stabilityThreshold: 200, pollInterval: 50 }
411
152
  });
412
153
 
413
- let timer = null;
154
+ let buildTimer = null;
414
155
  watcher.on("all", async (event, file) => {
415
- if (timer) clearTimeout(timer);
416
- timer = setTimeout(async () => {
417
- // If TS, we rely on TCS to trigger the rebuild (via Found 0 errors)
418
- // We verify path safety using absolute/relative calculations
419
- const relPath = path.relative(root, file);
420
- if (isTs && (relPath.startsWith("app") || relPath.startsWith("app" + path.sep))) return;
421
-
422
- // If TS is broken, rebuild() checks will prevent update, keeping server dead
423
- // If TS is healthy, we proceed
424
- if (isTs && !isTsHealthy) return;
156
+ if (!file) return;
157
+ const relPath = path.relative(root, file);
158
+ if (relPath.startsWith("dist") || relPath.startsWith(".titan") || relPath.startsWith("server") || relPath.startsWith("node_modules")) return;
159
+
160
+ if (buildTimer) clearTimeout(buildTimer);
161
+ buildTimer = setTimeout(() => {
162
+ console.clear();
163
+ console.log(cyan(`[TitanPL] File changed: ${relPath}. Rebuilding...`));
164
+ runBuildCycle();
165
+ }, 300); // Debounce
166
+ });
425
167
 
426
- try {
427
- await killServer();
428
- await rebuild();
429
- await startRustServer();
430
- } catch (e) {
431
- // console.log(red("[Titan] Build failed -- waiting for changes..."));
432
- }
433
- }, 300);
168
+ // Cleanup on exit
169
+ process.on('SIGINT', async () => {
170
+ await killServer();
171
+ process.exit(0);
434
172
  });
435
- }
436
173
 
437
- async function handleExit() {
438
- stopSpinner();
439
- console.log(gray("\n[Titan] Stopping server..."));
440
- await killServer();
441
- if (tsProcess) {
442
- if (process.platform === "win32") {
443
- try { execSync(`taskkill /pid ${tsProcess.pid} /f /t`, { stdio: 'ignore' }); } catch (e) { }
444
- } else {
445
- tsProcess.kill();
446
- }
447
- }
448
- process.exit(0);
174
+ return watcher;
449
175
  }
450
-
451
- process.on("SIGINT", handleExit);
452
- process.on("SIGTERM", handleExit);
453
-
454
- startDev();