@ezetgalaxy/titan 26.9.0 → 26.9.2

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 (54) hide show
  1. package/README.md +39 -17
  2. package/index.js +227 -120
  3. package/package.json +17 -5
  4. package/templates/{js → common}/app/titan.d.ts +1 -1
  5. package/templates/{rust → rust-js}/package.json +1 -1
  6. package/templates/rust-ts/app/actions/hello.ts +10 -4
  7. package/templates/rust-ts/app/app.ts +1 -1
  8. package/templates/rust-ts/titan/bundle.js +15 -9
  9. package/templates/rust-ts/titan/dev.js +2 -2
  10. package/templates/rust-ts/titan/runtime.d.ts +1 -0
  11. package/templates/rust-ts/titan/runtime.js +1 -0
  12. package/templates/rust-ts/titan/titan.d.ts +117 -0
  13. package/templates/rust-ts/titan/titan.js +95 -95
  14. package/templates/ts/app/actions/hello.ts +2 -0
  15. package/templates/ts/app/app.ts +1 -1
  16. package/templates/ts/titan/builder.js +121 -0
  17. package/templates/ts/titan/bundle.js +9 -11
  18. package/templates/ts/titan/dev.js +2 -2
  19. package/templates/ts/titan/runtime.d.ts +1 -0
  20. package/templates/ts/titan/runtime.js +1 -0
  21. package/templates/ts/titan/titan.d.ts +117 -0
  22. package/templates/ts/titan/titan.js +95 -95
  23. package/titanpl-sdk/README.md +4 -4
  24. package/titanpl-sdk/bin/run.js +74 -77
  25. package/titanpl-sdk/package.json +1 -1
  26. package/templates/rust/Dockerfile +0 -66
  27. package/templates/rust/_dockerignore +0 -3
  28. package/templates/rust/_gitignore +0 -38
  29. package/templates/rust/app/titan.d.ts +0 -101
  30. package/templates/rust-ts/Dockerfile +0 -66
  31. package/templates/rust-ts/_dockerignore +0 -3
  32. package/templates/rust-ts/_gitignore +0 -38
  33. package/templates/rust-ts/app/titan.d.ts +0 -101
  34. package/templates/ts/Dockerfile +0 -66
  35. package/templates/ts/_dockerignore +0 -3
  36. package/templates/ts/_gitignore +0 -38
  37. package/templates/ts/app/titan.d.ts +0 -102
  38. /package/templates/{js → common}/Dockerfile +0 -0
  39. /package/templates/{js → common}/_dockerignore +0 -0
  40. /package/templates/{js → common}/_gitignore +0 -0
  41. /package/templates/{rust → rust-js}/app/actions/hello.js +0 -0
  42. /package/templates/{rust → rust-js}/app/actions/rust_hello.rs +0 -0
  43. /package/templates/{rust → rust-js}/app/app.js +0 -0
  44. /package/templates/{rust → rust-js}/jsconfig.json +0 -0
  45. /package/templates/{rust → rust-js}/server/Cargo.lock +0 -0
  46. /package/templates/{rust → rust-js}/server/Cargo.toml +0 -0
  47. /package/templates/{rust → rust-js}/server/src/action_management.rs +0 -0
  48. /package/templates/{rust → rust-js}/server/src/errors.rs +0 -0
  49. /package/templates/{rust → rust-js}/server/src/extensions.rs +0 -0
  50. /package/templates/{rust → rust-js}/server/src/main.rs +0 -0
  51. /package/templates/{rust → rust-js}/server/src/utils.rs +0 -0
  52. /package/templates/{rust → rust-js}/titan/bundle.js +0 -0
  53. /package/templates/{rust → rust-js}/titan/dev.js +0 -0
  54. /package/templates/{rust → rust-js}/titan/titan.js +0 -0
package/README.md CHANGED
@@ -44,6 +44,7 @@ Titan = **TS/JS productivity × Rust performance × Zero DevOps**
44
44
  | Zero-config Docker deploy | ✅ Yes | ❌ No | ❌ No | ❌ No |
45
45
  | Action-based architecture | ✅ Yes | ❌ No | ❌ No | ❌ No |
46
46
  | Hot reload dev server | ✅ Yes | ❌ No | ❌ No | ❌ No |
