@ezetgalaxy/titan 26.7.4 → 26.8.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 (55) hide show
  1. package/README.md +88 -201
  2. package/index.js +552 -489
  3. package/package.json +6 -5
  4. package/templates/js/_gitignore +37 -0
  5. package/templates/{package.json → js/package.json} +4 -1
  6. package/templates/js/server/action_map.json +3 -0
  7. package/templates/js/server/actions/hello.jsbundle +48 -0
  8. package/templates/js/server/routes.json +16 -0
  9. package/templates/js/server/src/actions_rust/mod.rs +15 -0
  10. package/templates/{server → js/server}/src/extensions.rs +149 -17
  11. package/templates/{titan → js/titan}/bundle.js +22 -9
  12. package/templates/js/titan/dev.js +194 -0
  13. package/templates/{titan → js/titan}/titan.js +25 -1
  14. package/templates/rust/Dockerfile +66 -0
  15. package/templates/rust/_dockerignore +3 -0
  16. package/templates/rust/_gitignore +37 -0
  17. package/templates/rust/app/actions/hello.js +5 -0
  18. package/templates/rust/app/actions/rust_hello.rs +14 -0
  19. package/templates/rust/app/app.js +11 -0
  20. package/templates/rust/app/titan.d.ts +101 -0
  21. package/templates/rust/jsconfig.json +19 -0
  22. package/templates/rust/package.json +13 -0
  23. package/templates/rust/server/Cargo.lock +2869 -0
  24. package/templates/rust/server/Cargo.toml +27 -0
  25. package/templates/rust/server/action_map.json +3 -0
  26. package/templates/rust/server/actions/hello.jsbundle +47 -0
  27. package/templates/rust/server/routes.json +22 -0
  28. package/templates/rust/server/src/action_management.rs +131 -0
  29. package/templates/rust/server/src/actions_rust/mod.rs +19 -0
  30. package/templates/rust/server/src/actions_rust/rust_hello.rs +14 -0
  31. package/templates/rust/server/src/errors.rs +10 -0
  32. package/templates/rust/server/src/extensions.rs +989 -0
  33. package/templates/rust/server/src/main.rs +443 -0
  34. package/templates/rust/server/src/utils.rs +33 -0
  35. package/templates/rust/titan/bundle.js +157 -0
  36. package/templates/rust/titan/dev.js +194 -0
  37. package/templates/rust/titan/titan.js +122 -0
  38. package/titanpl-sdk/package.json +1 -1
  39. package/titanpl-sdk/templates/Dockerfile +4 -17
  40. package/titanpl-sdk/templates/server/src/extensions.rs +218 -423
  41. package/titanpl-sdk/templates/server/src/main.rs +68 -134
  42. package/scripts/make_dist.sh +0 -71
  43. package/templates/titan/dev.js +0 -144
  44. /package/templates/{Dockerfile → js/Dockerfile} +0 -0
  45. /package/templates/{.dockerignore → js/_dockerignore} +0 -0
  46. /package/templates/{app → js/app}/actions/hello.js +0 -0
  47. /package/templates/{app → js/app}/app.js +0 -0
  48. /package/templates/{app → js/app}/titan.d.ts +0 -0
  49. /package/templates/{jsconfig.json → js/jsconfig.json} +0 -0
  50. /package/templates/{server → js/server}/Cargo.lock +0 -0
  51. /package/templates/{server → js/server}/Cargo.toml +0 -0
  52. /package/templates/{server → js/server}/src/action_management.rs +0 -0
  53. /package/templates/{server → js/server}/src/errors.rs +0 -0
  54. /package/templates/{server → js/server}/src/main.rs +0 -0
  55. /package/templates/{server → js/server}/src/utils.rs +0 -0
package/index.js CHANGED
@@ -1,490 +1,553 @@
1
1
  #!/usr/bin/env node
