titanpl 3.0.0 β†’ 4.0.1

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 CHANGED
@@ -1,5 +1,5 @@
1
1
  <p align="center">
2
- <a href="https://titan-docs-ez.vercel.app/" target="_blank">
2
+ <a href="https://titanpl.vercel.app/" target="_blank">
3
3
  <img src="https://i.ibb.co/VpBsTg6m/tpl-Logo.png" width="120" alt="TitanPl Logo" />
4
4
  </a>
5
5
  </p>
@@ -237,15 +237,59 @@ Extend the runtime with custom Rust engines using **Titan Extensions**.
237
237
 
238
238
  ---
239
239
 
240
- # πŸ“¦ Deployment
240
+ # πŸ“¦ Building & Deployment
241
241
 
242
- Titan compiles your entire appβ€”JS/TS code, Rust code, and server logicβ€”into a **single executable**.
242
+ Titan offers a unified build system that packages your JS/TS code, assets, and the native engine into a single, self-contained `build/` directory.
243
243
 
244
- * **Tiny Docker Images**: Alpine-based, ~20MB compressed.
245
- * **Instant Startup**: No node_modules overhead.
246
- * **Secure**: No access to system APIs from JS unless explicitly bridged.
244
+ ### 1. Production Build
245
+ Run this command from your project root:
246
+ ```bash
247
+ titan build --release
248
+ ```
249
+ This will:
250
+ * Bundle your actions into optimized `.jsbundle` files.
251
+ * Extract all required Titan extensions and native libraries to `.ext/`.
252
+ * Copy your `public/`, `static/`, and other configured assets.
253
+ * Generate a `titan-server` executable in the `build/` folder.
254
+
255
+ ### 2. Run the Build
256
+ ```bash
257
+ cd build
258
+ ./titan-server
259
+ ```
260
+ Your application is now running as a standalone native server.
261
+
262
+ ### 🐳 Docker Deployment
263
+ The `titan build --release` command is optimized for Docker. Your `Dockerfile` can be as simple as:
264
+
265
+ ```dockerfile
266
+ FROM node:20-slim AS builder
267
+ WORKDIR /app
268
+ COPY . .
269
+ RUN npm install --include=optional
270
+ RUN npx titan build --release
271
+
272
+ FROM ubuntu:24.04
273
+ WORKDIR /app
274
+ COPY --from=builder /app/build .
275
+ CMD ["./titan-server", "run", "dist"]
276
+ ```
277
+ This produces a tiny, high-performance production image with zero `node_modules`.
278
+
279
+ ### πŸ›  Configuration (`tanfig.json`)
280
+ You can customize the build process in your `tanfig.json`:
281
+ ```json
282
+ {
283
+ "build": {
284
+ "purpose": "deploy",
285
+ "files": ["public", "db", "secrets"]
286
+ }
287
+ }
288
+ ```
289
+ * **`purpose`**: Use `deploy` to skip `node_modules` junctions for the smallest possible footprint.
290
+ * **`files`**: List any extra folders you want included in the `build/` output.
247
291
 
248
292
  ---
249
293
 
250
294
 
251
- **To know more read docs πŸ’Ÿ **Titan Planet docs:** https://titan-docs-ez.vercel.app/docs**
295
+ **To know more read docs πŸ’Ÿ **Titan Planet docs:** https://titanpl.vercel.app/docs**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "titanpl",
3
- "version": "3.0.0",
3
+ "version": "4.0.1",
4
4
  "description": "Titan Planet is a JavaScript-first backend framework that embeds JS actions into a Rust + Axum server and ships as a single native binary. Routes are compiled to static metadata; only actions run in the embedded JS runtime. No Node.js. No event loop in production.",
5
5
  "license": "ISC",
6
6
  "author": "ezetgalaxy",
@@ -5,13 +5,30 @@ The command-line interface (CLI) for Titan Planet. It provides the `titan` and `
5
5
  ## What it works (What it does)
6
6
  The CLI is responsible for bridging your JavaScript codebase with the underlying Rust/Axum engine. It handles scaffolding, compiling JS actions, generating metadata, and running the server.
7
7
 
8
+ ## Commands
9
+
10
+ | Command | Arguments | Description |
11
+ | :--- | :--- | :--- |
12
+ | `init` | `<dir>` | Initialize a new Titan project in the specified directory. |
13
+ | `dev` | | Start the development server with hot-reload and strict type checking. |
14
+ | `build` | | Bundle JavaScript/TypeScript actions into `.jsbundle` files. |
15
+ | `build` | `--release` | Create a self-contained production bundle in `build/` (incl. engine). |
16
+ | `start` | | Start the Titan server (checks for `build/` or local engine). |
17
+ | `update` | | Update the local engine to the latest version. |
18
+ | `ext create` | `<name>` | Scaffold a new native Extension. |
19
+ | `ext run` | | Run the current extension in a test harness. |
20
+ | `help` | | Show available commands and options. |
21
+
8
22
  ## How it works
9
23
  You can install this package globally or use it via your package runner (e.g., `npx`). Alternatively, you can install it as a dev dependency in your project.
10
24
 
11
25
  ```bash
