@t8n/ui 1.0.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 +276 -0
- package/index.d.ts +65 -0
- package/index.js +131 -0
- package/jsconfig.json +13 -0
- package/package.json +32 -0
- package/test-app/.dockerignore +3 -0
- package/test-app/Dockerfile +66 -0
- package/test-app/app/actions/hello.js +7 -0
- package/test-app/app/app.js +10 -0
- package/test-app/app/static/app.html +9 -0
- package/test-app/app/static/styles.css +9 -0
- package/test-app/app/titan.d.ts +249 -0
- package/test-app/eslint.config.js +8 -0
- package/test-app/jsconfig.json +20 -0
- package/test-app/package.json +25 -0
- package/test-app/server/Cargo.toml +32 -0
- package/test-app/server/src/action_management.rs +171 -0
- package/test-app/server/src/errors.rs +10 -0
- package/test-app/server/src/extensions/builtin.rs +828 -0
- package/test-app/server/src/extensions/external.rs +309 -0
- package/test-app/server/src/extensions/mod.rs +430 -0
- package/test-app/server/src/extensions/titan_core.js +178 -0
- package/test-app/server/src/main.rs +433 -0
- package/test-app/server/src/runtime.rs +314 -0
- package/test-app/server/src/utils.rs +33 -0
- package/test-app/server/titan_storage.json +5 -0
- package/test-app/titan/bundle.js +264 -0
- package/test-app/titan/dev.js +350 -0
- package/test-app/titan/error-box.js +268 -0
- package/test-app/titan/titan.js +129 -0
- package/titan.json +20 -0
package/README.md
ADDED
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
````md
|
|
2
|
+
# @titanpl/ui
|
|
3
|
+
|
|
4
|
+
A **minimal, explicit UI extension for TitanPL** that lets you render HTML and inject CSS **in a single render call** — with **no static routes**, **no magic**, and **full control**.
|
|
5
|
+
|
|
6
|
+
This extension is designed for **action-based routing** and **runtime-first** TitanPL apps.
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## ✨ Features
|
|
11
|
+
|
|
12
|
+
- `t.ui.render()` → render HTML + CSS in one call
|
|
13
|
+
- `t.ui.load()` → preload templates for reuse
|
|
14
|
+
- `t.ui.css()` → load CSS manually if needed
|
|
15
|
+
- Simple templating: `tpl{{ var }}`
|
|
16
|
+
- Explicit CSS injection (no hidden parsing)
|
|
17
|
+
- Memory + persistent (`ls`) caching
|
|
18
|
+
- Zero static file server
|
|
19
|
+
- Zero extra routes
|
|
20
|
+
- One extension, one mental model
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## 📦 Installation
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
npm install @titanpl/ui
|
|
28
|
+
````
|
|
29
|
+
|
|
30
|
+
TitanPL automatically loads the extension at runtime.
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## 📁 Project Structure
|
|
35
|
+
|
|
36
|
+
```
|
|
37
|
+
app/
|
|
38
|
+
├─ actions/
|
|
39
|
+
│ └─ hello.js
|
|
40
|
+
├─ static/
|
|
41
|
+
│ ├─ app.html
|
|
42
|
+
│ └─ styles.css
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
All paths are **relative to `app/`**.
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## 🧠 Core Idea
|
|
50
|
+
|
|
51
|
+
TitanPL does **nothing implicitly**.
|
|
52
|
+
|
|
53
|
+
* HTML is rendered only when you call `t.ui.render()` or `t.ui.load()`
|
|
54
|
+
* CSS is injected **only if you explicitly request it**
|
|
55
|
+
* You decide where CSS goes in HTML
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## 🧩 Template Syntax
|
|
60
|
+
|
|
61
|
+
### Variables
|
|
62
|
+
|
|
63
|
+
```html
|
|
64
|
+
tpl{{ name }}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### CSS injection point
|
|
68
|
+
|
|
69
|
+
```html
|
|
70
|
+
tpl{{ css }}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
## 🚀 Usage
|
|
76
|
+
|
|
77
|
+
### 1️⃣ HTML Template
|
|
78
|
+
|
|
79
|
+
`app/static/app.html`
|
|
80
|
+
|
|
81
|
+
```html
|
|
82
|
+
<!DOCTYPE html>
|
|
83
|
+
<html>
|
|
84
|
+
<head>
|
|
85
|
+
tpl{{ css }}
|
|
86
|
+
</head>
|
|
87
|
+
<body>
|
|
88
|
+
<h1>tpl{{ name }}</h1>
|
|
89
|
+
</body>
|
|
90
|
+
</html>
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
### 2️⃣ CSS File
|
|
96
|
+
|
|
97
|
+
`app/static/styles.css`
|
|
98
|
+
|
|
99
|
+
```css
|
|
100
|
+
body {
|
|
101
|
+
background-color: black;
|
|
102
|
+
height: 100vh;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
h1 {
|
|
106
|
+
color: rgb(32, 215, 215);
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
### 3️⃣ One-Shot Render (Recommended)
|
|
113
|
+
|
|
114
|
+
```js
|
|
115
|
+
export const hello = () => {
|
|
116
|
+
return t.ui.render(
|
|
117
|
+
"static/app.html",
|
|
118
|
+
{ name: "Titan" },
|
|
119
|
+
{ css: "static/styles.css" }
|
|
120
|
+
);
|
|
121
|
+
};
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
✔ HTML rendered
|
|
125
|
+
✔ CSS injected
|
|
126
|
+
✔ Single response
|
|
127
|
+
✔ No static routes
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
### 4️⃣ Multiple CSS Files
|
|
132
|
+
|
|
133
|
+
```js
|
|
134
|
+
return t.ui.render(
|
|
135
|
+
"static/app.html",
|
|
136
|
+
{ name: "Titan" },
|
|
137
|
+
{ css: ["static/base.css", "static/theme.css"] }
|
|
138
|
+
);
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
CSS files are injected **in order**.
|
|
142
|
+
|
|
143
|
+
---
|
|
144
|
+
|
|
145
|
+
## 🔁 Preloading Templates with `t.ui.load()`
|
|
146
|
+
|
|
147
|
+
Use this for **high-traffic routes**.
|
|
148
|
+
|
|
149
|
+
```js
|
|
150
|
+
const page = t.ui.load("static/app.html");
|
|
151
|
+
|
|
152
|
+
export const hello = () => {
|
|
153
|
+
return page(
|
|
154
|
+
{ name: "Titan" },
|
|
155
|
+
{ css: "static/styles.css" }
|
|
156
|
+
);
|
|
157
|
+
};
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### Why use `load()`?
|
|
161
|
+
|
|
162
|
+
* Template read once
|
|
163
|
+
* Faster per request
|
|
164
|
+
* Cleaner route code
|
|
165
|
+
|
|
166
|
+
---
|
|
167
|
+
|
|
168
|
+
## 🎯 Manual CSS Loading (Optional)
|
|
169
|
+
|
|
170
|
+
If you want full control:
|
|
171
|
+
|
|
172
|
+
```js
|
|
173
|
+
export const hello = () => {
|
|
174
|
+
return t.ui.render("static/app.html", {
|
|
175
|
+
name: "Titan",
|
|
176
|
+
css: t.ui.css("static/styles.css")
|
|
177
|
+
});
|
|
178
|
+
};
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
---
|
|
182
|
+
|
|
183
|
+
## 🧠 API Reference
|
|
184
|
+
|
|
185
|
+
### `t.ui.render(htmlPath, data?, options?)`
|
|
186
|
+
|
|
187
|
+
Render HTML once with optional CSS.
|
|
188
|
+
|
|
189
|
+
```js
|
|
190
|
+
t.ui.render(
|
|
191
|
+
"static/app.html",
|
|
192
|
+
{ name: "Titan" },
|
|
193
|
+
{ css: "static/styles.css" }
|
|
194
|
+
);
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
---
|
|
198
|
+
|
|
199
|
+
### `t.ui.load(htmlPath)`
|
|
200
|
+
|
|
201
|
+
Preload template and return a reusable renderer.
|
|
202
|
+
|
|
203
|
+
```js
|
|
204
|
+
const page = t.ui.load("static/app.html");
|
|
205
|
+
page(data, options);
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
---
|
|
209
|
+
|
|
210
|
+
### `t.ui.css(cssPath)`
|
|
211
|
+
|
|
212
|
+
Load CSS and return a `<style>` block.
|
|
213
|
+
|
|
214
|
+
```js
|
|
215
|
+
t.ui.css("static/styles.css");
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
---
|
|
219
|
+
|
|
220
|
+
### `t.ui.clearCache()`
|
|
221
|
+
|
|
222
|
+
Clear all cached HTML and CSS files.
|
|
223
|
+
|
|
224
|
+
```js
|
|
225
|
+
t.ui.clearCache();
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
---
|
|
229
|
+
|
|
230
|
+
## 🧠 Mental Model (Important)
|
|
231
|
+
|
|
232
|
+
```text
|
|
233
|
+
t.ui.render() → returns an HTML response
|
|
234
|
+
tpl{{ css }} → where styles are injected
|
|
235
|
+
tpl{{ var }} → template variables
|
|
236
|
+
YOU → control composition
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
No implicit behavior.
|
|
240
|
+
No auto static serving.
|
|
241
|
+
No hidden parsing.
|
|
242
|
+
|
|
243
|
+
---
|
|
244
|
+
|
|
245
|
+
## ❌ What This Extension Does NOT Do
|
|
246
|
+
|
|
247
|
+
* ❌ No static file server
|
|
248
|
+
* ❌ No `<link>` interception
|
|
249
|
+
* ❌ No auto CSS loading
|
|
250
|
+
* ❌ No HTML AST parsing
|
|
251
|
+
* ❌ No framework magic
|
|
252
|
+
|
|
253
|
+
This is **intentional**.
|
|
254
|
+
|
|
255
|
+
---
|
|
256
|
+
|
|
257
|
+
## ✅ Why This Fits TitanPL
|
|
258
|
+
|
|
259
|
+
* Runtime-first
|
|
260
|
+
* Predictable
|
|
261
|
+
* Explicit IO
|
|
262
|
+
* Action-based routing friendly
|
|
263
|
+
* Production-safe
|
|
264
|
+
* Easy to debug
|
|
265
|
+
|
|
266
|
+
---
|
|
267
|
+
|
|
268
|
+
## 🛣️ Possible Extensions (Future)
|
|
269
|
+
|
|
270
|
+
* `t.ui.js()` for inline JS
|
|
271
|
+
* Layouts / partials
|
|
272
|
+
* Scoped CSS
|
|
273
|
+
* Dot-path vars (`tpl{{ user.name }}`)
|
|
274
|
+
* HTML escaping / raw blocks
|
|
275
|
+
|
|
276
|
+
|
package/index.d.ts
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
// Type definitions for @titanpl/ui
|
|
2
|
+
// This file enables type inference when the UI extension
|
|
3
|
+
// is installed in a TitanPL project.
|
|
4
|
+
|
|
5
|
+
declare global {
|
|
6
|
+
namespace Titan {
|
|
7
|
+
interface Runtime {
|
|
8
|
+
/**
|
|
9
|
+
* @titanpl/ui Extension
|
|
10
|
+
*
|
|
11
|
+
* Unified UI rendering utilities for TitanPL
|
|
12
|
+
* (HTML + CSS, runtime-first, action-safe)
|
|
13
|
+
*/
|
|
14
|
+
ui: {
|
|
15
|
+
/**
|
|
16
|
+
* Render an HTML template in one call.
|
|
17
|
+
*
|
|
18
|
+
* @param htmlPath Path relative to `app/`
|
|
19
|
+
* @param data Template variables (tpl{{ var }})
|
|
20
|
+
* @param options Optional render options
|
|
21
|
+
*/
|
|
22
|
+
render(
|
|
23
|
+
htmlPath: string,
|
|
24
|
+
data?: Record<string, any>,
|
|
25
|
+
options?: {
|
|
26
|
+
/**
|
|
27
|
+
* CSS file or files to inline
|
|
28
|
+
* Paths are relative to `app/`
|
|
29
|
+
*/
|
|
30
|
+
css?: string | string[];
|
|
31
|
+
}
|
|
32
|
+
): unknown; // Titan Response
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Preload an HTML template and return a reusable renderer.
|
|
36
|
+
*
|
|
37
|
+
* @param htmlPath Path relative to `app/`
|
|
38
|
+
*/
|
|
39
|
+
load(
|
|
40
|
+
htmlPath: string
|
|
41
|
+
): (
|
|
42
|
+
data?: Record<string, any>,
|
|
43
|
+
options?: {
|
|
44
|
+
css?: string | string[];
|
|
45
|
+
}
|
|
46
|
+
) => unknown; // Titan Response
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Load a CSS file and return a <style> string.
|
|
50
|
+
*
|
|
51
|
+
* @param cssPath Path relative to `app/`
|
|
52
|
+
*/
|
|
53
|
+
css(cssPath: string): string;
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Clear all cached UI templates and styles.
|
|
57
|
+
*/
|
|
58
|
+
clearCache(): boolean;
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export {};
|
|
65
|
+
|
package/index.js
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TitanPL UI Extension (HTML + CSS)
|
|
3
|
+
* Provides:
|
|
4
|
+
* t.ui.load()
|
|
5
|
+
* t.ui.render()
|
|
6
|
+
* t.ui.css()
|
|
7
|
+
* t.ui.clearCache()
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
if (typeof Titan === "undefined") globalThis.Titan = t;
|
|
11
|
+
|
|
12
|
+
const EXT_KEY = "ui";
|
|
13
|
+
const { fs, ls } = t.core;
|
|
14
|
+
|
|
15
|
+
// --------------------------------------------------
|
|
16
|
+
// Internal cache (per isolate)
|
|
17
|
+
// --------------------------------------------------
|
|
18
|
+
const memoryCache = Object.create(null);
|
|
19
|
+
|
|
20
|
+
// --------------------------------------------------
|
|
21
|
+
// Resolve paths relative to app/
|
|
22
|
+
// --------------------------------------------------
|
|
23
|
+
function resolvePath(path) {
|
|
24
|
+
const root = "../app" || "."
|
|
25
|
+
return root + `/${path.replace(/^\/+/, "")}`;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// --------------------------------------------------
|
|
29
|
+
// Load file with memory + persistent cache
|
|
30
|
+
// --------------------------------------------------
|
|
31
|
+
function loadFile(path, prefix) {
|
|
32
|
+
if (memoryCache[path]) return memoryCache[path];
|
|
33
|
+
|
|
34
|
+
const cached = ls.get(`${prefix}:${path}`);
|
|
35
|
+
if (cached) {
|
|
36
|
+
memoryCache[path] = cached;
|
|
37
|
+
return cached;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const content = fs.readFile(path);
|
|
41
|
+
memoryCache[path] = content;
|
|
42
|
+
ls.set(`${prefix}:${path}`, content);
|
|
43
|
+
|
|
44
|
+
return content;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// --------------------------------------------------
|
|
48
|
+
// Render tpl{{ var }}
|
|
49
|
+
// --------------------------------------------------
|
|
50
|
+
function renderTemplate(template, data = {}) {
|
|
51
|
+
return template.replace(
|
|
52
|
+
/tpl\{\{\s*(\w+)\s*\}\}/g,
|
|
53
|
+
(m, k) => (k in data ? String(data[k]) : m)
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// --------------------------------------------------
|
|
58
|
+
// Inject CSS into render data
|
|
59
|
+
// --------------------------------------------------
|
|
60
|
+
function injectCSS(data, opts = {}) {
|
|
61
|
+
if (!opts.css) return data;
|
|
62
|
+
|
|
63
|
+
const cssFiles = Array.isArray(opts.css) ? opts.css : [opts.css];
|
|
64
|
+
|
|
65
|
+
const styles = cssFiles
|
|
66
|
+
.map(p => {
|
|
67
|
+
const css = loadFile(resolvePath(p), "css");
|
|
68
|
+
return `<style>\n${css}\n</style>`;
|
|
69
|
+
})
|
|
70
|
+
.join("\n");
|
|
71
|
+
|
|
72
|
+
return {
|
|
73
|
+
...data,
|
|
74
|
+
css: styles
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// --------------------------------------------------
|
|
79
|
+
// EXTENSION CONTRACT (MANDATORY FOR TITANPL)
|
|
80
|
+
// --------------------------------------------------
|
|
81
|
+
t[EXT_KEY] = {
|
|
82
|
+
/**
|
|
83
|
+
* Load HTML template once and return a reusable responder
|
|
84
|
+
*/
|
|
85
|
+
load(htmlPath) {
|
|
86
|
+
const fullPath = resolvePath(htmlPath);
|
|
87
|
+
const tpl = loadFile(fullPath, "html");
|
|
88
|
+
|
|
89
|
+
return (data = {}, opts = {}) => {
|
|
90
|
+
const finalData = injectCSS(data, opts);
|
|
91
|
+
const html = renderTemplate(tpl, finalData);
|
|
92
|
+
return t.response.html(html);
|
|
93
|
+
};
|
|
94
|
+
},
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* One-shot render with optional CSS
|
|
98
|
+
*/
|
|
99
|
+
render(htmlPath, data = {}, opts = {}) {
|
|
100
|
+
const fullPath = resolvePath(htmlPath);
|
|
101
|
+
const tpl = loadFile(fullPath, "html");
|
|
102
|
+
|
|
103
|
+
const finalData = injectCSS(data, opts);
|
|
104
|
+
const html = renderTemplate(tpl, finalData);
|
|
105
|
+
|
|
106
|
+
return t.response.html(html);
|
|
107
|
+
},
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Load CSS only (manual usage if needed)
|
|
111
|
+
*/
|
|
112
|
+
css(cssPath) {
|
|
113
|
+
const css = loadFile(resolvePath(cssPath), "css");
|
|
114
|
+
return `<style>\n${css}\n</style>`;
|
|
115
|
+
},
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Clear all cached UI files
|
|
119
|
+
*/
|
|
120
|
+
clearCache() {
|
|
121
|
+
for (const k in memoryCache) delete memoryCache[k];
|
|
122
|
+
|
|
123
|
+
ls.keys().forEach(k => {
|
|
124
|
+
if (k.startsWith("html:") || k.startsWith("css:")) {
|
|
125
|
+
ls.remove(k);
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
return true;
|
|
130
|
+
}
|
|
131
|
+
};
|
package/jsconfig.json
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@t8n/ui",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "A lightweight HTML templating engine for TitanPL with file caching and variable interpolation.",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"types": "index.d.ts",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"scripts": {
|
|
9
|
+
"test": "titan run ext"
|
|
10
|
+
},
|
|
11
|
+
"keywords": [
|
|
12
|
+
"titanpl",
|
|
13
|
+
"t8n",
|
|
14
|
+
"titan",
|
|
15
|
+
"titan planet",
|
|
16
|
+
"templating",
|
|
17
|
+
"html",
|
|
18
|
+
"template",
|
|
19
|
+
"engine",
|
|
20
|
+
"gravity",
|
|
21
|
+
"@titanpl/core",
|
|
22
|
+
"@ezetgalxy/titan"
|
|
23
|
+
],
|
|
24
|
+
"author": "Shoya",
|
|
25
|
+
"license": "ISC",
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"@titanpl/core": "2.1.0",
|
|
28
|
+
"chokidar": "^5.0.0",
|
|
29
|
+
"esbuild": "^0.27.2",
|
|
30
|
+
"titanpl-sdk": "^1.0.6"
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# ================================================================
|
|
2
|
+
# STAGE 1 — Build Titan (JS → Rust)
|
|
3
|
+
# ================================================================
|
|
4
|
+
FROM rust:1.91.1 AS builder
|
|
5
|
+
|
|
6
|
+
# Install Node for Titan CLI + bundler
|
|
7
|
+
RUN curl -fsSL https://deb.nodesource.com/setup_20.x | bash - \
|
|
8
|
+
&& apt-get install -y nodejs
|
|
9
|
+
|
|
10
|
+
# Install Titan CLI (latest)
|
|
11
|
+
RUN npm install -g @ezetgalaxy/titan@latest
|
|
12
|
+
|
|
13
|
+
WORKDIR /app
|
|
14
|
+
|
|
15
|
+
# Copy project files
|
|
16
|
+
COPY . .
|
|
17
|
+
|
|
18
|
+
# Install JS dependencies (needed for Titan DSL + bundler)
|
|
19
|
+
RUN npm install
|
|
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
|
+
|
|
35
|
+
# Build Titan metadata + bundle JS actions
|
|
36
|
+
RUN titan build
|
|
37
|
+
|
|
38
|
+
# Build Rust binary
|
|
39
|
+
RUN cd server && cargo build --release
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
# ================================================================
|
|
44
|
+
# STAGE 2 — Runtime Image (Lightweight)
|
|
45
|
+
# ================================================================
|
|
46
|
+
FROM debian:stable-slim
|
|
47
|
+
|
|
48
|
+
WORKDIR /app
|
|
49
|
+
|
|
50
|
+
# Copy Rust binary from builder stage
|
|
51
|
+
COPY --from=builder /app/server/target/release/titan-server ./titan-server
|
|
52
|
+
|
|
53
|
+
# Copy Titan routing metadata
|
|
54
|
+
COPY --from=builder /app/server/routes.json ./routes.json
|
|
55
|
+
COPY --from=builder /app/server/action_map.json ./action_map.json
|
|
56
|
+
|
|
57
|
+
# Copy Titan JS bundles
|
|
58
|
+
RUN mkdir -p /app/actions
|
|
59
|
+
COPY --from=builder /app/server/actions /app/actions
|
|
60
|
+
|
|
61
|
+
# Copy only Titan extensions
|
|
62
|
+
COPY --from=builder /app/.ext ./.ext
|
|
63
|
+
|
|
64
|
+
EXPOSE 5100
|
|
65
|
+
|
|
66
|
+
CMD ["./titan-server"]
|