@ezetgalaxy/titan 26.8.2 → 26.9.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.
Files changed (97) hide show
  1. package/README.md +65 -25
  2. package/index.js +62 -15
  3. package/package.json +2 -2
  4. package/templates/extension/README.md +104 -104
  5. package/templates/extension/index.js +27 -27
  6. package/templates/extension/jsconfig.json +12 -12
  7. package/templates/extension/native/Cargo.toml +9 -9
  8. package/templates/extension/native/src/lib.rs +5 -5
  9. package/templates/extension/package.json +20 -20
  10. package/templates/extension/titan.json +17 -17
  11. package/templates/js/Dockerfile +66 -66
  12. package/templates/js/_dockerignore +3 -3
  13. package/templates/js/_gitignore +1 -0
  14. package/templates/js/app/actions/hello.js +5 -5
  15. package/templates/js/app/titan.d.ts +87 -87
  16. package/templates/js/jsconfig.json +18 -18
  17. package/templates/js/server/src/action_management.rs +131 -131
  18. package/templates/js/server/src/errors.rs +10 -10
  19. package/templates/js/server/src/extensions.rs +989 -989
  20. package/templates/js/server/src/utils.rs +33 -33
  21. package/templates/js/titan/bundle.js +78 -78
  22. package/templates/js/titan/dev.js +35 -3
  23. package/templates/js/titan/titan.js +122 -122
  24. package/templates/rust/Dockerfile +66 -66
  25. package/templates/rust/_dockerignore +3 -3
  26. package/templates/rust/_gitignore +1 -0
  27. package/templates/rust/app/actions/hello.js +5 -5
  28. package/templates/rust/app/actions/rust_hello.rs +14 -14
  29. package/templates/rust/app/titan.d.ts +101 -101
  30. package/templates/rust/jsconfig.json +18 -18
  31. package/templates/rust/server/src/action_management.rs +131 -131
  32. package/templates/rust/server/src/errors.rs +10 -10
  33. package/templates/rust/server/src/extensions.rs +989 -989
  34. package/templates/rust/server/src/utils.rs +33 -33
  35. package/templates/rust/titan/dev.js +36 -12
  36. package/templates/rust-ts/Dockerfile +66 -0
  37. package/templates/rust-ts/_dockerignore +3 -0
  38. package/templates/rust-ts/_gitignore +38 -0
  39. package/templates/rust-ts/app/actions/hello.ts +5 -0
  40. package/templates/rust-ts/app/actions/rust_hello.rs +14 -0
  41. package/templates/rust-ts/app/app.ts +11 -0
  42. package/templates/rust-ts/app/titan.d.ts +101 -0
  43. package/templates/rust-ts/package.json +14 -0
  44. package/templates/rust-ts/server/Cargo.lock +2869 -0
  45. package/templates/rust-ts/server/Cargo.toml +39 -0
  46. package/templates/rust-ts/server/src/action_management.rs +131 -0
  47. package/templates/rust-ts/server/src/errors.rs +51 -0
  48. package/templates/rust-ts/server/src/extensions.rs +989 -0
  49. package/templates/rust-ts/server/src/main.rs +468 -0
  50. package/templates/rust-ts/server/src/utils.rs +33 -0
  51. package/templates/rust-ts/titan/bundle.js +157 -0
  52. package/templates/rust-ts/titan/dev.js +402 -0
  53. package/templates/rust-ts/titan/titan.js +122 -0
  54. package/templates/rust-ts/tsconfig.json +21 -0
  55. package/templates/ts/Dockerfile +66 -0
  56. package/templates/ts/_dockerignore +3 -0
  57. package/templates/ts/_gitignore +38 -0
  58. package/templates/ts/app/actions/hello.ts +9 -0
  59. package/templates/ts/app/app.ts +10 -0
  60. package/templates/ts/app/titan.d.ts +102 -0
  61. package/templates/ts/package.json +26 -0
  62. package/templates/ts/server/Cargo.lock +2869 -0
  63. package/templates/ts/server/Cargo.toml +27 -0
  64. package/templates/ts/server/src/action_management.rs +131 -0
  65. package/templates/ts/server/src/errors.rs +51 -0
  66. package/templates/ts/server/src/extensions.rs +989 -0
  67. package/templates/ts/server/src/main.rs +437 -0
  68. package/templates/ts/server/src/utils.rs +33 -0
  69. package/templates/ts/titan/bundle.js +78 -0
  70. package/templates/ts/titan/dev.js +402 -0
  71. package/templates/ts/titan/titan.js +122 -0
  72. package/templates/ts/tsconfig.json +16 -0
  73. package/titanpl-sdk/README.md +109 -109
  74. package/titanpl-sdk/bin/run.js +254 -254
  75. package/titanpl-sdk/index.d.ts +46 -46
  76. package/titanpl-sdk/index.js +5 -5
  77. package/titanpl-sdk/package.json +32 -32
  78. package/titanpl-sdk/templates/.dockerignore +3 -3
  79. package/titanpl-sdk/templates/Dockerfile +53 -53
  80. package/titanpl-sdk/templates/app/actions/hello.js +5 -5
  81. package/titanpl-sdk/templates/app/titan.d.ts +87 -87
  82. package/titanpl-sdk/templates/jsconfig.json +18 -18
  83. package/titanpl-sdk/templates/server/src/action_management.rs +131 -131
  84. package/titanpl-sdk/templates/server/src/errors.rs +10 -10
  85. package/titanpl-sdk/templates/server/src/extensions.rs +640 -640
  86. package/titanpl-sdk/templates/server/src/utils.rs +33 -33
  87. package/titanpl-sdk/templates/titan/bundle.js +65 -65
  88. package/titanpl-sdk/templates/titan/dev.js +113 -113
  89. package/titanpl-sdk/templates/titan/titan.js +98 -98
  90. package/templates/js/server/action_map.json +0 -3
  91. package/templates/js/server/actions/hello.jsbundle +0 -48
  92. package/templates/js/server/routes.json +0 -16
  93. package/templates/rust/server/action_map.json +0 -3
  94. package/templates/rust/server/actions/hello.jsbundle +0 -47
  95. package/templates/rust/server/routes.json +0 -22
  96. package/templates/rust/server/src/actions_rust/mod.rs +0 -19
  97. package/templates/rust/server/src/actions_rust/rust_hello.rs +0 -14