12
- npx titan help
26
+ npm install -g @titanpl/cli
27
+ titan help
13
28
  ```
14
29
 
15
30
  It parses your application source code, coordinates with `@titanpl/packet` to build the required JS endpoints, and then spins up the pre-compiled native core engine for your OS.
16
31
 
17
- **Important Note:** Currently, Titan Planet and its entire package ecosystem are only for Windows. The Linux version is in development (dev only) for the new architecture and will be launched later.
32
+ **Note:** All commands now prioritize `tanfig.json` for project configuration.
33
+
34
+ **Note on Platform Architecture:** Titan Planet's new v2 architecture supports Windows and Linux (incl. Docker). MacOS support is in active development.
@@ -53,7 +53,7 @@ ${bold(cyan("╰─────────────────────
53
53
  ${bold("Commands:")}
54
54
  ${cyan("init")} ${gray("Scaffold a new Titan project")}
55
55
  ${cyan("create")} ${gray("Create a new project or extension (e.g. 'titan create ext my-ext')")}
56
- ${cyan("build")} ${gray("Compile actions and build production dist")}
56
+ ${cyan("build")} ${gray("Compile actions and build production dist. Use --release or -r for a production-ready folder.")}
57
57
  ${cyan("dev")} ${gray("Start the Gravity Engine in dev/watch mode")}
58
58
  ${cyan("start")} ${gray("Start the production Gravity Engine")}
59
59
  ${cyan("update")} ${gray("Update an existing project to latest Titan version")}
@@ -122,9 +122,10 @@ const cmd = process.argv[2];
122
122
  }
123
123
 
124
124
  case "build":
125
- console.log(cyan("β†’ Building Titan project..."));
126
- await buildCommand();
127
- console.log(green("βœ” Build complete"));
125
+ const isRelease = process.argv.includes("--release") || process.argv.includes("-r");
126
+ console.log(cyan(`β†’ Building Titan project${isRelease ? " (Release mode)" : ""}...`));
127
+ await buildCommand(isRelease);
128
+ console.log(green(`βœ” ${isRelease ? "Release" : "Build"} complete`));
128
129
  break;
129
130
 
130
131
  case "dev":
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@titanpl/cli",
3
- "version": "3.0.0",
3
+ "version": "5.0.5",
4
4
  "description": "The unified CLI for Titan Planet. Use it to create, manage, build, and deploy high-performance backend projects.",
5
5
  "keywords": [
6
6
  "titanpl",
@@ -12,8 +12,16 @@
12
12
  "author": "ezetgalaxy",
13
13
  "type": "module",
14
14
  "main": "index.js",
15
+ "files": [
16
+ "index.js",
17
+ "src/",
18
+ "templates/",
19
+ "README.md"
20
+ ],
15
21
  "scripts": {
16
- "test": "echo \"Error: no test specified\" && exit 1"
22
+ "test": "echo \"Error: no test specified\" && exit 1",
23
+ "prepack": "node -e \"const fs=require('fs');const p=require('path');const src=p.resolve(__dirname,'..','..','templates');const dest=p.resolve(__dirname,'templates');if(fs.existsSync(src)&&!fs.existsSync(dest)){const cp=(s,d)=>{fs.mkdirSync(d,{recursive:true});for(const f of fs.readdirSync(s)){const sp=p.join(s,f),dp=p.join(d,f);fs.lstatSync(sp).isDirectory()?cp(sp,dp):fs.copyFileSync(sp,dp)}};cp(src,dest);console.log('Copied templates for packaging')}\"",
24
+ "postpack": "node -e \"const fs=require('fs');const p=require('path');const d=p.resolve(__dirname,'templates');if(fs.existsSync(d)){fs.rmSync(d,{recursive:true,force:true});console.log('Cleaned up templates after packaging')}\""
17
25
  },
18
26
  "bin": {
19
27
  "titan": "./index.js",
@@ -22,13 +30,12 @@
22
30
  },
23
31
  "optionalDependencies": {
24
32
  "@titanpl/engine-win32-x64": "2.0.4",
25
- "@titanpl/engine-linux-x64": "2.0.4",
26
- "@titanpl/engine-darwin-arm64": "2.0.4"
33
+ "@titanpl/engine-linux-x64": "2.0.4"
27
34
  },
28
35
  "dependencies": {
29
- "@titanpl/packet": "2.0.4",
36
+ "@titanpl/packet": "4.0.2",
30
37
  "prompts": "^2.4.2",
31
38
  "commander": "^11.0.0",
32
39
  "chalk": "^4.1.2"
33
40
  }
34
- }
41
+ }
@@ -1,6 +1,7 @@
1
- import { build } from "@titanpl/packet";
1
+ import { build, release } from "@titanpl/packet";
2
2
 
3
- export async function buildCommand() {
4
- const dist = await build(process.cwd());
5
- console.log("βœ” Build complete β†’", dist);
3
+ export async function buildCommand(isRelease = false) {
4
+ const buildFn = isRelease ? release : build;
5
+ const dist = await buildFn(process.cwd());
6
+ console.log(`βœ” ${isRelease ? 'Release' : 'Build'} complete β†’`, dist);
6
7
  }
@@ -164,7 +164,8 @@ export async function initCommand(projectName, templateName) {
164
164
  const remapping = {
165
165
  "_gitignore": ".gitignore",
166
166
  "_dockerignore": ".dockerignore",
167
- "_titan.json": "titan.json",
167
+ "_tanfig.json": "tanfig.json",
168
+ "_titan.json": "tanfig.json",
168
169
  ".env": ".env"
169
170
  };
170
171
  for (const [srcName, destName] of Object.entries(remapping)) {
@@ -175,6 +176,19 @@ export async function initCommand(projectName, templateName) {
175
176
  }
176
177
  }
177
178
 
179
+ // 4. Ensure tanfig.json exists with default build config
180
+ const tanfigPath = path.join(target, "tanfig.json");
181
+ if (!fs.existsSync(tanfigPath)) {
182
+ const defaultConfig = {
183
+ name: projName,
184
+ build: {
185
+ files: ["public", "static", "db", "config"]
186
+ }
187
+ };
188
+ fs.writeFileSync(tanfigPath, JSON.stringify(defaultConfig, null, 2));
189
+ }
190
+
191
+
178
192
  // Recursive template substitution
179
193
  const substitute = (dir) => {
180
194
  for (const file of fs.readdirSync(dir)) {
@@ -58,9 +58,30 @@ export async function updateCommand() {
58
58
  console.log(chalk.yellow(` ⚠️ Failed to update package.json: ${e.message}`));
59
59
  }
60
60
 
61
- // 2. Refresh Dockerfile and dotfiles from templates
62
- const commonDir = path.resolve(__dirname, '..', '..', '..', '..', 'templates', 'common');
63
- if (fs.existsSync(commonDir)) {
61
+ // 2. Migration: rename titan.json to tanfig.json if needed
62
+ const oldConfigPath = path.join(root, 'titan.json');
63
+ const newConfigPath = path.join(root, 'tanfig.json');
64
+ if (fs.existsSync(oldConfigPath) && !fs.existsSync(newConfigPath)) {
65
+ fs.renameSync(oldConfigPath, newConfigPath);
66
+ console.log(chalk.green(` βœ” Migrated titan.json to tanfig.json`));
67
+ }
68
+
69
+ // 3. Refresh Dockerfile and dotfiles from templates
70
+ let commonDir = null;
71
+ const tryCommonPaths = [
72
+ path.resolve(__dirname, '..', '..', '..', '..', 'templates', 'common'), // Monorepo
73
+ path.resolve(__dirname, '..', '..', 'templates', 'common'), // NPM cli package
74
+ path.resolve(__dirname, '..', '..', '..', 'templates', 'common') // Fallback
75
+ ];
76
+
77
+ for (const p of tryCommonPaths) {
78
+ if (fs.existsSync(p)) {
79
+ commonDir = p;
80
+ break;
81
+ }
82
+ }
83
+
84
+ if (commonDir) {
64
85
  const filesToSync = [
65
86
  ['Dockerfile', 'Dockerfile'],
66
87
  ['_dockerignore', '.dockerignore'],
@@ -19,6 +19,7 @@ export function resolveEngineBinaryPath() {
19
19
  const arch = os.arch();
20
20
  const pkgName = `@titanpl/engine-${platform}-${arch}`;
21
21
  const binName = platform === 'win32' ? 'titan-server.exe' : 'titan-server';
22
+ const shortPkgName = pkgName.split('/').pop();
22
23
 
23
24
  // 1. Monorepo search (local dev)
24
25
  const searchPaths = [
@@ -31,8 +32,14 @@ export function resolveEngineBinaryPath() {
31
32
  for (let startPath of searchPaths) {
32
33
  let current = startPath;
33
34
  for (let i = 0; i < 8; i++) {
34
- const potential = path.join(current, 'engine', 'target', 'release', binName);
35
- if (fs.existsSync(potential)) return potential;
35
+ // Check built binary (engine/target/release/...)
36
+ const builtBin = path.join(current, 'engine', 'target', 'release', binName);
37
+ if (fs.existsSync(builtBin)) return builtBin;
38
+
39
+ // Check package binary (packages/engine-*/bin/...)
40
+ const pkgBin = path.join(current, 'packages', shortPkgName, 'bin', binName);
41
+ if (fs.existsSync(pkgBin)) return pkgBin;
42
+
36
43
  const parent = path.dirname(current);
37
44
  if (parent === current) break;
38
45
  current = parent;
@@ -47,15 +54,38 @@ export function resolveEngineBinaryPath() {
47
54
  } catch (e) { }
48
55
 
49
56
  // 3. Fallback: sibling in node_modules (global install layout)
50
- const cliParent = path.dirname(path.dirname(__dirname)); // up from cli/src β†’ cli β†’ parent
51
- const siblingBin = path.join(cliParent, pkgName, 'bin', binName);
52
- if (fs.existsSync(siblingBin)) return siblingBin;
57
+ // We need to handle scoped packages correctly.
58
+ // If we are at .../node_modules/@titanpl/cli/src
59
+ // Up 1: .../node_modules/@titanpl/cli
60
+ // Up 2: .../node_modules/@titanpl
61
+ // Up 3: .../node_modules (This is where the engine package should be)
62
+ let current = __dirname;
63
+ for (let i = 0; i < 5; i++) {
64
+ const potentialNm = path.join(current, 'node_modules');
65
+ if (fs.existsSync(potentialNm)) {
66
+ const siblingBin = path.join(potentialNm, pkgName, 'bin', binName);
67
+ if (fs.existsSync(siblingBin)) return siblingBin;
68
+ }
69
+ const parent = path.dirname(current);
70
+ if (parent === current) break;
71
+ current = parent;
72
+ }
53
73
 
54
- // 4. Walk upwards from current dir searching for node_modules/@titanpl/engine-...
74
+ // 4. Walk upwards from current dir searching for binary in root or .ext/node_modules
55
75
  let searchDir = process.cwd();
56
76
  for (let i = 0; i < 5; i++) {
77
+ // Check root (Directly in build/ folder)
78
+ const rootBin = path.join(searchDir, binName);
79
+ if (fs.existsSync(rootBin)) return rootBin;
80
+
81
+ // Check node_modules
57
82
  const nmBin = path.join(searchDir, 'node_modules', pkgName, 'bin', binName);
58
83
  if (fs.existsSync(nmBin)) return nmBin;
84
+
85
+ // Check .ext (Release mode layout)
86
+ const extBin = path.join(searchDir, '.ext', pkgName, 'bin', binName);
87
+ if (fs.existsSync(extBin)) return extBin;
88
+
59
89
  const parent = path.dirname(searchDir);
60
90
  if (parent === searchDir) break;
61
91
  searchDir = parent;
@@ -110,7 +140,8 @@ export function startEngine(watchMode = false) {
110
140
  env: {
111
141
  ...process.env,
112
142
  TITAN_ENV: watchMode ? 'development' : 'production',
113
- Titan_Dev: watchMode ? '1' : '0'
143
+ TITAN_DEV: watchMode ? '1' : '0',
144
+ NODE_ENV: watchMode ? 'development' : 'production'
114
145
  }
115
146
  });
116
147
 
@@ -32,6 +32,25 @@ function ensureDist(root) {
32
32
  return dist;
33
33
  }
34
34
 
35
+ /**
36
+ * Recursive copy
37
+ */
38
+ function copyDir(src, dest, filter) {
39
+ if (filter && !filter(src)) return;
40
+
41
+ const stats = fs.lstatSync(src);
42
+ if (stats.isDirectory()) {
43
+ if (!fs.existsSync(dest)) {
44
+ fs.mkdirSync(dest, { recursive: true });
45
+ }
46
+ for (const file of fs.readdirSync(src)) {
47
+ copyDir(path.join(src, file), path.join(dest, file), filter);
48
+ }
49
+ } else {
50
+ fs.copyFileSync(src, dest);
51
+ }
52
+ }
53
+
35
54
  /**
36
55
  * Production build
37
56
  */
@@ -51,6 +70,192 @@ export async function build(root = process.cwd()) {
51
70
  return dist;
52
71
  }
53
72
 
73
+ /**
74
+ * Release build (Production ready folder)
75
+ */
76
+ export async function release(root = process.cwd()) {
77
+ const dist = await build(root);
78
+ const buildDir = path.join(root, "build");
79
+
80
+ // Read config
81
+ let config = {};
82
+ const configPath = fs.existsSync(path.join(root, "tanfig.json"))
83
+ ? path.join(root, "tanfig.json")
84
+ : fs.existsSync(path.join(root, "titan.json"))
85
+ ? path.join(root, "titan.json")
86
+ : null;
87
+
88
+ if (configPath) {
89
+ try {
90
+ config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
91
+ } catch (e) { }
92
+ }
93
+
94
+ const filesToCopy = config.build && config.build.files ? config.build.files : ["public", "static", "db", "config"];
95
+
96
+ // Clear or ensure build dir
97
+ if (fs.existsSync(buildDir)) {
98
+ fs.rmSync(buildDir, { recursive: true, force: true });
99
+ }
100
+ fs.mkdirSync(buildDir, { recursive: true });
101
+
102
+ // 1. Copy dist
103
+ copyDir(dist, path.join(buildDir, "dist"));
104
+
105
+ // 2. Extra files/folders from root based on config
106
+ for (const item of filesToCopy) {
107
+ const src = path.join(root, item);
108
+ if (fs.existsSync(src)) {
109
+ const dest = path.join(buildDir, item);
110
+ copyDir(src, dest);
111
+ }
112
+ }
113
+
114
+ // 3. Copy package.json
115
+ const pkgPath = path.join(root, "package.json");
116
+ if (fs.existsSync(pkgPath)) {
117
+ fs.copyFileSync(pkgPath, path.join(buildDir, "package.json"));
118
+ }
119
+
120
+ // 4. Create .env
121
+ fs.writeFileSync(path.join(buildDir, ".env"), "TITAN_DEV=0\n");
122
+
123
+ // 5. Extract extensions
124
+ const extDir = path.join(buildDir, ".ext");
125
+ fs.mkdirSync(extDir, { recursive: true });
126
+
127
+ const nodeModules = path.join(root, "node_modules");
128
+ if (fs.existsSync(nodeModules)) {
129
+ const findExtensions = (dir, depth = 0) => {
130
+ if (depth > 2) return;
131
+ if (!fs.existsSync(dir)) return;
132
+
133
+ const files = fs.readdirSync(dir);
134
+ for (const file of files) {
135
+ const fullPath = path.join(dir, file);
136
+ try {
137
+ const stats = fs.lstatSync(fullPath);
138
+ if (stats.isDirectory()) {
139
+ if (file === "node_modules" && depth > 0) continue;
140
+
141
+ const titanJson = path.join(fullPath, "titan.json");
142
+ if (fs.existsSync(titanJson)) {
143
+ let targetPkgName = file;
144
+ const parentDirName = path.basename(dir);
145
+ if (parentDirName.startsWith("@")) {
146
+ targetPkgName = path.join(parentDirName, file);
147
+ }
148
+
149
+ const destPath = path.join(extDir, targetPkgName);
150
+ fs.mkdirSync(path.dirname(destPath), { recursive: true });
151
+
152
+ copyDir(fullPath, destPath, (src) => {
153
+ const rel = path.relative(fullPath, src);
154
+ return !rel.startsWith("node_modules");
155
+ });
156
+ } else {
157
+ findExtensions(fullPath, depth + 1);
158
+ }
159
+ }
160
+ } catch (e) { }
161
+ }
162
+ };
163
+ findExtensions(nodeModules);
164
+ }
165
+
166
+ // 6. Copy Engine binaries
167
+ if (fs.existsSync(path.join(nodeModules, "@titanpl"))) {
168
+ const scopeDir = path.join(nodeModules, "@titanpl");
169
+ const folders = fs.readdirSync(scopeDir);
170
+ for (const folder of folders) {
171
+ if (folder.startsWith("engine-")) {
172
+ const engineDest = path.join(extDir, "@titanpl", folder);
173
+ copyDir(path.join(scopeDir, folder), engineDest);
174
+ }
175
+ }
176
+ }
177
+
178
+ // 7. Create node_modules junction to .ext for engine resolution
179
+ // If env is 'deploy' or 'production', we might want to skip this for a cleaner build
180
+ const buildEnv = config.build && (config.build.env || config.build.purpose) ? (config.build.env || config.build.purpose) : "test";
181
+
182
+ if (buildEnv !== "deploy" && buildEnv !== "production") {
183
+ const nmSymlink = path.join(buildDir, "node_modules");
184
+ if (!fs.existsSync(nmSymlink)) {
185
+ try {
186
+ // Junctions don't require admin on Windows
187
+ fs.symlinkSync(".ext", nmSymlink, "junction");
188
+ } catch (e) {
189
+ try {
190
+ fs.symlinkSync(".ext", nmSymlink, "dir");
191
+ } catch (e2) {
192
+ // Fallback or ignore if symlink creation is totally restricted
193
+ }
194
+ }
195
+ }
196
+ }
197
+
198
+ // 8. Create 'titan' executable link in the build root for easy starting
199
+ const binName = process.platform === "win32" ? "titan-server.exe" : "titan-server";
200
+ let engineBin = null;
201
+
202
+ // Strategy A: Search in the .ext we just populated
203
+ const findInExt = (dir) => {
204
+ if (!fs.existsSync(dir)) return null;
205
+ const entries = fs.readdirSync(dir);
206
+ for (const entry of entries) {
207
+ const full = path.join(dir, entry);
208
+ if (fs.statSync(full).isDirectory()) {
209
+ // Check both pkgRoot/bin/titan-server and pkgRoot/titan-server (some layouts differ)
210
+ const p1 = path.join(full, "bin", binName);
211
+ if (fs.existsSync(p1)) return p1;
212
+ const p2 = path.join(full, binName);
213
+ if (fs.existsSync(p2)) return p2;
214
+
215
+ const found = findInExt(full);
216
+ if (found) return found;
217
+ }
218
+ }
219
+ return null;
220
+ };
221
+
222
+ engineBin = findInExt(extDir);
223
+
224
+ // Strategy B: Monorepo fallback (if building inside the titanpl repo)
225
+ if (!engineBin) {
226
+ let current = root;
227
+ for (let i = 0; i < 5; i++) {
228
+ const potential = path.join(current, "engine", "target", "release", binName);
229
+ if (fs.existsSync(potential)) {
230
+ engineBin = potential;
231
+ break;
232
+ }
233
+ const parent = path.dirname(current);
234
+ if (parent === current) break;
235
+ current = parent;
236
+ }
237
+ }
238
+
239
+ if (engineBin) {
240
+ const linkName = binName; // Keep the original name 'titan-server'
241
+ const linkPath = path.join(buildDir, linkName);
242
+
243
+ if (!fs.existsSync(linkPath)) {
244
+ try {
245
+ // Always copy the binary to the root for maximum portability in the 'build' folder
246
+ fs.copyFileSync(engineBin, linkPath);
247
+ if (process.platform !== "win32") {
248
+ fs.chmodSync(linkPath, 0o755);
249
+ }
250
+ } catch (e) {
251
+ console.error(`[Titan] Failed to create titan binary: ${e.message}`);
252
+ }
253
+ }
254
+ }
255
+
256
+ return buildDir;
257
+ }
258
+
54
259
  /**
55
260
  * Dev mode build
56
261
  */
@@ -33,20 +33,32 @@ const NODE_BUILTIN_MAP = {
33
33
  "node:util": "@titanpl/node/util",
34
34
  };
35
35
 
36
- const titanNodeCompatPlugin = {
37
- name: "titan-node-compat",
38
- setup(build) {
39
- build.onResolve({ filter: /.*/ }, args => {
40
- if (NODE_BUILTIN_MAP[args.path]) {
41
- try {
42
- const resolved = require.resolve(NODE_BUILTIN_MAP[args.path]);
43
- return { path: resolved };
44
- } catch (e) {
45
- throw new Error(`[TitanPL] Failed to resolve Node shim: ${NODE_BUILTIN_MAP[args.path]}`);
36
+ const createTitanNodeCompatPlugin = (root) => {
37
+ const rootRequire = createRequire(path.join(root, 'package.json'));
38
+
39
+ return {
40
+ name: "titan-node-compat",
41
+ setup(build) {
42
+ build.onResolve({ filter: /.*/ }, args => {
43
+ if (NODE_BUILTIN_MAP[args.path]) {
44
+ const shimPkg = NODE_BUILTIN_MAP[args.path];
45
+ try {
46
+ // 1. Try to resolve from project root (local node_modules)
47
+ const resolved = rootRequire.resolve(shimPkg);
48
+ return { path: resolved };
49
+ } catch (e) {
50
+ try {
51
+ // 2. Fallback to CLI's own context
52
+ const resolved = require.resolve(shimPkg);
53
+ return { path: resolved };
54
+ } catch (e2) {
55
+ throw new Error(`[TitanPL] Failed to resolve Node shim: ${shimPkg}. Ensure @titanpl/node is installed.`);
56
+ }
57
+ }
46
58
  }
47
- }
48
- });
49
- }
59
+ });
60
+ }
61
+ };
50
62
  };
51
63
 
52
64
  function getTitanVersion() {
@@ -96,7 +108,7 @@ export async function bundleFile(options) {
96
108
  platform: 'node',
97
109
  target,
98
110
  logLevel: 'silent',
99
- plugins: [titanNodeCompatPlugin],
111
+ plugins: [createTitanNodeCompatPlugin(options.root || process.cwd())],
100
112
  banner: { js: "var Titan = t;" },
101
113
  footer: options.footer || {}
102
114
  });
@@ -135,6 +147,7 @@ export async function bundle(options = {}) {
135
147
 
136
148
  try {
137
149
  await bundleFile({
150
+ root,
138
151
  entryPoint,
139
152
  outfile,
140
153
  footer: {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@titanpl/packet",
3
- "version": "2.0.4",
3
+ "version": "4.0.2",
4
4
  "description": "The bundler for TitanPl servers.",
5
5
  "keywords": [
6
6
  "bundler",
@@ -34,20 +34,32 @@ const NODE_BUILTIN_MAP = {
34
34
  "node:util": "@titanpl/node/util",
35
35
  };
36
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]}`);
37
+ const createTitanNodeCompatPlugin = (root) => {
38
+ const rootRequire = createRequire(path.join(root, 'package.json'));
39
+
40
+ return {
41
+ name: "titan-node-compat",
42
+ setup(build) {
43
+ build.onResolve({ filter: /.*/ }, args => {
44
+ if (NODE_BUILTIN_MAP[args.path]) {
45
+ const shimPkg = NODE_BUILTIN_MAP[args.path];
46
+ try {
47
+ // 1. Try to resolve from project root (local node_modules)
48
+ const resolved = rootRequire.resolve(shimPkg);
49
+ return { path: resolved };
50
+ } catch (e) {
51
+ try {
52
+ // 2. Fallback to CLI's own context
53
+ const resolved = require.resolve(shimPkg);
54
+ return { path: resolved };
55
+ } catch (e2) {
56
+ throw new Error(`[TitanPL] Failed to resolve Node shim: ${shimPkg}. Ensure @titanpl/node is installed.`);
57
+ }
58
+ }
47
59
  }
48
- }
49
- });
50
- }
60
+ });
61
+ }
62
+ };
51
63
  };
