@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.
- package/README.md +88 -201
- package/index.js +552 -489
- package/package.json +6 -5
- package/templates/js/_gitignore +37 -0
- package/templates/{package.json → js/package.json} +4 -1
- package/templates/js/server/action_map.json +3 -0
- package/templates/js/server/actions/hello.jsbundle +48 -0
- package/templates/js/server/routes.json +16 -0
- package/templates/js/server/src/actions_rust/mod.rs +15 -0
- package/templates/{server → js/server}/src/extensions.rs +149 -17
- package/templates/{titan → js/titan}/bundle.js +22 -9
- package/templates/js/titan/dev.js +194 -0
- package/templates/{titan → js/titan}/titan.js +25 -1
- package/templates/rust/Dockerfile +66 -0
- package/templates/rust/_dockerignore +3 -0
- package/templates/rust/_gitignore +37 -0
- package/templates/rust/app/actions/hello.js +5 -0
- package/templates/rust/app/actions/rust_hello.rs +14 -0
- package/templates/rust/app/app.js +11 -0
- package/templates/rust/app/titan.d.ts +101 -0
- package/templates/rust/jsconfig.json +19 -0
- package/templates/rust/package.json +13 -0
- package/templates/rust/server/Cargo.lock +2869 -0
- package/templates/rust/server/Cargo.toml +27 -0
- package/templates/rust/server/action_map.json +3 -0
- package/templates/rust/server/actions/hello.jsbundle +47 -0
- package/templates/rust/server/routes.json +22 -0
- package/templates/rust/server/src/action_management.rs +131 -0
- package/templates/rust/server/src/actions_rust/mod.rs +19 -0
- package/templates/rust/server/src/actions_rust/rust_hello.rs +14 -0
- package/templates/rust/server/src/errors.rs +10 -0
- package/templates/rust/server/src/extensions.rs +989 -0
- package/templates/rust/server/src/main.rs +443 -0
- package/templates/rust/server/src/utils.rs +33 -0
- package/templates/rust/titan/bundle.js +157 -0
- package/templates/rust/titan/dev.js +194 -0
- package/templates/rust/titan/titan.js +122 -0
- package/titanpl-sdk/package.json +1 -1
- package/titanpl-sdk/templates/Dockerfile +4 -17
- package/titanpl-sdk/templates/server/src/extensions.rs +218 -423
- package/titanpl-sdk/templates/server/src/main.rs +68 -134
- package/scripts/make_dist.sh +0 -71
- package/templates/titan/dev.js +0 -144
- /package/templates/{Dockerfile → js/Dockerfile} +0 -0
- /package/templates/{.dockerignore → js/_dockerignore} +0 -0
- /package/templates/{app → js/app}/actions/hello.js +0 -0
- /package/templates/{app → js/app}/app.js +0 -0
- /package/templates/{app → js/app}/titan.d.ts +0 -0
- /package/templates/{jsconfig.json → js/jsconfig.json} +0 -0
- /package/templates/{server → js/server}/Cargo.lock +0 -0
- /package/templates/{server → js/server}/Cargo.toml +0 -0
- /package/templates/{server → js/server}/src/action_management.rs +0 -0
- /package/templates/{server → js/server}/src/errors.rs +0 -0
- /package/templates/{server → js/server}/src/main.rs +0 -0
- /package/templates/{server → js/server}/src/utils.rs +0 -0
|
@@ -1,27 +1,28 @@
|
|
|
1
|
+
use std::{collections::HashMap, fs, path::PathBuf, sync::Arc};
|
|
1
2
|
use anyhow::Result;
|
|
2
3
|
use axum::{
|
|
3
|
-
|
|
4
|
-
body::{Body, to_bytes},
|
|
4
|
+
body::{to_bytes, Body},
|
|
5
5
|
extract::State,
|
|
6
6
|
http::{Request, StatusCode},
|
|
7
7
|
response::{IntoResponse, Json},
|
|
8
8
|
routing::any,
|
|
9
|
+
Router,
|
|
9
10
|
};
|
|
10
11
|
use serde_json::Value;
|
|
11
|
-
use std::time::Instant;
|
|
12
|
-
use std::{collections::HashMap, fs, path::PathBuf, sync::Arc};
|
|
13
12
|
use tokio::net::TcpListener;
|
|
13
|
+
use std::time::Instant;
|
|
14
14
|
|
|
15
15
|
mod utils;
|
|
16
16
|
|
|
17
|
-
mod action_management;
|
|
18
17
|
mod extensions;
|
|
18
|
+
mod action_management;
|
|
19
19
|
|
|
20
|
+
use utils::{blue, white, yellow, green, gray, red};
|
|
21
|
+
use extensions::{init_v8, inject_extensions};
|
|
20
22
|
use action_management::{
|
|
21
|
-
|
|
23
|
+
resolve_actions_dir, find_actions_dir, match_dynamic_route,
|
|
24
|
+
DynamicRoute, RouteVal
|
|
22
25
|
};
|
|
23
|
-
use extensions::{init_v8, inject_extensions};
|
|
24
|
-
use utils::{blue, gray, green, red, white, yellow};
|
|
25
26
|
|
|
26
27
|
#[derive(Clone)]
|
|
27
28
|
struct AppState {
|
|
@@ -44,6 +45,7 @@ async fn dynamic_handler_inner(
|
|
|
44
45
|
State(state): State<AppState>,
|
|
45
46
|
req: Request<Body>,
|
|
46
47
|
) -> impl IntoResponse {
|
|
48
|
+
|
|
47
49
|
// ---------------------------
|
|
48
50
|
// BASIC REQUEST INFO
|
|
49
51
|
// ---------------------------
|
|
@@ -68,7 +70,10 @@ async fn dynamic_handler_inner(
|
|
|
68
70
|
q.split('&')
|
|
69
71
|
.filter_map(|pair| {
|
|
70
72
|
let mut it = pair.splitn(2, '=');
|
|
71
|
-
Some((
|
|
73
|
+
Some((
|
|
74
|
+
it.next()?.to_string(),
|
|
75
|
+
it.next().unwrap_or("").to_string(),
|
|
76
|
+
))
|
|
72
77
|
})
|
|
73
78
|
.collect()
|
|
74
79
|
})
|
|
@@ -78,7 +83,7 @@ async fn dynamic_handler_inner(
|
|
|
78
83
|
// HEADERS & BODY
|
|
79
84
|
// ---------------------------
|
|
80
85
|
let (parts, body) = req.into_parts();
|
|
81
|
-
|
|
86
|
+
|
|
82
87
|
let headers = parts
|
|
83
88
|
.headers
|
|
84
89
|
.iter()
|
|
@@ -87,7 +92,9 @@ async fn dynamic_handler_inner(
|
|
|
87
92
|
|
|
88
93
|
let body_bytes = match to_bytes(body, usize::MAX).await {
|
|
89
94
|
Ok(b) => b,
|
|
90
|
-
Err(_) =>
|
|
95
|
+
Err(_) => {
|
|
96
|
+
return (StatusCode::BAD_REQUEST, "Failed to read request body").into_response()
|
|
97
|
+
}
|
|
91
98
|
};
|
|
92
99
|
|
|
93
100
|
let body_str = String::from_utf8_lossy(&body_bytes).to_string();
|
|
@@ -112,32 +119,18 @@ async fn dynamic_handler_inner(
|
|
|
112
119
|
action_name = Some(name);
|
|
113
120
|
} else if route.r#type == "json" {
|
|
114
121
|
let elapsed = start.elapsed();
|
|
115
|
-
println!(
|
|
116
|
-
"{} {} {} {}",
|
|
117
|
-
blue("[Titan]"),
|
|
118
|
-
white(&format!("{} {}", method, path)),
|
|
119
|
-
white("→ json"),
|
|
120
|
-
gray(&format!("in {:.2?}", elapsed))
|
|
121
|
-
);
|
|
122
|
+
println!("{} {} {} {}", blue("[Titan]"), white(&format!("{} {}", method, path)), white("→ json"), gray(&format!("in {:.2?}", elapsed)));
|
|
122
123
|
return Json(route.value.clone()).into_response();
|
|
123
124
|
} else if let Some(s) = route.value.as_str() {
|
|
124
125
|
let elapsed = start.elapsed();
|
|
125
|
-
println!(
|
|
126
|
-
"{} {} {} {}",
|
|
127
|
-
blue("[Titan]"),
|
|
128
|
-
white(&format!("{} {}", method, path)),
|
|
129
|
-
white("→ reply"),
|
|
130
|
-
gray(&format!("in {:.2?}", elapsed))
|
|
131
|
-
);
|
|
126
|
+
println!("{} {} {} {}", blue("[Titan]"), white(&format!("{} {}", method, path)), white("→ reply"), gray(&format!("in {:.2?}", elapsed)));
|
|
132
127
|
return s.to_string().into_response();
|
|
133
128
|
}
|
|
134
129
|
}
|
|
135
130
|
|
|
136
131
|
// Dynamic route
|
|
137
132
|
if action_name.is_none() {
|
|
138
|
-
if let Some((action, p)) =
|
|
139
|
-
match_dynamic_route(&method, &path, state.dynamic_routes.as_slice())
|
|
140
|
-
{
|
|
133
|
+
if let Some((action, p)) = match_dynamic_route(&method, &path, state.dynamic_routes.as_slice()) {
|
|
141
134
|
route_kind = "dynamic";
|
|
142
135
|
route_label = action.clone();
|
|
143
136
|
action_name = Some(action);
|
|
@@ -149,13 +142,7 @@ async fn dynamic_handler_inner(
|
|
|
149
142
|
Some(a) => a,
|
|
150
143
|
None => {
|
|
151
144
|
let elapsed = start.elapsed();
|
|
152
|
-
println!(
|
|
153
|
-
"{} {} {} {}",
|
|
154
|
-
blue("[Titan]"),
|
|
155
|
-
white(&format!("{} {}", method, path)),
|
|
156
|
-
white("→ 404"),
|
|
157
|
-
gray(&format!("in {:.2?}", elapsed))
|
|
158
|
-
);
|
|
145
|
+
println!("{} {} {} {}", blue("[Titan]"), white(&format!("{} {}", method, path)), white("→ 404"), gray(&format!("in {:.2?}", elapsed)));
|
|
159
146
|
return (StatusCode::NOT_FOUND, "Not Found").into_response();
|
|
160
147
|
}
|
|
161
148
|
};
|
|
@@ -164,8 +151,7 @@ async fn dynamic_handler_inner(
|
|
|
164
151
|
// LOAD ACTION
|
|
165
152
|
// ---------------------------
|
|
166
153
|
let resolved = resolve_actions_dir();
|
|
167
|
-
let actions_dir = resolved
|
|
168
|
-
.exists()
|
|
154
|
+
let actions_dir = resolved.exists()
|
|
169
155
|
.then(|| resolved)
|
|
170
156
|
.or_else(|| find_actions_dir(&state.project_root))
|
|
171
157
|
.unwrap();
|
|
@@ -178,24 +164,17 @@ async fn dynamic_handler_inner(
|
|
|
178
164
|
}
|
|
179
165
|
}
|
|
180
166
|
|
|
181
|
-
let js_code =
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
serde_json::json!({"error": "Action bundle not found", "action": action_name}),
|
|
188
|
-
),
|
|
189
|
-
)
|
|
190
|
-
.into_response(),
|
|
191
|
-
};
|
|
167
|
+
let js_code = match fs::read_to_string(&action_path) {
|
|
168
|
+
Ok(c) => c,
|
|
169
|
+
Err(_) => {
|
|
170
|
+
return (StatusCode::INTERNAL_SERVER_ERROR, Json(serde_json::json!({"error": "Action bundle not found", "action": action_name}))).into_response()
|
|
171
|
+
}
|
|
172
|
+
};
|
|
192
173
|
|
|
193
174
|
// ---------------------------
|
|
194
175
|
// EXECUTE IN V8
|
|
195
176
|
// ---------------------------
|
|
196
|
-
let env_json = std::env::vars()
|
|
197
|
-
.map(|(k, v)| (k, Value::String(v)))
|
|
198
|
-
.collect::<serde_json::Map<_, _>>();
|
|
177
|
+
let env_json = std::env::vars().map(|(k, v)| (k, Value::String(v))).collect::<serde_json::Map<_, _>>();
|
|
199
178
|
|
|
200
179
|
let injected = format!(
|
|
201
180
|
r#"
|
|
@@ -236,42 +215,43 @@ async fn dynamic_handler_inner(
|
|
|
236
215
|
// We can use `task::spawn_blocking`.
|
|
237
216
|
let root = state.project_root.clone();
|
|
238
217
|
let action_name_for_v8 = action_name.clone();
|
|
239
|
-
|
|
218
|
+
|
|
240
219
|
let result_json: Value = tokio::task::spawn_blocking(move || {
|
|
241
220
|
let isolate = &mut v8::Isolate::new(v8::CreateParams::default());
|
|
242
221
|
let handle_scope = &mut v8::HandleScope::new(isolate);
|
|
243
222
|
let context = v8::Context::new(handle_scope, v8::ContextOptions::default());
|
|
244
223
|
let scope = &mut v8::ContextScope::new(handle_scope, context);
|
|
245
|
-
|
|
224
|
+
|
|
246
225
|
let global = context.global(scope);
|
|
247
226
|
|
|
248
227
|
// Inject extensions (t.read, etc)
|
|
249
228
|
inject_extensions(scope, global);
|
|
250
|
-
|
|
229
|
+
|
|
230
|
+
// Set metadata globals
|
|
251
231
|
// Set metadata globals
|
|
252
232
|
let root_str = v8::String::new(scope, root.to_str().unwrap_or(".")).unwrap();
|
|
253
233
|
let root_key = v8::String::new(scope, "__titan_root").unwrap();
|
|
254
234
|
global.set(scope, root_key.into(), root_str.into());
|
|
255
|
-
|
|
235
|
+
|
|
256
236
|
let action_str = v8::String::new(scope, &action_name_for_v8).unwrap();
|
|
257
237
|
let action_key = v8::String::new(scope, "__titan_action").unwrap();
|
|
258
238
|
global.set(scope, action_key.into(), action_str.into());
|
|
259
|
-
|
|
239
|
+
|
|
260
240
|
let source = v8::String::new(scope, &injected).unwrap();
|
|
261
|
-
|
|
241
|
+
|
|
262
242
|
let try_catch = &mut v8::TryCatch::new(scope);
|
|
263
|
-
|
|
243
|
+
|
|
264
244
|
let script = match v8::Script::compile(try_catch, source, None) {
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
245
|
+
Some(s) => s,
|
|
246
|
+
None => {
|
|
247
|
+
let err = try_catch.message().unwrap();
|
|
248
|
+
let msg = err.get(try_catch).to_rust_string_lossy(try_catch);
|
|
249
|
+
return serde_json::json!({ "error": msg, "phase": "compile" });
|
|
250
|
+
}
|
|
271
251
|
};
|
|
272
|
-
|
|
252
|
+
|
|
273
253
|
let result = script.run(try_catch);
|
|
274
|
-
|
|
254
|
+
|
|
275
255
|
match result {
|
|
276
256
|
Some(val) => {
|
|
277
257
|
// Convert v8 Value to Serde JSON
|
|
@@ -279,59 +259,37 @@ async fn dynamic_handler_inner(
|
|
|
279
259
|
let json_obj = v8::json::stringify(try_catch, val).unwrap();
|
|
280
260
|
let json_str = json_obj.to_rust_string_lossy(try_catch);
|
|
281
261
|
serde_json::from_str(&json_str).unwrap_or(Value::Null)
|
|
282
|
-
}
|
|
262
|
+
},
|
|
283
263
|
None => {
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
264
|
+
let err = try_catch.message().unwrap();
|
|
265
|
+
let msg = err.get(try_catch).to_rust_string_lossy(try_catch);
|
|
266
|
+
serde_json::json!({ "error": msg, "phase": "execution" })
|
|
287
267
|
}
|
|
288
268
|
}
|
|
289
|
-
})
|
|
290
|
-
.await
|
|
291
|
-
.unwrap_or(serde_json::json!({"error": "V8 task failed"}));
|
|
269
|
+
}).await.unwrap_or(serde_json::json!({"error": "V8 task failed"}));
|
|
292
270
|
|
|
293
271
|
// ---------------------------
|
|
294
272
|
// FINAL LOG
|
|
295
273
|
// ---------------------------
|
|
296
274
|
let elapsed = start.elapsed();
|
|
297
|
-
|
|
275
|
+
|
|
298
276
|
// Check for errors in result
|
|
299
277
|
if let Some(err) = result_json.get("error") {
|
|
300
|
-
println!(
|
|
301
|
-
"{} {} {} {}",
|
|
302
|
-
blue("[Titan]"),
|
|
303
|
-
red(&format!("{} {}", method, path)),
|
|
304
|
-
red("→ error"),
|
|
305
|
-
gray(&format!("in {:.2?}", elapsed))
|
|
306
|
-
);
|
|
278
|
+
println!("{} {} {} {}", blue("[Titan]"), red(&format!("{} {}", method, path)), red("→ error"), gray(&format!("in {:.2?}", elapsed)));
|
|
307
279
|
println!("{}", red(err.as_str().unwrap_or("Unknown")));
|
|
308
280
|
return (StatusCode::INTERNAL_SERVER_ERROR, Json(result_json)).into_response();
|
|
309
281
|
}
|
|
310
282
|
|
|
311
283
|
match route_kind {
|
|
312
|
-
"dynamic" => println!(
|
|
313
|
-
|
|
314
|
-
blue("[Titan]"),
|
|
315
|
-
green(&format!("{} {}", method, path)),
|
|
316
|
-
white("→"),
|
|
317
|
-
green(&route_label),
|
|
318
|
-
white("(dynamic)"),
|
|
319
|
-
gray(&format!("in {:.2?}", elapsed))
|
|
320
|
-
),
|
|
321
|
-
"exact" => println!(
|
|
322
|
-
"{} {} {} {} {}",
|
|
323
|
-
blue("[Titan]"),
|
|
324
|
-
white(&format!("{} {}", method, path)),
|
|
325
|
-
white("→"),
|
|
326
|
-
yellow(&route_label),
|
|
327
|
-
gray(&format!("in {:.2?}", elapsed))
|
|
328
|
-
),
|
|
284
|
+
"dynamic" => println!("{} {} {} {} {} {}", blue("[Titan]"), green(&format!("{} {}", method, path)), white("→"), green(&route_label), white("(dynamic)"), gray(&format!("in {:.2?}", elapsed))),
|
|
285
|
+
"exact" => println!("{} {} {} {} {}", blue("[Titan]"), white(&format!("{} {}", method, path)), white("→"), yellow(&route_label), gray(&format!("in {:.2?}", elapsed))),
|
|
329
286
|
_ => {}
|
|
330
287
|
}
|
|
331
288
|
|
|
332
289
|
Json(result_json).into_response()
|
|
333
290
|
}
|
|
334
291
|
|
|
292
|
+
|
|
335
293
|
// Entrypoint ---------------------------------------------------------------
|
|
336
294
|
|
|
337
295
|
#[tokio::main]
|
|
@@ -346,22 +304,28 @@ async fn main() -> Result<()> {
|
|
|
346
304
|
let port = json["__config"]["port"].as_u64().unwrap_or(3000);
|
|
347
305
|
let routes_json = json["routes"].clone();
|
|
348
306
|
let map: HashMap<String, RouteVal> = serde_json::from_value(routes_json).unwrap_or_default();
|
|
349
|
-
let dynamic_routes: Vec<DynamicRoute> =
|
|
350
|
-
serde_json::from_value(json["__dynamic_routes"].clone()).unwrap_or_default();
|
|
307
|
+
let dynamic_routes: Vec<DynamicRoute> = serde_json::from_value(json["__dynamic_routes"].clone()).unwrap_or_default();
|
|
351
308
|
|
|
352
|
-
//
|
|
353
|
-
let
|
|
309
|
+
// Project root — heuristics
|
|
310
|
+
let cwd = std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."));
|
|
311
|
+
// Ensure we find the 'app' directory as sibling if running from 'server'
|
|
312
|
+
let project_root = if cwd.join("../app").exists() {
|
|
313
|
+
cwd.join("../app")
|
|
314
|
+
} else {
|
|
315
|
+
cwd
|
|
316
|
+
};
|
|
354
317
|
|
|
355
318
|
let state = AppState {
|
|
356
319
|
routes: Arc::new(map),
|
|
357
320
|
dynamic_routes: Arc::new(dynamic_routes),
|
|
358
321
|
project_root: project_root.clone(),
|
|
359
322
|
};
|
|
360
|
-
|
|
323
|
+
|
|
361
324
|
// Load extensions
|
|
362
325
|
extensions::load_project_extensions(project_root.clone());
|
|
363
326
|
|
|
364
327
|
let app = Router::new()
|
|
328
|
+
|
|
365
329
|
.route("/", any(root_route))
|
|
366
330
|
.fallback(any(dynamic_route))
|
|
367
331
|
.with_state(state);
|
|
@@ -374,38 +338,8 @@ async fn main() -> Result<()> {
|
|
|
374
338
|
println!(" ██║ ██║ ██║ ██╔══██║██║╚██╗██║");
|
|
375
339
|
println!(" ██║ ██║ ██║ ██║ ██║██║ ╚████║");
|
|
376
340
|
println!(" ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝╚═╝ ╚═══╝\x1b[0m\n");
|
|
377
|
-
println!(
|
|
378
|
-
"\x1b[38;5;39mTitan server running at:\x1b[0m http://localhost:{}",
|
|
379
|
-
port
|
|
380
|
-
);
|
|
341
|
+
println!("\x1b[38;5;39mTitan server running at:\x1b[0m http://localhost:{}", port);
|
|
381
342
|
|
|
382
343
|
axum::serve(listener, app).await?;
|
|
383
344
|
Ok(())
|
|
384
345
|
}
|
|
385
|
-
|
|
386
|
-
fn resolve_project_root() -> PathBuf {
|
|
387
|
-
// 1. Check CWD (preferred for local dev/tooling)
|
|
388
|
-
if let Ok(cwd) = std::env::current_dir() {
|
|
389
|
-
if cwd.join("node_modules").exists()
|
|
390
|
-
|| cwd.join("package.json").exists()
|
|
391
|
-
|| cwd.join(".ext").exists()
|
|
392
|
-
{
|
|
393
|
-
return cwd;
|
|
394
|
-
}
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
// 2. Check executable persistence (Docker / Production)
|
|
398
|
-
// Walk up from the executable to find .ext or node_modules
|
|
399
|
-
if let Ok(exe) = std::env::current_exe() {
|
|
400
|
-
let mut current = exe.parent();
|
|
401
|
-
while let Some(dir) = current {
|
|
402
|
-
if dir.join(".ext").exists() || dir.join("node_modules").exists() {
|
|
403
|
-
return dir.to_path_buf();
|
|
404
|
-
}
|
|
405
|
-
current = dir.parent();
|
|
406
|
-
}
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
// 3. Fallback to CWD
|
|
410
|
-
std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."))
|
|
411
|
-
}
|
package/scripts/make_dist.sh
DELETED
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
set -e
|
|
3
|
-
|
|
4
|
-
echo "Building Titan distribution..."
|
|
5
|
-
|
|
6
|
-
# ---------------------------------------------
|
|
7
|
-
# Resolve directories
|
|
8
|
-
# ---------------------------------------------
|
|
9
|
-
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
|
10
|
-
SERVER_DIR="$ROOT/server"
|
|
11
|
-
DIST_DIR="$ROOT/dist"
|
|
12
|
-
|
|
13
|
-
# Clean and recreate dist/
|
|
14
|
-
rm -rf "$DIST_DIR"
|
|
15
|
-
mkdir -p "$DIST_DIR"
|
|
16
|
-
|
|
17
|
-
# ---------------------------------------------
|
|
18
|
-
# Copy release binary titan-server
|
|
19
|
-
# ---------------------------------------------
|
|
20
|
-
RELEASE_PATH="$SERVER_DIR/target/release"
|
|
21
|
-
|
|
22
|
-
echo "Looking for titan-server binary..."
|
|
23
|
-
|
|
24
|
-
if [ -f "$RELEASE_PATH/titan-server" ]; then
|
|
25
|
-
echo "✓ Found titan-server"
|
|
26
|
-
cp "$RELEASE_PATH/titan-server" "$DIST_DIR/"
|
|
27
|
-
else
|
|
28
|
-
echo "Binary not found directly, searching..."
|
|
29
|
-
BIN=$(ls "$RELEASE_PATH" | grep 'titan-server' || true)
|
|
30
|
-
|
|
31
|
-
if [ -n "$BIN" ]; then
|
|
32
|
-
echo "✓ Found matching binary: $BIN"
|
|
33
|
-
cp "$RELEASE_PATH/$BIN" "$DIST_DIR/titan-server"
|
|
34
|
-
else
|
|
35
|
-
echo "✗ titan-server binary not found in release folder."
|
|
36
|
-
echo "Did you run: cargo build --release ?"
|
|
37
|
-
exit 1
|
|
38
|
-
fi
|
|
39
|
-
fi
|
|
40
|
-
|
|
41
|
-
# ---------------------------------------------
|
|
42
|
-
# routes.json (JS bundler should generate routes.build.json)
|
|
43
|
-
# ---------------------------------------------
|
|
44
|
-
if [ -f "$ROOT/routes.build.json" ]; then
|
|
45
|
-
echo "✓ Using routes.build.json"
|
|
46
|
-
cp "$ROOT/routes.build.json" "$DIST_DIR/routes.json"
|
|
47
|
-
else
|
|
48
|
-
echo "⚠ No routes.build.json found. Creating empty routes.json"
|
|
49
|
-
echo "{}" > "$DIST_DIR/routes.json"
|
|
50
|
-
fi
|
|
51
|
-
|
|
52
|
-
# ---------------------------------------------
|
|
53
|
-
# Copy handlers if they exist
|
|
54
|
-
# ---------------------------------------------
|
|
55
|
-
mkdir -p "$DIST_DIR/handlers"
|
|
56
|
-
|
|
57
|
-
if [ -d "$ROOT/handlers" ]; then
|
|
58
|
-
echo "✓ Copying handlers/"
|
|
59
|
-
cp -r "$ROOT/handlers/"* "$DIST_DIR/handlers/" 2>/dev/null || true
|
|
60
|
-
else
|
|
61
|
-
echo "⚠ No handlers/ directory found."
|
|
62
|
-
fi
|
|
63
|
-
|
|
64
|
-
echo ""
|
|
65
|
-
echo "-------------------------------------------"
|
|
66
|
-
echo " ✔ Titan dist/ build complete"
|
|
67
|
-
echo "-------------------------------------------"
|
|
68
|
-
echo "Binary: dist/titan-server"
|
|
69
|
-
echo "Routes: dist/routes.json"
|
|
70
|
-
echo "Handlers: dist/handlers/"
|
|
71
|
-
echo ""
|
package/templates/titan/dev.js
DELETED
|
@@ -1,144 +0,0 @@
|
|
|
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
|
-
let serverProcess = null;
|
|
13
|
-
let isKilling = false;
|
|
14
|
-
|
|
15
|
-
async function killServer() {
|
|
16
|
-
if (!serverProcess) return;
|
|
17
|
-
|
|
18
|
-
isKilling = true;
|
|
19
|
-
const pid = serverProcess.pid;
|
|
20
|
-
const killPromise = new Promise((resolve) => {
|
|
21
|
-
if (serverProcess.exitCode !== null) return resolve();
|
|
22
|
-
serverProcess.once("close", resolve);
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
if (process.platform === "win32") {
|
|
26
|
-
try {
|
|
27
|
-
execSync(`taskkill /pid ${pid} /f /t`, { stdio: 'ignore' });
|
|
28
|
-
} catch (e) {
|
|
29
|
-
// Ignore errors if process is already dead
|
|
30
|
-
}
|
|
31
|
-
} else {
|
|
32
|
-
serverProcess.kill();
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
try {
|
|
36
|
-
await killPromise;
|
|
37
|
-
} catch (e) { }
|
|
38
|
-
serverProcess = null;
|
|
39
|
-
isKilling = false;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
async function startRustServer(retryCount = 0) {
|
|
43
|
-
// If we are retrying, give it more time (2s), otherwise standard 1s (increased from 500ms)
|
|
44
|
-
const waitTime = retryCount > 0 ? 2000 : 1000;
|
|
45
|
-
|
|
46
|
-
// Ensure any previous instance is killed
|
|
47
|
-
await killServer();
|
|
48
|
-
|
|
49
|
-
// Give the OS a moment to release file locks on the binary
|
|
50
|
-
await new Promise(r => setTimeout(r, waitTime));
|
|
51
|
-
|
|
52
|
-
const serverPath = path.join(process.cwd(), "server");
|
|
53
|
-
const startTime = Date.now();
|
|
54
|
-
|
|
55
|
-
if (retryCount > 0) {
|
|
56
|
-
console.log(`\x1b[33m[Titan] Retrying Rust server (Attempt ${retryCount})...\x1b[0m`);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// Windows often has file locking issues during concurrent linking/metadata generation
|
|
60
|
-
// We force 1 job and disable incremental compilation to be safe.
|
|
61
|
-
serverProcess = spawn("cargo", ["run", "--jobs", "1"], {
|
|
62
|
-
cwd: serverPath,
|
|
63
|
-
stdio: "inherit",
|
|
64
|
-
shell: true,
|
|
65
|
-
env: { ...process.env, CARGO_INCREMENTAL: "0" }
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
serverProcess.on("close", async (code) => {
|
|
69
|
-
if (isKilling) return;
|
|
70
|
-
|
|
71
|
-
console.log(`[Titan] Rust server exited: ${code}`);
|
|
72
|
-
|
|
73
|
-
// If exited with error and it was a short run (< 10s), likely a start-up error/lock
|
|
74
|
-
// Retry up to 3 times
|
|
75
|
-
const runTime = Date.now() - startTime;
|
|
76
|
-
if (code !== 0 && code !== null && runTime < 10000 && retryCount < 3) {
|
|
77
|
-
console.log(`\x1b[31m[Titan] Server crash detected (possibly file lock). Retrying automatically...\x1b[0m`);
|
|
78
|
-
await startRustServer(retryCount + 1);
|
|
79
|
-
}
|
|
80
|
-
});
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
async function rebuild() {
|
|
84
|
-
console.log("[Titan] Regenerating routes.json & action_map.json...");
|
|
85
|
-
execSync("node app/app.js", { stdio: "inherit" });
|
|
86
|
-
|
|
87
|
-
console.log("[Titan] Bundling JS actions...");
|
|
88
|
-
await bundle();
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
async function startDev() {
|
|
92
|
-
console.log("[Titan] Dev mode starting...");
|
|
93
|
-
|
|
94
|
-
if (fs.existsSync(path.join(process.cwd(), ".env"))) {
|
|
95
|
-
console.log("\x1b[33m[Titan] Env Configured\x1b[0m");
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
// FIRST BUILD
|
|
99
|
-
try {
|
|
100
|
-
await rebuild();
|
|
101
|
-
await startRustServer();
|
|
102
|
-
} catch (e) {
|
|
103
|
-
console.log("\x1b[31m[Titan] Initial build failed. Waiting for changes...\x1b[0m");
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
const watcher = chokidar.watch(["app", ".env"], {
|
|
107
|
-
ignoreInitial: true
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
let timer = null;
|
|
111
|
-
|
|
112
|
-
watcher.on("all", async (event, file) => {
|
|
113
|
-
if (timer) clearTimeout(timer);
|
|
114
|
-
|
|
115
|
-
timer = setTimeout(async () => {
|
|
116
|
-
if (file.includes(".env")) {
|
|
117
|
-
console.log("\x1b[33m[Titan] Env Refreshed\x1b[0m");
|
|
118
|
-
} else {
|
|
119
|
-
console.log(`[Titan] Change detected: ${file}`);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
try {
|
|
123
|
-
await rebuild();
|
|
124
|
-
console.log("[Titan] Restarting Rust server...");
|
|
125
|
-
await startRustServer();
|
|
126
|
-
} catch (e) {
|
|
127
|
-
console.log("\x1b[31m[Titan] Build failed -- waiting for changes...\x1b[0m");
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
}, 500);
|
|
131
|
-
});
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
// Handle graceful exit to release file locks
|
|
135
|
-
async function handleExit() {
|
|
136
|
-
console.log("\n[Titan] Stopping server...");
|
|
137
|
-
await killServer();
|
|
138
|
-
process.exit(0);
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
process.on("SIGINT", handleExit);
|
|
142
|
-
process.on("SIGTERM", handleExit);
|
|
143
|
-
|
|
144
|
-
startDev();
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|