@titanpl/packet 1.0.0 → 1.4.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.
@@ -1,121 +1,74 @@
1
- import { bundle } from "./bundle.js";
2
1
  import fs from "fs";
3
2
  import path from "path";
3
+ import { pathToFileURL } from "url";
4
+ import esbuild from "esbuild";
5
+
6
+ export async function buildMetadata(root, dist) {
7
+ const isTs = fs.existsSync(path.join(root, "tsconfig.json")) || fs.existsSync(path.join(root, "app", "app.ts"));
8
+ const appDir = path.join(root, "app");
9
+ const appFile = isTs ? "app.ts" : "app.js";
10
+ const appPath = path.join(appDir, appFile);
11
+
12
+ if (!fs.existsSync(appPath)) {
13
+ console.error(`\x1b[31m❌ app/${appFile} not found.\x1b[0m`);
14
+ process.exit(1);
15
+ }
4
16
 
5
- const cyan = (t) => `\x1b[36m${t}\x1b[0m`;
6
- const green = (t) => `\x1b[32m${t}\x1b[0m`;
7
-
8
- const routes = {};
9
- const dynamicRoutes = {};
10
- const actionMap = {};
11
-
12
- function addRoute(method, route) {
13
- const key = `${method.toUpperCase()}:${route}`;
14
-
15
-
16
- return {
17
- reply(value) {
18
- routes[key] = {
19
- type: typeof value === "object" ? "json" : "text",
20
- value
21
- };
22
- },
23
-
24
- action(name) {
25
- if (route.includes(":")) {
26
- if (!dynamicRoutes[method]) dynamicRoutes[method] = [];
27
- dynamicRoutes[method].push({
28
- method: method.toUpperCase(),
29
- pattern: route,
30
- action: name
31
- });
32
- } else {
33
- routes[key] = {
34
- type: "action",
35
- value: name
36
- };
37
- actionMap[key] = name;
38
- }
39
- }
40
- };
41
- }
42
-
43
- /**
44
- * @typedef {Object} RouteHandler
45
- * @property {(value: any) => void} reply - Send a direct response
46
- * @property {(name: string) => void} action - Bind to a server-side action
47
- */
48
-
49
- /**
50
- * Titan App Builder
51
- */
52
- const t = {
53
- /**
54
- * Define a GET route
55
- * @param {string} route
56
- * @returns {RouteHandler}
57
- */
58
- get(route) {
59
- return addRoute("GET", route);
60
- },
61
-
62
- /**
63
- * Define a POST route
64
- * @param {string} route
65
- * @returns {RouteHandler}
66
- */
67
- post(route) {
68
- return addRoute("POST", route);
69
- },
70
-
71
- log(module, msg) {
72
- console.log(`[\x1b[35m${module}\x1b[0m] ${msg}`);
73
- },
74
-
75
- /**
76
- * Start the Titan Server
77
- * @param {number} [port=3000]
78
- * @param {string} [msg=""]
79
- */
80
- async start(port = 3000, msg = "") {
81
- try {
82
- console.log(cyan("[Titan] Preparing runtime..."));
83
- await bundle();
17
+ try {
18
+ let appUrl;
19
+ if (isTs) {
20
+ const dotTitan = path.join(root, ".titan");
21
+ const compiledApp = path.join(dotTitan, "app.js");
84
22
 
85
- const base = path.join(process.cwd(), "server");
86
- if (!fs.existsSync(base)) {
87
- fs.mkdirSync(base, { recursive: true });
23
+ if (!fs.existsSync(dotTitan)) {
24
+ fs.mkdirSync(dotTitan, { recursive: true });
88
25
  }
89
26
 
90
- const routesPath = path.join(base, "routes.json");
91
- const actionMapPath = path.join(base, "action_map.json");
92
-
93
- fs.writeFileSync(
94
- routesPath,
95
- JSON.stringify(
96
- {
97
- __config: { port },
98
- routes,
99
- __dynamic_routes: Object.values(dynamicRoutes).flat()
100
- },
101
- null,
102
- 2
103
- )
104
- );
105
-
106
- fs.writeFileSync(
107
- actionMapPath,
108
- JSON.stringify(actionMap, null, 2)
109
- );
110
-
111
- console.log(green("✔ Titan metadata written successfully"));
112
- if (msg) console.log(cyan(msg));
113
-
114
- } catch (e) {
115
- console.error(`\x1b[31m[Titan] Build Error: ${e.message}\x1b[0m`);
116
- process.exit(1);
27
+ await esbuild.build({
28
+ entryPoints: [appPath],
29
+ outfile: compiledApp,
30
+ bundle: true,
31
+ platform: "node",
32
+ format: "esm",
33
+ packages: "external",
34
+ logLevel: "silent"
35
+ });
36
+ appUrl = pathToFileURL(compiledApp).href;
37
+ } else {
38
+ appUrl = pathToFileURL(appPath).href;
117
39
  }
118
- }
119
- };
120
40
 
121
- export default t;
41
+ const cacheBuster = `?t=${Date.now()}`;
42
+ await import(appUrl + cacheBuster);
43
+
44
+ const config = globalThis.__TITAN_CONFIG__ || { port: 3000, threads: 4, stack_mb: 8 };
45
+ const routes = globalThis.__TITAN_ROUTES_MAP__ || {};
46
+ const dynamicRoutes = globalThis.__TITAN_DYNAMIC_ROUTES__ || {};
47
+ const actionMap = globalThis.__TITAN_ACTION_MAP__ || {};
48
+
49
+ const routesPath = path.join(dist, "routes.json");
50
+ const actionMapPath = path.join(dist, "action_map.json");
51
+
52
+ fs.writeFileSync(
53
+ routesPath,
54
+ JSON.stringify(
55
+ {
56
+ __config: config,
57
+ routes,
58
+ __dynamic_routes: Object.values(dynamicRoutes).flat()
59
+ },
60
+ null,
61
+ 2
62
+ )
63
+ );
64
+
65
+ fs.writeFileSync(
66
+ actionMapPath,
67
+ JSON.stringify(actionMap, null, 2)
68
+ );
69
+
70
+ } catch (err) {
71
+ console.error("\x1b[31m❌ Failed to parse routes from application logic\x1b[0m", err);
72
+ process.exit(1);
73
+ }
74
+ }
@@ -1,123 +1,227 @@
1
- import fs from "fs";
2
- import path from "path";
3
- import esbuild from "esbuild";
4
- import { createRequire } from "module";
5
-
6
- const require = createRequire(import.meta.url);
7
-
8
- /* ------------------------------------------------------------------
9
- Node Builtin Rewrite Map
10
- ------------------------------------------------------------------ */
11
-
12
- const NODE_BUILTIN_MAP = {
13
- "fs": "@titanpl/node/fs",
14
- "node:fs": "@titanpl/node/fs",
15
-
16
- "path": "@titanpl/node/path",
17
- "node:path": "@titanpl/node/path",
18
-
19
- "os": "@titanpl/node/os",
20
- "node:os": "@titanpl/node/os",
21
-
22
- "crypto": "@titanpl/node/crypto",
23
- "node:crypto": "@titanpl/node/crypto",
24
-
25
- "process": "@titanpl/node/process",
26
- "node:process": "@titanpl/node/process",
27
-
28
- "events": "@titanpl/node/events",
29
- "node:events": "@titanpl/node/events",
30
-
31
- "util": "@titanpl/node/util",
32
- "node:util": "@titanpl/node/util"
33
- };
34
-
35
- /* ------------------------------------------------------------------
36
- Titan Node Compatibility Plugin
37
- ------------------------------------------------------------------ */
38
-
39
- const titanNodeCompatPlugin = {
40
- name: "titan-node-compat",
41
- setup(build) {
42
- build.onResolve({ filter: /.*/ }, args => {
43
- const replacement = NODE_BUILTIN_MAP[args.path];
44
- if (!replacement) return;
45
-
46
- // MUST return absolute path for esbuild
47
- const resolved = require.resolve(replacement);
48
- return { path: resolved };
49
- });
50
- }
51
- };
52
-
53
- /* ------------------------------------------------------------------
54
- Main Bundle Entry
55
- ------------------------------------------------------------------ */
56
-
57
- export async function bundle() {
58
- const root = process.cwd();
59
- const actionsDir = path.join(root, "app", "actions");
60
- const outDir = path.join(root, "server", "src", "actions");
61
-
62
- await bundleJs(actionsDir, outDir);
63
- }
64
-
65
- /* ------------------------------------------------------------------
66
- Bundle JS Actions
67
- ------------------------------------------------------------------ */
68
-
69
- async function bundleJs(actionsDir, outDir) {
70
- if (!fs.existsSync(actionsDir)) return;
71
-
72
- fs.mkdirSync(outDir, { recursive: true });
73
-
74
- // Clean old bundles
75
- const oldFiles = fs.readdirSync(outDir);
76
- for (const file of oldFiles) {
77
- fs.unlinkSync(path.join(outDir, file));
78
- }
79
-
80
- const files = fs
81
- .readdirSync(actionsDir)
82
- .filter(f => f.endsWith(".js") || f.endsWith(".ts"));
83
-
84
- if (files.length === 0) return;
85
-
86
- for (const file of files) {
87
- const actionName = path.basename(file, path.extname(file));
88
- const entry = path.join(actionsDir, file);
89
- const outfile = path.join(outDir, actionName + ".jsbundle");
90
-
91
- await esbuild.build({
92
- entryPoints: [entry],
93
- outfile,
94
- bundle: true,
95
- format: "iife",
96
- globalName: "__titan_exports",
97
- platform: "node", // important for npm libs
98
- target: "es2020",
99
- logLevel: "silent",
100
- plugins: [titanNodeCompatPlugin],
101
-
102
- banner: {
103
- js: "var Titan = t;"
104
- },
105
-
106
- footer: {
107
- js: `
108
- (function () {
109
- const fn =
110
- __titan_exports["${actionName}"] ||
111
- __titan_exports.default;
112
-
113
- if (typeof fn !== "function") {
114
- throw new Error("[Titan] Action '${actionName}' not found or not a function");
115
- }
116
-
117
- globalThis["${actionName}"] = globalThis.defineAction(fn);
118
- })();
119
- `
120
- }
121
- });
122
- }
123
- }
1
+ /**
2
+ * Bundle.js (TypeScript Version)
3
+ * Handles esbuild bundling with comprehensive error reporting and TypeScript type checking
4
+ */
5
+
6
+ import esbuild from 'esbuild';
7
+ import path from 'path';
8
+ import fs from 'fs';
9
+ import { fileURLToPath } from 'url';
10
+ import { createRequire } from 'module';
11
+ import ts from 'typescript';
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
+ */
23
+ const NODE_BUILTIN_MAP = {
24
+ "fs": "@titanpl/node/fs",
25
+ "node:fs": "@titanpl/node/fs",
26
+ "path": "@titanpl/node/path",
27
+ "node:path": "@titanpl/node/path",
28
+ "os": "@titanpl/node/os",
29
+ "node:os": "@titanpl/node/os",
30
+ "crypto": "@titanpl/node/crypto",
31
+ "node:crypto": "@titanpl/node/crypto",
32
+ "process": "@titanpl/node/process",
33
+ "util": "@titanpl/node/util",
34
+ "node:util": "@titanpl/node/util",
35
+ };
36
+
37
+ const titanNodeCompatPlugin = {
38
+ name: "titan-node-compat",
39
+ setup(build) {
40
+ build.onResolve({ filter: /.*/ }, args => {
41
+ if (NODE_BUILTIN_MAP[args.path]) {
42
+ try {
43
+ const resolved = require.resolve(NODE_BUILTIN_MAP[args.path]);
44
+ return { path: resolved };
45
+ } catch (e) {
46
+ throw new Error(`[TitanPL] Failed to resolve Node shim: ${NODE_BUILTIN_MAP[args.path]}`);
47
+ }
48
+ }
49
+ });
50
+ }
51
+ };
52
+
53
+ function getTitanVersion() {
54
+ try {
55
+ const pkgPath = require.resolve("@titanpl/cli/package.json");
56
+ return JSON.parse(fs.readFileSync(pkgPath, "utf-8")).version;
57
+ } catch (e) {
58
+ return "1.0.0";
59
+ }
60
+ }
61
+
62
+ export class BundleError extends Error {
63
+ constructor(message, errors = [], warnings = []) {
64
+ super(message);
65
+ this.name = 'BundleError';
66
+ this.errors = errors;
67
+ this.warnings = warnings;
68
+ this.isBundleError = true;
69
+ }
70
+ }
71
+
72
+ /**
73
+ * Run TypeScript type checking
74
+ */
75
+ async function checkTypes(root) {
76
+ const tsconfigPath = path.join(root, 'tsconfig.json');
77
+ if (!fs.existsSync(tsconfigPath)) return;
78
+
79
+ const configFile = ts.readConfigFile(tsconfigPath, ts.sys.readFile);
80
+ if (configFile.error) {
81
+ throw new BundleError("Failed to load tsconfig.json", [configFile.error]);
82
+ }
83
+
84
+ const parsedConfig = ts.parseJsonConfigFileContent(
85
+ configFile.config,
86
+ ts.sys,
87
+ root
88
+ );
89
+
90
+ const program = ts.createProgram(parsedConfig.fileNames, parsedConfig.options);
91
+ const diagnostics = ts.getPreEmitDiagnostics(program);
92
+
93
+ if (diagnostics.length > 0) {
94
+ const errors = diagnostics.map(d => {
95
+ const message = ts.flattenDiagnosticMessageText(d.messageText, '\n');
96
+ if (d.file) {
97
+ const { line, character } = d.file.getLineAndCharacterOfPosition(d.start);
98
+ return {
99
+ title: 'TypeScript Error',
100
+ message: message,
101
+ file: d.file.fileName,
102
+ line: line + 1,
103
+ column: character + 1,
104
+ location: `at ${d.file.fileName}:${line + 1}:${character + 1}`,
105
+ codeFrame: `${line + 1} | ${d.file.text.split('\n')[line]}\n${' '.repeat(String(line + 1).length)} | ${' '.repeat(character)}^`
106
+ };
107
+ }
108
+ return { title: 'TypeScript Error', message: message };
109
+ });
110
+
111
+ throw new BundleError(`TypeScript checking failed with ${diagnostics.length} error(s)`, errors);
112
+ }
113
+ }
114
+
115
+ async function validateEntryPoint(entryPoint) {
116
+ const absPath = path.resolve(entryPoint);
117
+ if (!fs.existsSync(absPath)) {
118
+ throw new BundleError(`Entry point does not exist: ${entryPoint}`, [{ text: `Cannot find file: ${absPath}`, location: { file: entryPoint } }]);
119
+ }
120
+ }
121
+
122
+ /**
123
+ * Bundles a single file
124
+ */
125
+ export async function bundleFile(options) {
126
+ const { entryPoint, outfile, format = 'iife', globalName = '__titan_exports', target = 'es2020' } = options;
127
+
128
+ await validateEntryPoint(entryPoint);
129
+
130
+ const outDir = path.dirname(outfile);
131
+ if (!fs.existsSync(outDir)) fs.mkdirSync(outDir, { recursive: true });
132
+
133
+ try {
134
+ const result = await esbuild.build({
135
+ entryPoints: [entryPoint],
136
+ bundle: true,
137
+ outfile,
138
+ format,
139
+ globalName,
140
+ platform: 'node',
141
+ target,
142
+ logLevel: 'silent',
143
+ plugins: [titanNodeCompatPlugin],
144
+ banner: { js: "var Titan = t;" },
145
+ footer: options.footer || {}
146
+ });
147
+
148
+ if (result.errors?.length) {
149
+ throw new BundleError(`Build failed`, result.errors);
150
+ }
151
+ } catch (err) {
152
+ if (err.errors) throw new BundleError(`Build failed`, err.errors);
153
+ throw err;
154
+ }
155
+ }
156
+
157
+ /**
158
+ * Main TS Bundler
159
+ */
160
+ export async function bundle(options = {}) {
161
+ const root = options.root || process.cwd();
162
+ const outDir = options.outDir || path.join(root, 'dist');
163
+ const titanVersion = getTitanVersion();
164
+
165
+ // 1. Mandatory Type Check for TS apps
166
+ try {
167
+ await checkTypes(root);
168
+ } catch (error) {
169
+ console.error();
170
+ if (error.isBundleError && error.errors?.length) {
171
+ for (let i = 0; i < error.errors.length; i++) {
172
+ const errorInfo = error.errors[i];
173
+ errorInfo.titanVersion = titanVersion;
174
+ console.error(renderErrorBox(errorInfo));
175
+ console.error();
176
+ }
177
+ } else {
178
+ console.error(renderErrorBox({ title: 'TypeScript Error', message: error.message, titanVersion }));
179
+ }
180
+ throw new Error('__TITAN_BUNDLE_FAILED__');
181
+ }
182
+
183
+ // 2. Bundle Actions
184
+ const actionsDir = path.join(root, 'app', 'actions');
185
+ const bundleDir = path.join(outDir, 'actions');
186
+
187
+ if (fs.existsSync(bundleDir)) fs.rmSync(bundleDir, { recursive: true, force: true });
188
+ fs.mkdirSync(bundleDir, { recursive: true });
189
+
190
+ if (!fs.existsSync(actionsDir)) return;
191
+
192
+ const files = fs.readdirSync(actionsDir).filter(f => (f.endsWith('.ts') || f.endsWith('.js')) && !f.endsWith('.d.ts'));
193
+
194
+ for (const file of files) {
195
+ const actionName = path.basename(file, path.extname(file));
196
+ const entryPoint = path.join(actionsDir, file);
197
+ const outfile = path.join(bundleDir, actionName + ".jsbundle");
198
+
199
+ try {
200
+ await bundleFile({
201
+ entryPoint,
202
+ outfile,
203
+ footer: {
204
+ js: `
205
+ (function () {
206
+ const fn = __titan_exports["${actionName}"] || __titan_exports.default;
207
+ if (typeof fn !== "function") throw new Error("[TitanPL] Action '${actionName}' not found or not a function");
208
+ globalThis["${actionName}"] = globalThis.defineAction(fn);
209
+ })();`
210
+ }
211
+ });
212
+ } catch (error) {
213
+ console.error();
214
+ if (error.isBundleError && error.errors?.length) {
215
+ for (const err of error.errors) {
216
+ const errorInfo = parseEsbuildError(err);
217
+ errorInfo.titanVersion = titanVersion;
218
+ console.error(renderErrorBox(errorInfo));
219
+ console.error();
220
+ }
221
+ } else {
222
+ console.error(renderErrorBox({ title: 'Build Error', message: error.message, titanVersion }));
223
+ }
224
+ throw new Error('__TITAN_BUNDLE_FAILED__');
225
+ }
226
+ }
227
+ }