@titanpl/cli 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/README.md ADDED
@@ -0,0 +1,17 @@
1
+ # @titanpl/cli
2
+
3
+ The command-line interface (CLI) for Titan Planet. It provides the `titan` and `tit` commands for initializing, building, and running Titan Planet servers.
4
+
5
+ ## What it works (What it does)
6
+ The CLI is responsible for bridging your JavaScript codebase with the underlying Rust/Axum engine. It handles scaffolding, compiling JS actions, generating metadata, and running the server.
7
+
8
+ ## How it works
9
+ You can install this package globally or use it via your package runner (e.g., `npx`). Alternatively, you can install it as a dev dependency in your project.
10
+
11
+ ```bash
12
+ npx titan help
13
+ ```
14
+
15
+ It parses your application source code, coordinates with `@titanpl/packet` to build the required JS endpoints, and then spins up the pre-compiled native core engine for your OS.
16
+
17
+ **Important Note:** Currently, Titan Planet and its entire package ecosystem are only for Windows. The Linux version is in development (dev only) for the new architecture and will be launched later.
package/index.js CHANGED
@@ -8,6 +8,7 @@ import { buildCommand } from "./src/commands/build.js";
8
8
  import { devCommand } from "./src/commands/dev.js";
9
9
  import { startCommand } from "./src/commands/start.js";
10
10
  import { migrateCommand } from "./src/commands/migrate.js";
11
+ import { updateCommand } from "./src/commands/update.js";
11
12
  import { initCommand } from "./src/commands/init.js";
12
13
 
13
14
  /* -------------------------------------------------------
@@ -51,9 +52,11 @@ ${bold(cyan("╰─────────────────────
51
52
 
52
53
  ${bold("Commands:")}
53
54
  ${cyan("init")} ${gray("Scaffold a new Titan project")}
55
+ ${cyan("create")} ${gray("Create a new project or extension (e.g. 'titan create ext my-ext')")}
54
56
  ${cyan("build")} ${gray("Compile actions and build production dist")}
55
57
  ${cyan("dev")} ${gray("Start the Gravity Engine in dev/watch mode")}
56
58
  ${cyan("start")} ${gray("Start the production Gravity Engine")}
59
+ ${cyan("update")} ${gray("Update an existing project to latest Titan version")}
57
60
  ${cyan("migrate")} ${gray("Migrate a legacy project to the new architecture")}
58
61
 
59
62
  ${bold("Options:")}
@@ -68,6 +71,7 @@ ${gray(" The Titan Planet Engine runs your JS/TS server natively without Node.j
68
71
  /* -------------------------------------------------------
69
72
  * CLI Router
70
73
  * ----------------------------------------------------- */
74
+ process.title = "TitanPL";
71
75
  const cmd = process.argv[2];
72
76
 
73
77
  (async () => {
@@ -105,6 +109,18 @@ const cmd = process.argv[2];
105
109
  break;
106
110
  }
107
111
 
112
+ case "create": {
113
+ const type = process.argv[3];
114
+ const name = process.argv[4];
115
+ if (type === "ext" || type === "extension") {
116
+ await initCommand(name, "extension");
117
+ } else {
118
+ // Fallback to init behavior
119
+ await initCommand(type, null);
120
+ }
121
+ break;
122
+ }
123
+
108
124
  case "build":
109
125
  console.log(cyan("→ Building Titan project..."));
110
126
  await buildCommand();
@@ -120,6 +136,10 @@ const cmd = process.argv[2];
120
136
  startCommand();
121
137
  break;
122
138
 
139
+ case "update":
140
+ await updateCommand();
141
+ break;
142
+
123
143
  case "migrate":
124
144
  await migrateCommand();
125
145
  break;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@titanpl/cli",
3
- "version": "1.0.0",
4
- "description": "The gravity and other builtins binary of TitanPl",
3
+ "version": "1.4.0",
4
+ "description": "The unified CLI for Titan Planet. Use it to create, manage, build, and deploy high-performance backend projects.",
5
5
  "keywords": [
6
6
  "titanpl",
7
7
  "titan",
@@ -16,17 +16,19 @@
16
16
  "test": "echo \"Error: no test specified\" && exit 1"
17
17
  },
18
18
  "bin": {
19
- "titan": "./index.js"
19
+ "titan": "./index.js",
20
+ "titanpl": "./index.js",
21
+ "tit": "./index.js"
20
22
  },
21
23
  "optionalDependencies": {
22
- "@titanpl/engine-win32-x64": "26.16.0",
23
- "@titanpl/engine-linux-x64": "26.16.0",
24
- "@titanpl/engine-darwin-arm64": "26.16.0"
24
+ "@titanpl/engine-win32-x64": "1.4.0",
25
+ "@titanpl/engine-linux-x64": "1.4.0",
26
+ "@titanpl/engine-darwin-arm64": "1.4.0"
25
27
  },
26
28
  "dependencies": {
27
- "@titanpl/packet": "26.16.0",
29
+ "@titanpl/packet": "1.4.0",
28
30
  "prompts": "^2.4.2",
29
31
  "commander": "^11.0.0",
30
32
  "chalk": "^4.1.2"
31
33
  }
32
- }
34
+ }
@@ -1,7 +1,5 @@
1
- import { startEngine } from "../engine.js";
1
+ import { dev } from "@titanpl/packet";
2
2
 