2
- import fs from "fs";
3
- import path from "path";
4
- import { execSync, spawn } from "child_process";
5
- import { fileURLToPath } from "url";
6
-
7
- /* Resolve __dirname for ES modules */
8
- const __filename = fileURLToPath(import.meta.url);
9
- const __dirname = path.dirname(__filename);
10
-
11
- /* -------------------------------------------------------
12
- * Colors
13
- * ----------------------------------------------------- */
14
- const cyan = (t) => `\x1b[36m${t}\x1b[0m`;
15
- const green = (t) => `\x1b[32m${t}\x1b[0m`;
16
- const yellow = (t) => `\x1b[33m${t}\x1b[0m`;
17
- const red = (t) => `\x1b[31m${t}\x1b[0m`;
18
- const bold = (t) => `\x1b[1m${t}\x1b[0m`;
19
-
20
- /* -------------------------------------------------------
21
- * Invocation detection (tit vs titan)
22
- * ----------------------------------------------------- */
23
- function wasInvokedAsTit() {
24
- const script = process.argv[1];
25
- if (script) {
26
- const base = path.basename(script, path.extname(script)).toLowerCase();
27
- if (base === "tit") return true;
28
- }
29
-
30
- try {
31
- const raw = process.env.npm_config_argv;
32
- if (raw) {
33
- const cfg = JSON.parse(raw);
34
- if (cfg.original && Array.isArray(cfg.original)) {
35
- // e.g. ["tit", "dev"]
36
- const first = cfg.original[0];
37
- if (first && first.includes("tit") && !first.includes("titan")) {
38
- return true;
39
- }
40
- }
41
- }
42
- } catch { }
43
-
44
- const lastCmd = process.env["_"];
45
- if (lastCmd) {
46
- const base = path.basename(lastCmd, path.extname(lastCmd)).toLowerCase();
47
- if (base === "tit") return true;
48
- }
49
-
50
- return false;
51
- }
52
-
53
- const isTitAlias = wasInvokedAsTit();
54
-
55
- if (isTitAlias) {
56
- console.log(
57
- yellow(
58
- "[Notice] `tit` is deprecated. Please use `titan` instead.\n" +
59
- " `tit` will continue to work for now."
60
- )
61
- );
62
- }
63
-
64
- /* -------------------------------------------------------
65
- * Args
66
- * ----------------------------------------------------- */
67
- const args = process.argv.slice(2);
68
- const cmd = args[0];
69
-
70
- /* -------------------------------------------------------
71
- * Titan version
72
- * ----------------------------------------------------- */
73
- const pkg = JSON.parse(
74
- fs.readFileSync(path.join(__dirname, "package.json"), "utf8")
75
- );
76
- const TITAN_VERSION = pkg.version;
77
-
78
- /* -------------------------------------------------------
79
- * Utils
80
- * ----------------------------------------------------- */
81
- function copyDir(src, dest, excludes = []) {
82
- fs.mkdirSync(dest, { recursive: true });
83
-
84
- for (const file of fs.readdirSync(src)) {
85
- // Skip excluded files/folders
86
- if (excludes.includes(file)) {
87
- continue;
88
- }
89
-
90
- const srcPath = path.join(src, file);
91
- const destPath = path.join(dest, file);
92
-
93
- if (fs.lstatSync(srcPath).isDirectory()) {
94
- copyDir(srcPath, destPath, excludes);
95
- } else {
96
- fs.copyFileSync(srcPath, destPath);
97
- }
98
- }
99
- }
100
-
101
- /* -------------------------------------------------------
102
- * HELP
103
- * ----------------------------------------------------- */
104
- function help() {
105
- console.log(`
106
- ${bold(cyan("Titan Planet"))} v${TITAN_VERSION}
107
-
108
- ${green("titan init <project>")} Create new Titan project
109
- ${green("titan create ext <name>")} Create new Titan extension
110
- ${green("titan dev")} Dev mode (hot reload)
111
- ${green("titan build")} Build production Rust server
112
- ${green("titan start")} Start production binary
113
- ${green("titan update")} Update Titan engine
114
- ${green("titan --version")} Show Titan CLI version
115
-
116
- ${yellow("Note: `tit` is supported as a legacy alias.")}
117
- `);
118
- }
119
-
120
- /* -------------------------------------------------------
121
- * INIT
122
- * ----------------------------------------------------- */
123
- function initProject(name) {
124
- if (!name) {
125
- console.log(red("Usage: titan init <project>"));
126
- return;
127
- }
128
-
129
- const target = path.join(process.cwd(), name);
130
- const templateDir = path.join(__dirname, "templates");
131
-
132
- if (fs.existsSync(target)) {
133
- console.log(yellow(`Folder already exists: ${target}`));
134
- return;
135
- }
136
-
137
- console.log(cyan(`Creating Titan project → ${target}`));
138
-
139
- // ----------------------------------------------------------
140
- // 1. Copy full template directory (excluding extension folder)
141
- // ----------------------------------------------------------
142
- copyDir(templateDir, target, ["extension"]);
143
-
144
- // ----------------------------------------------------------
145
- // 2. Explicitly install dotfiles
146
- // ----------------------------------------------------------
147
- const dotfiles = {
148
- "_gitignore": ".gitignore",
149
- "_dockerignore": ".dockerignore",
150
- };
151
-
152
- for (const [srcName, destName] of Object.entries(dotfiles)) {
153
- const src = path.join(templateDir, srcName);
154
- const dest = path.join(target, destName);
155
-
156
- if (fs.existsSync(src)) {
157
- fs.copyFileSync(src, dest);
158
- console.log(green(`✔ Added ${destName}`));
159
- }
160
- }
161
-
162
- // Dockerfile is safe as-is
163
- const dockerfileSrc = path.join(templateDir, "Dockerfile");
164
- if (fs.existsSync(dockerfileSrc)) {
165
- fs.copyFileSync(dockerfileSrc, path.join(target, "Dockerfile"));
166
- }
167
-
168
- console.log(green("✔ Titan project created!"));
169
- console.log(cyan("Installing dependencies..."));
170
-
171
- execSync(`npm install esbuild chokidar --silent`, {
172
- cwd: target,
173
- stdio: "inherit",
174
- });
175
-
176
- console.log(green("✔ Dependencies installed"));
177
- console.log(`
178
- Next steps:
179
- cd ${name}
180
- titan dev
181
- `);
182
- }
183
-
184
- /* -------------------------------------------------------
185
- * DEV SERVER
186
- * ----------------------------------------------------- */
187
- async function devServer() {
188
- const root = process.cwd();
189
- const devScript = path.join(root, "titan", "dev.js");
190
-
191
- if (!fs.existsSync(devScript)) {
192
- console.log(red("Error: titan/dev.js not found."));
193
- console.log("Try running `titan update` to fix missing files.");
194
- return;
195
- }
196
-
197
- const child = spawn("node", [devScript], {
198
- stdio: "inherit",
199
- cwd: root
200
- });
201
-
202
- child.on("close", (code) => {
203
- // Exit strictly if the dev script failed
204
- if (code !== 0) {
205
- process.exit(code);
206
- }
207
- });
208
- }
209
-
210
- /* -------------------------------------------------------
211
- * BUILD
212
- * ----------------------------------------------------- */
213
- function buildProd() {
214
- console.log(cyan("Titan: Building production output..."));
215
-
216
- const root = process.cwd();
217
- const appJs = path.join(root, "app", "app.js");
218
- const serverDir = path.join(root, "server");
219
- const actionsOut = path.join(serverDir, "actions");
220
-
221
- // BASIC CHECKS
222
- if (!fs.existsSync(appJs)) {
223
- console.log(red("ERROR: app/app.js not found."));
224
- process.exit(1);
225
- }
226
-
227
- // ----------------------------------------------------
228
- // 1) BUILD METADATA + BUNDLE ACTIONS (ONE TIME ONLY)
229
- // ----------------------------------------------------
230
- console.log(cyan("→ Building Titan metadata + bundling actions..."));
231
- execSync("node app/app.js --build", { stdio: "inherit" });
232
-
233
- // ensure actions directory exists
234
- fs.mkdirSync(actionsOut, { recursive: true });
235
-
236
- // verify bundled actions exist
237
- const bundles = fs.readdirSync(actionsOut).filter(f => f.endsWith(".jsbundle"));
238
- if (bundles.length === 0) {
239
- console.log(red("ERROR: No actions bundled."));
240
- console.log(red("Make sure your DSL outputs to server/actions."));
241
- process.exit(1);
242
- }
243
-
244
- bundles.forEach(file => {
245
- console.log(cyan(`→ Found action bundle: ${file}`));
246
- });
247
-
248
- console.log(green(" Actions ready in server/actions"));
249
-
250
- // ----------------------------------------------------
251
- // 2) BUILD RUST BINARY
252
- // ----------------------------------------------------
253
- console.log(cyan("→ Building Rust release binary..."));
254
- execSync("cargo build --release", {
255
- cwd: serverDir,
256
- stdio: "inherit"
257
- });
258
-
259
- console.log(green("✔ Titan production build complete!"));
260
- }
261
-
262
- /* -------------------------------------------------------
263
- * START
264
- * ----------------------------------------------------- */
265
- function startProd() {
266
- const isWin = process.platform === "win32";
267
- const bin = isWin ? "titan-server.exe" : "titan-server";
268
-
269
- const exe = path.join(process.cwd(), "server", "target", "release", bin);
270
- execSync(`"${exe}"`, { stdio: "inherit" });
271
- }
272
-
273
- /* -------------------------------------------------------
274
- * UPDATE
275
- * ----------------------------------------------------- */
276
-
277
- function updateTitan() {
278
- const root = process.cwd();
279
-
280
- const projectTitan = path.join(root, "titan");
281
- const projectServer = path.join(root, "server");
282
-
283
- const templatesRoot = path.join(__dirname, "templates");
284
- const templateTitan = path.join(templatesRoot, "titan");
285
- const templateServer = path.join(templatesRoot, "server");
286
-
287
- if (!fs.existsSync(projectTitan)) {
288
- console.log(red("Not a Titan project — titan/ folder missing."));
289
- return;
290
- }
291
-
292
- if (!fs.existsSync(templateServer)) {
293
- console.log(red("CLI is corrupted server template missing."));
294
- return;
295
- }
296
-
297
- console.log(cyan("Updating Titan runtime and server..."));
298
-
299
- // ----------------------------------------------------------
300
- // 1. Update titan/ runtime (authoritative, safe to replace)
301
- // ----------------------------------------------------------
302
- fs.rmSync(projectTitan, {
303
- recursive: true,
304
- force: true,
305
- maxRetries: 10,
306
- retryDelay: 500,
307
- });
308
-
309
- copyDir(templateTitan, projectTitan);
310
- console.log(green("✔ Updated titan/ runtime"));
311
-
312
- // ----------------------------------------------------------
313
- // 2. Update server/ WITHOUT deleting the folder
314
- // ----------------------------------------------------------
315
- if (!fs.existsSync(projectServer)) {
316
- fs.mkdirSync(projectServer);
317
- }
318
-
319
- // 2a. Overwrite Cargo.toml
320
- const srcCargo = path.join(templateServer, "Cargo.toml");
321
- const destCargo = path.join(projectServer, "Cargo.toml");
322
-
323
- if (fs.existsSync(srcCargo)) {
324
- fs.copyFileSync(srcCargo, destCargo);
325
- console.log(green("✔ Updated server/Cargo.toml"));
326
- }
327
-
328
- // 2b. Replace server/src only
329
- const projectSrc = path.join(projectServer, "src");
330
- const templateSrc = path.join(templateServer, "src");
331
-
332
- if (fs.existsSync(projectSrc)) {
333
- fs.rmSync(projectSrc, {
334
- recursive: true,
335
- force: true,
336
- maxRetries: 10,
337
- retryDelay: 500,
338
- });
339
- }
340
-
341
- copyDir(templateSrc, projectSrc);
342
- console.log(green(" Updated server/src/"));
343
-
344
- // Root-level config files
345
- [".gitignore", ".dockerignore", "Dockerfile", "jsconfig.json"].forEach((file) => {
346
- const src = path.join(templatesRoot, file);
347
- const dest = path.join(root, file);
348
-
349
- if (fs.existsSync(src)) {
350
- fs.copyFileSync(src, dest);
351
- console.log(green(`✔ Updated ${file}`));
352
- }
353
- });
354
-
355
- // app/titan.d.ts (JS typing contract)
356
- const appDir = path.join(root, "app");
357
- const srcDts = path.join(templateServer, "../app/titan.d.ts"); // templates/app/titan.d.ts
358
- const destDts = path.join(appDir, "titan.d.ts");
359
-
360
- if (fs.existsSync(srcDts)) {
361
- if (!fs.existsSync(appDir)) {
362
- fs.mkdirSync(appDir);
363
- }
364
-
365
- fs.copyFileSync(srcDts, destDts);
366
- console.log(green("✔ Updated app/titan.d.ts"));
367
- }
368
-
369
-
370
- console.log(bold(green("✔ Titan update complete")));
371
- }
372
-
373
-
374
-
375
- /* -------------------------------------------------------
376
- * CREATE EXTENSION
377
- * ----------------------------------------------------- */
378
- function createExtension(name) {
379
- if (!name) {
380
- console.log(red("Usage: titan create ext <name>"));
381
- return;
382
- }
383
-
384
-
385
- const folderName = name;
386
-
387
- const target = path.join(process.cwd(), folderName);
388
- const templateDir = path.join(__dirname, "templates", "extension");
389
-
390
- if (fs.existsSync(target)) {
391
- console.log(yellow(`Folder already exists: ${target}`));
392
- return;
393
- }
394
-
395
- if (!fs.existsSync(templateDir)) {
396
- console.log(red(`Extension template not found at ${templateDir}`));
397
- return;
398
- }
399
-
400
- console.log(cyan(`Creating Titan extension → ${target}`));
401
-
402
- // 1. Copy template
403
- copyDir(templateDir, target);
404
-
405
- // 2. Process templates (replace {{name}})
406
- const title = name;
407
- const nativeName = title.replace(/-/g, "_");
408
-
409
- const replaceAll = (filePath) => {
410
- if (fs.existsSync(filePath)) {
411
- let content = fs.readFileSync(filePath, "utf8");
412
- content = content.replace(/{{name}}/g, title);
413
- content = content.replace(/{{native_name}}/g, nativeName);
414
- fs.writeFileSync(filePath, content);
415
- }
416
- };
417
-
418
- const idxPath = path.join(target, "index.js");
419
- const readmePath = path.join(target, "README.md");
420
- const pkgPath = path.join(target, "package.json");
421
- const cargoPath = path.join(target, "native", "Cargo.toml");
422
-
423
- replaceAll(path.join(target, "titan.json"));
424
- replaceAll(idxPath);
425
- replaceAll(readmePath);
426
- replaceAll(pkgPath);
427
- replaceAll(cargoPath);
428
-
429
- console.log(cyan("Installing dependencies..."));
430
- try {
431
- execSync("npm install", { cwd: target, stdio: "inherit" });
432
- } catch (e) {
433
- console.log(yellow("Warning: Failed to install dependencies. You may need to run `npm install` manually."));
434
- }
435
-
436
- console.log(green("✔ Extension created!"));
437
- console.log(`
438
- Next steps:
439
- cd ${name}
440
- # If you have native code:
441
- cd native && cargo build --release
442
- # To test your extension
443
- titan run ext
444
- `);
445
- }
446
-
447
- function runExtension() {
448
- const localSdk = path.join(__dirname, "titanpl-sdk", "bin", "run.js");
449
-
450
- if (fs.existsSync(localSdk)) {
451
- console.log(cyan("[Titan] Using local SDK runner..."));
452
- try {
453
- execSync(`node "${localSdk}"`, { stdio: "inherit" });
454
- } catch (e) {
455
- // SDK runner handles its own errors
456
- }
457
- } else {
458
- console.log(cyan("[Titan] SDK not found locally, falling back to npx..."));
459
- try {
460
- execSync("npx -y titan-sdk", { stdio: "inherit" });
461
- } catch (e) {
462
- // SDK runner handles its own errors
463
- }
464
- }
465
- }
466
-
467
- /* -------------------------------------------------------
468
- * ROUTER
469
- * ----------------------------------------------------- */
470
- // "titan create ext <name>" -> args = ["create", "ext", "calc_ext"]
471
- if (cmd === "create" && args[1] === "ext") {
472
- createExtension(args[2]);
473
- } else if (cmd === "run" && args[1] === "ext") {
474
- runExtension();
475
- } else {
476
- switch (cmd) {
477
- case "init": initProject(args[1]); break;
478
- case "dev": devServer(); break;
479
- case "build": buildProd(); break;
480
- case "start": startProd(); break;
481
- case "update": updateTitan(); break;
482
- case "--version":
483
- case "-v":
484
- case "version":
485
- console.log(cyan(`Titan v${TITAN_VERSION}`));
486
- break;
487
- default:
488
- help();
489
- }
490
- }
2
+ import prompts from "prompts";
3
+ import fs from "fs";
4
+ import path from "path";
5
+ import { execSync, spawn } from "child_process";
6
+ import { fileURLToPath } from "url";
7
+
8
+ /* Resolve __dirname for ES modules */
9
+ const __filename = fileURLToPath(import.meta.url);
10
+ const __dirname = path.dirname(__filename);
11
+
12
+ /* -------------------------------------------------------
13
+ * Colors
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`;
21
+
22
+ /* -------------------------------------------------------
23
+ * Invocation detection (tit vs titan)
24
+ * ----------------------------------------------------- */
25
+ function wasInvokedAsTit() {
26
+ const script = process.argv[1];
27
+ if (script) {
28
+ const base = path.basename(script, path.extname(script)).toLowerCase();
29
+ if (base === "tit") return true;
30
+ }
31
+
32
+ try {
33
+ const raw = process.env.npm_config_argv;
34
+ if (raw) {
35
+ const cfg = JSON.parse(raw);
36
+ if (cfg.original && Array.isArray(cfg.original)) {
37
+ // e.g. ["tit", "dev"]
38
+ const first = cfg.original[0];
39
+ if (first && first.includes("tit") && !first.includes("titan")) {
40
+ return true;
41
+ }
42
+ }
43
+ }
44
+ } catch { }
45
+
46
+ const lastCmd = process.env["_"];
47
+ if (lastCmd) {
48
+ const base = path.basename(lastCmd, path.extname(lastCmd)).toLowerCase();
49
+ if (base === "tit") return true;
50
+ }
51
+
52
+ return false;
53
+ }
54
+
55
+ const isTitAlias = wasInvokedAsTit();
56
+
57
+ if (isTitAlias) {
58
+ console.log(
59
+ yellow(
60
+ "[Notice] `tit` is deprecated. Please use `titan` instead.\n" +
61
+ " `tit` will continue to work for now."
62
+ )
63
+ );
64
+ }
65
+
66
+ /* -------------------------------------------------------
67
+ * Args
68
+ * ----------------------------------------------------- */
69
+ const args = process.argv.slice(2);
70
+ const cmd = args[0];
71
+
72
+ /* -------------------------------------------------------
73
+ * Titan version
74
+ * ----------------------------------------------------- */
75
+ const pkg = JSON.parse(
76
+ fs.readFileSync(path.join(__dirname, "package.json"), "utf8")
77
+ );
78
+ const TITAN_VERSION = pkg.version;
79
+
80
+ /* -------------------------------------------------------
81
+ * Utils
82
+ * ----------------------------------------------------- */
83
+ function copyDir(src, dest, excludes = []) {
84
+ fs.mkdirSync(dest, { recursive: true });
85
+
86
+ for (const file of fs.readdirSync(src)) {
87
+ // Skip excluded files/folders
88
+ if (excludes.includes(file)) {
89
+ continue;
90
+ }
91
+
92
+ const srcPath = path.join(src, file);
93
+ const destPath = path.join(dest, file);
94
+
95
+ if (fs.lstatSync(srcPath).isDirectory()) {
96
+ copyDir(srcPath, destPath, excludes);
97
+ } else {
98
+ fs.copyFileSync(srcPath, destPath);
99
+ }
100
+ }
101
+ }
102
+
103
+ /* -------------------------------------------------------
104
+ * HELP
105
+ * ----------------------------------------------------- */
106
+ function help() {
107
+ console.log(`
108
+ ${bold(cyan("Titan Planet"))} v${TITAN_VERSION}
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
117
+
118
+ ${yellow("Note: `tit` is supported as a legacy alias.")}
119
+ `);
120
+ }
121
+
122
+ /* -------------------------------------------------------
123
+ * INIT
124
+ * ----------------------------------------------------- */
125
+ async function initProject(name, templateName) {
126
+ if (!name) {
127
+ console.log(red("Usage: titan init <project> [--template <js|rust>]"));
128
+ return;
129
+ }
130
+
131
+ let selectedTemplate = templateName;
132
+
133
+ if (!selectedTemplate) {
134
+ const response = await prompts({
135
+ type: 'select',
136
+ name: 'value',
137
+ message: 'Select a template:',
138
+ choices: [
139
+ { title: 'JavaScript', description: 'Standard Titan app with JS actions', value: 'js' },
140
+ { title: `Rust + JavaScript ${yellow('(Beta)')}`, description: 'High-performance Rust actions + JS flexibility', value: 'rust' }
141
+ ],
142
+ initial: 0
143
+ });
144
+
145
+ if (!response.value) {
146
+ console.log(red("✖ Operation cancelled"));
147
+ return;
148
+ }
149
+ selectedTemplate = response.value;
150
+ }
151
+
152
+ const target = path.join(process.cwd(), name);
153
+ const templateDir = path.join(__dirname, "templates", selectedTemplate);
154
+
155
+ if (!fs.existsSync(templateDir)) {
156
+ console.log(red(`Template '${selectedTemplate}' not found. Available: js, rust`));
157
+ return;
158
+ }
159
+
160
+ if (fs.existsSync(target)) {
161
+ console.log(yellow(`Folder already exists: ${target}`));
162
+ return;
163
+ }
164
+
165
+ console.log("\n" + bold(cyan("🚀 Initializing Titan Project...")));
166
+ console.log(gray(` Target: ${target}`));
167
+ console.log(gray(` Template: ${selectedTemplate === 'rust' ? 'Rust + JS (Native Perf)' : 'JavaScript (Standard)'}`));
168
+
169
+ // ----------------------------------------------------------
170
+ // 1. Copy full template directory
171
+ // ----------------------------------------------------------
172
+ copyDir(templateDir, target, ["_gitignore", "_dockerignore"]);
173
+
174
+ // ----------------------------------------------------------
175
+ // 2. Explicitly install dotfiles
176
+ // ----------------------------------------------------------
177
+ const dotfiles = {
178
+ "_gitignore": ".gitignore",
179
+ "_dockerignore": ".dockerignore",
180
+ };
181
+
182
+ for (const [srcName, destName] of Object.entries(dotfiles)) {
183
+ const src = path.join(templateDir, srcName);
184
+ const dest = path.join(target, destName);
185
+
186
+ if (fs.existsSync(src)) {
187
+ fs.copyFileSync(src, dest);
188
+ }
189
+ }
190
+
191
+ // Dockerfile is safe as-is
192
+ const dockerfileSrc = path.join(templateDir, "Dockerfile");
193
+ if (fs.existsSync(dockerfileSrc)) {
194
+ fs.copyFileSync(dockerfileSrc, path.join(target, "Dockerfile"));
195
+ }
196
+
197
+ console.log(green("✔ Project structure created"));
198
+ console.log(cyan("📦 Installing dependencies..."));
199
+
200
+ try {
201
+ execSync(`npm install esbuild chokidar --silent`, {
202
+ cwd: target,
203
+ stdio: "inherit",
204
+ });
205
+ console.log(green("✔ Dependencies installed"));
206
+ } catch (e) {
207
+ console.log(yellow("⚠ Failed to auto-install dependencies. Please run 'npm install' manually."));
208
+ }
209
+
210
+ console.log("\n" + bold(green("🎉 You're all set!")));
211
+ console.log(`
212
+ ${gray("Next steps:")}
213
+ ${cyan(`cd ${name}`)}
214
+ ${cyan("titan dev")}
215
+ `);
216
+ }
217
+
218
+ /* -------------------------------------------------------
219
+ * DEV SERVER
220
+ * ----------------------------------------------------- */
221
+ async function devServer() {
222
+ const root = process.cwd();
223
+ const devScript = path.join(root, "titan", "dev.js");
224
+
225
+ if (!fs.existsSync(devScript)) {
226
+ console.log(red("Error: titan/dev.js not found."));
227
+ console.log("Try running `titan update` to fix missing files.");
228
+ return;
229
+ }
230
+
231
+ const child = spawn("node", [devScript], {
232
+ stdio: "inherit",
233
+ cwd: root
234
+ });
235
+
236
+ child.on("close", (code) => {
237
+ // Exit strictly if the dev script failed
238
+ if (code !== 0) {
239
+ process.exit(code);
240
+ }
241
+ });
242
+ }
243
+
244
+ /* -------------------------------------------------------
245
+ * BUILD
246
+ * ----------------------------------------------------- */
247
+ function buildProd() {
248
+ console.log(cyan("Titan: Building production output..."));
249
+
250
+ const root = process.cwd();
251
+ const appJs = path.join(root, "app", "app.js");
252
+ const serverDir = path.join(root, "server");
253
+ const actionsOut = path.join(serverDir, "actions");
254
+
255
+ // BASIC CHECKS
256
+ if (!fs.existsSync(appJs)) {
257
+ console.log(red("ERROR: app/app.js not found."));
258
+ process.exit(1);
259
+ }
260
+
261
+ // ----------------------------------------------------
262
+ // 1) BUILD METADATA + BUNDLE ACTIONS (ONE TIME ONLY)
263
+ // ----------------------------------------------------
264
+ console.log(cyan("→ Building Titan metadata + bundling actions..."));
265
+ execSync("node app/app.js --build", { stdio: "inherit" });
266
+
267
+ // ensure actions directory exists
268
+ fs.mkdirSync(actionsOut, { recursive: true });
269
+
270
+ // verify bundled actions exist
271
+ const bundles = fs.readdirSync(actionsOut).filter(f => f.endsWith(".jsbundle"));
272
+ if (bundles.length === 0) {
273
+ console.log(red("ERROR: No actions bundled."));
274
+ console.log(red("Make sure your DSL outputs to server/actions."));
275
+ process.exit(1);
276
+ }
277
+
278
+ bundles.forEach(file => {
279
+ console.log(cyan(`→ Found action bundle: ${file}`));
280
+ });
281
+
282
+ console.log(green("✔ Actions ready in server/actions"));
283
+
284
+ // ----------------------------------------------------
285
+ // 2) BUILD RUST BINARY
286
+ // ----------------------------------------------------
287
+ console.log(cyan("→ Building Rust release binary..."));
288
+ execSync("cargo build --release", {
289
+ cwd: serverDir,
290
+ stdio: "inherit"
291
+ });
292
+
293
+ console.log(green(" Titan production build complete!"));
294
+ }
295
+
296
+ /* -------------------------------------------------------
297
+ * START
298
+ * ----------------------------------------------------- */
299
+ function startProd() {
300
+ const isWin = process.platform === "win32";
301
+ const bin = isWin ? "titan-server.exe" : "titan-server";
302
+
303
+ const exe = path.join(process.cwd(), "server", "target", "release", bin);
304
+ execSync(`"${exe}"`, { stdio: "inherit" });
305
+ }
306
+
307
+ /* -------------------------------------------------------
308
+ * UPDATE
309
+ * ----------------------------------------------------- */
310
+
311
+ function updateTitan() {
312
+ const root = process.cwd();
313
+
314
+ const projectTitan = path.join(root, "titan");
315
+ const projectServer = path.join(root, "server");
316
+ const projectPkg = path.join(root, "package.json");
317
+
318
+ let templateType = "js"; // Default
319
+ if (fs.existsSync(projectPkg)) {
320
+ try {
321
+ const pkg = JSON.parse(fs.readFileSync(projectPkg, "utf-8"));
322
+ if (pkg.titan && pkg.titan.template) {
323
+ templateType = pkg.titan.template;
324
+ }
325
+ } catch (e) { }
326
+ }
327
+
328
+ const templatesRoot = path.join(__dirname, "templates", templateType);
329
+ const templateTitan = path.join(templatesRoot, "titan");
330
+ const templateServer = path.join(templatesRoot, "server");
331
+
332
+ if (!fs.existsSync(projectTitan)) {
333
+ console.log(red("Not a Titan project — titan/ folder missing."));
334
+ return;
335
+ }
336
+
337
+ if (!fs.existsSync(templateServer)) {
338
+ console.log(red("CLI is corrupted — server template missing."));
339
+ return;
340
+ }
341
+
342
+ console.log(cyan("Updating Titan runtime and server..."));
343
+
344
+ // ----------------------------------------------------------
345
+ // 1. Update titan/ runtime (authoritative, safe to replace)
346
+ // ----------------------------------------------------------
347
+ fs.rmSync(projectTitan, {
348
+ recursive: true,
349
+ force: true,
350
+ maxRetries: 10,
351
+ retryDelay: 500,
352
+ });
353
+
354
+ copyDir(templateTitan, projectTitan);
355
+ console.log(green("✔ Updated titan/ runtime"));
356
+
357
+ // ----------------------------------------------------------
358
+ // 2. Update server/ WITHOUT deleting the folder
359
+ // ----------------------------------------------------------
360
+ if (!fs.existsSync(projectServer)) {
361
+ fs.mkdirSync(projectServer);
362
+ }
363
+
364
+ // 2a. Overwrite Cargo.toml
365
+ const srcCargo = path.join(templateServer, "Cargo.toml");
366
+ const destCargo = path.join(projectServer, "Cargo.toml");
367
+
368
+ if (fs.existsSync(srcCargo)) {
369
+ fs.copyFileSync(srcCargo, destCargo);
370
+ console.log(green("✔ Updated server/Cargo.toml"));
371
+ }
372
+
373
+ // 2b. Replace server/src only
374
+ const projectSrc = path.join(projectServer, "src");
375
+ const templateSrc = path.join(templateServer, "src");
376
+
377
+ if (fs.existsSync(projectSrc)) {
378
+ fs.rmSync(projectSrc, {
379
+ recursive: true,
380
+ force: true,
381
+ maxRetries: 10,
382
+ retryDelay: 500,
383
+ });
384
+ }
385
+
386
+ copyDir(templateSrc, projectSrc);
387
+ console.log(green("✔ Updated server/src/"));
388
+
389
+ // Root-level config files
390
+ const rootFiles = {
391
+ "_gitignore": ".gitignore",
392
+ "_dockerignore": ".dockerignore",
393
+ "Dockerfile": "Dockerfile",
394
+ "jsconfig.json": "jsconfig.json"
395
+ };
396
+
397
+ for (const [srcName, destName] of Object.entries(rootFiles)) {
398
+ const src = path.join(templatesRoot, srcName);
399
+ const dest = path.join(root, destName);
400
+
401
+ if (fs.existsSync(src)) {
402
+ fs.copyFileSync(src, dest);
403
+ console.log(green(`✔ Updated ${destName}`));
404
+ }
405
+ }
406
+
407
+ // app/titan.d.ts (JS typing contract)
408
+ const appDir = path.join(root, "app");
409
+ const srcDts = path.join(templateServer, "../app/titan.d.ts"); // templates/app/titan.d.ts
410
+ const destDts = path.join(appDir, "titan.d.ts");
411
+
412
+ if (fs.existsSync(srcDts)) {
413
+ if (!fs.existsSync(appDir)) {
414
+ fs.mkdirSync(appDir);
415
+ }
416
+
417
+ fs.copyFileSync(srcDts, destDts);
418
+ console.log(green("✔ Updated app/titan.d.ts"));
419
+ }
420
+
421
+
422
+ console.log(bold(green("✔ Titan update complete")));
423
+ }
424
+
425
+
426
+
427
+ /* -------------------------------------------------------
428
+ * CREATE EXTENSION
429
+ * ----------------------------------------------------- */
430
+ function createExtension(name) {
431
+ if (!name) {
432
+ console.log(red("Usage: titan create ext <name>"));
433
+ return;
434
+ }
435
+
436
+
437
+ const folderName = name;
438
+
439
+ const target = path.join(process.cwd(), folderName);
440
+ const templateDir = path.join(__dirname, "templates", "extension");
441
+
442
+ if (fs.existsSync(target)) {
443
+ console.log(yellow(`Folder already exists: ${target}`));
444
+ return;
445
+ }
446
+
447
+ if (!fs.existsSync(templateDir)) {
448
+ console.log(red(`Extension template not found at ${templateDir}`));
449
+ return;
450
+ }
451
+
452
+ console.log(cyan(`Creating Titan extension → ${target}`));
453
+
454
+ // 1. Copy template
455
+ copyDir(templateDir, target);
456
+
457
+ // 2. Process templates (replace {{name}})
458
+ const title = name;
459
+ const nativeName = title.replace(/-/g, "_");
460
+
461
+ const replaceAll = (filePath) => {
462
+ if (fs.existsSync(filePath)) {
463
+ let content = fs.readFileSync(filePath, "utf8");
464
+ content = content.replace(/{{name}}/g, title);
465
+ content = content.replace(/{{native_name}}/g, nativeName);
466
+ fs.writeFileSync(filePath, content);
467
+ }
468
+ };
469
+
470
+ const idxPath = path.join(target, "index.js");
471
+ const readmePath = path.join(target, "README.md");
472
+ const pkgPath = path.join(target, "package.json");
473
+ const cargoPath = path.join(target, "native", "Cargo.toml");
474
+
475
+ replaceAll(path.join(target, "titan.json"));
476
+ replaceAll(idxPath);
477
+ replaceAll(readmePath);
478
+ replaceAll(pkgPath);
479
+ replaceAll(cargoPath);
480
+
481
+ console.log(cyan("Installing dependencies..."));
482
+ try {
483
+ execSync("npm install", { cwd: target, stdio: "inherit" });
484
+ } catch (e) {
485
+ console.log(yellow("Warning: Failed to install dependencies. You may need to run `npm install` manually."));
486
+ }
487
+
488
+ console.log(green("✔ Extension created!"));
489
+ console.log(`
490
+ Next steps:
491
+ cd ${name}
492
+ # If you have native code:
493
+ cd native && cargo build --release
494
+ # To test your extension
495
+ titan run ext
496
+ `);
497
+ }
498
+
499
+ function runExtension() {
500
+ const localSdk = path.join(__dirname, "titanpl-sdk", "bin", "run.js");
501
+
502
+ if (fs.existsSync(localSdk)) {
503
+ console.log(cyan("[Titan] Using local SDK runner..."));
504
+ try {
505
+ execSync(`node "${localSdk}"`, { stdio: "inherit" });
506
+ } catch (e) {
507
+ // SDK runner handles its own errors
508
+ }
509
+ } else {
510
+ console.log(cyan("[Titan] SDK not found locally, falling back to npx..."));
511
+ try {
512
+ execSync("npx -y titan-sdk", { stdio: "inherit" });
513
+ } catch (e) {
514
+ // SDK runner handles its own errors
515
+ }
516
+ }
517
+ }
518
+
519
+ /* -------------------------------------------------------
520
+ * ROUTER
521
+ * ----------------------------------------------------- */
522
+ // "titan create ext <name>" -> args = ["create", "ext", "calc_ext"]
523
+ if (cmd === "create" && args[1] === "ext") {
524
+ createExtension(args[2]);
525
+ } else if (cmd === "run" && args[1] === "ext") {
526
+ runExtension();
527
+ } else {
528
+ switch (cmd) {
529
+ case "init": {
530
+ const projName = args[1];
531
+ let tpl = null;
532
+
533
+ const tIndex = args.indexOf("--template") > -1 ? args.indexOf("--template") : args.indexOf("-t");
534
+ if (tIndex > -1 && args[tIndex + 1]) {
535
+ tpl = args[tIndex + 1];
536
+ }
537
+
538
+ initProject(projName, tpl);
539
+ break;
540
+ }
541
+ case "dev": devServer(); break;
542
+ case "build": buildProd(); break;
543
+ case "start": startProd(); break;
544
+ case "update": updateTitan(); break;
545
+ case "--version":
546
+ case "-v":
547
+ case "version":
548
+ console.log(cyan(`Titan v${TITAN_VERSION}`));
549
+ break;
550
+ default:
551
+ help();
552
+ }
553
+ }