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 +51 -7
- package/package.json +1 -1
- package/packages/cli/README.md +19 -2
- package/packages/cli/index.js +5 -4
- package/packages/cli/package.json +13 -6
- package/packages/cli/src/commands/build.js +5 -4
- package/packages/cli/src/commands/init.js +15 -1
- package/packages/cli/src/commands/update.js +24 -3
- package/packages/cli/src/engine.js +38 -7
- package/packages/packet/index.js +205 -0
- package/packages/packet/js/titan/bundle.js +27 -14
- package/packages/packet/package.json +1 -1
- package/packages/packet/ts/titan/bundle.js +27 -14
- package/templates/common/Dockerfile +11 -49
- package/templates/common/README.md +85 -0
- package/templates/common/_gitignore +1 -0
- package/templates/common/_tanfig.json +14 -0
- package/templates/js/package.json +5 -1
- package/templates/ts/package.json +6 -1
package/README.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<p align="center">
|
|
2
|
-
<a href="https://
|
|
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
|
|
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
|
-
|
|
245
|
-
|
|
246
|
-
|
|
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://
|
|
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
|
+
"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",
|
package/packages/cli/README.md
CHANGED
|
@@ -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
|
-
|
|
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
|
-
**
|
|
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.
|
package/packages/cli/index.js
CHANGED
|
@@ -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
|
-
|
|
126
|
-
|
|
127
|
-
|
|
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
|
+
"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": "
|
|
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
|
|
5
|
-
|
|
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
|
-
"
|
|
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.
|
|
62
|
-
const
|
|
63
|
-
|
|
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
|
-
|
|
35
|
-
|
|
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
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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
|
|
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
|
-
|
|
143
|
+
TITAN_DEV: watchMode ? '1' : '0',
|
|
144
|
+
NODE_ENV: watchMode ? 'development' : 'production'
|
|
114
145
|
}
|
|
115
146
|
});
|
|
116
147
|
|
package/packages/packet/index.js
CHANGED
|
@@ -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
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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: [
|
|
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: {
|
|
@@ -34,20 +34,32 @@ const NODE_BUILTIN_MAP = {
|
|
|
34
34
|
"node:util": "@titanpl/node/util",
|
|
35
35
|
};
|
|
36
36
|
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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: [
|
|
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
|
|
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
|
-
|
|
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
|
-
#
|
|
59
|
-
COPY --from=builder /app/
|
|
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
|
-
#
|
|
78
|
-
|
|
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
|
|
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
|
-
#
|
|
97
|
-
|
|
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.
|
|
@@ -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
|
+
}
|