3
3
  export async function devCommand() {
4
- // The Titan Engine now handles all compilation, watch, and routing natively.
5
- // There is no need for JS-based watchers or Node.js process managers.
6
- startEngine(true);
4
+ await dev(process.cwd());
7
5
  }
@@ -112,25 +112,58 @@ export async function initCommand(projectName, templateName) {
112
112
  console.log(chalk.cyan(`\n→ Creating new Titan project in '${projName}'...\n`));
113
113
 
114
114
  // 1. Copy common
115
- copyDir(commonDir, target, ["_gitignore", "_dockerignore"]);
115
+ copyDir(commonDir, target);
116
116
 
117
117
  // 2. Copy specific template
118
- copyDir(templateDir, target, ["_gitignore", "_dockerignore"]);
118
+ copyDir(templateDir, target);
119
119
 
120
- // 3. Dotfiles
121
- const dotfiles = {
120
+ // 3. Dotfiles and Template Remapping
121
+ const remapping = {
122
122
  "_gitignore": ".gitignore",
123
123
  "_dockerignore": ".dockerignore",
124
+ "_titan.json": "titan.json",
124
125
  ".env": ".env"
125
126
  };
126
- for (const [srcName, destName] of Object.entries(dotfiles)) {
127
- const src = path.join(commonDir, srcName);
127
+ for (const [srcName, destName] of Object.entries(remapping)) {
128
+ const src = path.join(target, srcName);
128
129
  const dest = path.join(target, destName);
129
130
  if (fs.existsSync(src)) {
130
- fs.copyFileSync(src, dest);
131
+ fs.renameSync(src, dest);
131
132
  }
132
133
  }
133
134
 
135
+ // Recursive template substitution
136
+ const substitute = (dir) => {
137
+ for (const file of fs.readdirSync(dir)) {
138
+ const fullPath = path.join(dir, file);
139
+ if (fs.lstatSync(fullPath).isDirectory()) {
140
+ if (file !== "node_modules" && file !== ".git" && file !== "target") {
141
+ substitute(fullPath);
142
+ }
143
+ } else {
144
+ // Only process text files
145
+ const ext = path.extname(file).toLowerCase();
146
+ const textExts = ['.js', '.ts', '.json', '.md', '.txt', '.rs', '.toml', '.html', '.css', '.d.ts'];
147
+ if (textExts.includes(ext) || file === ".env" || file === "Dockerfile") {
148
+ let content = fs.readFileSync(fullPath, 'utf8');
149
+ let changed = false;
150
+ if (content.includes("{{name}}")) {
151
+ content = content.replace(/{{name}}/g, projName);
152
+ changed = true;
153
+ }
154
+ if (content.includes("{{native_name}}")) {
155
+ content = content.replace(/{{native_name}}/g, projName.replace(/-/g, '_'));
156
+ changed = true;
157
+ }
158
+ if (changed) {
159
+ fs.writeFileSync(fullPath, content);
160
+ }
161
+ }
162
+ }
163
+ }
164
+ };
165
+ substitute(target);
166
+
134
167
  console.log(chalk.gray(` Installing dependencies...`));
135
168
 
136
169
  await new Promise((resolve, reject) => {
@@ -70,6 +70,36 @@ export async function migrateCommand() {
70
70
  }
71
71
  }
72
72
 
73
+ // 4. Synchronize Dockerfile and other common files
74
+ try {
75
+ const commonDir = path.resolve(__dirname, '..', '..', '..', '..', 'templates', 'common');
76
+ if (fs.existsSync(commonDir)) {
77
+ const filesToSync = [
78
+ ['Dockerfile', 'Dockerfile'],
79
+ ['_dockerignore', '.dockerignore'],
80
+ ['_gitignore', '.gitignore'],
81
+ ['app/t.native.d.ts', 'app/t.native.d.ts'],
82
+ ['app/t.native.js', 'app/t.native.js']
83
+ ];
84
+
85
+ for (const [srcRel, destRel] of filesToSync) {
86
+ const src = path.join(commonDir, srcRel);
87
+ const dest = path.join(root, destRel);
88
+ if (fs.existsSync(src)) {
89
+ // Create parent dir if needed
90
+ const parent = path.dirname(dest);
91
+ if (!fs.existsSync(parent)) {
92
+ fs.mkdirSync(parent, { recursive: true });
93
+ }
94
+ fs.copyFileSync(src, dest);
95
+ }
96
+ }
97
+ console.log(` Synchronized Dockerfiles and native definitions.`);
98
+ }
99
+ } catch (e) {
100
+ console.log(` ⚠️ Failed to synchronize common template files.`);
101
+ }
102
+
73
103
  console.log(`\n🎉 Migration complete!`);
74
104
  console.log(` Please run 'npm install' to fetch the new dependencies.`);
75
105
  console.log(` Then run 'titan dev' to start your application.\n`);
@@ -0,0 +1,90 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import { fileURLToPath } from 'url';
4
+ import chalk from 'chalk';
5
+
6
+ const __filename = fileURLToPath(import.meta.url);
7
+ const __dirname = path.dirname(__filename);
8
+
9
+ export async function updateCommand() {
10
+ const root = process.cwd();
11
+ console.log(chalk.cyan(`\n→ Updating Titan project to latest...`));
12
+
13
+ const pkgPath = path.join(root, 'package.json');
14
+ if (!fs.existsSync(pkgPath)) {
15
+ console.log(chalk.red(`✖ No package.json found. Are you in a project root ? `));
16
+ return;
17
+ }
18
+
19
+ // 1. Update package.json versions
20
+ try {
21
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
22
+ let modified = false;
23
+
24
+ const titanDeps = [
25
+ 'titanpl',
26
+ 'titanpl-sdk',
27
+ '@titanpl/cli',
28
+ '@titanpl/route',
29
+ '@titanpl/native',
30
+ '@titanpl/packet',
31
+ '@titanpl/core',
32
+ '@titanpl/node'
33
+ ];
34
+
35
+ if (pkg.dependencies) {
36
+ for (const dep of titanDeps) {
37
+ if (pkg.dependencies[dep]) {
38
+ pkg.dependencies[dep] = "latest";
39
+ modified = true;
40
+ }
41
+ }
42
+ }
43
+
44
+ if (pkg.devDependencies) {
45
+ for (const dep of titanDeps) {
46
+ if (pkg.devDependencies[dep]) {
47
+ pkg.devDependencies[dep] = "latest";
48
+ modified = true;
49
+ }
50
+ }
51
+ }
52
+
53
+ if (modified) {
54
+ fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n');
55
+ console.log(chalk.green(` ✔ Updated Titan dependencies in package.json`));
56
+ }
57
+ } catch (e) {
58
+ console.log(chalk.yellow(` ⚠️ Failed to update package.json: ${e.message}`));
59
+ }
60
+
61
+ // 2. Refresh Dockerfile and dotfiles from templates
62
+ const commonDir = path.resolve(__dirname, '..', '..', '..', '..', 'templates', 'common');
63
+ if (fs.existsSync(commonDir)) {
64
+ const filesToSync = [
65
+ ['Dockerfile', 'Dockerfile'],
66
+ ['_dockerignore', '.dockerignore'],
67
+ ['_gitignore', '.gitignore'],
68
+ ];
69
+
70
+ for (const [srcName, destName] of filesToSync) {
71
+ const src = path.join(commonDir, srcName);
72
+ const dest = path.join(root, destName);
73
+ if (fs.existsSync(src)) {
74
+ fs.copyFileSync(src, dest);
75
+ console.log(chalk.green(` ✔ Synchronized ${destName}`));
76
+ }
77
+ }
78
+
79
+ // Also update app/t.native.d.ts if it exists
80
+ const nativeTypesSrc = path.join(commonDir, 'app', 't.native.d.ts');
81
+ const nativeTypesDest = path.join(root, 'app', 't.native.d.ts');
82
+ if (fs.existsSync(nativeTypesSrc) && fs.existsSync(path.join(root, 'app'))) {
83
+ fs.copyFileSync(nativeTypesSrc, nativeTypesDest);
84
+ console.log(chalk.green(` ✔ Updated app/t.native.d.ts`));
85
+ }
86
+ }
87
+
88
+ console.log(chalk.green(`\n✔ Update complete!\n`));
89
+ console.log(chalk.yellow(` Please run 'npm install' to apply changes.\n`));
90
+ }
package/src/engine.js CHANGED
@@ -16,16 +16,38 @@ export function getEngineBinaryPath() {
16
16
  const pkgName = `@titanpl/engine-${platform}-${arch}`;
17
17
  const binName = platform === 'win32' ? 'titan-server.exe' : 'titan-server';
18
18
 
19
- // 1. Monorepo Dev Fallback
20
- const serverExeName = platform === 'win32' ? 'titan-server.exe' : 'titan-server';
21
- const localFallback = path.resolve(__dirname, '..', '..', '..', 'engine', 'target', 'release', serverExeName);
22
- if (fs.existsSync(localFallback)) {
23
- return localFallback;
19
+ // 1. Robust Binary Discovery (Monorepo, Local, or Global)
20
+ const searchPaths = [
21
+ __dirname,
22
+ process.cwd(),
23
+ path.join(process.cwd(), '..'),
24
+ path.join(process.cwd(), '..', '..')
25
+ ];
26
+
27
+ for (let startPath of searchPaths) {
28
+ let current = startPath;
29
+ for (let i = 0; i < 8; i++) {
30
+ const potential = path.join(current, 'engine', 'target', 'release', binName);
31
+ if (fs.existsSync(potential)) return potential;
32
+
33
+ const parent = path.dirname(current);
34
+ if (parent === current) break;
35
+ current = parent;
36
+ }
24
37
  }
25
38
 
26
39
  // 2. Resolve installed optional dependency
27
40
  try {
28
- const pkgPath = require.resolve(`${pkgName}/package.json`);
41
+ // We use a try/catch specifically for the resolve to provide a better diagnostic
42
+ let pkgPath;
43
+ try {
44
+ pkgPath = require.resolve(`${pkgName}/package.json`);
45
+ } catch (e) {
46
+ // Fallback: check if the binary exists in a standard location even if require.resolve fails
47
+ const fallbackBin = path.join(process.cwd(), 'node_modules', pkgName, 'bin', binName);
48
+ if (fs.existsSync(fallbackBin)) return fallbackBin;
49
+ throw e;
50
+ }
29
51
  const pkgDir = path.dirname(pkgPath);
30
52
  const binaryPath = path.join(pkgDir, 'bin', binName);
31
53
 
@@ -58,7 +80,7 @@ export function startEngine(watchMode = false) {
58
80
  console.log(`🚀 Starting Titan Engine...`);
59
81
 
60
82
  const engineProcess = spawn(binaryPath, args, {
61
- stdio: 'inherit',
83
+ stdio: ['inherit', 'pipe', 'pipe'],
62
84
  env: {
63
85
  ...process.env,
64
86
  TITAN_ENV: watchMode ? 'development' : 'production',
@@ -66,9 +88,54 @@ export function startEngine(watchMode = false) {
66
88
  }
67
89
  });
68
90
 
91
+ let stderrBuffer = "";
92
+
93
+ engineProcess.stdout.pipe(process.stdout);
94
+ engineProcess.stderr.on('data', (data) => {
95
+ const chunk = data.toString();
96
+ stderrBuffer += chunk;
97
+ process.stderr.write(data);
98
+ });
99
+
69
100
  engineProcess.on('close', (code) => {
70
- if (code !== 0 && code !== null && !engineProcess._isKilling) {
71
- console.error(`\n❌ [Titan Engine died with exit code ${code}]`);
101
+ if (code !== 0 && code !== null) {
102
+ // Check for port binding errors
103
+ const isPortError = stderrBuffer.includes("Address already in use") ||
104
+ stderrBuffer.includes("address in use") ||
105
+ stderrBuffer.includes("os error 10048") || // Windows
106
+ stderrBuffer.includes("EADDRINUSE") ||
107
+ stderrBuffer.includes("AddrInUse");
108
+
109
+ if (isPortError) {
110
+ // Try to read intended port
111
+ let port = 5100;
112
+ try {
113
+ const routesPath = path.join(process.cwd(), "dist", "routes.json");
114
+ if (fs.existsSync(routesPath)) {
115
+ const routesConfig = JSON.parse(fs.readFileSync(routesPath, "utf8"));
116
+ if (routesConfig && routesConfig.__config && routesConfig.__config.port) {
117
+ port = routesConfig.__config.port;
118
+ }
119
+ }
120
+ } catch (e) { }
121
+
122
+ const cyan = (t) => `\x1b[36m${t}\x1b[0m`;
123
+ const yellow = (t) => `\x1b[33m${t}\x1b[0m`;
124
+ const red = (t) => `\x1b[31m${t}\x1b[0m`;
125
+
126
+ console.log("");
127
+ console.log(red("⏣ Your application cannot enter this orbit"));
128
+ console.log(red(`↳ Another application is already bound to port ${port}.`));
129
+ console.log("");
130
+
131
+ console.log(yellow("Recommended Actions:"));
132
+ console.log(yellow(" 1.") + " Release the occupied orbit (stop the other service).");
133
+ console.log(yellow(" 2.") + " Assign your application to a new orbit in " + cyan("app/app.js"));
134
+ console.log(yellow(" Example: ") + cyan(`t.start(${port + 1}, "Titan Running!")`));
135
+ console.log("");
136
+ } else {
137
+ console.error(`\n❌ [Titan Engine died with exit code ${code}]`);
138
+ }
72
139
  }
73
140
  });
74
141