@titanpl/cli 1.0.0 → 1.5.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 +17 -0
- package/index.js +20 -0
- package/package.json +10 -8
- package/src/commands/dev.js +2 -4
- package/src/commands/init.js +40 -7
- package/src/commands/migrate.js +30 -0
- package/src/commands/update.js +90 -0
- package/src/engine.js +76 -9
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.
|
|
4
|
-
"description": "The
|
|
3
|
+
"version": "1.5.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": "
|
|
23
|
-
"@titanpl/engine-linux-x64": "
|
|
24
|
-
"@titanpl/engine-darwin-arm64": "
|
|
24
|
+
"@titanpl/engine-win32-x64": "1.5.0",
|
|
25
|
+
"@titanpl/engine-linux-x64": "1.5.0",
|
|
26
|
+
"@titanpl/engine-darwin-arm64": "1.5.0"
|
|
25
27
|
},
|
|
26
28
|
"dependencies": {
|
|
27
|
-
"@titanpl/packet": "
|
|
29
|
+
"@titanpl/packet": "1.5.0",
|
|
28
30
|
"prompts": "^2.4.2",
|
|
29
31
|
"commander": "^11.0.0",
|
|
30
32
|
"chalk": "^4.1.2"
|
|
31
33
|
}
|
|
32
|
-
}
|
|
34
|
+
}
|
package/src/commands/dev.js
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { dev } from "@titanpl/packet";
|
|
2
2
|
|
|
3
3
|
export async function devCommand() {
|
|
4
|
-
|
|
5
|
-
// There is no need for JS-based watchers or Node.js process managers.
|
|
6
|
-
startEngine(true);
|
|
4
|
+
await dev(process.cwd());
|
|
7
5
|
}
|
package/src/commands/init.js
CHANGED
|
@@ -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
|
|
115
|
+
copyDir(commonDir, target);
|
|
116
116
|
|
|
117
117
|
// 2. Copy specific template
|
|
118
|
-
copyDir(templateDir, target
|
|
118
|
+
copyDir(templateDir, target);
|
|
119
119
|
|
|
120
|
-
// 3. Dotfiles
|
|
121
|
-
const
|
|
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(
|
|
127
|
-
const src = path.join(
|
|
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.
|
|
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) => {
|
package/src/commands/migrate.js
CHANGED
|
@@ -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
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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
|
-
|
|
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
|
|
71
|
-
|
|
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
|
|