@ezetgalaxy/titan 25.11.7 → 25.11.8
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/index.js +51 -1
- package/package.json +1 -1
- package/templates/.dockerignore +3 -0
- package/templates/Dockerfile +40 -0
- package/templates/server/Cargo.lock +7 -0
- package/templates/server/Cargo.toml +1 -0
- package/templates/server/src/main.rs +100 -78
- package/templates/server/action_map.json +0 -3
- package/templates/server/actions/hello.jsbundle +0 -6
- package/templates/server/routes.json +0 -15
package/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
1
|
+
#!/usr/bin/env node
|
|
2
2
|
import fs from "fs";
|
|
3
3
|
import path from "path";
|
|
4
4
|
import { execSync, spawn } from "child_process";
|
|
@@ -43,6 +43,7 @@ ${green("tit init <project>")} Create new Titan project
|
|
|
43
43
|
${green("tit dev")} Dev mode (hot reload)
|
|
44
44
|
${green("tit build")} Build production Rust server
|
|
45
45
|
${green("tit start")} Start production binary
|
|
46
|
+
${green("tit update")} Update to latest version
|
|
46
47
|
`);
|
|
47
48
|
}
|
|
48
49
|
|
|
@@ -61,6 +62,14 @@ function initProject(name) {
|
|
|
61
62
|
console.log(cyan(`Creating Titan project → ${target}`));
|
|
62
63
|
|
|
63
64
|
copyDir(templateDir, target);
|
|
65
|
+
copyDir(templateDir, target);
|
|
66
|
+
|
|
67
|
+
[".gitignore", ".dockerignore", "Dockerfile"].forEach((file) => {
|
|
68
|
+
const src = path.join(templateDir, file);
|
|
69
|
+
const dest = path.join(target, file);
|
|
70
|
+
if (fs.existsSync(src)) fs.copyFileSync(src, dest);
|
|
71
|
+
});
|
|
72
|
+
|
|
64
73
|
|
|
65
74
|
console.log(green("✔ Titan project created!"));
|
|
66
75
|
console.log(cyan("Installing dependencies..."));
|
|
@@ -187,6 +196,42 @@ function startProd() {
|
|
|
187
196
|
execSync(`"${exe}"`, { stdio: "inherit" });
|
|
188
197
|
}
|
|
189
198
|
|
|
199
|
+
// ------------------------------------------
|
|
200
|
+
// TITAN UPDATE — Upgrade titan/ runtime
|
|
201
|
+
// ------------------------------------------
|
|
202
|
+
function updateTitan() {
|
|
203
|
+
const projectTitan = path.join(process.cwd(), "titan");
|
|
204
|
+
const cliTitan = path.join(__dirname, "templates", "titan");
|
|
205
|
+
|
|
206
|
+
if (!fs.existsSync(projectTitan)) {
|
|
207
|
+
console.log(red("No titan/ folder found in this project."));
|
|
208
|
+
console.log(yellow("Make sure you are inside a Titan project."));
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
console.log(cyan("Titan: Updating runtime files..."));
|
|
213
|
+
|
|
214
|
+
// Backup old titan folder
|
|
215
|
+
const backupDir = path.join(process.cwd(), `titan_backup_${Date.now()}`);
|
|
216
|
+
fs.renameSync(projectTitan, backupDir);
|
|
217
|
+
|
|
218
|
+
console.log(green(`✔ Backup created → ${backupDir}`));
|
|
219
|
+
|
|
220
|
+
copyDir(cliTitan, projectTitan);
|
|
221
|
+
copyDir(templateDir, target);
|
|
222
|
+
|
|
223
|
+
[".gitignore", ".dockerignore", "Dockerfile"].forEach((file) => {
|
|
224
|
+
const src = path.join(templateDir, file);
|
|
225
|
+
const dest = path.join(target, file);
|
|
226
|
+
if (fs.existsSync(src)) fs.copyFileSync(src, dest);
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
console.log(green("✔ Titan runtime updated successfully!"));
|
|
231
|
+
console.log(cyan("Your project now has the latest Titan features."));
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
|
|
190
235
|
// ROUTER
|
|
191
236
|
switch (cmd) {
|
|
192
237
|
case "init":
|
|
@@ -205,6 +250,11 @@ switch (cmd) {
|
|
|
205
250
|
startProd();
|
|
206
251
|
break;
|
|
207
252
|
|
|
253
|
+
case "update":
|
|
254
|
+
updateTitan();
|
|
255
|
+
break;
|
|
256
|
+
|
|
208
257
|
default:
|
|
209
258
|
help();
|
|
210
259
|
}
|
|
260
|
+
|
package/package.json
CHANGED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# Stage 1 — Build Titan (JS → Rust)
|
|
2
|
+
FROM rust:1.91.1 AS builder
|
|
3
|
+
|
|
4
|
+
# Install Node for Titan CLI
|
|
5
|
+
RUN curl -fsSL https://deb.nodesource.com/setup_20.x | bash - \
|
|
6
|
+
&& apt-get install -y nodejs
|
|
7
|
+
|
|
8
|
+
# Install Titan CLI AND its required dependencies
|
|
9
|
+
RUN npm install -g @ezetgalaxy/titan esbuild chokidar
|
|
10
|
+
|
|
11
|
+
WORKDIR /app
|
|
12
|
+
|
|
13
|
+
# Copy project
|
|
14
|
+
COPY . .
|
|
15
|
+
|
|
16
|
+
# Install Titan dependencies LOCALLY
|
|
17
|
+
RUN npm install esbuild chokidar
|
|
18
|
+
|
|
19
|
+
# Build Titan metadata (routes + bundles)
|
|
20
|
+
RUN tit build
|
|
21
|
+
|
|
22
|
+
# Build Rust release binary
|
|
23
|
+
RUN cd server && cargo build --release
|
|
24
|
+
|
|
25
|
+
# Stage 2 — Run Titan server
|
|
26
|
+
FROM debian:stable-slim
|
|
27
|
+
|
|
28
|
+
WORKDIR /app
|
|
29
|
+
|
|
30
|
+
# Copy final Rust binary
|
|
31
|
+
COPY --from=builder /app/server/target/release/server ./titan-server
|
|
32
|
+
|
|
33
|
+
# Copy Titan runtime files
|
|
34
|
+
COPY --from=builder /app/server/routes.json .
|
|
35
|
+
COPY --from=builder /app/server/action_map.json .
|
|
36
|
+
COPY --from=builder /app/server/actions ./actions
|
|
37
|
+
|
|
38
|
+
EXPOSE 3000
|
|
39
|
+
|
|
40
|
+
CMD ["./titan-server"]
|
|
@@ -393,6 +393,12 @@ version = "0.15.0"
|
|
|
393
393
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
394
394
|
checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f"
|
|
395
395
|
|
|
396
|
+
[[package]]
|
|
397
|
+
name = "dotenvy"
|
|
398
|
+
version = "0.15.7"
|
|
399
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
400
|
+
checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b"
|
|
401
|
+
|
|
396
402
|
[[package]]
|
|
397
403
|
name = "dynify"
|
|
398
404
|
version = "0.1.2"
|
|
@@ -1807,6 +1813,7 @@ dependencies = [
|
|
|
1807
1813
|
"axum",
|
|
1808
1814
|
"boa_engine",
|
|
1809
1815
|
"dotenv",
|
|
1816
|
+
"dotenvy",
|
|
1810
1817
|
"reqwest",
|
|
1811
1818
|
"serde",
|
|
1812
1819
|
"serde_json",
|
|
@@ -9,11 +9,18 @@ use axum::{
|
|
|
9
9
|
routing::any,
|
|
10
10
|
Router,
|
|
11
11
|
};
|
|
12
|
-
use boa_engine::{
|
|
12
|
+
use boa_engine::{
|
|
13
|
+
Context,
|
|
14
|
+
Source
|
|
15
|
+
};
|
|
16
|
+
|
|
13
17
|
use serde::Deserialize;
|
|
14
18
|
use serde_json::Value;
|
|
15
19
|
use tokio::net::TcpListener;
|
|
16
20
|
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
|
|
17
24
|
// ----------------------
|
|
18
25
|
// Route structures
|
|
19
26
|
// ----------------------
|
|
@@ -63,72 +70,88 @@ async fn dynamic_handler_inner(
|
|
|
63
70
|
// ACTION ROUTE
|
|
64
71
|
// --------------------------
|
|
65
72
|
"action" => {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
+
let action_name = route.value.as_str().unwrap_or("").trim();
|
|
74
|
+
if action_name.is_empty() {
|
|
75
|
+
return (
|
|
76
|
+
StatusCode::INTERNAL_SERVER_ERROR,
|
|
77
|
+
"Invalid action name",
|
|
78
|
+
)
|
|
79
|
+
.into_response();
|
|
80
|
+
}
|
|
73
81
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
82
|
+
// correct action path
|
|
83
|
+
let action_path = state
|
|
84
|
+
.project_root
|
|
85
|
+
.join("server")
|
|
86
|
+
.join("actions")
|
|
87
|
+
.join(format!("{}.jsbundle", action_name));
|
|
88
|
+
|
|
89
|
+
if !action_path.exists() {
|
|
90
|
+
return (
|
|
91
|
+
StatusCode::NOT_FOUND,
|
|
92
|
+
format!("Action bundle not found: {:?}", action_path),
|
|
93
|
+
)
|
|
94
|
+
.into_response();
|
|
95
|
+
}
|
|
88
96
|
|
|
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
|
-
|
|
97
|
+
// read JS bundle
|
|
98
|
+
let js_code = match fs::read_to_string(action_path) {
|
|
99
|
+
Ok(v) => v,
|
|
100
|
+
Err(e) => {
|
|
101
|
+
return (
|
|
102
|
+
StatusCode::INTERNAL_SERVER_ERROR,
|
|
103
|
+
format!("Failed reading action bundle: {}", e),
|
|
104
|
+
)
|
|
105
|
+
.into_response();
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
// ---------------------------
|
|
110
|
+
// ENV injection (correct way)
|
|
111
|
+
// ---------------------------
|
|
112
|
+
let mut env_map = serde_json::Map::new();
|
|
113
|
+
for (k, v) in std::env::vars() {
|
|
114
|
+
env_map.insert(k, Value::String(v));
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
let env_json = serde_json::Value::Object(env_map);
|
|
118
|
+
|
|
119
|
+
// Final JS code to execute in Boa
|
|
120
|
+
let injected = format!(
|
|
121
|
+
r#"
|
|
122
|
+
globalThis.process = {{
|
|
123
|
+
env: {}
|
|
124
|
+
}};
|
|
125
|
+
|
|
126
|
+
const __titan_req = {};
|
|
127
|
+
{};
|
|
128
|
+
|
|
129
|
+
{}(__titan_req);
|
|
130
|
+
"#,
|
|
131
|
+
env_json.to_string(),
|
|
132
|
+
body_str,
|
|
133
|
+
js_code,
|
|
134
|
+
action_name
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
// Execute JS safely
|
|
138
|
+
let mut ctx = Context::default();
|
|
139
|
+
let result = match ctx.eval(Source::from_bytes(&injected)) {
|
|
140
|
+
Ok(v) => v,
|
|
141
|
+
Err(e) => {
|
|
142
|
+
return Json(json_error(e.to_string())).into_response();
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
// Convert Boa -> JSON
|
|
147
|
+
let result_json: Value = match result.to_json(&mut ctx) {
|
|
148
|
+
Ok(Some(v)) => v,
|
|
149
|
+
Ok(None) => serde_json::json!({ "error": "JS returned undefined" }),
|
|
150
|
+
Err(e) => json_error(e.to_string()),
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
return Json(result_json).into_response();
|
|
154
|
+
}
|
|
132
155
|
|
|
133
156
|
// --------------------------
|
|
134
157
|
// STATIC JSON
|
|
@@ -154,11 +177,15 @@ fn json_error(msg: String) -> Value {
|
|
|
154
177
|
serde_json::json!({ "error": msg })
|
|
155
178
|
}
|
|
156
179
|
|
|
180
|
+
|
|
181
|
+
|
|
157
182
|
// ----------------------
|
|
158
183
|
// MAIN
|
|
159
184
|
// ----------------------
|
|
160
185
|
#[tokio::main]
|
|
161
186
|
async fn main() -> Result<()> {
|
|
187
|
+
dotenvy::dotenv().ok();
|
|
188
|
+
|
|
162
189
|
let raw = fs::read_to_string("./routes.json").unwrap_or_else(|_| "{}".to_string());
|
|
163
190
|
let json: Value = serde_json::from_str(&raw).unwrap_or_default();
|
|
164
191
|
|
|
@@ -190,20 +217,15 @@ async fn main() -> Result<()> {
|
|
|
190
217
|
//
|
|
191
218
|
// TITAN BANNER
|
|
192
219
|
//
|
|
193
|
-
println!(
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
██║ ██║ ██║
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
);
|
|
220
|
+
println!("\n\x1b[38;5;208m████████╗██╗████████╗ █████╗ ███╗ ██╗");
|
|
221
|
+
println!("╚══██╔══╝██║╚══██╔══╝██╔══██╗████╗ ██║");
|
|
222
|
+
println!(" ██║ ██║ ██║ ███████║██╔██╗ ██║");
|
|
223
|
+
println!(" ██║ ██║ ██║ ██╔══██║██║╚██╗██║");
|
|
224
|
+
println!(" ██║ ██║ ██║ ██║ ██║██║ ╚████║");
|
|
225
|
+
println!(" ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝╚═╝ ╚═══╝\x1b[0m\n");
|
|
226
|
+
|
|
227
|
+
println!("\x1b[38;5;39mTitan server running at:\x1b[0m http://localhost:{}", port);
|
|
202
228
|
|
|
203
|
-
println!(
|
|
204
|
-
"\x1b[38;5;39mTitan server running at:\x1b[0m \x1b[97mhttp://localhost:{}\x1b[0m\n",
|
|
205
|
-
port
|
|
206
|
-
);
|
|
207
229
|
axum::serve(listener, app).await?;
|
|
208
230
|
Ok(())
|
|
209
231
|
}
|