@ezetgalaxy/titan 26.8.0 → 26.8.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.
- package/package.json +1 -1
- package/templates/js/server/src/main.rs +1 -6
- package/templates/js/titan/bundle.js +1 -1
- package/templates/js/titan/dev.js +258 -194
- package/templates/rust/server/Cargo.toml +12 -0
- package/templates/rust/server/src/main.rs +0 -6
- package/templates/rust/titan/bundle.js +157 -157
- package/templates/rust/titan/dev.js +266 -194
- package/templates/rust/titan/titan.js +122 -122
- package/templates/js/server/src/actions_rust/mod.rs +0 -15
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ezetgalaxy/titan",
|
|
3
|
-
"version": "26.8.
|
|
3
|
+
"version": "26.8.2",
|
|
4
4
|
"description": "Titan Planet is a JavaScript-first backend framework that embeds JS actions into a Rust + Axum server and ships as a single native binary. Routes are compiled to static metadata; only actions run in the embedded JS runtime. No Node.js. No event loop in production.",
|
|
5
5
|
"license": "ISC",
|
|
6
6
|
"author": "ezetgalaxy",
|
|
@@ -368,12 +368,7 @@ async fn main() -> Result<()> {
|
|
|
368
368
|
|
|
369
369
|
let listener = TcpListener::bind(format!("0.0.0.0:{}", port)).await?;
|
|
370
370
|
|
|
371
|
-
|
|
372
|
-
println!("╚══██╔══╝██║╚══██╔══╝██╔══██╗████╗ ██║");
|
|
373
|
-
println!(" ██║ ██║ ██║ ███████║██╔██╗ ██║");
|
|
374
|
-
println!(" ██║ ██║ ██║ ██╔══██║██║╚██╗██║");
|
|
375
|
-
println!(" ██║ ██║ ██║ ██║ ██║██║ ╚████║");
|
|
376
|
-
println!(" ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝╚═╝ ╚═══╝\x1b[0m\n");
|
|
371
|
+
|
|
377
372
|
println!(
|
|
378
373
|
"\x1b[38;5;39mTitan server running at:\x1b[0m http://localhost:{}",
|
|
379
374
|
port
|
|
@@ -28,7 +28,7 @@ async function bundleJs() {
|
|
|
28
28
|
const files = fs.readdirSync(actionsDir).filter(f => f.endsWith(".js") || f.endsWith(".ts"));
|
|
29
29
|
if (files.length === 0) return;
|
|
30
30
|
|
|
31
|
-
console.log(`[Titan] Bundling ${files.length} JS actions...`);
|
|
31
|
+
// console.log(`[Titan] Bundling ${files.length} JS actions...`);
|
|
32
32
|
|
|
33
33
|
for (const file of files) {
|
|
34
34
|
const actionName = path.basename(file, path.extname(file));
|
|
@@ -1,194 +1,258 @@
|
|
|
1
|
-
import chokidar from "chokidar";
|
|
2
|
-
import { spawn, execSync } from "child_process";
|
|
3
|
-
import path from "path";
|
|
4
|
-
import { fileURLToPath } from "url";
|
|
5
|
-
import fs from "fs";
|
|
6
|
-
import { bundle } from "./bundle.js";
|
|
7
|
-
|
|
8
|
-
// Required for __dirname in ES modules
|
|
9
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
10
|
-
const __dirname = path.dirname(__filename);
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
// Colors
|
|
14
|
-
import { createRequire } from "module";
|
|
15
|
-
|
|
16
|
-
// Colors
|
|
17
|
-
const cyan = (t) => `\x1b[36m${t}\x1b[0m`;
|
|
18
|
-
const green = (t) => `\x1b[32m${t}\x1b[0m`;
|
|
19
|
-
const yellow = (t) => `\x1b[33m${t}\x1b[0m`;
|
|
20
|
-
const red = (t) => `\x1b[31m${t}\x1b[0m`;
|
|
21
|
-
const gray = (t) => `\x1b[90m${t}\x1b[0m`;
|
|
22
|
-
const bold = (t) => `\x1b[1m${t}\x1b[0m`;
|
|
23
|
-
|
|
24
|
-
function getTitanVersion() {
|
|
25
|
-
try {
|
|
26
|
-
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
let
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
1
|
+
import chokidar from "chokidar";
|
|
2
|
+
import { spawn, execSync } from "child_process";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import { fileURLToPath } from "url";
|
|
5
|
+
import fs from "fs";
|
|
6
|
+
import { bundle } from "./bundle.js";
|
|
7
|
+
|
|
8
|
+
// Required for __dirname in ES modules
|
|
9
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
10
|
+
const __dirname = path.dirname(__filename);
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
// Colors
|
|
14
|
+
import { createRequire } from "module";
|
|
15
|
+
|
|
16
|
+
// Colors
|
|
17
|
+
const cyan = (t) => `\x1b[36m${t}\x1b[0m`;
|
|
18
|
+
const green = (t) => `\x1b[32m${t}\x1b[0m`;
|
|
19
|
+
const yellow = (t) => `\x1b[33m${t}\x1b[0m`;
|
|
20
|
+
const red = (t) => `\x1b[31m${t}\x1b[0m`;
|
|
21
|
+
const gray = (t) => `\x1b[90m${t}\x1b[0m`;
|
|
22
|
+
const bold = (t) => `\x1b[1m${t}\x1b[0m`;
|
|
23
|
+
|
|
24
|
+
function getTitanVersion() {
|
|
25
|
+
try {
|
|
26
|
+
const require = createRequire(import.meta.url);
|
|
27
|
+
const pkgPath = require.resolve("@ezetgalaxy/titan/package.json");
|
|
28
|
+
return JSON.parse(fs.readFileSync(pkgPath, "utf-8")).version;
|
|
29
|
+
} catch (e) {
|
|
30
|
+
try {
|
|
31
|
+
// Check levels up to find the framework root
|
|
32
|
+
let cur = __dirname;
|
|
33
|
+
for (let i = 0; i < 5; i++) {
|
|
34
|
+
const pkgPath = path.join(cur, "package.json");
|
|
35
|
+
if (fs.existsSync(pkgPath)) {
|
|
36
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
|
|
37
|
+
if (pkg.name === "@ezetgalaxy/titan") return pkg.version;
|
|
38
|
+
}
|
|
39
|
+
cur = path.join(cur, "..");
|
|
40
|
+
}
|
|
41
|
+
} catch (e2) { }
|
|
42
|
+
}
|
|
43
|
+
return "0.1.0";
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
let serverProcess = null;
|
|
47
|
+
let isKilling = false;
|
|
48
|
+
|
|
49
|
+
// ... (killServer same as before)
|
|
50
|
+
async function killServer() {
|
|
51
|
+
if (!serverProcess) return;
|
|
52
|
+
|
|
53
|
+
isKilling = true;
|
|
54
|
+
const pid = serverProcess.pid;
|
|
55
|
+
const killPromise = new Promise((resolve) => {
|
|
56
|
+
if (serverProcess.exitCode !== null) return resolve();
|
|
57
|
+
serverProcess.once("close", resolve);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
if (process.platform === "win32") {
|
|
61
|
+
try {
|
|
62
|
+
execSync(`taskkill /pid ${pid} /f /t`, { stdio: 'ignore' });
|
|
63
|
+
} catch (e) {
|
|
64
|
+
// Ignore errors if process is already dead
|
|
65
|
+
}
|
|
66
|
+
} else {
|
|
67
|
+
serverProcess.kill();
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
try {
|
|
71
|
+
await killPromise;
|
|
72
|
+
} catch (e) { }
|
|
73
|
+
serverProcess = null;
|
|
74
|
+
isKilling = false;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const delay = (ms) => new Promise(res => setTimeout(res, ms));
|
|
78
|
+
|
|
79
|
+
let spinnerTimer = null;
|
|
80
|
+
const frames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
81
|
+
let frameIdx = 0;
|
|
82
|
+
|
|
83
|
+
function startSpinner(text) {
|
|
84
|
+
if (spinnerTimer) clearInterval(spinnerTimer);
|
|
85
|
+
process.stdout.write("\x1B[?25l"); // Hide cursor
|
|
86
|
+
spinnerTimer = setInterval(() => {
|
|
87
|
+
process.stdout.write(`\r ${cyan(frames[frameIdx])} ${gray(text)}`);
|
|
88
|
+
frameIdx = (frameIdx + 1) % frames.length;
|
|
89
|
+
}, 80);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function stopSpinner(success = true, text = "") {
|
|
93
|
+
if (spinnerTimer) {
|
|
94
|
+
clearInterval(spinnerTimer);
|
|
95
|
+
spinnerTimer = null;
|
|
96
|
+
}
|
|
97
|
+
process.stdout.write("\r\x1B[K"); // Clear line
|
|
98
|
+
process.stdout.write("\x1B[?25h"); // Show cursor
|
|
99
|
+
if (text) {
|
|
100
|
+
if (success) {
|
|
101
|
+
console.log(` ${green("✔")} ${green(text)}`);
|
|
102
|
+
} else {
|
|
103
|
+
console.log(` ${red("✖")} ${red(text)}`);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
async function startRustServer(retryCount = 0) {
|
|
109
|
+
const waitTime = retryCount > 0 ? 500 : 200;
|
|
110
|
+
|
|
111
|
+
await killServer();
|
|
112
|
+
await delay(waitTime);
|
|
113
|
+
|
|
114
|
+
const serverPath = path.join(process.cwd(), "server");
|
|
115
|
+
const startTime = Date.now();
|
|
116
|
+
|
|
117
|
+
startSpinner("Stabilizing your app on its orbit...");
|
|
118
|
+
|
|
119
|
+
let isReady = false;
|
|
120
|
+
let stdoutBuffer = "";
|
|
121
|
+
let buildLogs = "";
|
|
122
|
+
|
|
123
|
+
// If it takes more than 15s, update the message
|
|
124
|
+
const slowTimer = setTimeout(() => {
|
|
125
|
+
if (!isReady && !isKilling) {
|
|
126
|
+
startSpinner("Still stabilizing... (the first orbit takes longer)");
|
|
127
|
+
}
|
|
128
|
+
}, 15000);
|
|
129
|
+
|
|
130
|
+
serverProcess = spawn("cargo", ["run", "--quiet"], {
|
|
131
|
+
cwd: serverPath,
|
|
132
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
133
|
+
env: { ...process.env, CARGO_INCREMENTAL: "1" }
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
serverProcess.on("error", (err) => {
|
|
137
|
+
stopSpinner(false, "Failed to start orbit");
|
|
138
|
+
console.error(red(`[Titan] Error: ${err.message}`));
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
serverProcess.stderr.on("data", (data) => {
|
|
142
|
+
const str = data.toString();
|
|
143
|
+
if (isReady) {
|
|
144
|
+
process.stderr.write(data);
|
|
145
|
+
} else {
|
|
146
|
+
buildLogs += str;
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
serverProcess.stdout.on("data", (data) => {
|
|
151
|
+
const out = data.toString();
|
|
152
|
+
|
|
153
|
+
if (!isReady) {
|
|
154
|
+
stdoutBuffer += out;
|
|
155
|
+
if (stdoutBuffer.includes("Titan server running") || stdoutBuffer.includes("████████╗")) {
|
|
156
|
+
isReady = true;
|
|
157
|
+
clearTimeout(slowTimer);
|
|
158
|
+
stopSpinner(true, "Your app is now orbiting Titan Planet");
|
|
159
|
+
process.stdout.write(stdoutBuffer);
|
|
160
|
+
stdoutBuffer = "";
|
|
161
|
+
}
|
|
162
|
+
} else {
|
|
163
|
+
process.stdout.write(data);
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
serverProcess.on("close", async (code) => {
|
|
168
|
+
clearTimeout(slowTimer);
|
|
169
|
+
if (isKilling) return;
|
|
170
|
+
const runTime = Date.now() - startTime;
|
|
171
|
+
|
|
172
|
+
if (code !== 0 && code !== null) {
|
|
173
|
+
stopSpinner(false, "Orbit stabilization failed");
|
|
174
|
+
if (!isReady) {
|
|
175
|
+
console.log(gray("\n--- Build Logs ---"));
|
|
176
|
+
console.log(buildLogs);
|
|
177
|
+
console.log(gray("------------------\n"));
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (runTime < 15000 && retryCount < 5) {
|
|
181
|
+
await delay(2000);
|
|
182
|
+
await startRustServer(retryCount + 1);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
async function rebuild() {
|
|
189
|
+
try {
|
|
190
|
+
execSync("node app/app.js", { stdio: "ignore" });
|
|
191
|
+
// bundle is called inside app.js (t.start)
|
|
192
|
+
} catch (e) {
|
|
193
|
+
stopSpinner(false, "Failed to prepare runtime");
|
|
194
|
+
console.log(red(`[Titan] Error: ${e.message}`));
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
async function startDev() {
|
|
199
|
+
const root = process.cwd();
|
|
200
|
+
const actionsDir = path.join(root, "app", "actions");
|
|
201
|
+
let hasRust = false;
|
|
202
|
+
if (fs.existsSync(actionsDir)) {
|
|
203
|
+
hasRust = fs.readdirSync(actionsDir).some(f => f.endsWith(".rs"));
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const mode = hasRust ? "Rust + JS Actions" : "JS Actions";
|
|
207
|
+
const version = getTitanVersion();
|
|
208
|
+
|
|
209
|
+
console.clear();
|
|
210
|
+
console.log("");
|
|
211
|
+
console.log(` ${bold(cyan("Titan Planet"))} ${gray("v" + version)} ${yellow("[ Dev Mode ]")}`);
|
|
212
|
+
console.log("");
|
|
213
|
+
console.log(` ${gray("Type: ")} ${mode}`);
|
|
214
|
+
console.log(` ${gray("Hot Reload: ")} ${green("Enabled")}`);
|
|
215
|
+
|
|
216
|
+
if (fs.existsSync(path.join(root, ".env"))) {
|
|
217
|
+
console.log(` ${gray("Env: ")} ${yellow("Loaded")}`);
|
|
218
|
+
}
|
|
219
|
+
console.log("");
|
|
220
|
+
|
|
221
|
+
try {
|
|
222
|
+
await rebuild();
|
|
223
|
+
await startRustServer();
|
|
224
|
+
} catch (e) {
|
|
225
|
+
// console.log(red("[Titan] Initial build failed. Waiting for changes..."));
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const watcher = chokidar.watch(["app", ".env"], {
|
|
229
|
+
ignoreInitial: true,
|
|
230
|
+
awaitWriteFinish: { stabilityThreshold: 500, pollInterval: 100 }
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
let timer = null;
|
|
234
|
+
watcher.on("all", async (event, file) => {
|
|
235
|
+
if (timer) clearTimeout(timer);
|
|
236
|
+
timer = setTimeout(async () => {
|
|
237
|
+
try {
|
|
238
|
+
await killServer();
|
|
239
|
+
await rebuild();
|
|
240
|
+
await startRustServer();
|
|
241
|
+
} catch (e) {
|
|
242
|
+
// console.log(red("[Titan] Build failed -- waiting for changes..."));
|
|
243
|
+
}
|
|
244
|
+
}, 300);
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
async function handleExit() {
|
|
249
|
+
stopSpinner();
|
|
250
|
+
console.log(gray("\n[Titan] Stopping server..."));
|
|
251
|
+
await killServer();
|
|
252
|
+
process.exit(0);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
process.on("SIGINT", handleExit);
|
|
256
|
+
process.on("SIGTERM", handleExit);
|
|
257
|
+
|
|
258
|
+
startDev();
|
|
@@ -25,3 +25,15 @@ jsonwebtoken = "9"
|
|
|
25
25
|
postgres = { version = "0.19", features = ["with-serde_json-1"] }
|
|
26
26
|
libloading = "0.8"
|
|
27
27
|
walkdir = "2"
|
|
28
|
+
|
|
29
|
+
[profile.dev]
|
|
30
|
+
opt-level = 0
|
|
31
|
+
debug = 1
|
|
32
|
+
incremental = true
|
|
33
|
+
|
|
34
|
+
[profile.release]
|
|
35
|
+
opt-level = 3
|
|
36
|
+
lto = true
|
|
37
|
+
codegen-units = 1
|
|
38
|
+
panic = "abort"
|
|
39
|
+
strip = true
|
|
@@ -400,12 +400,6 @@ async fn main() -> Result<()> {
|
|
|
400
400
|
|
|
401
401
|
let listener = TcpListener::bind(format!("0.0.0.0:{}", port)).await?;
|
|
402
402
|
|
|
403
|
-
println!("\n\x1b[38;5;208m████████╗██╗████████╗ █████╗ ███╗ ██╗");
|
|
404
|
-
println!("╚══██╔══╝██║╚══██╔══╝██╔══██╗████╗ ██║");
|
|
405
|
-
println!(" ██║ ██║ ██║ ███████║██╔██╗ ██║");
|
|
406
|
-
println!(" ██║ ██║ ██║ ██╔══██║██║╚██╗██║");
|
|
407
|
-
println!(" ██║ ██║ ██║ ██║ ██║██║ ╚████║");
|
|
408
|
-
println!(" ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝╚═╝ ╚═══╝\x1b[0m\n");
|
|
409
403
|
println!(
|
|
410
404
|
"\x1b[38;5;39mTitan server running at:\x1b[0m http://localhost:{}",
|
|
411
405
|
port
|