47
+ | Modular, Isolated Templates | ✅ Yes | ❌ No | ❌ No | ❌ No |
47
48
 
48
49
  ---
49
50
 
@@ -59,15 +60,26 @@ npm install -g @ezetgalaxy/titan
59
60
  ```
60
61
 
61
62
  ### 3. Initialize & Run
63
+ Titan guides you through selecting the perfect architecture for your needs.
64
+
62
65
  ```bash
63
66
  titan init my-app
64
- # Follow the interactive prompt to choose:
65
- # - JavaScript (Standard)
66
- # - TypeScript (Strict)
67
- # - Rust + JavaScript (Beta)
68
- # - Rust + TypeScript (Beta)
69
67
  ```
70
68
 
69
+ **Select your language:**
70
+ 1. `JavaScript` (Fast, lightweight)
71
+ 2. `TypeScript` (Strict, typed)
72
+
73
+ **Select your architecture:**
74
+ 1. `Standard` (Pure JS/TS)
75
+ 2. `Rust + JS/TS (Hybrid)` (High-performance native actions)
76
+
77
+ This creates one of four isolated environments:
78
+ * **Standard JS:** Lightweight server, zero Rust overhead.
79
+ * **Standard TS:** Strict server, zero Rust overhead.
80
+ * **Hybrid JS:** Full Rust integration + JS flexibility.
81
+ * **Hybrid TS:** Full Rust integration + TS strictness.
82
+
71
83
  Inside your project:
72
84
  ```bash
73
85
  cd my-app
@@ -76,7 +88,7 @@ titan dev
76
88
 
77
89
  You'll see the Titan Dev Server spin up:
78
90
  ```
79
- Titan Planet v26.9.0 [ Dev Mode ]
91
+ Titan Planet v26.9.1 [ Dev Mode ]
80
92
 
81
93
  Type: Rust + TS Actions
82
94
  Hot Reload: Enabled
@@ -93,34 +105,45 @@ You'll see the Titan Dev Server spin up:
93
105
 
94
106
  Titan is unique because it allows you to write endpoints in **JavaScript, TypeScript, and Rust** within the same project.
95
107
 
108
+ | Feature | Status | Notes |
109
+ | :--- | :--- | :--- |
110
+ | **Standard JavaScript** | ✅ Stable | Production Ready |
111
+ | **Standard TypeScript** | 🚧 Beta | **Ready for Dev**, Production Under Testing |
112
+ | **Rust + JS (Hybrid)** | 🧪 Experimental | **Dev Only**, Production Under Testing |
113
+ | **Rust + TS (Hybrid)** | 🧪 Experimental | **Dev Only**, Production Under Testing |
114
+
96
115
  ### 🔵 TypeScript Actions (`app/actions/hello.ts`)
97
116
  Fully typed, strict, and auto-compiled.
117
+
98
118
  ```typescript
99
- import { Context } from '@ezetgalaxy/titan';
119
+ import { defineAction } from "../../titan/titan";
100
120
 
101
- export function run(req: Context) {
121
+ interface HelloResponse {
122
+ message: string;
123
+ user_name: string;
124
+ }
125
+
126
+ // "defineAction" provides automatic type inference for "req"
127
+ export const hello = defineAction((req): HelloResponse => {
102
128
  t.log("Handling request with strict types...");
103
-
104
- // Type checking allows safe property access
105
- const user = req.body as { name: string };
106
-
129
+
107
130
  return {
108
131
  message: "Hello from TypeScript!",
109
- user_name: user.name
132
+ user_name: req.body.name || "Guest"
110
133
  };
111
- }
134
+ });
112
135
  ```
113
136
 
114
137
  ### 🟡 JavaScript Actions (`app/actions/hello.js`)
115
138
  Perfect for business logic, rapid prototyping, and IO-bound tasks.
116
139
  ```javascript
