@ezetgalaxy/titan 26.7.2 → 26.7.5
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 +4 -1
- package/index.js +488 -489
- package/package.json +3 -3
- package/templates/Dockerfile +17 -4
- package/templates/_gitignore +25 -0
- package/templates/package.json +1 -1
- package/templates/server/action_map.json +3 -0
- package/templates/server/actions/hello.jsbundle +45 -0
- package/templates/server/routes.json +16 -0
- package/templates/server/src/extensions.rs +433 -216
- package/templates/server/src/main.rs +134 -68
- package/titanpl-sdk/README.md +14 -6
- package/titanpl-sdk/assets/titanpl-sdk.png +0 -0
- package/titanpl-sdk/package.json +2 -1
- package/titanpl-sdk/templates/Dockerfile +17 -4
- package/titanpl-sdk/templates/server/src/extensions.rs +423 -218
- package/titanpl-sdk/templates/server/src/main.rs +134 -68
- package/scripts/make_dist.sh +0 -71
|
@@ -1,28 +1,27 @@
|
|
|
1
|
-
use std::{collections::HashMap, fs, path::PathBuf, sync::Arc};
|
|
2
1
|
use anyhow::Result;
|
|
3
2
|
use axum::{
|
|
4
|
-
|
|
3
|
+
Router,
|
|
4
|
+
body::{Body, to_bytes},
|
|
5
5
|
extract::State,
|
|
6
6
|
http::{Request, StatusCode},
|
|
7
7
|
response::{IntoResponse, Json},
|
|
8
8
|
routing::any,
|
|
9
|
-
Router,
|
|
10
9
|
};
|
|
11
10
|
use serde_json::Value;
|
|
12
|
-
use tokio::net::TcpListener;
|
|
13
11
|
use std::time::Instant;
|
|
12
|
+
use std::{collections::HashMap, fs, path::PathBuf, sync::Arc};
|
|
13
|
+
use tokio::net::TcpListener;
|
|
14
14
|
|
|
15
15
|
mod utils;
|
|
16
16
|
|
|
17
|
-
mod extensions;
|
|
18
17
|
mod action_management;
|
|
18
|
+
mod extensions;
|
|
19
19
|
|
|
20
|
-
use utils::{blue, white, yellow, green, gray, red};
|
|
21
|
-
use extensions::{init_v8, inject_extensions};
|
|
22
20
|
use action_management::{
|
|
23
|
-
|
|
24
|
-
DynamicRoute, RouteVal
|
|
21
|
+
DynamicRoute, RouteVal, find_actions_dir, match_dynamic_route, resolve_actions_dir,
|
|
25
22
|
};
|
|
23
|
+
use extensions::{init_v8, inject_extensions};
|
|
24
|
+
use utils::{blue, gray, green, red, white, yellow};
|
|
26
25
|
|
|
27
26
|
#[derive(Clone)]
|
|
28
27
|
struct AppState {
|
|
@@ -45,7 +44,6 @@ async fn dynamic_handler_inner(
|
|
|
45
44
|
State(state): State<AppState>,
|
|
46
45
|
req: Request<Body>,
|
|
47
46
|
) -> impl IntoResponse {
|
|
48
|
-
|
|
49
47
|
// ---------------------------
|
|
50
48
|
// BASIC REQUEST INFO
|
|
51
49
|
// ---------------------------
|
|
@@ -70,10 +68,7 @@ async fn dynamic_handler_inner(
|
|
|
70
68
|
q.split('&')
|
|
71
69
|
.filter_map(|pair| {
|
|
72
70
|
let mut it = pair.splitn(2, '=');
|
|
73
|
-
Some((
|
|
74
|
-
it.next()?.to_string(),
|
|
75
|
-
it.next().unwrap_or("").to_string(),
|
|
76
|
-
))
|
|
71
|
+
Some((it.next()?.to_string(), it.next().unwrap_or("").to_string()))
|
|
77
72
|
})
|
|
78
73
|
.collect()
|
|
79
74
|
})
|
|
@@ -83,7 +78,7 @@ async fn dynamic_handler_inner(
|
|
|
83
78
|
// HEADERS & BODY
|
|
84
79
|
// ---------------------------
|
|
85
80
|
let (parts, body) = req.into_parts();
|
|
86
|
-
|
|
81
|
+
|
|
87
82
|
let headers = parts
|
|
88
83
|
.headers
|
|
89
84
|
.iter()
|
|
@@ -92,9 +87,7 @@ async fn dynamic_handler_inner(
|
|
|
92
87
|
|
|
93
88
|
let body_bytes = match to_bytes(body, usize::MAX).await {
|
|
94
89
|
Ok(b) => b,
|
|
95
|
-
Err(_) =>
|
|
96
|
-
return (StatusCode::BAD_REQUEST, "Failed to read request body").into_response()
|
|
97
|
-
}
|
|
90
|
+
Err(_) => return (StatusCode::BAD_REQUEST, "Failed to read request body").into_response(),
|
|
98
91
|
};
|
|
99
92
|
|
|
100
93
|
let body_str = String::from_utf8_lossy(&body_bytes).to_string();
|
|
@@ -119,18 +112,32 @@ async fn dynamic_handler_inner(
|
|
|
119
112
|
action_name = Some(name);
|
|
120
113
|
} else if route.r#type == "json" {
|
|
121
114
|
let elapsed = start.elapsed();
|
|
122
|
-
println!(
|
|
115
|
+
println!(
|
|
116
|
+
"{} {} {} {}",
|
|
117
|
+
blue("[Titan]"),
|
|
118
|
+
white(&format!("{} {}", method, path)),
|
|
119
|
+
white("→ json"),
|
|
120
|
+
gray(&format!("in {:.2?}", elapsed))
|
|
121
|
+
);
|
|
123
122
|
return Json(route.value.clone()).into_response();
|
|
124
123
|
} else if let Some(s) = route.value.as_str() {
|
|
125
124
|
let elapsed = start.elapsed();
|
|
126
|
-
println!(
|
|
125
|
+
println!(
|
|
126
|
+
"{} {} {} {}",
|
|
127
|
+
blue("[Titan]"),
|
|
128
|
+
white(&format!("{} {}", method, path)),
|
|
129
|
+
white("→ reply"),
|
|
130
|
+
gray(&format!("in {:.2?}", elapsed))
|
|
131
|
+
);
|
|
127
132
|
return s.to_string().into_response();
|
|
128
133
|
}
|
|
129
134
|
}
|
|
130
135
|
|
|
131
136
|
// Dynamic route
|
|
132
137
|
if action_name.is_none() {
|
|
133
|
-
if let Some((action, p)) =
|
|
138
|
+
if let Some((action, p)) =
|
|
139
|
+
match_dynamic_route(&method, &path, state.dynamic_routes.as_slice())
|
|
140
|
+
{
|
|
134
141
|
route_kind = "dynamic";
|
|
135
142
|
route_label = action.clone();
|
|
136
143
|
action_name = Some(action);
|
|
@@ -142,7 +149,13 @@ async fn dynamic_handler_inner(
|
|
|
142
149
|
Some(a) => a,
|
|
143
150
|
None => {
|
|
144
151
|
let elapsed = start.elapsed();
|
|
145
|
-
println!(
|
|
152
|
+
println!(
|
|
153
|
+
"{} {} {} {}",
|
|
154
|
+
blue("[Titan]"),
|
|
155
|
+
white(&format!("{} {}", method, path)),
|
|
156
|
+
white("→ 404"),
|
|
157
|
+
gray(&format!("in {:.2?}", elapsed))
|
|
158
|
+
);
|
|
146
159
|
return (StatusCode::NOT_FOUND, "Not Found").into_response();
|
|
147
160
|
}
|
|
148
161
|
};
|
|
@@ -151,7 +164,8 @@ async fn dynamic_handler_inner(
|
|
|
151
164
|
// LOAD ACTION
|
|
152
165
|
// ---------------------------
|
|
153
166
|
let resolved = resolve_actions_dir();
|
|
154
|
-
let actions_dir = resolved
|
|
167
|
+
let actions_dir = resolved
|
|
168
|
+
.exists()
|
|
155
169
|
.then(|| resolved)
|
|
156
170
|
.or_else(|| find_actions_dir(&state.project_root))
|
|
157
171
|
.unwrap();
|
|
@@ -164,17 +178,24 @@ async fn dynamic_handler_inner(
|
|
|
164
178
|
}
|
|
165
179
|
}
|
|
166
180
|
|
|
167
|
-
let js_code =
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
181
|
+
let js_code =
|
|
182
|
+
match fs::read_to_string(&action_path) {
|
|
183
|
+
Ok(c) => c,
|
|
184
|
+
Err(_) => return (
|
|
185
|
+
StatusCode::INTERNAL_SERVER_ERROR,
|
|
186
|
+
Json(
|
|
187
|
+
serde_json::json!({"error": "Action bundle not found", "action": action_name}),
|
|
188
|
+
),
|
|
189
|
+
)
|
|
190
|
+
.into_response(),
|
|
191
|
+
};
|
|
173
192
|
|
|
174
193
|
// ---------------------------
|
|
175
194
|
// EXECUTE IN V8
|
|
176
195
|
// ---------------------------
|
|
177
|
-
let env_json = std::env::vars()
|
|
196
|
+
let env_json = std::env::vars()
|
|
197
|
+
.map(|(k, v)| (k, Value::String(v)))
|
|
198
|
+
.collect::<serde_json::Map<_, _>>();
|
|
178
199
|
|
|
179
200
|
let injected = format!(
|
|
180
201
|
r#"
|
|
@@ -215,43 +236,42 @@ async fn dynamic_handler_inner(
|
|
|
215
236
|
// We can use `task::spawn_blocking`.
|
|
216
237
|
let root = state.project_root.clone();
|
|
217
238
|
let action_name_for_v8 = action_name.clone();
|
|
218
|
-
|
|
239
|
+
|
|
219
240
|
let result_json: Value = tokio::task::spawn_blocking(move || {
|
|
220
241
|
let isolate = &mut v8::Isolate::new(v8::CreateParams::default());
|
|
221
242
|
let handle_scope = &mut v8::HandleScope::new(isolate);
|
|
222
243
|
let context = v8::Context::new(handle_scope, v8::ContextOptions::default());
|
|
223
244
|
let scope = &mut v8::ContextScope::new(handle_scope, context);
|
|
224
|
-
|
|
245
|
+
|
|
225
246
|
let global = context.global(scope);
|
|
226
247
|
|
|
227
248
|
// Inject extensions (t.read, etc)
|
|
228
249
|
inject_extensions(scope, global);
|
|
229
|
-
|
|
230
|
-
// Set metadata globals
|
|
250
|
+
|
|
231
251
|
// Set metadata globals
|
|
232
252
|
let root_str = v8::String::new(scope, root.to_str().unwrap_or(".")).unwrap();
|
|
233
253
|
let root_key = v8::String::new(scope, "__titan_root").unwrap();
|
|
234
254
|
global.set(scope, root_key.into(), root_str.into());
|
|
235
|
-
|
|
255
|
+
|
|
236
256
|
let action_str = v8::String::new(scope, &action_name_for_v8).unwrap();
|
|
237
257
|
let action_key = v8::String::new(scope, "__titan_action").unwrap();
|
|
238
258
|
global.set(scope, action_key.into(), action_str.into());
|
|
239
|
-
|
|
259
|
+
|
|
240
260
|
let source = v8::String::new(scope, &injected).unwrap();
|
|
241
|
-
|
|
261
|
+
|
|
242
262
|
let try_catch = &mut v8::TryCatch::new(scope);
|
|
243
|
-
|
|
263
|
+
|
|
244
264
|
let script = match v8::Script::compile(try_catch, source, None) {
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
265
|
+
Some(s) => s,
|
|
266
|
+
None => {
|
|
267
|
+
let err = try_catch.message().unwrap();
|
|
268
|
+
let msg = err.get(try_catch).to_rust_string_lossy(try_catch);
|
|
269
|
+
return serde_json::json!({ "error": msg, "phase": "compile" });
|
|
270
|
+
}
|
|
251
271
|
};
|
|
252
|
-
|
|
272
|
+
|
|
253
273
|
let result = script.run(try_catch);
|
|
254
|
-
|
|
274
|
+
|
|
255
275
|
match result {
|
|
256
276
|
Some(val) => {
|
|
257
277
|
// Convert v8 Value to Serde JSON
|
|
@@ -259,37 +279,59 @@ async fn dynamic_handler_inner(
|
|
|
259
279
|
let json_obj = v8::json::stringify(try_catch, val).unwrap();
|
|
260
280
|
let json_str = json_obj.to_rust_string_lossy(try_catch);
|
|
261
281
|
serde_json::from_str(&json_str).unwrap_or(Value::Null)
|
|
262
|
-
}
|
|
282
|
+
}
|
|
263
283
|
None => {
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
284
|
+
let err = try_catch.message().unwrap();
|
|
285
|
+
let msg = err.get(try_catch).to_rust_string_lossy(try_catch);
|
|
286
|
+
serde_json::json!({ "error": msg, "phase": "execution" })
|
|
267
287
|
}
|
|
268
288
|
}
|
|
269
|
-
})
|
|
289
|
+
})
|
|
290
|
+
.await
|
|
291
|
+
.unwrap_or(serde_json::json!({"error": "V8 task failed"}));
|
|
270
292
|
|
|
271
293
|
// ---------------------------
|
|
272
294
|
// FINAL LOG
|
|
273
295
|
// ---------------------------
|
|
274
296
|
let elapsed = start.elapsed();
|
|
275
|
-
|
|
297
|
+
|
|
276
298
|
// Check for errors in result
|
|
277
299
|
if let Some(err) = result_json.get("error") {
|
|
278
|
-
println!(
|
|
300
|
+
println!(
|
|
301
|
+
"{} {} {} {}",
|
|
302
|
+
blue("[Titan]"),
|
|
303
|
+
red(&format!("{} {}", method, path)),
|
|
304
|
+
red("→ error"),
|
|
305
|
+
gray(&format!("in {:.2?}", elapsed))
|
|
306
|
+
);
|
|
279
307
|
println!("{}", red(err.as_str().unwrap_or("Unknown")));
|
|
280
308
|
return (StatusCode::INTERNAL_SERVER_ERROR, Json(result_json)).into_response();
|
|
281
309
|
}
|
|
282
310
|
|
|
283
311
|
match route_kind {
|
|
284
|
-
"dynamic" => println!(
|
|
285
|
-
|
|
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
|
+
),
|
|
286
329
|
_ => {}
|
|
287
330
|
}
|
|
288
331
|
|
|
289
332
|
Json(result_json).into_response()
|
|
290
333
|
}
|
|
291
334
|
|
|
292
|
-
|
|
293
335
|
// Entrypoint ---------------------------------------------------------------
|
|
294
336
|
|
|
295
337
|
#[tokio::main]
|
|
@@ -304,28 +346,22 @@ async fn main() -> Result<()> {
|
|
|
304
346
|
let port = json["__config"]["port"].as_u64().unwrap_or(3000);
|
|
305
347
|
let routes_json = json["routes"].clone();
|
|
306
348
|
let map: HashMap<String, RouteVal> = serde_json::from_value(routes_json).unwrap_or_default();
|
|
307
|
-
let dynamic_routes: Vec<DynamicRoute> =
|
|
349
|
+
let dynamic_routes: Vec<DynamicRoute> =
|
|
350
|
+
serde_json::from_value(json["__dynamic_routes"].clone()).unwrap_or_default();
|
|
308
351
|
|
|
309
|
-
//
|
|
310
|
-
let
|
|
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
|
-
};
|
|
352
|
+
// Identify project root (where .ext or node_modules lives)
|
|
353
|
+
let project_root = resolve_project_root();
|
|
317
354
|
|
|
318
355
|
let state = AppState {
|
|
319
356
|
routes: Arc::new(map),
|
|
320
357
|
dynamic_routes: Arc::new(dynamic_routes),
|
|
321
358
|
project_root: project_root.clone(),
|
|
322
359
|
};
|
|
323
|
-
|
|
360
|
+
|
|
324
361
|
// Load extensions
|
|
325
362
|
extensions::load_project_extensions(project_root.clone());
|
|
326
363
|
|
|
327
364
|
let app = Router::new()
|
|
328
|
-
|
|
329
365
|
.route("/", any(root_route))
|
|
330
366
|
.fallback(any(dynamic_route))
|
|
331
367
|
.with_state(state);
|
|
@@ -338,8 +374,38 @@ async fn main() -> Result<()> {
|
|
|
338
374
|
println!(" ██║ ██║ ██║ ██╔══██║██║╚██╗██║");
|
|
339
375
|
println!(" ██║ ██║ ██║ ██║ ██║██║ ╚████║");
|
|
340
376
|
println!(" ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝╚═╝ ╚═══╝\x1b[0m\n");
|
|
341
|
-
println!(
|
|
377
|
+
println!(
|
|
378
|
+
"\x1b[38;5;39mTitan server running at:\x1b[0m http://localhost:{}",
|
|
379
|
+
port
|
|
380
|
+
);
|
|
342
381
|
|
|
343
382
|
axum::serve(listener, app).await?;
|
|
344
383
|
Ok(())
|
|
345
384
|
}
|
|
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/titanpl-sdk/README.md
CHANGED
|
@@ -1,11 +1,19 @@
|
|
|
1
1
|
|
|
2
|
-
|
|
2
|
+
<div align="center">
|
|
3
|
+
<img src="./assets/titanpl-sdk.png" alt="Titan SDK Logo" width="120" />
|
|
4
|
+
<h1>Titan SDK</h1>
|
|
5
|
+
<p>
|
|
6
|
+
<b>The Developer Toolkit for Titan Planet. Type safety, IntelliSense, and Extension Testing.</b>
|
|
7
|
+
</p>
|
|
8
|
+
</div>
|
|
3
9
|
|
|
4
|
-
|
|
10
|
+
<div align="center">
|
|
5
11
|
|
|
6
|
-
[](https://www.npmjs.com/package/titanpl-sdk)
|
|
7
13
|
[](https://opensource.org/licenses/ISC)
|
|
8
14
|
|
|
15
|
+
</div>
|
|
16
|
+
|
|
9
17
|
---
|
|
10
18
|
|
|
11
19
|
## 🌌 Overview
|
|
@@ -14,7 +22,7 @@
|
|
|
14
22
|
|
|
15
23
|
It provides the necessary **Type Definitions** to make your IDE understand the global `t` object and a **Lite Test Harness** to verify your extensions before they ever touch a production binary.
|
|
16
24
|
|
|
17
|
-
> **Note:** The actual implementation of `t.log`, `t.fetch`, and other APIs are embedded directly into the [Titan Planet Binary](https://github.com/ezet-galaxy
|
|
25
|
+
> **Note:** The actual implementation of `t.log`, `t.fetch`, and other APIs are embedded directly into the [Titan Planet Binary](https://github.com/ezet-galaxy/titanpl). This SDK simply provides the "blueprints" (types) and a "sandbox" (test runner).
|
|
18
26
|
|
|
19
27
|
---
|
|
20
28
|
|
|
@@ -90,12 +98,12 @@ The SDK provides types for the native APIs provided by the Titan Planet engine:
|
|
|
90
98
|
|
|
91
99
|
## 🌍 Community & Documentation
|
|
92
100
|
|
|
93
|
-
- **Core Framework**: [Titan Planet](https://github.com/ezet-galaxy
|
|
101
|
+
- **Core Framework**: [Titan Planet](https://github.com/ezet-galaxy/titanpl)
|
|
94
102
|
- **Official Docs**: [Titan Planet Docs](https://titan-docs-ez.vercel.app/docs)
|
|
95
103
|
- **Author**: [ezetgalaxy](https://github.com/ezet-galaxy)
|
|
96
104
|
|
|
97
105
|
---
|
|
98
106
|
|
|
99
107
|
<p align="center">
|
|
100
|
-
Built with ❤️ for the <a href="https://
|
|
108
|
+
Built with ❤️ for the <a href="https://titan-docs-ez.vercel.app/">Titan Planet</a> ecosystem.
|
|
101
109
|
</p>
|
|
Binary file
|
package/titanpl-sdk/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "titanpl-sdk",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6",
|
|
4
4
|
"description": "Development SDK for Titan Planet. Provides TypeScript type definitions for the global 't' runtime object and a 'lite' test-harness runtime for building and verifying extensions.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
"bin/",
|
|
13
13
|
"templates/",
|
|
14
14
|
"titan",
|
|
15
|
+
"assets",
|
|
15
16
|
"index.js",
|
|
16
17
|
"index.d.ts",
|
|
17
18
|
"README.md"
|
|
@@ -18,6 +18,20 @@ COPY . .
|
|
|
18
18
|
# Install JS dependencies (needed for Titan DSL + bundler)
|
|
19
19
|
RUN npm install
|
|
20
20
|
|
|
21
|
+
SHELL ["/bin/bash", "-c"]
|
|
22
|
+
|
|
23
|
+
# Extract Titan extensions into .ext
|
|
24
|
+
RUN mkdir -p /app/.ext && \
|
|
25
|
+
find /app/node_modules -maxdepth 5 -type f -name "titan.json" -print0 | \
|
|
26
|
+
while IFS= read -r -d '' file; do \
|
|
27
|
+
pkg_dir="$(dirname "$file")"; \
|
|
28
|
+
pkg_name="$(basename "$pkg_dir")"; \
|
|
29
|
+
echo "Copying Titan extension: $pkg_name from $pkg_dir"; \
|
|
30
|
+
cp -r "$pkg_dir" "/app/.ext/$pkg_name"; \
|
|
31
|
+
done && \
|
|
32
|
+
echo "Extensions in .ext:" && \
|
|
33
|
+
ls -R /app/.ext
|
|
34
|
+
|
|
21
35
|
# Build Titan metadata + bundle JS actions
|
|
22
36
|
RUN titan build
|
|
23
37
|
|
|
@@ -34,7 +48,7 @@ FROM debian:stable-slim
|
|
|
34
48
|
WORKDIR /app
|
|
35
49
|
|
|
36
50
|
# Copy Rust binary from builder stage
|
|
37
|
-
COPY --from=builder /app/server/target/release/server ./titan-server
|
|
51
|
+
COPY --from=builder /app/server/target/release/titan-server ./titan-server
|
|
38
52
|
|
|
39
53
|
# Copy Titan routing metadata
|
|
40
54
|
COPY --from=builder /app/server/routes.json ./routes.json
|
|
@@ -44,10 +58,9 @@ COPY --from=builder /app/server/action_map.json ./action_map.json
|
|
|
44
58
|
RUN mkdir -p /app/actions
|
|
45
59
|
COPY --from=builder /app/server/actions /app/actions
|
|
46
60
|
|
|
47
|
-
|
|
61
|
+
# Copy only Titan extensions
|
|
62
|
+
COPY --from=builder /app/.ext ./.ext
|
|
48
63
|
|
|
49
|
-
# Expose Titan port
|
|
50
64
|
EXPOSE 3000
|
|
51
65
|
|
|
52
|
-
# Start Titan
|
|
53
66
|
CMD ["./titan-server"]
|