@@ -0,0 +1,402 @@
1
+ import chokidar from "chokidar";
2
+ import { spawn, execSync } from "child_process";
3
+ import path from "path";
4
+ import { fileURLToPath } from "url";
5
+ import fs from "fs";
6
+ import esbuild from "esbuild";
7
+ import { createRequire } from "module";
8
+
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
+ const cyan = (t) => `\x1b[36m${t}\x1b[0m`;
16
+ const green = (t) => `\x1b[32m${t}\x1b[0m`;
17
+ const yellow = (t) => `\x1b[33m${t}\x1b[0m`;
18
+ const red = (t) => `\x1b[31m${t}\x1b[0m`;
19
+ const gray = (t) => `\x1b[90m${t}\x1b[0m`;
20
+ const bold = (t) => `\x1b[1m${t}\x1b[0m`;
21
+
22
+ function getTitanVersion() {
23
+ try {
24
+ const require = createRequire(import.meta.url);
25
+ const pkgPath = require.resolve("@ezetgalaxy/titan/package.json");
26
+ return JSON.parse(fs.readFileSync(pkgPath, "utf-8")).version;
27
+ } 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) { }
47
+ }
48
+ return "0.1.0";
49
+ }
50
+
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();
73
+ }
74
+
75
+ try {
76
+ await killPromise;
77
+ } 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
+
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
+ }
111
+ }
112
+
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
+
134
+ // If it takes more than 30s, update the message
135
+ const slowTimer = setTimeout(() => {
136
+ if (!isReady && !isKilling) {
137
+ startSpinner("Still stabilizing... (the first orbit takes longer)");
138
+ }
139
+ }, 30000);
140
+
141
+ serverProcess = spawn("cargo", ["run", "--quiet"], {
142
+ cwd: serverPath,
143
+ stdio: ["ignore", "pipe", "pipe"],
144
+ env: { ...process.env, CARGO_INCREMENTAL: "1" }
145
+ });
146
+
147
+ serverProcess.on("error", (err) => {
148
+ stopSpinner(false, "Failed to start orbit");
149
+ console.error(red(`[Titan] Error: ${err.message}`));
150
+ });
151
+
152
+ serverProcess.stderr.on("data", (data) => {
153
+ const str = data.toString();
154
+ if (isReady) {
155
+ process.stderr.write(data);
156
+ } else {
157
+ buildLogs += str;
158
+ }
159
+ });
160
+
161
+ serverProcess.stdout.on("data", (data) => {
162
+ const out = data.toString();
163
+
164
+ if (!isReady) {
165
+ stdoutBuffer += out;
166
+ if (stdoutBuffer.includes("Titan server running") || stdoutBuffer.includes("████████╗")) {
167
+ isReady = true;
168
+ clearTimeout(slowTimer);
169
+ stopSpinner(true, "Your app is now orbiting Titan Planet");
170
+
171
+ if (isFirstBoot) {
172
+ process.stdout.write(stdoutBuffer);
173
+ isFirstBoot = false;
174
+ } else {
175
+ // On subsequent reloads, only print non-banner lines from the buffer
176
+ const lines = stdoutBuffer.split("\n");
177
+ for (const line of lines) {
178
+ const isBanner = line.includes("Titan server running") ||
179
+ line.includes("████████╗") ||
180
+ line.includes("╚══") ||
181
+ line.includes(" ██║") ||
182
+ line.includes(" ╚═╝");
183
+ if (!isBanner && line.trim()) {
184
+ process.stdout.write(line + "\n");
185
+ }
186
+ }
187
+ }
188
+ stdoutBuffer = "";
189
+ }
190
+ } else {
191
+ process.stdout.write(data);
192
+ }
193
+ });
194
+
195
+ serverProcess.on("close", async (code) => {
196
+ clearTimeout(slowTimer);
197
+ if (isKilling) return;
198
+ const runTime = Date.now() - startTime;
199
+
200
+ if (code !== 0 && code !== null) {
201
+ stopSpinner(false, "Orbit stabilization failed");
202
+ if (!isReady) {
203
+ console.log(gray("\n--- Build Logs ---"));
204
+ console.log(buildLogs);
205
+ console.log(gray("------------------\n"));
206
+ }
207
+
208
+ if (runTime < 15000 && retryCount < 5) {
209
+ await delay(2000);
210
+ await startRustServer(retryCount + 1);
211
+ }
212
+ }
213
+ });
214
+ }
215
+
216
+ async function rebuild() {
217
+ if (isTs && !isTsHealthy) return; // Don't rebuild if TS is broken
218
+
219
+ try {
220
+ const root = process.cwd();
221
+ const appTs = path.join(root, "app", "app.ts");
222
+ const dotTitan = path.join(root, ".titan");
223
+ const compiledApp = path.join(dotTitan, "app.js");
224
+
225
+ if (fs.existsSync(appTs)) {
226
+ if (!fs.existsSync(dotTitan)) fs.mkdirSync(dotTitan, { recursive: true });
227
+
228
+ await esbuild.build({
229
+ entryPoints: [appTs],
230
+ outfile: compiledApp,
231
+ bundle: true,
232
+ platform: "node",
233
+ format: "esm",
234
+ packages: "external",
235
+ logLevel: "silent"
236
+ });
237
+
238
+ execSync(`node "${compiledApp}"`, { stdio: "ignore" });
239
+ } else {
240
+ execSync("node app/app.js", { stdio: "ignore" });
241
+ }
242
+ } catch (e) {
243
+ stopSpinner(false, "Failed to prepare runtime");
244
+ console.log(red(`[Titan] Error: ${e.message}`));
245
+ }
246
+ }
247
+
248
+ let tsProcess = null;
249
+ let isTsHealthy = false; // STRICT: Assume unhealthy until checked
250
+
251
+ function startTypeChecker() {
252
+ const root = process.cwd();
253
+ if (!fs.existsSync(path.join(root, "tsconfig.json"))) return;
254
+
255
+ let tscPath;
256
+ try {
257
+ const require = createRequire(import.meta.url);
258
+ tscPath = require.resolve("typescript/bin/tsc");
259
+ } catch (e) {
260
+ tscPath = path.join(root, "node_modules", "typescript", "bin", "tsc");
261
+ }
262
+
263
+ if (!fs.existsSync(tscPath)) {
264
+ return;
265
+ }
266
+
267
+ const args = [tscPath, "--noEmit", "--watch", "--preserveWatchOutput", "--pretty"];
268
+
269
+ tsProcess = spawn(process.execPath, args, {
270
+ cwd: root,
271
+ stdio: "pipe",
272
+ shell: false
273
+ });
274
+
275
+ tsProcess.stdout.on("data", (data) => {
276
+ const lines = data.toString().split("\n");
277
+ for (const line of lines) {
278
+ if (line.trim().includes("File change detected") || line.trim().includes("Starting compilation")) {
279
+ isTsHealthy = false;
280
+ continue;
281
+ }
282
+ if (line.includes("Found 0 errors")) {
283
+ isTsHealthy = true;
284
+ // TS is happy, so we rebuild and restart (or start) the server
285
+ rebuild().then(startRustServer);
286
+
287
+ } else if (line.includes("error TS")) {
288
+ isTsHealthy = false;
289
+ if (serverProcess) {
290
+ console.log(red(`[Titan] TypeScript error detected. Stopping server...`));
291
+ killServer();
292
+ }
293
+ process.stdout.write(line + "\n");
294
+ } else if (line.match(/Found [1-9]\d* error/)) {
295
+ isTsHealthy = false;
296
+ if (serverProcess) {
297
+ console.log(red(`[Titan] TypeScript compilation failed. Stopping server...`));
298
+ killServer();
299
+ }
300
+ process.stdout.write(line + "\n");
301
+ } else if (line.trim()) {
302
+ process.stdout.write(gray(`[TS] ${line}\n`));
303
+ }
304
+ }
305
+ });
306
+
307
+ tsProcess.stderr.on("data", (data) => {
308
+ process.stdout.write(data);
309
+ });
310
+ }
311
+
312
+ let isTs = false;
313
+
314
+ async function startDev() {
315
+ const root = process.cwd();
316
+ const actionsDir = path.join(root, "app", "actions");
317
+ let hasRust = false;
318
+ if (fs.existsSync(actionsDir)) {
319
+ hasRust = fs.readdirSync(actionsDir).some(f => f.endsWith(".rs"));
320
+ }
321
+
322
+ isTs = fs.existsSync(path.join(root, "tsconfig.json")) ||
323
+ fs.existsSync(path.join(root, "app", "app.ts"));
324
+
325
+ let mode = "";
326
+ if (hasRust) {
327
+ mode = isTs ? "Rust + TS Actions" : "Rust + JS Actions";
328
+ } else {
329
+ mode = isTs ? "TS Actions" : "JS Actions";
330
+ }
331
+ const version = getTitanVersion();
332
+
333
+ console.clear();
334
+ console.log("");
335
+ console.log(` ${bold(cyan("Titan Planet"))} ${gray("v" + version)} ${yellow("[ Dev Mode ]")}`);
336
+ console.log("");
337
+ console.log(` ${gray("Type: ")} ${mode}`);
338
+ console.log(` ${gray("Hot Reload: ")} ${green("Enabled")}`);
339
+
340
+ if (fs.existsSync(path.join(root, ".env"))) {
341
+ console.log(` ${gray("Env: ")} ${yellow("Loaded")}`);
342
+ }
343
+ console.log("");
344
+
345
+ if (isTs) {
346
+ startTypeChecker();
347
+ } else {
348
+ // If no TS, start immediately
349
+ try {
350
+ await rebuild();
351
+ await startRustServer();
352
+ } catch (e) {
353
+ }
354
+ }
355
+
356
+ const watcher = chokidar.watch(["app", ".env"], {
357
+ ignoreInitial: true,
358
+ awaitWriteFinish: { stabilityThreshold: 500, pollInterval: 100 }
359
+ });
360
+
361
+ let timer = null;
362
+ watcher.on("all", async (event, file) => {
363
+ if (timer) clearTimeout(timer);
364
+ timer = setTimeout(async () => {
365
+ // If TS, we rely on TCS to trigger the rebuild (via Found 0 errors)
366
+ // We verify path safety using absolute/relative calculations
367
+ const relPath = path.relative(root, file);
368
+ if (isTs && (relPath.startsWith("app") || relPath.startsWith("app" + path.sep))) return;
369
+
370
+ // If TS is broken, rebuild() checks will prevent update, keeping server dead
371
+ // If TS is healthy, we proceed
372
+ if (isTs && !isTsHealthy) return;
373
+
374
+ try {
375
+ await killServer();
376
+ await rebuild();
377
+ await startRustServer();
378
+ } catch (e) {
379
+ // console.log(red("[Titan] Build failed -- waiting for changes..."));
380
+ }
381
+ }, 300);
382
+ });
383
+ }
384
+
385
+ async function handleExit() {
386
+ stopSpinner();
387
+ console.log(gray("\n[Titan] Stopping server..."));
388
+ await killServer();
389
+ if (tsProcess) {
390
+ if (process.platform === "win32") {
391
+ try { execSync(`taskkill /pid ${tsProcess.pid} /f /t`, { stdio: 'ignore' }); } catch (e) { }
392
+ } else {
393
+ tsProcess.kill();
394
+ }
395
+ }
396
+ process.exit(0);
397
+ }
398
+
399
+ process.on("SIGINT", handleExit);
400
+ process.on("SIGTERM", handleExit);
401
+
402
+ startDev();
@@ -0,0 +1,122 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ import { bundle } from "./bundle.js";
4
+
5
+ const cyan = (t) => `\x1b[36m${t}\x1b[0m`;
6
+ const green = (t) => `\x1b[32m${t}\x1b[0m`;
7
+
8
+ const routes = {};
9
+ const dynamicRoutes = {};
10
+ const actionMap = {};
11
+
12
+ function addRoute(method, route) {
13
+ const key = `${method.toUpperCase()}:${route}`;
14
+
15
+
16
+ return {
17
+ reply(value) {
18
+ routes[key] = {
19
+ type: typeof value === "object" ? "json" : "text",
20
+ value
21
+ };
22
+ },
23
+
24
+ action(name) {
25
+ if (route.includes(":")) {
26
+ if (!dynamicRoutes[method]) dynamicRoutes[method] = [];
27
+ dynamicRoutes[method].push({
28
+ method: method.toUpperCase(),
29
+ pattern: route,
30
+ action: name
31
+ });
32
+ } else {
33
+ routes[key] = {
34
+ type: "action",
35
+ value: name
36
+ };
37
+ actionMap[key] = name;
38
+ }
39
+ }
40
+ };
41
+ }
42
+
43
+ /**
44
+ * @typedef {Object} RouteHandler
45
+ * @property {(value: any) => void} reply - Send a direct response
46
+ * @property {(name: string) => void} action - Bind to a server-side action
47
+ */
48
+
49
+ /**
50
+ * Titan App Builder
51
+ */
52
+ const t = {
53
+ /**
54
+ * Define a GET route
55
+ * @param {string} route
56
+ * @returns {RouteHandler}
57
+ */
58
+ get(route) {
59
+ return addRoute("GET", route);
60
+ },
61
+
62
+ /**
63
+ * Define a POST route
64
+ * @param {string} route
65
+ * @returns {RouteHandler}
66
+ */
67
+ post(route) {
68
+ return addRoute("POST", route);
69
+ },
70
+
71
+ log(module, msg) {
72
+ console.log(`[\x1b[35m${module}\x1b[0m] ${msg}`);
73
+ },
74
+
75
+ /**
76
+ * Start the Titan Server
77
+ * @param {number} [port=3000]
78
+ * @param {string} [msg=""]
79
+ */
80
+ async start(port = 3000, msg = "") {
81
+ try {
82
+ console.log(cyan("[Titan] Preparing runtime..."));
83
+ await bundle();
84
+
85
+ const base = path.join(process.cwd(), "server");
86
+ if (!fs.existsSync(base)) {
87
+ fs.mkdirSync(base, { recursive: true });
88
+ }
89
+
90
+ const routesPath = path.join(base, "routes.json");
91
+ const actionMapPath = path.join(base, "action_map.json");
92
+
93
+ fs.writeFileSync(
94
+ routesPath,
95
+ JSON.stringify(
96
+ {
97
+ __config: { port },
98
+ routes,
99
+ __dynamic_routes: Object.values(dynamicRoutes).flat()
100
+ },
101
+ null,
102
+ 2
103
+ )
104
+ );
105
+
106
+ fs.writeFileSync(
107
+ actionMapPath,
108
+ JSON.stringify(actionMap, null, 2)
109
+ );
110
+
111
+ console.log(green("✔ Titan metadata written successfully"));
112
+ if (msg) console.log(cyan(msg));
113
+
114
+ } catch (e) {
115
+ console.error(`\x1b[31m[Titan] Build Error: ${e.message}\x1b[0m`);
116
+ process.exit(1);
117
+ }
118
+ }
119
+ };
120
+
121
+
122
+ export default t;
@@ -0,0 +1,21 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ESNext",
4
+ "module": "ESNext",
5
+ "moduleResolution": "node",
6
+ "esModuleInterop": true,
7
+ "forceConsistentCasingInFileNames": true,
8
+ "strict": true,
9
+ "skipLibCheck": true,
10
+ "baseUrl": ".",
11
+ "paths": {
12
+ "*": [
13
+ "*"
14
+ ]
15
+ }
16
+ },
17
+ "include": [
18
+ "app/**/*",
19
+ "titan/**/*"
20
+ ]
21
+ }
@@ -0,0 +1,66 @@
1
+ # ================================================================
2
+ # STAGE 1 — Build Titan (JS → Rust)
3
+ # ================================================================
4
+ FROM rust:1.91.1 AS builder
5
+
6
+ # Install Node for Titan CLI + bundler
7
+ RUN curl -fsSL https://deb.nodesource.com/setup_20.x | bash - \
8
+ && apt-get install -y nodejs
9
+
10
+ # Install Titan CLI (latest)
11
+ RUN npm install -g @ezetgalaxy/titan@latest
12
+
13
+ WORKDIR /app
14
+
15
+ # Copy project files
16
+ COPY . .
17
+
18
+ # Install JS dependencies (needed for Titan DSL + bundler)
19
+ RUN npm install
20
+
21
+ SHELL ["/bin/bash", "-c"]
22
+
23
+ # Extract Titan extensions into .ext
24
+ RUN mkdir -p /app/.ext && \
25
+ find /app/node_modules -maxdepth 5 -type f -name "titan.json" -print0 | \
26
+ while IFS= read -r -d '' file; do \
27
+ pkg_dir="$(dirname "$file")"; \
28
+ pkg_name="$(basename "$pkg_dir")"; \
29
+ echo "Copying Titan extension: $pkg_name from $pkg_dir"; \
30
+ cp -r "$pkg_dir" "/app/.ext/$pkg_name"; \
31
+ done && \
32
+ echo "Extensions in .ext:" && \
33
+ ls -R /app/.ext
34
+
35
+ # Build Titan metadata + bundle JS actions
36
+ RUN titan build
37
+
38
+ # Build Rust binary
39
+ RUN cd server && cargo build --release
40
+
41
+
42
+
43
+ # ================================================================
44
+ # STAGE 2 — Runtime Image (Lightweight)
45
+ # ================================================================
46
+ FROM debian:stable-slim
47
+
48
+ WORKDIR /app
49
+
50
+ # Copy Rust binary from builder stage
51
+ COPY --from=builder /app/server/target/release/titan-server ./titan-server
52
+
53
+ # Copy Titan routing metadata
54
+ COPY --from=builder /app/server/routes.json ./routes.json
55
+ COPY --from=builder /app/server/action_map.json ./action_map.json
56
+
57
+ # Copy Titan JS bundles
58
+ RUN mkdir -p /app/actions
59
+ COPY --from=builder /app/server/actions /app/actions
60
+
61
+ # Copy only Titan extensions
62
+ COPY --from=builder /app/.ext ./.ext
63
+
64
+ EXPOSE 3000
65
+
66
+ CMD ["./titan-server"]
@@ -0,0 +1,3 @@
1
+ node_modules
2
+ npm-debug.log
3
+ .git
@@ -0,0 +1,38 @@
1
+ # Node & Packages
2
+ node_modules/
3
+ npm-debug.log*
4
+ yarn-debug.log*
5
+ yarn-error.log*
6
+ package-lock.json
7
+ yarn.lock
8
+
9
+ # Titan Runtime (Auto-generated - DO NOT COMMIT)
10
+ titan/server-bin*
11
+ .titan/
12
+ .ext/
13
+ server/routes.json
14
+ server/action_map.json
15
+ server/actions/
16
+ server/titan/
17
+ server/src/actions_rust/
18
+
19
+ # Rust Build Artifacts
20
+ server/target/
21
+ Cargo.lock
22
+
23
+ # OS Files
24
+ .DS_Store
25
+ Thumbs.db
26
+ *.tmp
27
+ *.bak
28
+
29
+ # Environment & Secrets
30
+ .env
31
+ .env.local
32
+ .env.*.local
33
+
34
+ # IDEs
35
+ .vscode/
36
+ .idea/
37
+ *.swp
38
+ *.swo
@@ -0,0 +1,9 @@
1
+ interface HelloResponse {
2
+ message: string;
3
+ }
4
+
5
+ export const hello = defineAction((req): HelloResponse => {
6
+ return {
7
+ message: `Hello from Titan ${req.body.name || "World"}`,
8
+ };
9
+ });
@@ -0,0 +1,10 @@
1
+ import t from "../titan/titan.js";
2
+
3
+
4
+
5
+
6
+ t.post("/hello").action("hello") // pass a json payload { "name": "titan" }
7
+
8
+ t.get("/").reply("Ready to land on Titan Planet 🚀");
9
+
10
+ t.start(3000, "Titan Running!");