@titanpl/packet 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/index.js +63 -0
- package/js/titan/builder.js +73 -0
- package/js/titan/bundle.js +261 -0
- package/js/titan/dev.js +85 -0
- package/js/titan/error-box.js +277 -0
- package/js/titan/titan.js +129 -0
- package/package.json +18 -0
- package/ts/titan/builder.js +121 -0
- package/ts/titan/bundle.js +123 -0
- package/ts/titan/dev.js +454 -0
- package/ts/titan/titan.d.ts +17 -0
- package/ts/titan/titan.js +124 -0
package/index.js
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import path from "path";
|
|
2
|
+
import fs from "fs";
|
|
3
|
+
|
|
4
|
+
import { bundle as bundleActions } from "./js/titan/bundle.js";
|
|
5
|
+
import { dev as devServer } from "./js/titan/dev.js";
|
|
6
|
+
import { buildMetadata } from "./js/titan/builder.js";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Ensure dist directory exists
|
|
10
|
+
*/
|
|
11
|
+
function ensureDist(root) {
|
|
12
|
+
const dist = path.join(root, "dist");
|
|
13
|
+
const actions = path.join(dist, "actions");
|
|
14
|
+
|
|
15
|
+
if (!fs.existsSync(dist)) {
|
|
16
|
+
fs.mkdirSync(dist, { recursive: true });
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (!fs.existsSync(actions)) {
|
|
20
|
+
fs.mkdirSync(actions, { recursive: true });
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return dist;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Production build
|
|
28
|
+
*/
|
|
29
|
+
export async function build(root = process.cwd()) {
|
|
30
|
+
const dist = ensureDist(root);
|
|
31
|
+
|
|
32
|
+
await buildMetadata(root, dist);
|
|
33
|
+
await bundleActions({
|
|
34
|
+
root,
|
|
35
|
+
outDir: dist,
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
return dist;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Dev mode build (watch mode handled inside dev.js)
|
|
43
|
+
*/
|
|
44
|
+
export async function dev(options = {}) {
|
|
45
|
+
const root = typeof options === 'string' ? options : (options.root || process.cwd());
|
|
46
|
+
const dist = ensureDist(root);
|
|
47
|
+
|
|
48
|
+
await devServer({
|
|
49
|
+
root,
|
|
50
|
+
outDir: dist,
|
|
51
|
+
onRebuild: options.onRebuild
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
return dist;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Direct export of lower-level tools (optional)
|
|
59
|
+
*/
|
|
60
|
+
export {
|
|
61
|
+
bundleActions,
|
|
62
|
+
buildMetadata
|
|
63
|
+
};
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import { pathToFileURL } from "url";
|
|
4
|
+
|
|
5
|
+
export async function buildMetadata(root, dist) {
|
|
6
|
+
const appJsPath = path.join(root, "app", "app.js");
|
|
7
|
+
const appTsPath = path.join(root, "app", "app.ts");
|
|
8
|
+
|
|
9
|
+
if (!fs.existsSync(appJsPath) && !fs.existsSync(appTsPath)) {
|
|
10
|
+
console.error("❌ app/app.js or app/app.ts not found.");
|
|
11
|
+
process.exit(1);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// Load the app file to populate globalThis.__TITAN_ROUTES_MAP__
|
|
15
|
+
try {
|
|
16
|
+
let appUrl;
|
|
17
|
+
if (fs.existsSync(appTsPath)) {
|
|
18
|
+
const dotTitan = path.join(root, ".titan");
|
|
19
|
+
const compiledApp = path.join(dotTitan, "app.js");
|
|
20
|
+
|
|
21
|
+
if (!fs.existsSync(dotTitan)) {
|
|
22
|
+
fs.mkdirSync(dotTitan, { recursive: true });
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const esbuild = await import("esbuild");
|
|
26
|
+
await esbuild.build({
|
|
27
|
+
entryPoints: [appTsPath],
|
|
28
|
+
outfile: compiledApp,
|
|
29
|
+
bundle: true,
|
|
30
|
+
platform: "node",
|
|
31
|
+
format: "esm",
|
|
32
|
+
packages: "external",
|
|
33
|
+
logLevel: "silent"
|
|
34
|
+
});
|
|
35
|
+
appUrl = pathToFileURL(compiledApp).href;
|
|
36
|
+
} else {
|
|
37
|
+
appUrl = pathToFileURL(appJsPath).href;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const cacheBuster = `?t=${Date.now()}`;
|
|
41
|
+
await import(appUrl + cacheBuster);
|
|
42
|
+
|
|
43
|
+
const config = globalThis.__TITAN_CONFIG__ || { port: 3000, threads: 4, stack_mb: 8 };
|
|
44
|
+
const routes = globalThis.__TITAN_ROUTES_MAP__ || {};
|
|
45
|
+
const dynamicRoutes = globalThis.__TITAN_DYNAMIC_ROUTES__ || {};
|
|
46
|
+
const actionMap = globalThis.__TITAN_ACTION_MAP__ || {};
|
|
47
|
+
|
|
48
|
+
const routesPath = path.join(dist, "routes.json");
|
|
49
|
+
const actionMapPath = path.join(dist, "action_map.json");
|
|
50
|
+
|
|
51
|
+
fs.writeFileSync(
|
|
52
|
+
routesPath,
|
|
53
|
+
JSON.stringify(
|
|
54
|
+
{
|
|
55
|
+
__config: config,
|
|
56
|
+
routes,
|
|
57
|
+
__dynamic_routes: Object.values(dynamicRoutes).flat()
|
|
58
|
+
},
|
|
59
|
+
null,
|
|
60
|
+
2
|
|
61
|
+
)
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
fs.writeFileSync(
|
|
65
|
+
actionMapPath,
|
|
66
|
+
JSON.stringify(actionMap, null, 2)
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
} catch (err) {
|
|
70
|
+
console.error("❌ Failed to parse routes from app.js", err);
|
|
71
|
+
process.exit(1);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
@@ -0,0 +1,261 @@
|
|
|
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(options = {}) {
|
|
184
|
+
const root = options.root || process.cwd();
|
|
185
|
+
const outDir = options.outDir || path.join(root, 'dist');
|
|
186
|
+
|
|
187
|
+
const actionsDir = path.join(root, 'app', 'actions');
|
|
188
|
+
const bundleDir = path.join(outDir, 'actions');
|
|
189
|
+
|
|
190
|
+
if (fs.existsSync(bundleDir)) {
|
|
191
|
+
fs.rmSync(bundleDir, { recursive: true, force: true });
|
|
192
|
+
}
|
|
193
|
+
await fs.promises.mkdir(bundleDir, { recursive: true });
|
|
194
|
+
|
|
195
|
+
if (!fs.existsSync(actionsDir)) return;
|
|
196
|
+
|
|
197
|
+
const files = fs.readdirSync(actionsDir).filter(f =>
|
|
198
|
+
(f.endsWith('.js') || f.endsWith('.ts')) && !f.endsWith('.d.ts')
|
|
199
|
+
);
|
|
200
|
+
|
|
201
|
+
for (const file of files) {
|
|
202
|
+
const actionName = path.basename(file, path.extname(file));
|
|
203
|
+
const entryPoint = path.join(actionsDir, file);
|
|
204
|
+
const outfile = path.join(bundleDir, actionName + ".jsbundle");
|
|
205
|
+
|
|
206
|
+
try {
|
|
207
|
+
await bundleFile({
|
|
208
|
+
entryPoint,
|
|
209
|
+
outfile,
|
|
210
|
+
format: 'iife',
|
|
211
|
+
globalName: '__titan_exports',
|
|
212
|
+
platform: 'node',
|
|
213
|
+
target: 'es2020',
|
|
214
|
+
banner: { js: "var Titan = t;" },
|
|
215
|
+
footer: {
|
|
216
|
+
js: `
|
|
217
|
+
(function () {
|
|
218
|
+
const fn =
|
|
219
|
+
__titan_exports["${actionName}"] ||
|
|
220
|
+
__titan_exports.default;
|
|
221
|
+
|
|
222
|
+
if (typeof fn !== "function") {
|
|
223
|
+
throw new Error("[Titan] Action '${actionName}' not found or not a function");
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
globalThis["${actionName}"] = globalThis.defineAction(fn);
|
|
227
|
+
})();
|
|
228
|
+
`
|
|
229
|
+
}
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
} catch (error) {
|
|
233
|
+
|
|
234
|
+
console.error();
|
|
235
|
+
|
|
236
|
+
const titanVersion = getTitanVersion();
|
|
237
|
+
|
|
238
|
+
if (error.isBundleError && error.errors?.length) {
|
|
239
|
+
for (let i = 0; i < error.errors.length; i++) {
|
|
240
|
+
const errorInfo = parseEsbuildError(error.errors[i]);
|
|
241
|
+
if (error.errors.length > 1) {
|
|
242
|
+
errorInfo.title = `Build Error ${i + 1}/${error.errors.length}`;
|
|
243
|
+
}
|
|
244
|
+
errorInfo.titanVersion = titanVersion;
|
|
245
|
+
console.error(renderErrorBox(errorInfo));
|
|
246
|
+
console.error();
|
|
247
|
+
}
|
|
248
|
+
} else {
|
|
249
|
+
const errorInfo = {
|
|
250
|
+
title: 'Build Error',
|
|
251
|
+
file: entryPoint,
|
|
252
|
+
message: error.message || 'Unknown error',
|
|
253
|
+
titanVersion
|
|
254
|
+
};
|
|
255
|
+
console.error(renderErrorBox(errorInfo));
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
throw new Error('__TITAN_BUNDLE_FAILED__');
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
package/js/titan/dev.js
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import chokidar from "chokidar";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import fs from "fs";
|
|
4
|
+
import { bundle } from "./bundle.js";
|
|
5
|
+
import { buildMetadata } from "./builder.js";
|
|
6
|
+
import { createRequire } from "module";
|
|
7
|
+
|
|
8
|
+
const cyan = (t) => `\x1b[36m${t}\x1b[0m`;
|
|
9
|
+
const green = (t) => `\x1b[32m${t}\x1b[0m`;
|
|
10
|
+
const yellow = (t) => `\x1b[33m${t}\x1b[0m`;
|
|
11
|
+
const red = (t) => `\x1b[31m${t}\x1b[0m`;
|
|
12
|
+
const gray = (t) => `\x1b[90m${t}\x1b[0m`;
|
|
13
|
+
const bold = (t) => `\x1b[1m${t}\x1b[0m`;
|
|
14
|
+
|
|
15
|
+
function getTitanVersion() {
|
|
16
|
+
try {
|
|
17
|
+
const require = createRequire(import.meta.url);
|
|
18
|
+
const pkgPath = require.resolve("@titanpl/cli/package.json");
|
|
19
|
+
return JSON.parse(fs.readFileSync(pkgPath, "utf-8")).version;
|
|
20
|
+
} catch (e) {
|
|
21
|
+
return "1.0.0";
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export async function dev(options) {
|
|
26
|
+
const root = options.root || process.cwd();
|
|
27
|
+
const outDir = options.outDir || path.join(root, "dist");
|
|
28
|
+
const onRebuild = options.onRebuild || (() => { });
|
|
29
|
+
|
|
30
|
+
const isTs = fs.existsSync(path.join(root, "tsconfig.json")) || fs.existsSync(path.join(root, "app", "app.ts"));
|
|
31
|
+
const actionsDir = path.join(root, "app", "actions");
|
|
32
|
+
let hasRust = false;
|
|
33
|
+
if (fs.existsSync(actionsDir)) {
|
|
34
|
+
hasRust = fs.readdirSync(actionsDir).some(f => f.endsWith(".rs"));
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
let mode = "";
|
|
38
|
+
if (hasRust) {
|
|
39
|
+
mode = isTs ? "Rust + TS Actions" : "Rust + JS Actions";
|
|
40
|
+
} else {
|
|
41
|
+
mode = isTs ? "TS Actions" : "JS Actions";
|
|
42
|
+
}
|
|
43
|
+
const version = getTitanVersion();
|
|
44
|
+
|
|
45
|
+
console.clear();
|
|
46
|
+
console.log("");
|
|
47
|
+
console.log(` ${bold(cyan("⏣ Titan Planet"))} ${gray("v" + version)} ${yellow("[ Dev Mode ]")}`);
|
|
48
|
+
console.log("");
|
|
49
|
+
console.log(` ${gray("Type: ")} ${mode}`);
|
|
50
|
+
console.log(` ${gray("Hot Reload: ")} ${green("Enabled")}`);
|
|
51
|
+
|
|
52
|
+
if (fs.existsSync(path.join(root, ".env"))) {
|
|
53
|
+
console.log(` ${gray("Env: ")} ${yellow("Loaded")}`);
|
|
54
|
+
}
|
|
55
|
+
console.log("");
|
|
56
|
+
|
|
57
|
+
// Initial build
|
|
58
|
+
await buildMetadata(root, outDir);
|
|
59
|
+
await bundle({ root, outDir });
|
|
60
|
+
|
|
61
|
+
// Watch for changes inside app/
|
|
62
|
+
const appDir = path.join(root, "app");
|
|
63
|
+
const envFile = path.join(root, ".env");
|
|
64
|
+
|
|
65
|
+
const watcher = chokidar.watch([appDir, envFile], {
|
|
66
|
+
ignoreInitial: true,
|
|
67
|
+
awaitWriteFinish: { stabilityThreshold: 300, pollInterval: 100 }
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
watcher.on("all", async (event, file) => {
|
|
71
|
+
if (!file) return;
|
|
72
|
+
const relPath = path.relative(root, file);
|
|
73
|
+
if (relPath.startsWith("dist") || relPath.startsWith("server")) return;
|
|
74
|
+
|
|
75
|
+
try {
|
|
76
|
+
await buildMetadata(root, outDir);
|
|
77
|
+
await bundle({ root, outDir });
|
|
78
|
+
onRebuild();
|
|
79
|
+
} catch (err) {
|
|
80
|
+
console.error(red(`[Titan] Build failed, awaiting changes...`));
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
return watcher;
|
|
85
|
+
}
|