titanpl 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.
Files changed (74) hide show
  1. package/LICENSE +15 -0
  2. package/README.md +254 -0
  3. package/index.js +24 -0
  4. package/package.json +83 -0
  5. package/packages/cli/index.js +141 -0
  6. package/templates/common/.env +1 -0
  7. package/templates/common/Dockerfile +66 -0
  8. package/templates/common/_dockerignore +35 -0
  9. package/templates/common/_gitignore +33 -0
  10. package/templates/common/app/t.native.d.ts +2043 -0
  11. package/templates/common/app/t.native.js +39 -0
  12. package/templates/extension/README.md +65 -0
  13. package/templates/extension/index.d.ts +27 -0
  14. package/templates/extension/index.js +28 -0
  15. package/templates/extension/jsconfig.json +14 -0
  16. package/templates/extension/native/Cargo.toml +9 -0
  17. package/templates/extension/native/src/lib.rs +5 -0
  18. package/templates/extension/package-lock.json +522 -0
  19. package/templates/extension/package.json +26 -0
  20. package/templates/extension/titan.json +18 -0
  21. package/templates/js/app/actions/getuser.js +9 -0
  22. package/templates/js/app/app.js +7 -0
  23. package/templates/js/eslint.config.js +5 -0
  24. package/templates/js/jsconfig.json +27 -0
  25. package/templates/js/package.json +27 -0
  26. package/templates/rust-js/app/actions/getuser.js +9 -0
  27. package/templates/rust-js/app/actions/rust_hello.rs +14 -0
  28. package/templates/rust-js/app/app.js +9 -0
  29. package/templates/rust-js/eslint.config.js +5 -0
  30. package/templates/rust-js/jsconfig.json +27 -0
  31. package/templates/rust-js/package.json +27 -0
  32. package/templates/rust-js/titan/bundle.js +157 -0
  33. package/templates/rust-js/titan/dev.js +323 -0
  34. package/templates/rust-js/titan/titan.js +126 -0
  35. package/templates/rust-ts/app/actions/getuser.ts +9 -0
  36. package/templates/rust-ts/app/actions/rust_hello.rs +14 -0
  37. package/templates/rust-ts/app/app.ts +9 -0
  38. package/templates/rust-ts/eslint.config.js +12 -0
  39. package/templates/rust-ts/package.json +29 -0
  40. package/templates/rust-ts/titan/bundle.js +163 -0
  41. package/templates/rust-ts/titan/dev.js +435 -0
  42. package/templates/rust-ts/titan/titan.d.ts +19 -0
  43. package/templates/rust-ts/titan/titan.js +124 -0
  44. package/templates/rust-ts/tsconfig.json +28 -0
  45. package/templates/ts/app/actions/getuser.ts +9 -0
  46. package/templates/ts/app/app.ts +7 -0
  47. package/templates/ts/eslint.config.js +12 -0
  48. package/templates/ts/package.json +29 -0
  49. package/templates/ts/tsconfig.json +28 -0
  50. package/titanpl-sdk/LICENSE +15 -0
  51. package/titanpl-sdk/README.md +109 -0
  52. package/titanpl-sdk/assets/titanpl-sdk.png +0 -0
  53. package/titanpl-sdk/bin/run.js +274 -0
  54. package/titanpl-sdk/index.js +5 -0
  55. package/titanpl-sdk/package-lock.json +28 -0
  56. package/titanpl-sdk/package.json +40 -0
  57. package/titanpl-sdk/templates/app/actions/hello.js +5 -0
  58. package/titanpl-sdk/templates/app/app.js +7 -0
  59. package/titanpl-sdk/templates/jsconfig.json +19 -0
  60. package/titanpl-sdk/templates/server/Cargo.toml +52 -0
  61. package/titanpl-sdk/templates/server/src/action_management.rs +175 -0
  62. package/titanpl-sdk/templates/server/src/errors.rs +12 -0
  63. package/titanpl-sdk/templates/server/src/extensions/builtin.rs +1038 -0
  64. package/titanpl-sdk/templates/server/src/extensions/external.rs +338 -0
  65. package/titanpl-sdk/templates/server/src/extensions/mod.rs +580 -0
  66. package/titanpl-sdk/templates/server/src/extensions/titan_core.js +249 -0
  67. package/titanpl-sdk/templates/server/src/fast_path.rs +719 -0
  68. package/titanpl-sdk/templates/server/src/main.rs +607 -0
  69. package/titanpl-sdk/templates/server/src/runtime.rs +284 -0
  70. package/titanpl-sdk/templates/server/src/utils.rs +33 -0
  71. package/titanpl-sdk/templates/titan/bundle.js +259 -0
  72. package/titanpl-sdk/templates/titan/dev.js +390 -0
  73. package/titanpl-sdk/templates/titan/error-box.js +277 -0
  74. package/titanpl-sdk/templates/titan/titan.js +129 -0
