titanpl 4.0.2 ā 7.0.0-beta
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/package.json +11 -5
- package/packages/cli/index.js +25 -11
- package/packages/cli/package.json +5 -5
- package/packages/cli/src/commands/build-ext.js +157 -0
- package/packages/cli/src/commands/build.js +12 -0
- package/packages/cli/src/commands/create.js +160 -0
- package/packages/cli/src/commands/init.js +5 -11
- package/packages/cli/src/commands/run-ext.js +104 -0
- package/packages/engine-darwin-arm64/README.md +0 -2
- package/packages/engine-darwin-arm64/package.json +1 -1
- package/packages/engine-linux-x64/README.md +0 -2
- package/packages/engine-linux-x64/package.json +1 -1
- package/packages/engine-win32-x64/README.md +0 -1
- package/packages/engine-win32-x64/bin/titan-server.exe +0 -0
- package/packages/engine-win32-x64/package.json +1 -1
- package/packages/native/README.md +0 -1
- package/packages/native/index.d.ts +10 -0
- package/packages/native/index.js +4 -0
- package/packages/native/package.json +1 -1
- package/packages/native/t.native.d.ts +175 -44
- package/packages/packet/README.md +0 -1
- package/packages/packet/index.js +19 -2
- package/packages/packet/package.json +1 -1
- package/packages/route/README.md +21 -0
- package/packages/route/index.d.ts +1 -0
- package/packages/route/index.js +22 -0
- package/packages/route/package.json +1 -1
- package/packages/sdk/index.js +2 -0
- package/packages/sdk/package.json +18 -0
- package/packages/sdk/test/index.js +120 -0
- package/templates/common/Dockerfile +9 -45
- package/templates/common/_tanfig.json +17 -13
- package/templates/extension/index.d.ts +26 -22
- package/templates/extension/index.js +15 -15
- package/templates/extension/native/Cargo.toml +5 -3
- package/templates/extension/native/src/lib.rs +2 -3
- package/templates/extension/package.json +10 -20
- package/templates/extension/titan.json +5 -16
- package/templates/extension/utils/registerExtension.js +44 -0
- package/templates/js/package.json +8 -8
- package/templates/rust-js/package.json +4 -4
- package/templates/rust-ts/package.json +4 -4
- package/templates/ts/package.json +8 -8
- package/templates/common/app/t.native.d.ts +0 -2043
- package/templates/common/app/t.native.js +0 -39
- package/titanpl-sdk/LICENSE +0 -15
- package/titanpl-sdk/README.md +0 -111
- package/titanpl-sdk/assets/titanpl-sdk.png +0 -0
- package/titanpl-sdk/bin/run.js +0 -274
- package/titanpl-sdk/index.js +0 -5
- package/titanpl-sdk/package-lock.json +0 -28
- package/titanpl-sdk/package.json +0 -40
- package/titanpl-sdk/templates/app/actions/hello.js +0 -5
- package/titanpl-sdk/templates/app/app.js +0 -7
- package/titanpl-sdk/templates/jsconfig.json +0 -19
- package/titanpl-sdk/templates/server/Cargo.toml +0 -52
- package/titanpl-sdk/templates/server/src/action_management.rs +0 -175
- package/titanpl-sdk/templates/server/src/errors.rs +0 -12
- package/titanpl-sdk/templates/server/src/extensions/builtin.rs +0 -1055
- package/titanpl-sdk/templates/server/src/extensions/external.rs +0 -338
- package/titanpl-sdk/templates/server/src/extensions/mod.rs +0 -580
- package/titanpl-sdk/templates/server/src/extensions/titan_core.js +0 -249
- package/titanpl-sdk/templates/server/src/fast_path.rs +0 -719
- package/titanpl-sdk/templates/server/src/main.rs +0 -607
- package/titanpl-sdk/templates/server/src/runtime.rs +0 -284
- package/titanpl-sdk/templates/server/src/utils.rs +0 -33
- package/titanpl-sdk/templates/titan/bundle.js +0 -259
- package/titanpl-sdk/templates/titan/dev.js +0 -390
- package/titanpl-sdk/templates/titan/error-box.js +0 -277
- package/titanpl-sdk/templates/titan/titan.js +0 -129
package/package.json
CHANGED
|
@@ -1,19 +1,20 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "titanpl",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "7.0.0-beta",
|
|
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",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"main": "index.js",
|
|
9
9
|
"bin": {
|
|
10
|
-
"tit": "
|
|
11
|
-
"titan": "
|
|
12
|
-
"t8n": "
|
|
10
|
+
"tit-dev": "node ../../packages/cli/index.js",
|
|
11
|
+
"titan-dev": "node ../../packages/cli/index.js",
|
|
12
|
+
"t8n-dev": "node ../../packages/cli/index.js",
|
|
13
|
+
"test-start": "taskkill /f /im titan-server.exe; ../../engine/target/release/titan-server.exe run dist"
|
|
13
14
|
},
|
|
14
15
|
"workspaces": [
|
|
15
16
|
"packages/*",
|
|
16
|
-
"
|
|
17
|
+
"test-apps/*"
|
|
17
18
|
],
|
|
18
19
|
"files": [
|
|
19
20
|
"index.js",
|
|
@@ -61,6 +62,11 @@
|
|
|
61
62
|
"help": "cd build && node ../index.js help",
|
|
62
63
|
"update": "cd build && node ../index.js update",
|
|
63
64
|
"publish": "node scripts/publish.mjs",
|
|
65
|
+
"titan-dev": "node packages/cli/index.js",
|
|
66
|
+
"tit-dev": "node packages/cli/index.js",
|
|
67
|
+
"t8n-dev": "node packages/cli/index.js",
|
|
68
|
+
"engine:build": "cd engine && cargo build --release",
|
|
69
|
+
"engine:dev": "cd engine && cargo build",
|
|
64
70
|
"test": "vitest run",
|
|
65
71
|
"test:watch": "vitest",
|
|
66
72
|
"test:coverage": "vitest run --coverage",
|
package/packages/cli/index.js
CHANGED
|
@@ -11,6 +11,10 @@ import { migrateCommand } from "./src/commands/migrate.js";
|
|
|
11
11
|
import { updateCommand } from "./src/commands/update.js";
|
|
12
12
|
import { initCommand } from "./src/commands/init.js";
|
|
13
13
|
|
|
14
|
+
import { createCommand } from "./src/commands/create.js";
|
|
15
|
+
import { buildExtensionCommand } from "./src/commands/build-ext.js";
|
|
16
|
+
import { runExtensionCommand } from "./src/commands/run-ext.js";
|
|
17
|
+
|
|
14
18
|
/* -------------------------------------------------------
|
|
15
19
|
* Resolve __dirname (ESM safe)
|
|
16
20
|
* ----------------------------------------------------- */
|
|
@@ -54,6 +58,7 @@ ${bold(cyan("ā°āāāāāāāāāāāāāāāāāāāāā
|
|
|
54
58
|
${cyan("init")} ${gray("Scaffold a new Titan project")}
|
|
55
59
|
${cyan("create")} ${gray("Create a new project or extension (e.g. 'titan create ext my-ext')")}
|
|
56
60
|
${cyan("build")} ${gray("Compile actions and build production dist. Use --release or -r for a production-ready folder.")}
|
|
61
|
+
${cyan("run")} ${gray("Run the production Gravity Engine or an extension sandbox")}
|
|
57
62
|
${cyan("dev")} ${gray("Start the Gravity Engine in dev/watch mode")}
|
|
58
63
|
${cyan("start")} ${gray("Start the production Gravity Engine")}
|
|
59
64
|
${cyan("update")} ${gray("Update an existing project to latest Titan version")}
|
|
@@ -112,26 +117,35 @@ const cmd = process.argv[2];
|
|
|
112
117
|
case "create": {
|
|
113
118
|
const type = process.argv[3];
|
|
114
119
|
const name = process.argv[4];
|
|
115
|
-
|
|
116
|
-
await initCommand(name, "extension");
|
|
117
|
-
} else {
|
|
118
|
-
// Fallback to init behavior
|
|
119
|
-
await initCommand(type, null);
|
|
120
|
-
}
|
|
120
|
+
await createCommand(type, name);
|
|
121
121
|
break;
|
|
122
122
|
}
|
|
123
123
|
|
|
124
|
-
case "build":
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
124
|
+
case "build": {
|
|
125
|
+
if (process.argv[3] === "ext" || process.argv[3] === "extension") {
|
|
126
|
+
await buildExtensionCommand();
|
|
127
|
+
} else {
|
|
128
|
+
const isRelease = process.argv.includes("--release") || process.argv.includes("-r");
|
|
129
|
+
console.log(cyan(`ā Building Titan project${isRelease ? " (Release mode)" : ""}...`));
|
|
130
|
+
await buildCommand(isRelease);
|
|
131
|
+
console.log(green(`ā ${isRelease ? "Release" : "Build"} complete`));
|
|
132
|
+
}
|
|
129
133
|
break;
|
|
134
|
+
}
|
|
130
135
|
|
|
131
136
|
case "dev":
|
|
132
137
|
await devCommand();
|
|
133
138
|
break;
|
|
134
139
|
|
|
140
|
+
case "run":
|
|
141
|
+
if (process.argv[3] === "ext" || process.argv[3] === "extension") {
|
|
142
|
+
await runExtensionCommand();
|
|
143
|
+
} else {
|
|
144
|
+
console.log(cyan("ā Starting Titan Server..."));
|
|
145
|
+
startCommand();
|
|
146
|
+
}
|
|
147
|
+
break;
|
|
148
|
+
|
|
135
149
|
case "start":
|
|
136
150
|
console.log(cyan("ā Starting Titan Server..."));
|
|
137
151
|
startCommand();
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@titanpl/cli",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "7.0.0-beta",
|
|
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",
|
|
@@ -29,13 +29,13 @@
|
|
|
29
29
|
"tit": "./index.js"
|
|
30
30
|
},
|
|
31
31
|
"optionalDependencies": {
|
|
32
|
-
"@titanpl/engine-win32-x64": "
|
|
33
|
-
"@titanpl/engine-linux-x64": "
|
|
32
|
+
"@titanpl/engine-win32-x64": "7.0.0-beta",
|
|
33
|
+
"@titanpl/engine-linux-x64": "7.0.0-beta"
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@titanpl/packet": "
|
|
36
|
+
"@titanpl/packet": "7.0.0-beta",
|
|
37
37
|
"prompts": "^2.4.2",
|
|
38
38
|
"commander": "^11.0.0",
|
|
39
39
|
"chalk": "^4.1.2"
|
|
40
40
|
}
|
|
41
|
-
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { execSync } from 'child_process';
|
|
4
|
+
import chalk from 'chalk';
|
|
5
|
+
|
|
6
|
+
export async function buildExtensionCommand() {
|
|
7
|
+
const titanJsonPath = path.join(process.cwd(), 'titan.json');
|
|
8
|
+
if (!fs.existsSync(titanJsonPath)) {
|
|
9
|
+
console.log(chalk.red("ā No titan.json found in current directory."));
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const titanJson = JSON.parse(fs.readFileSync(titanJsonPath, 'utf8'));
|
|
14
|
+
const type = titanJson.type;
|
|
15
|
+
|
|
16
|
+
if (type === 'js') {
|
|
17
|
+
console.log(chalk.yellow("! JS-only extensions do not require a build step."));
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
console.log(chalk.cyan(`\nā Building ${type.toUpperCase()} extension '${titanJson.name}'...\n`));
|
|
22
|
+
|
|
23
|
+
if (type === 'wasm') {
|
|
24
|
+
await buildWasmExtension(titanJson);
|
|
25
|
+
} else if (type === 'native') {
|
|
26
|
+
await buildNativeExtension(titanJson);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async function buildWasmExtension(titanJson) {
|
|
31
|
+
const nativeDir = path.join(process.cwd(), 'native');
|
|
32
|
+
if (!fs.existsSync(nativeDir)) {
|
|
33
|
+
console.log(chalk.red("ā native/ directory not found."));
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
console.log(chalk.gray(" Compiling Rust to WebAssembly..."));
|
|
38
|
+
try {
|
|
39
|
+
// We assume wasm-pack or similar is used, or just bare cargo with wasm32 target
|
|
40
|
+
// The spec mentions: native/pkg/my_ext.wasm, which is wasm-pack's default
|
|
41
|
+
execSync('wasm-pack build --target web', { cwd: nativeDir, stdio: 'inherit' });
|
|
42
|
+
} catch (err) {
|
|
43
|
+
console.log(chalk.red("ā Wasm compilation failed. Make sure 'wasm-pack' is installed."));
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
console.log(chalk.gray(" Generating bindings..."));
|
|
48
|
+
const libRsPath = path.join(nativeDir, 'src/lib.rs');
|
|
49
|
+
const libRs = fs.readFileSync(libRsPath, 'utf8');
|
|
50
|
+
|
|
51
|
+
const exports = [];
|
|
52
|
+
const exportRegex = /#\[titan::export\]\s+pub\s+fn\s+(\w+)\s*\(([^)]*)\)\s*(?:->\s*([^\{]+))?/g;
|
|
53
|
+
let match;
|
|
54
|
+
while ((match = exportRegex.exec(libRs)) !== null) {
|
|
55
|
+
const name = match[1];
|
|
56
|
+
const paramsRaw = match[2];
|
|
57
|
+
const params = paramsRaw.split(',').map(p => p.trim().split(':')[0].trim()).filter(p => p);
|
|
58
|
+
exports.push({ name, params });
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const bindingsJs = `// Auto-generated by titan build ext
|
|
62
|
+
import * as wasm from './my_ext_bg.wasm';
|
|
63
|
+
export const { ${exports.map(e => e.name).join(', ')} } = wasm;
|
|
64
|
+
`;
|
|
65
|
+
|
|
66
|
+
const pkgDir = path.join(nativeDir, 'pkg');
|
|
67
|
+
fs.writeFileSync(path.join(pkgDir, 'bindings.js'), bindingsJs);
|
|
68
|
+
|
|
69
|
+
// Update index.js
|
|
70
|
+
const indexJs = `import { registerExtension } from './utils/registerExtension.js';
|
|
71
|
+
import * as native from './native/pkg/bindings.js';
|
|
72
|
+
|
|
73
|
+
const myExt = {
|
|
74
|
+
${exports.map(e => `${e.name}: (${e.params.join(', ')}) => native.${e.name}(${e.params.join(', ')})`).join(',\n ')}
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
registerExtension("${titanJson.name.split('/').pop()}", myExt);
|
|
78
|
+
`;
|
|
79
|
+
fs.writeFileSync(path.join(process.cwd(), 'index.js'), indexJs);
|
|
80
|
+
|
|
81
|
+
// Update titan.json
|
|
82
|
+
titanJson.wasm = "native/pkg/my_ext_bg.wasm";
|
|
83
|
+
titanJson.bindings = "native/pkg/bindings.js";
|
|
84
|
+
fs.writeFileSync(path.join(process.cwd(), 'titan.json'), JSON.stringify(titanJson, null, 2));
|
|
85
|
+
|
|
86
|
+
console.log(chalk.green("ā Wasm build complete."));
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async function buildNativeExtension(titanJson) {
|
|
90
|
+
const nativeDir = path.join(process.cwd(), 'native');
|
|
91
|
+
if (!fs.existsSync(nativeDir)) {
|
|
92
|
+
console.log(chalk.red("ā native/ directory not found."));
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
console.log(chalk.gray(" Compiling Rust native library..."));
|
|
97
|
+
try {
|
|
98
|
+
execSync('cargo build --release', { cwd: nativeDir, stdio: 'inherit' });
|
|
99
|
+
} catch (err) {
|
|
100
|
+
console.log(chalk.red("ā Native compilation failed."));
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const libName = titanJson.name.split('/').pop().replace(/-/g, '_');
|
|
105
|
+
const isWindows = process.platform === 'win32';
|
|
106
|
+
const isMac = process.platform === 'darwin';
|
|
107
|
+
|
|
108
|
+
let binName = `lib${libName}.so`;
|
|
109
|
+
if (isWindows) binName = `${libName}.dll`;
|
|
110
|
+
if (isMac) binName = `lib${libName}.dylib`;
|
|
111
|
+
|
|
112
|
+
const buildDir = path.join(nativeDir, 'build');
|
|
113
|
+
if (!fs.existsSync(buildDir)) fs.mkdirSync(buildDir);
|
|
114
|
+
|
|
115
|
+
const targetDir = path.join(nativeDir, 'target/release');
|
|
116
|
+
const srcPath = path.join(targetDir, binName);
|
|
117
|
+
const destPath = path.join(buildDir, binName);
|
|
118
|
+
|
|
119
|
+
if (fs.existsSync(srcPath)) {
|
|
120
|
+
fs.copyFileSync(srcPath, destPath);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Parse exports for index.js
|
|
124
|
+
const libRsPath = path.join(nativeDir, 'src/lib.rs');
|
|
125
|
+
const libRs = fs.readFileSync(libRsPath, 'utf8');
|
|
126
|
+
const exports = [];
|
|
127
|
+
const exportRegex = /#\[titan::native_export\]\s+pub\s+extern\s+"C"\s+fn\s+(\w+)\s*\(([^)]*)\)/g;
|
|
128
|
+
let match;
|
|
129
|
+
while ((match = exportRegex.exec(libRs)) !== null) {
|
|
130
|
+
const name = match[1];
|
|
131
|
+
const paramsRaw = match[2];
|
|
132
|
+
const params = paramsRaw.split(',').map(p => p.trim().split(':')[0].trim()).filter(p => p);
|
|
133
|
+
exports.push({ name, params });
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Update index.js
|
|
137
|
+
const indexJs = `import { registerExtension } from './utils/registerExtension.js';
|
|
138
|
+
|
|
139
|
+
// native bindings are injected by Gravity's NativeHost IPC bridge
|
|
140
|
+
const myExt = {
|
|
141
|
+
${exports.map(e => `${e.name}: (${e.params.join(', ')}) => t.__native.call("${titanJson.name}", "${e.name}", [${e.params.join(', ')}])`).join(',\n ')}
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
registerExtension("${titanJson.name.split('/').pop()}", myExt);
|
|
145
|
+
`;
|
|
146
|
+
fs.writeFileSync(path.join(process.cwd(), 'index.js'), indexJs);
|
|
147
|
+
|
|
148
|
+
// Update titan.json
|
|
149
|
+
titanJson.native = {
|
|
150
|
+
linux: `native/build/lib${libName}.so`,
|
|
151
|
+
windows: `native/build/${libName}.dll`,
|
|
152
|
+
macos: `native/build/lib${libName}.dylib`
|
|
153
|
+
};
|
|
154
|
+
fs.writeFileSync(path.join(process.cwd(), 'titan.json'), JSON.stringify(titanJson, null, 2));
|
|
155
|
+
|
|
156
|
+
console.log(chalk.green("ā Native build complete."));
|
|
157
|
+
}
|
|
@@ -1,7 +1,19 @@
|
|
|
1
1
|
import { build, release } from "@titanpl/packet";
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import path from 'path';
|
|
2
4
|
|
|
3
5
|
export async function buildCommand(isRelease = false) {
|
|
4
6
|
const buildFn = isRelease ? release : build;
|
|
5
7
|
const dist = await buildFn(process.cwd());
|
|
8
|
+
|
|
9
|
+
const tanfigPath = path.join(process.cwd(), 'tanfig.json');
|
|
10
|
+
if (fs.existsSync(tanfigPath)) {
|
|
11
|
+
fs.copyFileSync(tanfigPath, path.join(dist, 'tanfig.json'));
|
|
12
|
+
}
|
|
13
|
+
const pkgPath = path.join(process.cwd(), 'package.json');
|
|
14
|
+
if (fs.existsSync(pkgPath)) {
|
|
15
|
+
fs.copyFileSync(pkgPath, path.join(dist, 'package.json'));
|
|
16
|
+
}
|
|
17
|
+
|
|
6
18
|
console.log(`ā ${isRelease ? 'Release' : 'Build'} complete ā`, dist);
|
|
7
19
|
}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { fileURLToPath } from 'url';
|
|
4
|
+
import prompts from 'prompts';
|
|
5
|
+
import chalk from 'chalk';
|
|
6
|
+
import { copyDir } from './init.js';
|
|
7
|
+
|
|
8
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
9
|
+
const __dirname = path.dirname(__filename);
|
|
10
|
+
|
|
11
|
+
export async function createCommand(type, name) {
|
|
12
|
+
if (type === 'ext' || type === 'extension') {
|
|
13
|
+
await createExtension(name);
|
|
14
|
+
} else {
|
|
15
|
+
console.log(chalk.red(`\nā Unknown creation type: ${type}`));
|
|
16
|
+
console.log(chalk.gray(` Available types: ext\n`));
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async function createExtension(extensionName) {
|
|
21
|
+
let name = extensionName;
|
|
22
|
+
if (!name) {
|
|
23
|
+
const res = await prompts({
|
|
24
|
+
type: 'text',
|
|
25
|
+
name: 'name',
|
|
26
|
+
message: 'Extension name:',
|
|
27
|
+
initial: 'my-ext'
|
|
28
|
+
});
|
|
29
|
+
name = res.name;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (!name) {
|
|
33
|
+
console.log(chalk.red("ā Extension name is required."));
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const res = await prompts({
|
|
38
|
+
type: 'select',
|
|
39
|
+
name: 'type',
|
|
40
|
+
message: 'What type of extension do you want to create?',
|
|
41
|
+
choices: [
|
|
42
|
+
{ title: 'js ā JavaScript only, zero build step, always safe', value: 'js' },
|
|
43
|
+
{ title: 'wasm ā Rust compiled to WebAssembly, sandboxed, auto-bound', value: 'wasm' },
|
|
44
|
+
{ title: 'native ā Rust compiled to .so/.dll, out-of-process, requires allowNative', value: 'native' },
|
|
45
|
+
],
|
|
46
|
+
initial: 0
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
const extType = res.type;
|
|
50
|
+
if (!extType) return;
|
|
51
|
+
|
|
52
|
+
const targetDir = path.resolve(process.cwd(), name);
|
|
53
|
+
if (fs.existsSync(targetDir)) {
|
|
54
|
+
console.log(chalk.red(`\nā Directory '${name}' already exists.\n`));
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Find template path (similar to init.js)
|
|
59
|
+
let templateDir = path.resolve(__dirname, '..', '..', '..', '..', 'templates', 'extension');
|
|
60
|
+
if (!fs.existsSync(templateDir)) {
|
|
61
|
+
templateDir = path.resolve(__dirname, '..', '..', 'templates', 'extension');
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
console.log(chalk.cyan(`\nā Creating ${extType.toUpperCase()} extension '${name}'...\n`));
|
|
65
|
+
|
|
66
|
+
if (extType === 'js') {
|
|
67
|
+
try {
|
|
68
|
+
console.log(chalk.gray(` Cloning JS template from https://github.com/t8nlab/extTemplate.git...`));
|
|
69
|
+
const { execSync } = await import('child_process');
|
|
70
|
+
execSync(`git clone https://github.com/t8nlab/extTemplate.git "${targetDir}"`, { stdio: 'pipe' });
|
|
71
|
+
// Remove git history
|
|
72
|
+
fs.rmSync(path.join(targetDir, '.git'), { recursive: true, force: true });
|
|
73
|
+
} catch (err) {
|
|
74
|
+
console.log(chalk.yellow(` Git clone failed, falling back to local template...`));
|
|
75
|
+
copyDir(templateDir, targetDir);
|
|
76
|
+
}
|
|
77
|
+
} else {
|
|
78
|
+
// 1. Copy the source template
|
|
79
|
+
copyDir(templateDir, targetDir);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// 2. Perform transformations
|
|
83
|
+
transformExtension(targetDir, name, extType);
|
|
84
|
+
|
|
85
|
+
console.log(chalk.green(`\nā Extension '${name}' created successfully!`));
|
|
86
|
+
console.log(chalk.yellow(` cd ${name}`));
|
|
87
|
+
if (extType !== 'js') {
|
|
88
|
+
console.log(chalk.yellow(` titan build ext`));
|
|
89
|
+
}
|
|
90
|
+
console.log(chalk.yellow(` titan run ext\n`));
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function transformExtension(target, name, type) {
|
|
94
|
+
// 1. Remove optional native folder if it's a JS extension
|
|
95
|
+
if (type === 'js') {
|
|
96
|
+
const nativeDir = path.join(target, 'native');
|
|
97
|
+
if (fs.existsSync(nativeDir)) {
|
|
98
|
+
fs.rmSync(nativeDir, { recursive: true, force: true });
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// 2. Patch package.json
|
|
103
|
+
const pkgPath = path.join(target, 'package.json');
|
|
104
|
+
if (fs.existsSync(pkgPath)) {
|
|
105
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
|
106
|
+
pkg.name = name;
|
|
107
|
+
pkg.version = "1.0.0";
|
|
108
|
+
// Ensure dependencies
|
|
109
|
+
pkg.dependencies = pkg.dependencies || {};
|
|
110
|
+
pkg.dependencies["@titanpl/sdk"] = "2.0.0";
|
|
111
|
+
fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2));
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// 3. Patch titan.json
|
|
115
|
+
const titanPath = path.join(target, 'titan.json');
|
|
116
|
+
if (fs.existsSync(titanPath)) {
|
|
117
|
+
const titan = JSON.parse(fs.readFileSync(titanPath, 'utf8'));
|
|
118
|
+
titan.name = name;
|
|
119
|
+
titan.type = type;
|
|
120
|
+
titan.version = "1.0.0";
|
|
121
|
+
fs.writeFileSync(titanPath, JSON.stringify(titan, null, 2));
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// 4. Cleanup git if it exists accidentally
|
|
125
|
+
const gitDir = path.join(target, '.git');
|
|
126
|
+
if (fs.existsSync(gitDir)) {
|
|
127
|
+
fs.rmSync(gitDir, { recursive: true, force: true });
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// 5. Recursive template substitution for any {{name}} in files
|
|
131
|
+
substituteTemplates(target, name);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function substituteTemplates(dir, name) {
|
|
135
|
+
for (const file of fs.readdirSync(dir)) {
|
|
136
|
+
const fullPath = path.join(dir, file);
|
|
137
|
+
if (fs.lstatSync(fullPath).isDirectory()) {
|
|
138
|
+
substituteTemplates(fullPath, name);
|
|
139
|
+
} else {
|
|
140
|
+
const ext = path.extname(file).toLowerCase();
|
|
141
|
+
const textExts = ['.js', '.ts', '.json', '.md', '.txt', '.rs', '.toml', '.html', '.css', '.d.ts'];
|
|
142
|
+
if (textExts.includes(ext)) {
|
|
143
|
+
let content = fs.readFileSync(fullPath, 'utf8');
|
|
144
|
+
let changed = false;
|
|
145
|
+
if (content.includes("{{name}}")) {
|
|
146
|
+
content = content.replace(/{{name}}/g, name);
|
|
147
|
+
changed = true;
|
|
148
|
+
}
|
|
149
|
+
if (content.includes("workspace:*")) {
|
|
150
|
+
content = content.replace(/"@titanpl\/sdk": "workspace:\*"/g, '"@titanpl/sdk": "2.0.0"');
|
|
151
|
+
content = content.replace(/workspace:\*/g, "6.0.0");
|
|
152
|
+
changed = true;
|
|
153
|
+
}
|
|
154
|
+
if (changed) {
|
|
155
|
+
fs.writeFileSync(fullPath, content);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
@@ -176,17 +176,7 @@ export async function initCommand(projectName, templateName) {
|
|
|
176
176
|
}
|
|
177
177
|
}
|
|
178
178
|
|
|
179
|
-
// 4.
|
|
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
|
-
}
|
|
179
|
+
// 4. Substitution is handled below by substitute()
|
|
190
180
|
|
|
191
181
|
|
|
192
182
|
// Recursive template substitution
|
|
@@ -212,6 +202,10 @@ export async function initCommand(projectName, templateName) {
|
|
|
212
202
|
content = content.replace(/{{native_name}}/g, projName.replace(/-/g, '_'));
|
|
213
203
|
changed = true;
|
|
214
204
|
}
|
|
205
|
+
if (content.includes("workspace:*")) {
|
|
206
|
+
content = content.replace(/workspace:\*/g, "6.0.0");
|
|
207
|
+
changed = true;
|
|
208
|
+
}
|
|
215
209
|
if (changed) {
|
|
216
210
|
fs.writeFileSync(fullPath, content);
|
|
217
211
|
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import chalk from 'chalk';
|
|
4
|
+
import http from 'node:http';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Runs the extension sandbox using a native Node.js server (zero dependencies).
|
|
8
|
+
*/
|
|
9
|
+
export async function runExtensionCommand() {
|
|
10
|
+
const titanJsonPath = path.join(process.cwd(), 'titan.json');
|
|
11
|
+
if (!fs.existsSync(titanJsonPath)) {
|
|
12
|
+
console.log(chalk.red("ā No titan.json found in current directory."));
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const titanJson = JSON.parse(fs.readFileSync(titanJsonPath, 'utf8'));
|
|
17
|
+
const port = 3000;
|
|
18
|
+
|
|
19
|
+
const server = http.createServer((req, res) => {
|
|
20
|
+
if (req.url === '/test' && req.method === 'GET') {
|
|
21
|
+
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
22
|
+
res.end(`
|
|
23
|
+
<!DOCTYPE html>
|
|
24
|
+
<html lang="en">
|
|
25
|
+
<head>
|
|
26
|
+
<meta charset="UTF-8">
|
|
27
|
+
<title>Titan Extension Sandbox - ${titanJson.name}</title>
|
|
28
|
+
<style>
|
|
29
|
+
body { font-family: 'Inter', sans-serif; background: #0f172a; color: #f8fafc; padding: 2rem; margin: 0; }
|
|
30
|
+
.container { max-width: 800px; margin: 0 auto; }
|
|
31
|
+
.card { background: #1e293b; padding: 2rem; border-radius: 1rem; border: 1px solid #334155; box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.3); }
|
|
32
|
+
h1 { color: #38bdf8; margin-top: 0; display: flex; align-items: center; gap: 1rem; }
|
|
33
|
+
.badge { background: #0ea5e9; color: white; padding: 0.2rem 0.6rem; border-radius: 9999px; font-size: 0.75rem; text-transform: uppercase; letter-spacing: 0.05em; }
|
|
34
|
+
.method-list { margin-top: 2rem; }
|
|
35
|
+
.method-item { background: #334155; padding: 1rem; margin-bottom: 1rem; border-radius: 0.5rem; display: flex; justify-content: space-between; align-items: center; }
|
|
36
|
+
.method-name { font-family: monospace; font-weight: bold; color: #7dd3fc; }
|
|
37
|
+
button { background: #38bdf8; color: #0f172a; border: none; padding: 0.5rem 1.25rem; border-radius: 0.375rem; cursor: pointer; font-weight: 600; transition: all 0.2s; }
|
|
38
|
+
button:hover { background: #7dd3fc; transform: translateY(-1px); }
|
|
39
|
+
.output-container { margin-top: 2rem; }
|
|
40
|
+
pre { background: #020617; padding: 1.5rem; border-radius: 0.5rem; border: 1px solid #1e293b; color: #10b981; overflow-x: auto; font-family: 'Fira Code', monospace; line-height: 1.5; }
|
|
41
|
+
.log-entry { margin-bottom: 0.5rem; }
|
|
42
|
+
.status-ok { color: #10b981; }
|
|
43
|
+
.status-call { color: #fbbf24; }
|
|
44
|
+
</style>
|
|
45
|
+
</head>
|
|
46
|
+
<body>
|
|
47
|
+
<div class="container">
|
|
48
|
+
<div class="card">
|
|
49
|
+
<h1>
|
|
50
|
+
Extension Sandbox
|
|
51
|
+
<span class="badge">${titanJson.type}</span>
|
|
52
|
+
</h1>
|
|
53
|
+
<p>Testing extension: <code style="color: #38bdf8;">${titanJson.name}</code> v${titanJson.version}</p>
|
|
54
|
+
|
|
55
|
+
<div class="method-list">
|
|
56
|
+
<div class="method-item">
|
|
57
|
+
<span class="method-name">hello()</span>
|
|
58
|
+
<button onclick="callMethod('hello', [])">Invoke</button>
|
|
59
|
+
</div>
|
|
60
|
+
<!-- Dynamic methods would be listed here -->
|
|
61
|
+
</div>
|
|
62
|
+
|
|
63
|
+
<div class="output-container">
|
|
64
|
+
<h3>Live Output Log</h3>
|
|
65
|
+
<pre id="output"><div class="log-entry status-ok">[System] Sandbox ready at localhost:${port}</div></pre>
|
|
66
|
+
</div>
|
|
67
|
+
</div>
|
|
68
|
+
</div>
|
|
69
|
+
|
|
70
|
+
<script>
|
|
71
|
+
const output = document.getElementById('output');
|
|
72
|
+
function log(msg, type = 'ok') {
|
|
73
|
+
const entry = document.createElement('div');
|
|
74
|
+
entry.className = 'log-entry status-' + type;
|
|
75
|
+
entry.textContent = \`[\${new Date().toLocaleTimeString()}] \${msg}\`;
|
|
76
|
+
output.appendChild(entry);
|
|
77
|
+
output.scrollTop = output.scrollHeight;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async function callMethod(name, args) {
|
|
81
|
+
log(\`Calling \${name}...\`, 'call');
|
|
82
|
+
try {
|
|
83
|
+
// In a real sandbox this would fetch a /call endpoint
|
|
84
|
+
const res = "Hello from ${titanJson.name}!";
|
|
85
|
+
log(\`Result: \${JSON.stringify(res)}\`, 'ok');
|
|
86
|
+
} catch (e) {
|
|
87
|
+
log(\`Error: \${e.message}\`, 'error');
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
</script>
|
|
91
|
+
</body>
|
|
92
|
+
</html>`);
|
|
93
|
+
} else {
|
|
94
|
+
res.writeHead(404);
|
|
95
|
+
res.end('Not Found');
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
server.listen(port, () => {
|
|
100
|
+
console.log(chalk.cyan(`\nšŖ Titan Extension Sandbox running at:`));
|
|
101
|
+
console.log(chalk.bold(` http://localhost:${port}/test\n`));
|
|
102
|
+
console.log(chalk.gray(` Press Ctrl+C to stop.\n`));
|
|
103
|
+
});
|
|
104
|
+
}
|
|
@@ -9,5 +9,3 @@ This package is the compiled core Rust + Axum server embedded with the Boa JavaS
|
|
|
9
9
|
This package is listed as an "optional dependency" in `@titanpl/cli`. During package installation, your package manager identifies the OS and automatically downloads this binary if it matches `darwin` + `arm64`. You do not need to install it directly.
|
|
10
10
|
|
|
11
11
|
When you run `titan start`, the CLI locates this binary and executes it as your web server.
|
|
12
|
-
|
|
13
|
-
**Important Note:** Currently, Titan Planet and its entire package ecosystem are only for Windows. The Linux and macOS versions are in development (dev only) for the new architecture and will be launched later.
|
|
@@ -7,5 +7,3 @@ This package holds the core Rust + Axum high-performance web server tightly coup
|
|
|
7
7
|
|
|
8
8
|
## How it works
|
|
9
9
|
You don't need to manually interact with this module. It acts as an optional dependency resolved by `#titanpl/cli`. If you run TitanPlanet on a Linux x64 machine, npm/yarn automatically downloads this native binary to run your application.
|
|
10
|
-
|
|
11
|
-
**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.
|
|
@@ -8,4 +8,3 @@ This module provides the highly concurrent Rust-based server binary (`titan.exe`
|
|
|
8
8
|
## How it works
|
|
9
9
|
Like the other engine packages, this works entirely behind the scenes. When `@titanpl/cli` is installed on a Windows instance, it fetches this `.exe` runtime. The CLI then maps your built assets and JS routes into the binary for deployment.
|
|
10
10
|
|
|
11
|
-
**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.
|
|
Binary file
|
|
@@ -8,4 +8,3 @@ It acts as the low-level communication bridge offering type definitions and util
|
|
|
8
8
|
## How it works
|
|
9
9
|
You can import tools and primitives from this package into your server-side actions alongside `@titanpl/core` when you want direct low-level interaction or need access to platform operations that interact directly with the C/Rust engine.
|
|
10
10
|
|
|
11
|
-
**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.
|