117
- export function run(req) {
140
+ export const hello = defineAction((req) => {
118
141
  t.log("Handling user request...");
119
142
  return {
120
143
  message: "Hello from JavaScript!",
121
144
  user_id: req.params.id
122
145
  };
123
- }
146
+ });
124
147
  ```
125
148
 
126
149
  ### 🔴 Rust Actions (Beta)
@@ -212,4 +235,3 @@ Titan is **not** a Node.js framework. It is a Rust server that speaks JavaScript
212
235
  * Strict TypeScript Support
213
236
  * Native Rust Performance
214
237
  * Zero-Config Cloud Deployment
215
-
package/index.js CHANGED
@@ -3,7 +3,7 @@ import prompts from "prompts";
3
3
  import fs from "fs";
4
4
  import path from "path";
5
5
  import { execSync, spawn } from "child_process";
6
- import { fileURLToPath } from "url";
6
+ import { fileURLToPath, pathToFileURL } from "url";
7
7
 
8
8
  /* Resolve __dirname for ES modules */
9
9
  const __filename = fileURLToPath(import.meta.url);
@@ -12,17 +12,17 @@ const __dirname = path.dirname(__filename);
12
12
  /* -------------------------------------------------------
13
13
  * Colors
14
14
  * ----------------------------------------------------- */
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 bold = (t) => `\x1b[1m${t}\x1b[0m`;
20
- const gray = (t) => `\x1b[90m${t}\x1b[0m`;
15
+ export const cyan = (t) => `\x1b[36m${t}\x1b[0m`;
16
+ export const green = (t) => `\x1b[32m${t}\x1b[0m`;
17
+ export const yellow = (t) => `\x1b[33m${t}\x1b[0m`;
18
+ export const red = (t) => `\x1b[31m${t}\x1b[0m`;
19
+ export const bold = (t) => `\x1b[1m${t}\x1b[0m`;
20
+ export const gray = (t) => `\x1b[90m${t}\x1b[0m`;
21
21
 
22
22
  /* -------------------------------------------------------
23
23
  * Invocation detection (tit vs titan)
24
24
  * ----------------------------------------------------- */
25
- function wasInvokedAsTit() {
25
+ export function wasInvokedAsTit() {
26
26
  const script = process.argv[1];
27
27
  if (script) {
28
28
  const base = path.basename(script, path.extname(script)).toLowerCase();
@@ -51,7 +51,6 @@ function wasInvokedAsTit() {
51
51
 
52
52
  return false;
53
53
  }
54
-
55
54
  const isTitAlias = wasInvokedAsTit();
56
55
 
57
56
  if (isTitAlias) {
@@ -63,24 +62,25 @@ if (isTitAlias) {
63
62
  );
64
63
  }
65
64
 
66
- /* -------------------------------------------------------
67
- * Args
68
- * ----------------------------------------------------- */
69
- const args = process.argv.slice(2);
70
- const cmd = args[0];
71
-
72
65
  /* -------------------------------------------------------
73
66
  * Titan version
74
67
  * ----------------------------------------------------- */
75
- const pkg = JSON.parse(
76
- fs.readFileSync(path.join(__dirname, "package.json"), "utf8")
77
- );
78
- const TITAN_VERSION = pkg.version;
68
+ let TITAN_VERSION = "0.1.0";
69
+ try {
70
+ const pkg = JSON.parse(
71
+ fs.readFileSync(path.join(__dirname, "package.json"), "utf8")
72
+ );
73
+ TITAN_VERSION = pkg.version;
74
+ } catch (e) {
75
+ // Use default version
76
+ }
77
+
78
+ export { TITAN_VERSION };
79
79
 
80
80
  /* -------------------------------------------------------
81
81
  * Utils
82
82
  * ----------------------------------------------------- */
83
- function copyDir(src, dest, excludes = []) {
83
+ export function copyDir(src, dest, excludes = []) {
84
84
  fs.mkdirSync(dest, { recursive: true });
85
85
 
86
86
  for (const file of fs.readdirSync(src)) {
@@ -103,26 +103,27 @@ function copyDir(src, dest, excludes = []) {
103
103
  /* -------------------------------------------------------
104
104
  * HELP
105
105
  * ----------------------------------------------------- */
106
- function help() {
106
+ export function help() {
107
107
  console.log(`
108
- ${bold(cyan("Titan Planet"))} v${TITAN_VERSION}
108
+ ${bold(cyan("Titan Planet"))} v${TITAN_VERSION}
109
109
 
110
- ${green("titan init <project>")} Create new Titan project
111
- ${green("titan create ext <name>")} Create new Titan extension
112
- ${green("titan dev")} Dev mode (hot reload)
113
- ${green("titan build")} Build production Rust server
114
- ${green("titan start")} Start production binary
115
- ${green("titan update")} Update Titan engine
116
- ${green("titan --version")} Show Titan CLI version
110
+ ${green("titan init <project> [-t <template>]")} Create new Titan project
111
+ ${green("titan create ext <name>")} Create new Titan extension
112
+ ${green("titan dev")} Dev mode (hot reload)
113
+ ${green("titan build")} Build production Rust server
114
+ ${green("titan start")} Start production binary
115
+ ${green("titan update")} Update Titan engine
116
+ ${green("titan --version")} Show Titan CLI version
117
117
 
118
- ${yellow("Note: `tit` is supported as a legacy alias.")}
118
+ ${yellow("Note: `tit` is supported as a legacy alias.")}
119
119
  `);
120
120
  }
121
121
 
122
122
  /* -------------------------------------------------------
123
123
  * INIT
124
124
  * ----------------------------------------------------- */
125
- async function initProject(name, templateName) {
125
+ export async function initProject(name, templateName) {
126
+ // console.log(`DEBUG: initProject name=${name}, templateName=${templateName}`);
126
127
  let projName = name;
127
128
 
128
129
  if (!projName) {
@@ -150,7 +151,7 @@ async function initProject(name, templateName) {
150
151
  message: 'Select language:',
151
152
  choices: [
152
153
  { title: 'JavaScript', value: 'js' },
153
- { title: 'TypeScript', value: 'ts' }
154
+ { title: 'TypeScript', value: 'ts' },
154
155
  ],
155
156
  initial: 0
156
157
  });
@@ -188,7 +189,7 @@ async function initProject(name, templateName) {
188
189
  const arch = archRes.value;
189
190
 
190
191
  if (lang === 'js') {
191
- selectedTemplate = arch === 'standard' ? 'js' : 'rust';
192
+ selectedTemplate = arch === 'standard' ? 'js' : 'rust-js';
192
193
  } else {
193
194
  selectedTemplate = arch === 'standard' ? 'ts' : 'rust-ts';
194
195
  }
@@ -196,11 +197,16 @@ async function initProject(name, templateName) {
196
197
 
197
198
  const target = path.join(process.cwd(), projName);
198
199
  const templateDir = path.join(__dirname, "templates", selectedTemplate);
200
+ const commonDir = path.join(__dirname, "templates", "common");
199
201
 
200
202
  if (!fs.existsSync(templateDir)) {
201
203
  console.log(red(`Template '${selectedTemplate}' not found.`));
202
204
  return;
203
205
  }
206
+ if (!fs.existsSync(commonDir)) {
207
+ console.log(red(`Common template folder not found.`));
208
+ return;
209
+ }
204
210
 
205
211
  if (fs.existsSync(target)) {
206
212
  console.log(yellow(`Folder already exists: ${target}`));
@@ -212,12 +218,17 @@ async function initProject(name, templateName) {
212
218
  console.log(gray(` Template: ${selectedTemplate}`));
213
219
 
214
220
  // ----------------------------------------------------------
215
- // 1. Copy full template directory
221
+ // 1. Copy full COMMON directory
222
+ // ----------------------------------------------------------
223
+ copyDir(commonDir, target, ["_gitignore", "_dockerignore"]);
224
+
225
+ // ----------------------------------------------------------
226
+ // 2. Copy full SELECTED template directory
216
227
  // ----------------------------------------------------------
217
228
  copyDir(templateDir, target, ["_gitignore", "_dockerignore"]);
218
229
 
219
230
  // ----------------------------------------------------------
220
- // 2. Explicitly install dotfiles
231
+ // 3. Explicitly install dotfiles from COMMON directory
221
232
  // ----------------------------------------------------------
222
233
  const dotfiles = {
223
234
  "_gitignore": ".gitignore",
@@ -225,7 +236,7 @@ async function initProject(name, templateName) {
225
236
  };
226
237
 
227
238
  for (const [srcName, destName] of Object.entries(dotfiles)) {
228
- const src = path.join(templateDir, srcName);
239
+ const src = path.join(commonDir, srcName);
229
240
  const dest = path.join(target, destName);
230
241
 
231
242
  if (fs.existsSync(src)) {
@@ -233,10 +244,18 @@ async function initProject(name, templateName) {
233
244
  }
234
245
  }
235
246
 
236
- // Dockerfile is safe as-is
237
- const dockerfileSrc = path.join(templateDir, "Dockerfile");
238
- if (fs.existsSync(dockerfileSrc)) {
239
- fs.copyFileSync(dockerfileSrc, path.join(target, "Dockerfile"));
247
+ const pkgPath = path.join(target, "package.json");
248
+
249
+ if (fs.existsSync(pkgPath)) {
250
+ try {
251
+ const pkgContent = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
252
+ if (!pkgContent.titan) pkgContent.titan = {};
253
+ pkgContent.titan.template = selectedTemplate;
254
+ fs.writeFileSync(pkgPath, JSON.stringify(pkgContent, null, 2));
255
+ console.log(gray(` Metadata set: ${selectedTemplate}`));
256
+ } catch (e) {
257
+ console.log(yellow("⚠ Could not write template metadata to package.json"));
258
+ }
240
259
  }
241
260
 
242
261
  console.log(green("✔ Project structure created"));
@@ -263,7 +282,7 @@ async function initProject(name, templateName) {
263
282
  /* -------------------------------------------------------
264
283
  * DEV SERVER
265
284
  * ----------------------------------------------------- */
266
- async function devServer() {
285
+ export async function devServer() {
267
286
  const root = process.cwd();
268
287
  const devScript = path.join(root, "titan", "dev.js");
269
288
 
@@ -289,25 +308,79 @@ async function devServer() {
289
308
  /* -------------------------------------------------------
290
309
  * BUILD
291
310
  * ----------------------------------------------------- */
292
- function buildProd() {
311
+ export async function buildProd() {
293
312
  console.log(cyan("Titan: Building production output..."));
294
313
 
295
314
  const root = process.cwd();
296
315
  const appJs = path.join(root, "app", "app.js");
316
+ const appTs = path.join(root, "app", "app.ts");
297
317
  const serverDir = path.join(root, "server");
298
318
  const actionsOut = path.join(serverDir, "actions");
299
319
 
300
320
  // BASIC CHECKS
301
- if (!fs.existsSync(appJs)) {
302
- console.log(red("ERROR: app/app.js not found."));
321
+ if (!fs.existsSync(appJs) && !fs.existsSync(appTs)) {
322
+ console.log(red("ERROR: app/app.js or app/app.ts not found."));
303
323
  process.exit(1);
304
324
  }
305
325
 
326
+ // COMPILE TYPESCRIPT IF NEEDED
327
+ if (fs.existsSync(path.join(root, "tsconfig.json"))) {
328
+ console.log(cyan("→ Compiling TypeScript..."));
329
+ try {
330
+ // We use esbuild for speed and consistency with dev mode
331
+ const { buildSync } = await import("esbuild");
332
+ buildSync({
333
+ entryPoints: [path.join(root, "app", "app.ts")],
334
+ outfile: appJs,
335
+ bundle: true,
336
+ platform: "node",
337
+ format: "esm",
338
+ external: ["fs", "path", "esbuild", "chokidar", "typescript"],
339
+ packages: "external",
340
+ });
341
+ console.log(green("✔ TypeScript compiled"));
342
+ } catch (e) {
343
+ console.log(red("ERROR: Failed to compile TypeScript."));
344
+ console.error(e);
345
+ process.exit(1);
346
+ }
347
+ }
348
+
306
349
  // ----------------------------------------------------
307
- // 1) BUILD METADATA + BUNDLE ACTIONS (ONE TIME ONLY)
350
+ // 1) BUILD METADATA + BUNDLE ACTIONS
308
351
  // ----------------------------------------------------
309
- console.log(cyan("→ Building Titan metadata + bundling actions..."));
310
- execSync("node app/app.js --build", { stdio: "inherit" });
352
+ console.log(cyan("→ Building Titan metadata..."));
353
+
354
+ // Si es TypeScript, compilar primero
355
+ if (fs.existsSync(appTs)) {
356
+ const dotTitan = path.join(root, ".titan");
357
+ const compiledApp = path.join(dotTitan, "app.js");
358
+
359
+ if (!fs.existsSync(dotTitan)) fs.mkdirSync(dotTitan, { recursive: true });
360
+
361
+ // Importar esbuild dinámicamente
362
+ const esbuild = await import("esbuild");
363
+ await esbuild.build({
364
+ entryPoints: [appTs],
365
+ outfile: compiledApp,
366
+ bundle: true,
367
+ platform: "node",
368
+ format: "esm",
369
+ packages: "external",
370
+ logLevel: "silent"
371
+ });
372
+
373
+ execSync(`node "${compiledApp}" --build`, { stdio: "inherit" });
374
+ } else {
375
+ execSync("node app/app.js --build", { stdio: "inherit" });
376
+ }
377
+
378
+ console.log(cyan("→ Bundling actions..."));
379
+ const bundlePath = path.join(root, "titan", "bundle.js");
380
+ // Convert Windows path to file:// URL for ESM import
381
+ const bundleUrl = pathToFileURL(bundlePath).href;
382
+ const { bundle } = await import(bundleUrl);
383
+ await bundle();
311
384
 
312
385
  // ensure actions directory exists
313
386
  fs.mkdirSync(actionsOut, { recursive: true });
@@ -315,9 +388,13 @@ function buildProd() {
315
388
  // verify bundled actions exist
316
389
  const bundles = fs.readdirSync(actionsOut).filter(f => f.endsWith(".jsbundle"));
317
390
  if (bundles.length === 0) {
318
- console.log(red("ERROR: No actions bundled."));
319
- console.log(red("Make sure your DSL outputs to server/actions."));
320
- process.exit(1);
391
+ const rustActionsDir = path.join(serverDir, "src", "actions_rust");
392
+ const hasRustActions = fs.existsSync(rustActionsDir) &&
393
+ fs.readdirSync(rustActionsDir).some(f => f.endsWith(".rs") && f !== "mod.rs");
394
+
395
+ if (!hasRustActions) {
396
+ console.log(yellow("⚠ Warning: No JS or Rust actions found."));
397
+ }
321
398
  }
322
399
 
323
400
  bundles.forEach(file => {
@@ -330,37 +407,54 @@ function buildProd() {
330
407
  // 2) BUILD RUST BINARY
331
408
  // ----------------------------------------------------
332
409
  console.log(cyan("→ Building Rust release binary..."));
333
- execSync("cargo build --release", {
334
- cwd: serverDir,
335
- stdio: "inherit"
336
- });
337
410
 
338
- console.log(green("✔ Titan production build complete!"));
411
+ // Only build rust if it's a rust project (check Cargo.toml)
412
+ if (fs.existsSync(path.join(serverDir, "Cargo.toml"))) {
413
+ execSync("cargo build --release", {
414
+ cwd: serverDir,
415
+ stdio: "inherit"
416
+ });
417
+ console.log(green("✔ Titan production build complete!"));
418
+ } else {
419
+ console.log(green("✔ Titan production build complete (pure JS/TS)!"));
420
+ }
339
421
  }
340
422
 
341
423
  /* -------------------------------------------------------
342
424
  * START
343
425
  * ----------------------------------------------------- */
344
- function startProd() {
426
+ export function startProd() {
345
427
  const isWin = process.platform === "win32";
346
428
  const bin = isWin ? "titan-server.exe" : "titan-server";
429
+ const root = process.cwd();
347
430
 
348
- const exe = path.join(process.cwd(), "server", "target", "release", bin);
349
- execSync(`"${exe}"`, { stdio: "inherit" });
431
+ const exe = path.join(root, "server", "target", "release", bin);
432
+
433
+ if (fs.existsSync(exe)) {
434
+ execSync(`"${exe}"`, { stdio: "inherit" });
435
+ } else {
436
+ // Fallback to pure node start if no rust binary
437
+ const appJs = path.join(root, "app", "app.js");
438
+ // Actually, typically we run the bundled/compiled app if we don't have rust server?
439
+ // But wait, the pure TS template runs `node .titan/app.js` in Docker.
440
+ // But locally `titan start` relies on `app/app.js` being compiled?
441
+ // In `buildProd` above we compiled to `app/app.js`.
442
+ // Let's check for `.titan/app.js` which is dev artifact? No, use the prod build artifact.
443
+ execSync(`node "${appJs}"`, { stdio: "inherit" });
444
+ }
350
445
  }
351
446
 
352
447
  /* -------------------------------------------------------
353
448
  * UPDATE
354
449
  * ----------------------------------------------------- */
355
-
356
- function updateTitan() {
450
+ export function updateTitan() {
357
451
  const root = process.cwd();
358
452
 
359
453
  const projectTitan = path.join(root, "titan");
360
454
  const projectServer = path.join(root, "server");
361
455
  const projectPkg = path.join(root, "package.json");
362
456
 
363
- let templateType = "js"; // Default
457
+ let templateType = "js";
364
458
  if (fs.existsSync(projectPkg)) {
365
459
  try {
366
460
  const pkg = JSON.parse(fs.readFileSync(projectPkg, "utf-8"));
@@ -379,10 +473,8 @@ function updateTitan() {
379
473
  return;
380
474
  }
381
475
 
382
- if (!fs.existsSync(templateServer)) {
383
- console.log(red(`CLI seems corrupted or incomplete.`));
384
- console.log(red(`Expected server template at: ${templateServer}`));
385
- console.log(yellow(`If you are running from npx, try clearing cache or installing a specific version.`));
476
+ if (!fs.existsSync(templatesRoot)) {
477
+ console.log(red(`Template type '${templateType}' not found in CLI templates.`));
386
478
  return;
387
479
  }
388
480
 
@@ -391,15 +483,18 @@ function updateTitan() {
391
483
  // ----------------------------------------------------------
392
484
  // 1. Update titan/ runtime (authoritative, safe to replace)
393
485
  // ----------------------------------------------------------
394
- fs.rmSync(projectTitan, {
395
- recursive: true,
396
- force: true,
397
- maxRetries: 10,
398
- retryDelay: 500,
399
- });
400
-
401
- copyDir(templateTitan, projectTitan);
402
- console.log(green("✔ Updated titan/ runtime"));
486
+ if (fs.existsSync(templateTitan)) {
487
+ fs.rmSync(projectTitan, {
488
+ recursive: true,
489
+ force: true,
490
+ maxRetries: 10,
491
+ retryDelay: 500,
492
+ });
493
+ copyDir(templateTitan, projectTitan);
494
+ console.log(green("✔ Updated titan/ runtime"));
495
+ } else {
496
+ console.log(yellow(`⚠ No titan/ folder found in template '${templateType}', skipping.`));
497
+ }
403
498
 
404
499
  // ----------------------------------------------------------
405
500
  // 2. Update server/ WITHOUT deleting the folder
@@ -421,18 +516,19 @@ function updateTitan() {
421
516
  const projectSrc = path.join(projectServer, "src");
422
517
  const templateSrc = path.join(templateServer, "src");
423
518
 
424
- if (fs.existsSync(projectSrc)) {
425
- fs.rmSync(projectSrc, {
426
- recursive: true,
427
- force: true,
428
- maxRetries: 10,
429
- retryDelay: 500,
430
- });
519
+ if (fs.existsSync(templateSrc)) {
520
+ if (fs.existsSync(projectSrc)) {
521
+ fs.rmSync(projectSrc, {
522
+ recursive: true,
523
+ force: true,
524
+ maxRetries: 10,
525
+ retryDelay: 500,
526
+ });
527
+ }
528
+ copyDir(templateSrc, projectSrc);
529
+ console.log(green("✔ Updated server/src/"));
431
530
  }
432
531
 
433
- copyDir(templateSrc, projectSrc);
434
- console.log(green("✔ Updated server/src/"));
435
-
436
532
  // Root-level config files
437
533
  const rootFiles = {
438
534
  "_gitignore": ".gitignore",
@@ -453,15 +549,16 @@ function updateTitan() {
453
549
 
454
550
  // app/titan.d.ts (JS typing contract)
455
551
  const appDir = path.join(root, "app");
456
- const srcDts = path.join(templateServer, "../app/titan.d.ts"); // templates/app/titan.d.ts
552
+ const srcDts = path.join(templateServer, "../app/titan.d.ts");
553
+ const fallbackDts = path.join(templatesRoot, "app", "titan.d.ts");
554
+ const finalDtsSrc = fs.existsSync(srcDts) ? srcDts : (fs.existsSync(fallbackDts) ? fallbackDts : null);
457
555
  const destDts = path.join(appDir, "titan.d.ts");
458
556
 
459
- if (fs.existsSync(srcDts)) {
557
+ if (finalDtsSrc) {
460
558
  if (!fs.existsSync(appDir)) {
461
559
  fs.mkdirSync(appDir);
462
560
  }
463
-
464
- fs.copyFileSync(srcDts, destDts);
561
+ fs.copyFileSync(finalDtsSrc, destDts);
465
562
  console.log(green("✔ Updated app/titan.d.ts"));
466
563
  }
467
564
 
@@ -474,7 +571,7 @@ function updateTitan() {
474
571
  /* -------------------------------------------------------
475
572
  * CREATE EXTENSION
476
573
  * ----------------------------------------------------- */
477
- function createExtension(name) {
574
+ export function createExtension(name) {
478
575
  if (!name) {
479
576
  console.log(red("Usage: titan create ext <name>"));
480
577
  return;
@@ -543,7 +640,7 @@ Next steps:
543
640
  `);
544
641
  }
545
642
 
546
- function runExtension() {
643
+ export function runExtension() {
547
644
  const localSdk = path.join(__dirname, "titanpl-sdk", "bin", "run.js");
548
645
 
549
646
  if (fs.existsSync(localSdk)) {
@@ -566,35 +663,45 @@ function runExtension() {
566
663
  /* -------------------------------------------------------
567
664
  * ROUTER
568
665
  * ----------------------------------------------------- */
569
- // "titan create ext <name>" -> args = ["create", "ext", "calc_ext"]
570
- if (cmd === "create" && args[1] === "ext") {
571
- createExtension(args[2]);
572
- } else if (cmd === "run" && args[1] === "ext") {
573
- runExtension();
574
- } else {
575
- switch (cmd) {
576
- case "init": {
577
- const projName = args[1];
578
- let tpl = null;
579
-
580
- const tIndex = args.indexOf("--template") > -1 ? args.indexOf("--template") : args.indexOf("-t");
581
- if (tIndex > -1 && args[tIndex + 1]) {
582
- tpl = args[tIndex + 1];
666
+ const isMainModule = process.argv[1] === fileURLToPath(import.meta.url);
667
+
668
+ if (isMainModule) {
669
+ const args = process.argv.slice(2);
670
+ // console.log("DEBUG: args", args);
671
+ const cmd = args[0];
672
+
673
+ (async () => {
674
+ // "titan create ext <name>" -> args = ["create", "ext", "calc_ext"]
675
+ if (cmd === "create" && args[1] === "ext") {
676
+ createExtension(args[2]);
677
+ } else if (cmd === "run" && args[1] === "ext") {
678
+ runExtension();
679
+ } else {
680
+ switch (cmd) {
681
+ case "init": {
682
+ const projName = args[1];
683
+ let tpl = null;
684
+
685
+ const tIndex = args.indexOf("--template") > -1 ? args.indexOf("--template") : args.indexOf("-t");
686
+ if (tIndex > -1 && args[tIndex + 1]) {
687
+ tpl = args[tIndex + 1];
688
+ }
689
+
690
+ await initProject(projName, tpl);
691
+ break;
692
+ }
693
+ case "dev": devServer(); break;
694
+ case "build": await buildProd(); break;
695
+ case "start": startProd(); break;
696
+ case "update": updateTitan(); break;
697
+ case "--version":
698
+ case "-v":
699
+ case "version":
700
+ console.log(cyan(`Titan v${TITAN_VERSION}`));
701
+ break;
702
+ default:
703
+ help();
583
704
  }
584
-
585
- initProject(projName, tpl);
586
- break;
587
705
  }
588
- case "dev": devServer(); break;
589
- case "build": buildProd(); break;
590
- case "start": startProd(); break;
591
- case "update": updateTitan(); break;
592
- case "--version":
593
- case "-v":
594
- case "version":
595
- console.log(cyan(`Titan v${TITAN_VERSION}`));
596
- break;
597
- default:
598
- help();
599
- }
600
- }
706
+ })();
707
+ }