@@ -0,0 +1,284 @@
1
+ //! Worker Pool Management (Performance Optimized)
2
+ //!
3
+ //! Features:
4
+ //! 1. Work-stealing fallback strategy.
5
+ //! 2. Bounded channel capacity for pipeline handling.
6
+ //! 3. Batch-ready architecture for HTTP pipelining.
7
+ //! 4. Zero-copy / deferred cloning where possible.
8
+
9
+ use bytes::Bytes;
10
+ use crossbeam::channel::{bounded, Sender, TrySendError};
11
+ use std::sync::atomic::{AtomicUsize, Ordering};
12
+ use std::thread;
13
+ use tokio::sync::mpsc;
14
+ use tokio::sync::oneshot;
15
+ use smallvec::SmallVec;
16
+
17
+ use crate::extensions::{self, AsyncOpRequest, TitanRuntime, WorkerAsyncResult};
18
+
19
+ pub struct RuntimeManager {
20
+ request_txs: Vec<Sender<WorkerCommand>>,
21
+ round_robin_counter: AtomicUsize,
22
+ num_workers: usize,
23
+ _workers: Vec<thread::JoinHandle<()>>,
24
+ }
25
+
26
+ pub enum WorkerCommand {
27
+ Request(RequestTask),
28
+ Resume {
29
+ drift_id: u32,
30
+ result: WorkerAsyncResult,
31
+ },
32
+ }
33
+
34
+ #[allow(dead_code)]
35
+ pub struct RequestTask {
36
+ pub action_name: String,
37
+ pub body: Option<Bytes>,
38
+ pub method: String,
39
+ pub path: String,
40
+ pub headers: SmallVec<[(String, String); 8]>,
41
+ pub params: SmallVec<[(String, String); 4]>,
42
+ pub query: SmallVec<[(String, String); 4]>,
43
+ pub response_tx: oneshot::Sender<WorkerResult>,
44
+ }
45
+
46
+ pub struct WorkerResult {
47
+ pub json: serde_json::Value,
48
+ pub timings: Vec<(String, f64)>,
49
+ }
50
+
51
+ impl RuntimeManager {
52
+ pub fn new(
53
+ project_root: std::path::PathBuf,
54
+ num_threads: usize,
55
+ stack_size: usize,
56
+ ) -> Self {
57
+ let (async_tx, mut async_rx) = mpsc::channel::<AsyncOpRequest>(2048);
58
+ let tokio_handle = tokio::runtime::Handle::current();
59
+
60
+ // Spawn Tokio Async Handler (for drift operations)
61
+ tokio_handle.spawn(async move {
62
+ while let Some(req) = async_rx.recv().await {
63
+ let drift_id = req.drift_id;
64
+ let respond_tx = req.respond_tx;
65
+ tokio::spawn(async move {
66
+ let start = std::time::Instant::now();
67
+ let result = extensions::builtin::run_async_operation(req.op).await;
68
+ let duration_ms = start.elapsed().as_secs_f64() * 1000.0;
69
+ let _ = respond_tx.send(WorkerAsyncResult {
70
+ drift_id,
71
+ result,
72
+ duration_ms,
73
+ });
74
+ });
75
+ }
76
+ });
77
+
78
+ // Create worker channels
79
+ let channel_capacity = 256;
80
+ let mut workers = Vec::with_capacity(num_threads);
81
+
82
+ let mut channels: Vec<(Sender<WorkerCommand>, crossbeam::channel::Receiver<WorkerCommand>)> =
83
+ Vec::with_capacity(num_threads);
84
+
85
+ for _ in 0..num_threads {
86
+ let (tx, rx) = bounded(channel_capacity);
87
+ channels.push((tx, rx));
88
+ }
89
+
90
+ let mut final_txs: Vec<Sender<WorkerCommand>> = Vec::with_capacity(num_threads);
91
+ for (tx, _) in &channels {
92
+ final_txs.push(tx.clone());
93
+ }
94
+
95
+ // Spawn Worker Threads
96
+ for (i, (tx, rx)) in channels.into_iter().enumerate() {
97
+ let my_tx = tx.clone();
98
+ let root = project_root.clone();
99
+ let handle = tokio_handle.clone();
100
+ let async_tx = async_tx.clone();
101
+
102
+ let handle = thread::Builder::new()
103
+ .name(format!("titan-worker-{}", i))
104
+ .stack_size(stack_size)
105
+ .spawn(move || {
106
+ let mut rt = extensions::init_runtime_worker(
107
+ i,
108
+ root,
109
+ my_tx,
110
+ handle,
111
+ async_tx,
112
+ stack_size,
113
+ );
114
+ rt.bind_to_isolate();
115
+
116
+ loop {
117
+ match rx.recv() {
118
+ Ok(cmd) => match cmd {
119
+ WorkerCommand::Request(task) => {
120
+ handle_new_request(task, &mut rt);
121
+ }
122
+ WorkerCommand::Resume { drift_id, result } => {
123
+ handle_resume(drift_id, result, &mut rt);
124
+ }
125
+ },
126
+ Err(_) => break,
127
+ }
128
+ }
129
+ })
130
+ .expect("Failed to spawn worker");
131
+
132
+ workers.push(handle);
133
+ }
134
+
135
+ Self {
136
+ request_txs: final_txs,
137
+ round_robin_counter: AtomicUsize::new(0),
138
+ num_workers: num_threads,
139
+ _workers: workers,
140
+ }
141
+ }
142
+
143
+ /// Execute an action on a worker. Uses round-robin with work-stealing fallback.
144
+ pub async fn execute(
145
+ &self,
146
+ action: String,
147
+ method: String,
148
+ path: String,
149
+ body: Option<Bytes>,
150
+ headers: SmallVec<[(String, String); 8]>,
151
+ params: SmallVec<[(String, String); 4]>,
152
+ query: SmallVec<[(String, String); 4]>,
153
+ ) -> Result<(serde_json::Value, Vec<(String, f64)>), String> {
154
+ let (tx, rx) = oneshot::channel();
155
+ let task = RequestTask {
156
+ action_name: action,
157
+ body,
158
+ method,
159
+ path,
160
+ headers,
161
+ params,
162
+ query,
163
+ response_tx: tx,
164
+ };
165
+
166
+ // Work-Stealing Distribution
167
+ let start_idx = self.round_robin_counter.fetch_add(1, Ordering::Relaxed) % self.num_workers;
168
+ let mut cmd = WorkerCommand::Request(task);
169
+
170
+ for attempt in 0..self.num_workers {
171
+ let idx = (start_idx + attempt) % self.num_workers;
172
+ match self.request_txs[idx].try_send(cmd) {
173
+ Ok(()) => {
174
+ return match rx.await {
175
+ Ok(res) => Ok((res.json, res.timings)),
176
+ Err(_) => Err("Worker channel closed".to_string()),
177
+ };
178
+ }
179
+ Err(TrySendError::Full(returned)) => {
180
+ cmd = returned;
181
+ }
182
+ Err(TrySendError::Disconnected(_)) => {
183
+ return Err("Worker disconnected".to_string());
184
+ }
185
+ }
186
+ }
187
+
188
+ // All workers full — blocking send to the original target as last resort
189
+ self.request_txs[start_idx]
190
+ .send(cmd)
191
+ .map_err(|e| e.to_string())?;
192
+
193
+ match rx.await {
194
+ Ok(res) => Ok((res.json, res.timings)),
195
+ Err(_) => Err("Worker channel closed".to_string()),
196
+ }
197
+ }
198
+ }
199
+
200
+ /// Handle a new incoming request.
201
+ ///
202
+ /// OPTIMIZATION: Deferred cloning.
203
+ /// Only stores data if drift (async suspend) happens.
204
+ fn handle_new_request(task: RequestTask, rt: &mut TitanRuntime) {
205
+ rt.request_counter += 1;
206
+ let request_id = rt.request_counter;
207
+
208
+ // Move response_tx into pending (partial move of task — other fields remain accessible)
209
+ rt.pending_requests.insert(request_id, task.response_tx);
210
+
211
+ let drift_count = rt.drift_counter;
212
+ rt.request_start_counters.insert(request_id, drift_count);
213
+
214
+ // Execute action — pass references, body is O(1) Bytes clone
215
+ extensions::execute_action_optimized(
216
+ rt,
217
+ request_id,
218
+ &task.action_name,
219
+ task.body.clone(), // Bytes::clone() is O(1) refcount bump
220
+ &task.method,
221
+ &task.path,
222
+ &task.headers,
223
+ &task.params,
224
+ &task.query,
225
+ );
226
+
227
+ // Deferred cloning decision
228
+ if !rt.pending_requests.contains_key(&request_id) {
229
+ // Completed synchronously — no data needed, minimal cleanup
230
+ rt.request_start_counters.remove(&request_id);
231
+ } else {
232
+ // Suspended via drift — MOVE (not clone) data for resume replay.
233
+ rt.active_requests.insert(
234
+ request_id,
235
+ extensions::RequestData {
236
+ action_name: task.action_name,
237
+ body: task.body,
238
+ method: task.method,
239
+ path: task.path,
240
+ headers: task.headers.into_vec(),
241
+ params: task.params.into_vec(),
242
+ query: task.query.into_vec(),
243
+ },
244
+ );
245
+ }
246
+ }
247
+
248
+ fn handle_resume(drift_id: u32, result: WorkerAsyncResult, rt: &mut TitanRuntime) {
249
+ let req_id = rt.drift_to_request.get(&drift_id).copied().unwrap_or(0);
250
+
251
+ let timing_type = if result.result.get("error").is_some() {
252
+ "drift_error"
253
+ } else {
254
+ "drift"
255
+ };
256
+ rt.request_timings
257
+ .entry(req_id)
258
+ .or_default()
259
+ .push((timing_type.to_string(), result.duration_ms));
260
+
261
+ rt.completed_drifts.insert(drift_id, result.result);
262
+
263
+ if let Some(req_data) = rt.active_requests.get(&req_id).cloned() {
264
+ let start_counter = rt.request_start_counters.get(&req_id).copied().unwrap_or(0);
265
+ rt.drift_counter = start_counter;
266
+
267
+ extensions::execute_action_optimized(
268
+ rt,
269
+ req_id,
270
+ &req_data.action_name,
271
+ req_data.body,
272
+ &req_data.method,
273
+ &req_data.path,
274
+ &req_data.headers,
275
+ &req_data.params,
276
+ &req_data.query,
277
+ );
278
+ }
279
+
280
+ if req_id != 0 && !rt.pending_requests.contains_key(&req_id) {
281
+ rt.active_requests.remove(&req_id);
282
+ rt.request_start_counters.remove(&req_id);
283
+ }
284
+ }
@@ -0,0 +1,33 @@
1
+ //! Terminal styling and utility functions.
2
+
3
+ pub fn blue(s: &str) -> String {
4
+ format!("\x1b[38;5;39m{}\x1b[0m", s)
5
+ }
6
+ pub fn white(s: &str) -> String {
7
+ format!("\x1b[39m{}\x1b[0m", s)
8
+ }
9
+ pub fn yellow(s: &str) -> String {
10
+ format!("\x1b[33m{}\x1b[0m", s)
11
+ }
12
+ pub fn green(s: &str) -> String {
13
+ format!("\x1b[32m{}\x1b[0m", s)
14
+ }
15
+ pub fn gray(s: &str) -> String {
16
+ format!("\x1b[90m{}\x1b[0m", s)
17
+ }
18
+ pub fn red(s: &str) -> String {
19
+ format!("\x1b[31m{}\x1b[0m", s)
20
+ }
21
+
22
+ pub fn parse_expires_in(value: &str) -> Option<u64> {
23
+ let (num, unit) = value.split_at(value.len() - 1);
24
+ let n: u64 = num.parse().ok()?;
25
+
26
+ match unit {
27
+ "s" => Some(n),
28
+ "m" => Some(n * 60),
29
+ "h" => Some(n * 60 * 60),
30
+ "d" => Some(n * 60 * 60 * 24),
31
+ _ => None,
32
+ }
33
+ }
@@ -0,0 +1,259 @@
1
+ /**
2
+ * Bundle.js
3
+ * Handles esbuild bundling with comprehensive error reporting
4
+ * RULE: This file handles ALL esbuild errors and prints error boxes directly
5
+ */
6
+
7
+ import esbuild from 'esbuild';
8
+ import path from 'path';
9
+ import fs from 'fs';
10
+ import { fileURLToPath } from 'url';
11
+ import { createRequire } from 'module';
12
+ import { renderErrorBox, parseEsbuildError } from './error-box.js';
13
+
14
+ const __filename = fileURLToPath(import.meta.url);
15
+ const __dirname = path.dirname(__filename);
16
+
17
+ // Required for resolving node_modules inside ESM
18
+ const require = createRequire(import.meta.url);
19
+
20
+ /**
21
+ * Titan Node Builtin Rewrite Map
22
+ * Rewrites Node builtins to @titanpl/node shims
23
+ */
24
+ const NODE_BUILTIN_MAP = {
25
+ "fs": "@titanpl/node/fs",
26
+ "node:fs": "@titanpl/node/fs",
27
+
28
+ "path": "@titanpl/node/path",
29
+ "node:path": "@titanpl/node/path",
30
+
31
+ "os": "@titanpl/node/os",
32
+ "node:os": "@titanpl/node/os",
33
+
34
+ "crypto": "@titanpl/node/crypto",
35
+ "node:crypto": "@titanpl/node/crypto",
36
+
37
+ "process": "@titanpl/node/process",
38
+
39
+ "util": "@titanpl/node/util",
40
+ "node:util": "@titanpl/node/util",
41
+ };
42
+
43
+ /**
44
+ * Titan Node Compatibility Plugin
45
+ * Rewrites require/import of Node builtins
46
+ * Returns absolute paths (required by esbuild)
47
+ */
48
+ const titanNodeCompatPlugin = {
49
+ name: "titan-node-compat",
50
+ setup(build) {
51
+ build.onResolve({ filter: /.*/ }, args => {
52
+ if (NODE_BUILTIN_MAP[args.path]) {
53
+ try {
54
+ const resolved = require.resolve(NODE_BUILTIN_MAP[args.path]);
55
+ return { path: resolved };
56
+ } catch (e) {
57
+ throw new Error(
58
+ `[Titan] Failed to resolve Node shim: ${NODE_BUILTIN_MAP[args.path]}`
59
+ );
60
+ }
61
+ }
62
+ });
63
+ }
64
+ };
65
+
66
+ /**
67
+ * Get Titan version for error branding
68
+ */
69
+ function getTitanVersion() {
70
+ try {
71
+ const pkgPath = require.resolve("@ezetgalaxy/titan/package.json");
72
+ return JSON.parse(fs.readFileSync(pkgPath, "utf-8")).version;
73
+ } catch (e) {
74
+ return "0.1.0";
75
+ }
76
+ }
77
+
78
+ /**
79
+ * Custom error class for bundle errors
80
+ */
81
+ export class BundleError extends Error {
82
+ constructor(message, errors = [], warnings = []) {
83
+ super(message);
84
+ this.name = 'BundleError';
85
+ this.errors = errors;
86
+ this.warnings = warnings;
87
+ this.isBundleError = true;
88
+ }
89
+ }
90
+
91
+ /**
92
+ * Validate entry file exists
93
+ */
94
+ async function validateEntryPoint(entryPoint) {
95
+ const absPath = path.resolve(entryPoint);
96
+
97
+ if (!fs.existsSync(absPath)) {
98
+ throw new BundleError(
99
+ `Entry point does not exist: ${entryPoint}`,
100
+ [{ text: `Cannot find file: ${absPath}`, location: { file: entryPoint } }]
101
+ );
102
+ }
103
+
104
+ try {
105
+ await fs.promises.access(absPath, fs.constants.R_OK);
106
+ } catch {
107
+ throw new BundleError(
108
+ `Entry point is not readable: ${entryPoint}`,
109
+ [{ text: `Cannot read file: ${absPath}`, location: { file: entryPoint } }]
110
+ );
111
+ }
112
+ }
113
+
114
+ /**
115
+ * Bundles a single file
116
+ */
117
+ export async function bundleFile(options) {
118
+ const {
119
+ entryPoint,
120
+ outfile,
121
+ format = 'iife',
122
+ minify = false,
123
+ sourcemap = false,
124
+ platform = 'neutral',
125
+ globalName = '__titan_exports',
126
+ target = 'es2020',
127
+ banner = {},
128
+ footer = {}
129
+ } = options;
130
+
131
+ await validateEntryPoint(entryPoint);
132
+
133
+ const outDir = path.dirname(outfile);
134
+ await fs.promises.mkdir(outDir, { recursive: true });
135
+
136
+ try {
137
+ const result = await esbuild.build({
138
+ entryPoints: [entryPoint],
139
+ bundle: true,
140
+ outfile,
141
+ format,
142
+ globalName,
143
+ platform,
144
+ target,
145
+ banner,
146
+ footer,
147
+ minify,
148
+ sourcemap,
149
+ logLevel: 'silent',
150
+ logLimit: 0,
151
+ write: true,
152
+ metafile: false,
153
+ plugins: [titanNodeCompatPlugin],
154
+ });
155
+
156
+ if (result.errors?.length) {
157
+ throw new BundleError(
158
+ `Build failed with ${result.errors.length} error(s)`,
159
+ result.errors,
160
+ result.warnings || []
161
+ );
162
+ }
163
+
164
+ } catch (err) {
165
+ if (err.errors?.length) {
166
+ throw new BundleError(
167
+ `Build failed with ${err.errors.length} error(s)`,
168
+ err.errors,
169
+ err.warnings || []
170
+ );
171
+ }
172
+
173
+ throw new BundleError(
174
+ `Unexpected build error: ${err.message}`,
175
+ [{ text: err.message, location: { file: entryPoint } }]
176
+ );
177
+ }
178
+ }
179
+
180
+ /**
181
+ * Main bundler
182
+ */
183
+ export async function bundle() {
184
+ const root = process.cwd();
185
+ const actionsDir = path.join(root, 'app', 'actions');
186
+ const bundleDir = path.join(root, 'server', 'src', 'actions');
187
+
188
+ if (fs.existsSync(bundleDir)) {
189
+ fs.rmSync(bundleDir, { recursive: true, force: true });
190
+ }
191
+ await fs.promises.mkdir(bundleDir, { recursive: true });
192
+
193
+ if (!fs.existsSync(actionsDir)) return;
194
+
195
+ const files = fs.readdirSync(actionsDir).filter(f =>
196
+ (f.endsWith('.js') || f.endsWith('.ts')) && !f.endsWith('.d.ts')
197
+ );
198
+
199
+ for (const file of files) {
200
+ const actionName = path.basename(file, path.extname(file));
201
+ const entryPoint = path.join(actionsDir, file);
202
+ const outfile = path.join(bundleDir, actionName + ".jsbundle");
203
+
204
+ try {
205
+ await bundleFile({
206
+ entryPoint,
207
+ outfile,
208
+ format: 'iife',
209
+ globalName: '__titan_exports',
210
+ platform: 'node',
211
+ target: 'es2020',
212
+ banner: { js: "var Titan = t;" },
213
+ footer: {
214
+ js: `
215
+ (function () {
216
+ const fn =
217
+ __titan_exports["${actionName}"] ||
218
+ __titan_exports.default;
219
+
220
+ if (typeof fn !== "function") {
221
+ throw new Error("[Titan] Action '${actionName}' not found or not a function");
222
+ }
223
+
224
+ globalThis["${actionName}"] = globalThis.defineAction(fn);
225
+ })();
226
+ `
227
+ }
228
+ });
229
+
230
+ } catch (error) {
231
+
232
+ console.error();
233
+
234
+ const titanVersion = getTitanVersion();
235
+
236
+ if (error.isBundleError && error.errors?.length) {
237
+ for (let i = 0; i < error.errors.length; i++) {
238
+ const errorInfo = parseEsbuildError(error.errors[i]);
239
+ if (error.errors.length > 1) {
240
+ errorInfo.title = `Build Error ${i + 1}/${error.errors.length}`;
241
+ }
242
+ errorInfo.titanVersion = titanVersion;
243
+ console.error(renderErrorBox(errorInfo));
244
+ console.error();
245
+ }
246
+ } else {
247
+ const errorInfo = {
248
+ title: 'Build Error',
249
+ file: entryPoint,
250
+ message: error.message || 'Unknown error',
251
+ titanVersion
252
+ };
253
+ console.error(renderErrorBox(errorInfo));
254
+ }
255
+
256
+ throw new Error('__TITAN_BUNDLE_FAILED__');
257
+ }
258
+ }
259
+ }