titanpl-sdk 2.0.3 → 2.0.4

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.
@@ -1,264 +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
- /**
18
- * Get Titan version for error branding
19
- */
20
- function getTitanVersion() {
21
- try {
22
- const require = createRequire(import.meta.url);
23
- const pkgPath = require.resolve("@ezetgalaxy/titan/package.json");
24
- return JSON.parse(fs.readFileSync(pkgPath, "utf-8")).version;
25
- } catch (e) {
26
- return "0.1.0";
27
- }
28
- }
29
-
30
- /**
31
- * Custom error class for bundle errors
32
- */
33
- export class BundleError extends Error {
34
- constructor(message, errors = [], warnings = []) {
35
- super(message);
36
- this.name = 'BundleError';
37
- this.errors = errors;
38
- this.warnings = warnings;
39
- this.isBundleError = true;
40
- }
41
- }
42
-
43
- /**
44
- * Validates that the entry point exists and is readable
45
- * @param {string} entryPoint - Entry file path
46
- * @throws {BundleError} If file doesn't exist or isn't readable
47
- */
48
- async function validateEntryPoint(entryPoint) {
49
- const absPath = path.resolve(entryPoint);
50
-
51
- if (!fs.existsSync(absPath)) {
52
- throw new BundleError(
53
- `Entry point does not exist: ${entryPoint}`,
54
- [{
55
- text: `Cannot find file: ${absPath}`,
56
- location: { file: entryPoint }
57
- }]
58
- );
59
- }
60
-
61
- try {
62
- await fs.promises.access(absPath, fs.constants.R_OK);
63
- } catch (err) {
64
- throw new BundleError(
65
- `Entry point is not readable: ${entryPoint}`,
66
- [{
67
- text: `Cannot read file: ${absPath}`,
68
- location: { file: entryPoint }
69
- }]
70
- );
71
- }
72
- }
73
-
74
- /**
75
- * Bundles a single JavaScript/TypeScript file using esbuild
76
- * @param {Object} options - Bundle options
77
- * @returns {Promise<void>}
78
- * @throws {BundleError} If bundling fails
79
- */
80
- export async function bundleFile(options) {
81
- const {
82
- entryPoint,
83
- outfile,
84
- format = 'iife',
85
- minify = false,
86
- sourcemap = false,
87
- platform = 'neutral',
88
- globalName = '__titan_exports',
89
- target = 'es2020',
90
- banner = {},
91
- footer = {}
92
- } = options;
93
-
94
- // Validate entry point exists
95
- await validateEntryPoint(entryPoint);
96
-
97
- // Ensure output directory exists
98
- const outDir = path.dirname(outfile);
99
- await fs.promises.mkdir(outDir, { recursive: true });
100
-
101
- try {
102
- // Run esbuild with error logging enabled
103
- const result = await esbuild.build({
104
- entryPoints: [entryPoint],
105
- bundle: true,
106
- outfile,
107
- format,
108
- globalName,
109
- platform,
110
- target,
111
- banner,
112
- footer,
113
- minify,
114
- sourcemap,
115
- logLevel: 'silent', // We handle all errors ourselves
116
- logLimit: 0,
117
- color: false,
118
- write: true,
119
- metafile: false,
120
- });
121
-
122
- // Check for errors in the result
123
- if (result.errors && result.errors.length > 0) {
124
- throw new BundleError(
125
- `Build failed with ${result.errors.length} error(s)`,
126
- result.errors,
127
- result.warnings || []
128
- );
129
- }
130
-
131
- } catch (err) {
132
- if (err.errors && err.errors.length > 0) {
133
- // This is an esbuild error with detailed error information
134
- throw new BundleError(
135
- `Build failed with ${err.errors.length} error(s)`,
136
- err.errors,
137
- err.warnings || []
138
- );
139
- }
140
-
141
- // Other unexpected errors
142
- throw new BundleError(
143
- `Unexpected build error: ${err.message}`,
144
- [{
145
- text: err.message,
146
- location: { file: entryPoint }
147
- }]
148
- );
149
- }
150
- }
151
-
152
- /**
153
- * Main bundle function - scans app/actions and bundles all files
154
- * RULE: This function handles ALL esbuild errors and prints error boxes directly
155
- * RULE: After printing error box, throws Error("__TITAN_BUNDLE_FAILED__")
156
- * @returns {Promise<void>}
157
- */
158
- export async function bundle() {
159
- const root = process.cwd();
160
- const actionsDir = path.join(root, 'app', 'actions');
161
- const bundleDir = path.join(root, 'server', 'src', 'actions');
162
-
163
- // Ensure bundle directory exists and is clean
164
- if (fs.existsSync(bundleDir)) {
165
- fs.rmSync(bundleDir, { recursive: true, force: true });
166
- }
167
- await fs.promises.mkdir(bundleDir, { recursive: true });
168
-
169
- // Check if actions directory exists
170
- if (!fs.existsSync(actionsDir)) {
171
- return; // No actions to bundle
172
- }
173
-
174
- // Get all JS/TS files in actions directory
175
- const files = fs.readdirSync(actionsDir).filter(f =>
176
- (f.endsWith('.js') || f.endsWith('.ts')) && !f.endsWith('.d.ts')
177
- );
178
-
179
- if (files.length === 0) {
180
- return; // No action files
181
- }
182
-
183
- // Bundle each action file
184
- for (const file of files) {
185
- const actionName = path.basename(file, path.extname(file));
186
- const entryPoint = path.join(actionsDir, file);
187
- const outfile = path.join(bundleDir, actionName + ".jsbundle");
188
-
189
- try {
190
- await bundleFile({
191
- entryPoint,
192
- outfile,
193
- format: 'iife',
194
- globalName: '__titan_exports',
195
- platform: 'neutral',
196
- target: 'es2020',
197
- minify: false,
198
- sourcemap: false,
199
- banner: {
200
- js: "var Titan = t;"
201
- },
202
- footer: {
203
- js: `
204
- (function () {
205
- const fn =
206
- __titan_exports["${actionName}"] ||
207
- __titan_exports.default;
208
-
209
- if (typeof fn !== "function") {
210
- throw new Error("[Titan] Action '${actionName}' not found or not a function");
211
- }
212
-
213
- globalThis["${actionName}"] = globalThis.defineAction(fn);
214
- })();
215
- `
216
- }
217
- });
218
- } catch (error) {
219
- // RULE: Handle esbuild errors HERE and print error boxes
220
- if (error.isBundleError && error.errors && error.errors.length > 0) {
221
- // Print error box for each esbuild error
222
- console.error(); // Empty line for spacing
223
-
224
- const titanVersion = getTitanVersion();
225
-
226
- for (let i = 0; i < error.errors.length; i++) {
227
- const esbuildError = error.errors[i];
228
- const errorInfo = parseEsbuildError(esbuildError);
229
-
230
- // Add error number to title if multiple errors
231
- if (error.errors.length > 1) {
232
- errorInfo.title = `Build Error ${i + 1}/${error.errors.length}`;
233
- }
234
-
235
- // Add Titan version
236
- errorInfo.titanVersion = titanVersion;
237
-
238
- // Print the error box
239
- console.error(renderErrorBox(errorInfo));
240
-
241
- if (i < error.errors.length - 1) {
242
- console.error(); // Empty line between errors
243
- }
244
- }
245
-
246
- console.error(); // Empty line after all errors
247
- } else {
248
- // Other errors
249
- console.error();
250
- const errorInfo = {
251
- title: 'Build Error',
252
- file: entryPoint,
253
- message: error.message || 'Unknown error',
254
- titanVersion: getTitanVersion()
255
- };
256
- console.error(renderErrorBox(errorInfo));
257
- console.error();
258
- }
259
-
260
- // RULE: Throw special error to signal bundle failure
261
- throw new Error('__TITAN_BUNDLE_FAILED__');
262
- }
263
- }
264
- }
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("titanpl/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
+ }
@@ -25,7 +25,7 @@ const bold = (t) => `\x1b[1m${t}\x1b[0m`;
25
25
  function getTitanVersion() {
26
26
  try {
27
27
  const require = createRequire(import.meta.url);
28
- const pkgPath = require.resolve("@ezetgalaxy/titan/package.json");
28
+ const pkgPath = require.resolve("titanpl/package.json");
29
29
  return JSON.parse(fs.readFileSync(pkgPath, "utf-8")).version;
30
30
  } catch (e) {
31
31
  try {
@@ -34,7 +34,7 @@ function getTitanVersion() {
34
34
  const pkgPath = path.join(cur, "package.json");
35
35
  if (fs.existsSync(pkgPath)) {
36
36
  const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
37
- if (pkg.name === "@ezetgalaxy/titan") return pkg.version;
37
+ if (pkg.name === "titanpl") return pkg.version;
38
38
  }
39
39
  cur = path.join(cur, "..");
40
40
  }
@@ -135,8 +135,8 @@ async function startRustServer(retryCount = 0) {
135
135
 
136
136
  serverProcess = spawn("cargo", ["run", "--quiet"], {
137
137
  cwd: serverPath,
138
- stdio: ["ignore", "pipe", "pipe"], // Capture stderr to detect port conflicts
139
- env: { ...process.env, CARGO_INCREMENTAL: "1" }
138
+ stdio: ["ignore", "pipe", "pipe"],
139
+ env: { ...process.env, CARGO_INCREMENTAL: "1", TITAN_DEV: "1" }
140
140
  });
141
141
 
142
142
  serverProcess.on("error", (err) => {
@@ -195,17 +195,34 @@ async function startRustServer(retryCount = 0) {
195
195
  stderrBuffer.includes("AddrInUse");
196
196
 
197
197
  if (isPortError) {
198
+ if (retryCount < 3) {
199
+ // It's likely the previous process hasn't fully released the port
200
+ await delay(1000);
201
+ await startRustServer(retryCount + 1);
202
+ return;
203
+ }
204
+
198
205
  stopSpinner(false, "Orbit stabilization failed");
206
+
207
+ // Try to read intended port
208
+ let port = 3000;
209
+ try {
210
+ const routesConfig = JSON.parse(fs.readFileSync(path.join(process.cwd(), "server", "routes.json"), "utf8"));
211
+ if (routesConfig && routesConfig.__config && routesConfig.__config.port) {
212
+ port = routesConfig.__config.port;
213
+ }
214
+ } catch (e) { }
215
+
199
216
  console.log("");
200
217
 
201
218
  console.log(red("⏣ Your application cannot enter this orbit"));
202
- console.log(red("↳ Another application is already bound to this port."));
219
+ console.log(red(`↳ Another application is already bound to port ${port}.`));
203
220
  console.log("");
204
221
 
205
222
  console.log(yellow("Recommended Actions:"));
206
223
  console.log(yellow(" 1.") + " Release the occupied orbit (stop the other service).");
207
224
  console.log(yellow(" 2.") + " Assign your application to a new orbit in " + cyan("app/app.js"));
208
- console.log(yellow(" Example: ") + cyan('t.start(3001, "Titan Running!")'));
225
+ console.log(yellow(" Example: ") + cyan(`t.start(${port + 1}, "Titan Running!")`));
209
226
  console.log("");
210
227
 
211
228
  return;
@@ -230,6 +247,28 @@ async function startRustServer(retryCount = 0) {
230
247
  });
231
248
  }
232
249
 
250
+ function prepareRuntime() {
251
+ try {
252
+ const nm = path.join(process.cwd(), "node_modules");
253
+ const titanPkg = path.join(nm, "@titanpl");
254
+ const routePkg = path.join(titanPkg, "route");
255
+
256
+ if (!fs.existsSync(nm)) fs.mkdirSync(nm, { recursive: true });
257
+ if (!fs.existsSync(titanPkg)) fs.mkdirSync(titanPkg, { recursive: true });
258
+
259
+ if (!fs.existsSync(routePkg)) {
260
+ fs.mkdirSync(routePkg, { recursive: true });
261
+ fs.writeFileSync(path.join(routePkg, "package.json"), JSON.stringify({
262
+ name: "@titanpl/route",
263
+ main: "../../../titan/titan.js",
264
+ type: "module"
265
+ }, null, 2));
266
+ }
267
+ } catch (e) {
268
+ // Ignore errors
269
+ }
270
+ }
271
+
233
272
  /**
234
273
  * Rebuild JS runtime
235
274
  * RULE: Only show "✖ Runtime preparation failed" on error
@@ -280,6 +319,7 @@ async function rebuild() {
280
319
  }
281
320
 
282
321
  async function startDev() {
322
+ prepareRuntime();
283
323
  const root = process.cwd();
284
324
  const actionsDir = path.join(root, "app", "actions");
285
325
  let hasRust = false;