52
64
 
53
65
  function getTitanVersion() {
@@ -140,7 +152,7 @@ export async function bundleFile(options) {
140
152
  platform: 'node',
141
153
  target,
142
154
  logLevel: 'silent',
143
- plugins: [titanNodeCompatPlugin],
155
+ plugins: [createTitanNodeCompatPlugin(options.root || process.cwd())],
144
156
  banner: { js: "var Titan = t;" },
145
157
  footer: options.footer || {}
146
158
  });
@@ -198,6 +210,7 @@ export async function bundle(options = {}) {
198
210
 
199
211
  try {
200
212
  await bundleFile({
213
+ root,
201
214
  entryPoint,
202
215
  outfile,
203
216
  footer: {
@@ -14,31 +14,14 @@ ENV NODE_ENV=production
14
14
 
15
15
  COPY package.json package-lock.json* ./
16
16
 
17
- # Install with optional dependencies so it grabs the correct engine for the Linux builder
17
+ # Install dependencies (including optional engines)
18
18
  RUN npm install --include=optional
19
19
 
20
- # ------------------------------------------------
21
- # Extract Titan Extensions (packages with titan.json)
22
- # ------------------------------------------------
23
- RUN mkdir -p /app/.ext && \
24
- find node_modules -mindepth 2 -maxdepth 3 -type f -name "titan.json" | while read file; do \
25
- pkg_dir=$(dirname "$file"); \
26
- pkg_name=$(basename "$pkg_dir"); \
27
- echo "Extracting Titan extension: $pkg_name"; \
28
- cp -a "$pkg_dir" "/app/.ext/$pkg_name"; \
29
- rm -rf "/app/.ext/$pkg_name/node_modules"; \
30
- done
31
-
32
- # ------------------------------------------------
33
- # Copy ANY installed Titan Engine (Architecture agnostic)
34
- # ------------------------------------------------
35
- RUN mkdir -p /app/.ext/@titanpl && \
36
- cp -r node_modules/@titanpl/engine-linux-* /app/.ext/@titanpl/
37
-
38
20
  COPY . .
39
21
 
40
- # Run the Titan build step
41
- RUN npx titan build
22
+ # Run the Titan release build step
23
+ # This extracts extensions to .ext and prepares the 'build/' folder
24
+ RUN npx titan build --release
42
25
 
43
26
 
44
27
  # ================================================================
@@ -55,31 +38,11 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
55
38
  ca-certificates curl \
56
39
  && rm -rf /var/lib/apt/lists/*
57
40
 
58
- # copy dist contents into /app/dist
59
- COPY --from=builder /app/dist/ ./dist/
60
-
61
- # titan extensions + engine
62
- COPY --from=builder /app/.ext ./.ext
63
-
64
- # runtime assets
65
- COPY --from=builder /app/package.json ./package.json
66
-
67
- # ---------------- OPTIONAL APP FOLDERS ----------------
68
- # Static assets
69
- # COPY --from=builder /app/app/static ./static
70
-
71
- # Public assets
72
- # COPY --from=builder /app/app/public ./public
73
-
74
- # DB
75
- # COPY --from=builder /app/app/db ./db
41
+ # Copy the entire release build folder prepared by Stage 1
42
+ COPY --from=builder /app/build ./
76
43
 
77
- # CRITICAL SYSTEM SETUP:
78
- # 1. Mandatory .env file (Engine requires it for config parsing)
79
- # 2. Node modules symlink for extension JS dependency resolution
80
- RUN echo "TITAN_DEV=0" > .env && \
81
- ln -s /app/.ext /app/node_modules && \
82
- chown -R titan:titan /app
44
+ # Ensure permissions
45
+ RUN chown -R titan:titan /app
83
46
 
84
47
  # Standard environment variables
85
48
  ENV HOST=0.0.0.0
@@ -89,10 +52,9 @@ ENV TITAN_DEV=0
89
52
  USER titan
90
53
  EXPOSE 5100
91
54
 
92
- # Health check to ensure the server is alive
55
+ # Health check
93
56
  HEALTHCHECK --interval=30s --timeout=5s --start-period=5s --retries=3 \
94
57
  CMD curl -f http://localhost:5100/ || exit 1
95
58
 
96
- # DYNAMIC ENTRYPOINT: Finds the correct architecture binary and starts it
97
- # This allows the SAME image to work on x64 vs ARM64 servers.
98
- CMD ["/bin/sh", "-c", "exec $(find .ext/@titanpl/engine-linux-* -name titan-server -type f | head -n 1) run dist"]
59
+ # Start the server using the 'titan-server' binary created by the release process
60
+ CMD ["./titan-server", "run", "dist"]
@@ -0,0 +1,85 @@
1
+ # ⏣ Titan Project
2
+
3
+ Welcome to your new **Titan Planet** project! Titan is a high-performance web framework designed for scale, speed, and developer happiness.
4
+
5
+ ## πŸš€ Getting Started
6
+
7
+ ### 1. Install Dependencies
8
+ ```bash
9
+ npm install
10
+ ```
11
+
12
+ ### 2. Start Development Server
13
+ Run the project in development mode with hot-reloading:
14
+ ```bash
15
+ titan dev
16
+ ```
17
+
18
+ ### 3. Build for Production
19
+ Create a self-contained production bundle in the `build/` directory:
20
+ ```bash
21
+ titan build --release
22
+ ```
23
+
24
+ ### 4. Run Production Server
25
+ ```bash
26
+ cd build
27
+ titan start
28
+ ```
29
+
30
+ ---
31
+
32
+ ## πŸ“‚ Project Structure
33
+
34
+ - `app/actions/` - Your JavaScript/TypeScript backend logic.
35
+ - `public/` - Static assets served directly (images, robots.txt, etc.).
36
+ - `tanfig.json` - Core project configuration and build settings.
37
+ - `.env` - Environment variables.
38
+
39
+ ---
40
+
41
+ ## πŸ›  Configuration (`tanfig.json`)
42
+
43
+ Your project uses `tanfig.json` to control the build and runtime behavior.
44
+
45
+ ```json
46
+ {
47
+ "name": "my-titan-app",
48
+ "build": {
49
+ "purpose": "test",
50
+ "files": ["public", "static", "db", "config"]
51
+ }
52
+ }
53
+ ```
54
+
55
+ ### Build Options:
56
+ - **`purpose`**:
57
+ - `test`: (Default) Creates a `node_modules` junction for local testing.
58
+ - `deploy`: Slim build without `node_modules`, ready for production.
59
+ - **`files`**: List of folders/files from the root to include in the production `build/` folder.
60
+
61
+ ---
62
+
63
+ ## 🐳 Docker Deployment
64
+
65
+ This project comes with a pre-configured, multi-stage `Dockerfile` optimized for Titan's native engine.
66
+
67
+ ### Build Image
68
+ ```bash
69
+ docker build -t my-titan-app .
70
+ ```
71
+
72
+ ### Run Container
73
+ ```bash
74
+ docker run -p 5100:5100 my-titan-app
75
+ ```
76
+
77
+ ---
78
+
79
+ ## 🌐 Community & Support
80
+
81
+ - **Documentation**: [docs.titanpl.com](https://titanpl.vercel.app)
82
+ - **GitHub**: [github.com/t8nlab/titanpl](https://github.com/t8nlab/titanpl)
83
+ - **Discord**: [Join our community](https://discord.gg/titanpl)
84
+
85
+ Built with ❀️ by the **Titan Planet** team.
@@ -11,6 +11,7 @@ dist/
11
11
  .titan/
12
12
  .ext/
13
13
  .env
14
+ build/
14
15
 
15
16
  # Rust Build Artifacts (If using Hybrid)
16
17
  server/target/
@@ -0,0 +1,14 @@
1
+ {
2
+ "name": "{{name}}",
3
+ "description": "A powerful Titan Planet project",
4
+ "version": "1.0.0",
5
+ "build": {
6
+ "purpose": "test",
7
+ "files": [
8
+ "public",
9
+ "static",
10
+ "db",
11
+ "config"
12
+ ]
13
+ }
14
+ }
@@ -13,6 +13,10 @@
13
13
  "@titanpl/node": "latest",
14
14
  "@titanpl/packet": "2.0.4"
15
15
  },
16
+ "optionalDependencies": {
17
+ "@titanpl/engine-linux-x64": "2.0.5",
18
+ "@titanpl/engine-win32-x64": "2.0.3"
19
+ },
16
20
  "scripts": {
17
21
  "build": "titan build",
18
22
  "dev": "titan dev",
@@ -25,4 +29,4 @@
25
29
  "eslint-plugin-titanpl": "latest"
26
30
  },
27
31
  "version": "2.0.4"
28
- }
32
+ }
@@ -14,6 +14,11 @@
14
14
  "@titanpl/packet": "2.0.4",
15
15
  "typescript": "^5.0.0"
16
16
  },
17
+ "optionalDependencies": {
18
+ "@titanpl/engine-linux-arm64": "2.0.5",
19
+ "@titanpl/engine-linux-x64": "2.0.5",
20
+ "@titanpl/engine-win32-x64": "2.0.3"
21
+ },
17
22
  "scripts": {
18
23
  "build": "titan build",
19
24
  "dev": "titan dev",
@@ -27,4 +32,4 @@
27
32
  "@typescript-eslint/parser": "^8.54.0"
28
33
  },
29
34
  "version": "2.0.4"
30
- }
35
+ }