@titanpl/cli 2.0.0 → 2.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,182 +1,196 @@
1
- import fs from 'fs';
2
- import path from 'path';
3
- import { fileURLToPath } from 'url';
4
- import { spawn } from 'child_process';
5
- import prompts from 'prompts';
6
- import chalk from 'chalk';
7
-
8
- const __filename = fileURLToPath(import.meta.url);
9
- const __dirname = path.dirname(__filename);
10
-
11
- export function copyDir(src, dest, excludes = []) {
12
- fs.mkdirSync(dest, { recursive: true });
13
-
14
- for (const file of fs.readdirSync(src)) {
15
- if (excludes.includes(file)) continue;
16
-
17
- const srcPath = path.join(src, file);
18
- const destPath = path.join(dest, file);
19
-
20
- if (fs.lstatSync(srcPath).isDirectory()) {
21
- copyDir(srcPath, destPath, excludes);
22
- } else {
23
- fs.copyFileSync(srcPath, destPath);
24
- }
25
- }
26
- }
27
-
28
- export async function initCommand(projectName, templateName) {
29
- let projName = projectName;
30
-
31
- if (!projName) {
32
- const res = await prompts({
33
- type: 'text',
34
- name: 'name',
35
- message: 'Project name:',
36
- initial: 'my-titan-app'
37
- });
38
- projName = res.name;
39
- }
40
-
41
- if (!projName) {
42
- console.log(chalk.red("✖ Initialization cancelled."));
43
- process.exit(1);
44
- }
45
-
46
- let selectedTemplate = templateName;
47
-
48
- if (!selectedTemplate) {
49
- const langRes = await prompts({
50
- type: 'select',
51
- name: 'value',
52
- message: 'Select language:',
53
- choices: [
54
- { title: 'JavaScript', value: 'js' },
55
- { title: 'TypeScript', value: 'ts' },
56
- ],
57
- initial: 0
58
- });
59
-
60
- if (!langRes.value) {
61
- console.log(chalk.red("✖ Operation cancelled."));
62
- process.exit(1);
63
- }
64
- const lang = langRes.value;
65
-
66
- const archRes = await prompts({
67
- type: 'select',
68
- name: 'value',
69
- message: 'Select template:',
70
- choices: [
71
- {
72
- title: `Standard (${lang.toUpperCase()})`,
73
- description: `Standard Titan app with ${lang.toUpperCase()} actions`,
74
- value: 'standard'
75
- },
76
- {
77
- title: `Rust + ${lang.toUpperCase()} (Hybrid)`,
78
- description: `High-performance Rust actions + ${lang.toUpperCase()} flexibility`,
79
- value: 'hybrid'
80
- }
81
- ],
82
- initial: 0
83
- });
84
-
85
- if (!archRes.value) {
86
- console.log(chalk.red("✖ Operation cancelled."));
87
- process.exit(1);
88
- }
89
- const arch = archRes.value;
90
-
91
- if (lang === 'js') {
92
- selectedTemplate = arch === 'standard' ? 'js' : 'rust-js';
93
- } else {
94
- selectedTemplate = arch === 'standard' ? 'ts' : 'rust-ts';
95
- }
96
- }
97
-
98
- const target = path.resolve(process.cwd(), projName);
99
- const templateDir = path.resolve(__dirname, '..', '..', '..', '..', 'templates', selectedTemplate);
100
- const commonDir = path.resolve(__dirname, '..', '..', '..', '..', 'templates', 'common');
101
-
102
- if (!fs.existsSync(templateDir) || !fs.existsSync(commonDir)) {
103
- console.log(chalk.red(`✖ Error finding template paths.Are you in a valid Titan monorepo ? `));
104
- process.exit(1);
105
- }
106
-
107
- if (fs.existsSync(target)) {
108
- console.log(chalk.red(`✖ Directory '${projName}' already exists.`));
109
- process.exit(1);
110
- }
111
-
112
- console.log(chalk.cyan(`\n→ Creating new Titan project in '${projName}'...\n`));
113
-
114
- // 1. Copy common
115
- copyDir(commonDir, target);
116
-
117
- // 2. Copy specific template
118
- copyDir(templateDir, target);
119
-
120
- // 3. Dotfiles and Template Remapping
121
- const remapping = {
122
- "_gitignore": ".gitignore",
123
- "_dockerignore": ".dockerignore",
124
- "_titan.json": "titan.json",
125
- ".env": ".env"
126
- };
127
- for (const [srcName, destName] of Object.entries(remapping)) {
128
- const src = path.join(target, srcName);
129
- const dest = path.join(target, destName);
130
- if (fs.existsSync(src)) {
131
- fs.renameSync(src, dest);
132
- }
133
- }
134
-
135
- // Recursive template substitution
136
- const substitute = (dir) => {
137
- for (const file of fs.readdirSync(dir)) {
138
- const fullPath = path.join(dir, file);
139
- if (fs.lstatSync(fullPath).isDirectory()) {
140
- if (file !== "node_modules" && file !== ".git" && file !== "target") {
141
- substitute(fullPath);
142
- }
143
- } else {
144
- // Only process text files
145
- const ext = path.extname(file).toLowerCase();
146
- const textExts = ['.js', '.ts', '.json', '.md', '.txt', '.rs', '.toml', '.html', '.css', '.d.ts'];
147
- if (textExts.includes(ext) || file === ".env" || file === "Dockerfile") {
148
- let content = fs.readFileSync(fullPath, 'utf8');
149
- let changed = false;
150
- if (content.includes("{{name}}")) {
151
- content = content.replace(/{{name}}/g, projName);
152
- changed = true;
153
- }
154
- if (content.includes("{{native_name}}")) {
155
- content = content.replace(/{{native_name}}/g, projName.replace(/-/g, '_'));
156
- changed = true;
157
- }
158
- if (changed) {
159
- fs.writeFileSync(fullPath, content);
160
- }
161
- }
162
- }
163
- }
164
- };
165
- substitute(target);
166
-
167
- console.log(chalk.gray(` Installing dependencies...`));
168
-
169
- await new Promise((resolve, reject) => {
170
- const npm = spawn('npm', ['install'], {
171
- cwd: target,
172
- stdio: 'inherit',
173
- shell: true
174
- });
175
- npm.on('error', reject);
176
- npm.on('close', resolve);
177
- });
178
-
179
- console.log(chalk.green(`\n✔ Project '${projName}' created successfully!\n`));
180
- console.log(chalk.yellow(` cd ${projName}`));
181
- console.log(chalk.yellow(` npm run dev\n`));
182
- }
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import { fileURLToPath } from 'url';
4
+ import { execSync } from 'child_process';
5
+ import prompts from 'prompts';
6
+ import chalk from 'chalk';
7
+
8
+ const __filename = fileURLToPath(import.meta.url);
9
+ const __dirname = path.dirname(__filename);
10
+
11
+ export function copyDir(src, dest, excludes = []) {
12
+ fs.mkdirSync(dest, { recursive: true });
13
+
14
+ for (const file of fs.readdirSync(src)) {
15
+ if (excludes.includes(file)) continue;
16
+
17
+ const srcPath = path.join(src, file);
18
+ const destPath = path.join(dest, file);
19
+
20
+ if (fs.lstatSync(srcPath).isDirectory()) {
21
+ copyDir(srcPath, destPath, excludes);
22
+ } else {
23
+ fs.copyFileSync(srcPath, destPath);
24
+ }
25
+ }
26
+ }
27
+
28
+ export async function initCommand(projectName, templateName) {
29
+ let projName = projectName;
30
+
31
+ if (!projName) {
32
+ const res = await prompts({
33
+ type: 'text',
34
+ name: 'name',
35
+ message: 'Project name:',
36
+ initial: 'my-titan-app'
37
+ });
38
+ projName = res.name;
39
+ }
40
+
41
+ if (!projName) {
42
+ console.log(chalk.red("✖ Initialization cancelled."));
43
+ process.exit(1);
44
+ }
45
+
46
+ let selectedTemplate = templateName;
47
+
48
+ if (!selectedTemplate) {
49
+ const langRes = await prompts({
50
+ type: 'select',
51
+ name: 'value',
52
+ message: 'Select language:',
53
+ choices: [
54
+ { title: 'JavaScript', value: 'js' },
55
+ { title: 'TypeScript', value: 'ts' },
56
+ ],
57
+ initial: 0
58
+ });
59
+
60
+ if (!langRes.value) {
61
+ console.log(chalk.red("✖ Operation cancelled."));
62
+ process.exit(1);
63
+ }
64
+ const lang = langRes.value;
65
+
66
+ const archRes = await prompts({
67
+ type: 'select',
68
+ name: 'value',
69
+ message: 'Select template:',
70
+ choices: [
71
+ {
72
+ title: `Standard (${lang.toUpperCase()})`,
73
+ description: `Standard Titan app with ${lang.toUpperCase()} actions`,
74
+ value: 'standard'
75
+ },
76
+ {
77
+ title: `Rust + ${lang.toUpperCase()} (Hybrid)`,
78
+ description: `High-performance Rust actions + ${lang.toUpperCase()} flexibility`,
79
+ value: 'hybrid'
80
+ }
81
+ ],
82
+ initial: 0
83
+ });
84
+
85
+ if (!archRes.value) {
86
+ console.log(chalk.red("✖ Operation cancelled."));
87
+ process.exit(1);
88
+ }
89
+ const arch = archRes.value;
90
+
91
+ if (lang === 'js') {
92
+ selectedTemplate = arch === 'standard' ? 'js' : 'rust-js';
93
+ } else {
94
+ selectedTemplate = arch === 'standard' ? 'ts' : 'rust-ts';
95
+ }
96
+ }
97
+
98
+ const target = path.resolve(process.cwd(), projName);
99
+ let templateDir = path.resolve(__dirname, '..', '..', '..', '..', 'templates', selectedTemplate);
100
+ let commonDir = path.resolve(__dirname, '..', '..', '..', '..', 'templates', 'common');
101
+
102
+ // Robust monorepo/fallback template search: look upwards from cwd
103
+ if (!fs.existsSync(templateDir) || !fs.existsSync(commonDir)) {
104
+ let searchDir = process.cwd();
105
+ while (searchDir !== path.parse(searchDir).root) {
106
+ const potentialTemplateDir = path.join(searchDir, 'templates', selectedTemplate);
107
+ const potentialCommonDir = path.join(searchDir, 'templates', 'common');
108
+ if (fs.existsSync(potentialTemplateDir) && fs.existsSync(potentialCommonDir)) {
109
+ templateDir = potentialTemplateDir;
110
+ commonDir = potentialCommonDir;
111
+ break;
112
+ }
113
+ const sdkPotentialTemplateDir = path.join(searchDir, 'titanpl-sdk', 'templates', selectedTemplate);
114
+ const sdkPotentialCommonDir = path.join(searchDir, 'titanpl-sdk', 'templates', 'common');
115
+ if (fs.existsSync(sdkPotentialTemplateDir) && fs.existsSync(sdkPotentialCommonDir)) {
116
+ templateDir = sdkPotentialTemplateDir;
117
+ commonDir = sdkPotentialCommonDir;
118
+ break;
119
+ }
120
+ searchDir = path.dirname(searchDir);
121
+ }
122
+ }
123
+
124
+ if (!fs.existsSync(templateDir) || !fs.existsSync(commonDir)) {
125
+ console.log(chalk.red(`✖ Error finding template paths. Are you in a valid Titan monorepo?`));
126
+ process.exit(1);
127
+ }
128
+
129
+ if (fs.existsSync(target)) {
130
+ console.log(chalk.red(`✖ Directory '${projName}' already exists.`));
131
+ process.exit(1);
132
+ }
133
+
134
+ console.log(chalk.cyan(`\n→ Creating new Titan project in '${projName}'...\n`));
135
+
136
+ // 1. Copy common
137
+ copyDir(commonDir, target);
138
+
139
+ // 2. Copy specific template
140
+ copyDir(templateDir, target);
141
+
142
+ // 3. Dotfiles and Template Remapping
143
+ const remapping = {
144
+ "_gitignore": ".gitignore",
145
+ "_dockerignore": ".dockerignore",
146
+ "_titan.json": "titan.json",
147
+ ".env": ".env"
148
+ };
149
+ for (const [srcName, destName] of Object.entries(remapping)) {
150
+ const src = path.join(target, srcName);
151
+ const dest = path.join(target, destName);
152
+ if (fs.existsSync(src)) {
153
+ fs.renameSync(src, dest);
154
+ }
155
+ }
156
+
157
+ // Recursive template substitution
158
+ const substitute = (dir) => {
159
+ for (const file of fs.readdirSync(dir)) {
160
+ const fullPath = path.join(dir, file);
161
+ if (fs.lstatSync(fullPath).isDirectory()) {
162
+ if (file !== "node_modules" && file !== ".git" && file !== "target") {
163
+ substitute(fullPath);
164
+ }
165
+ } else {
166
+ // Only process text files
167
+ const ext = path.extname(file).toLowerCase();
168
+ const textExts = ['.js', '.ts', '.json', '.md', '.txt', '.rs', '.toml', '.html', '.css', '.d.ts'];
169
+ if (textExts.includes(ext) || file === ".env" || file === "Dockerfile") {
170
+ let content = fs.readFileSync(fullPath, 'utf8');
171
+ let changed = false;
172
+ if (content.includes("{{name}}")) {
173
+ content = content.replace(/{{name}}/g, projName);
174
+ changed = true;
175
+ }
176
+ if (content.includes("{{native_name}}")) {
177
+ content = content.replace(/{{native_name}}/g, projName.replace(/-/g, '_'));
178
+ changed = true;
179
+ }
180
+ if (changed) {
181
+ fs.writeFileSync(fullPath, content);
182
+ }
183
+ }
184
+ }
185
+ }
186
+ };
187
+ substitute(target);
188
+
189
+ console.log(chalk.gray(` Installing dependencies...`));
190
+
191
+ execSync('npm install', { cwd: target, stdio: 'inherit' });
192
+
193
+ console.log(chalk.green(`\n✔ Project '${projName}' created successfully!\n`));
194
+ console.log(chalk.yellow(` cd ${projName}`));
195
+ console.log(chalk.yellow(` npm run dev\n`));
196
+ }
@@ -1,106 +1,124 @@
1
- import fs from 'fs';
2
- import path from 'path';
3
-
4
- export async function migrateCommand() {
5
- const root = process.cwd();
6
- console.log(`\n🔍 Checking project for legacy Titan architecture...`);
7
-
8
- const serverDir = path.join(root, 'server');
9
- const titanDir = path.join(root, 'titan');
10
- const pkgPath = path.join(root, 'package.json');
11
-
12
- if (!fs.existsSync(serverDir) && !fs.existsSync(titanDir)) {
13
- console.log(`✅ This project is already using the modern Titan runtime architecture.`);
14
- return;
15
- }
16
-
17
- console.log(`\n⚠️ Legacy server architecture detected. Migrating to runtime-first model...`);
18
-
19
- // 1. Delete server/
20
- if (fs.existsSync(serverDir)) {
21
- console.log(` Deleting legacy server/ folder...`);
22
- fs.rmSync(serverDir, { recursive: true, force: true });
23
- }
24
-
25
- // 2. Delete titan/ folder
26
- if (fs.existsSync(titanDir)) {
27
- console.log(` Deleting legacy titan/ runtime folder...`);
28
- fs.rmSync(titanDir, { recursive: true, force: true });
29
- }
30
-
31
- // 3. Update package.json
32
- if (fs.existsSync(pkgPath)) {
33
- console.log(` Updating package.json...`);
34
- try {
35
- const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
36
- let modified = false;
37
-
38
- // Update scripts
39
- if (pkg.scripts) {
40
- if (pkg.scripts.build && pkg.scripts.build.includes('cd server')) {
41
- pkg.scripts.build = "titan build";
42
- modified = true;
43
- }
44
- if (pkg.scripts.start && pkg.scripts.start.includes('cd server')) {
45
- pkg.scripts.start = "titan start";
46
- modified = true;
47
- }
48
- if (pkg.scripts.dev && pkg.scripts.dev.includes('titan/dev.js')) {
49
- pkg.scripts.dev = "titan dev";
50
- modified = true;
51
- }
52
- }
53
-
54
- // Add dependencies
55
- pkg.dependencies = pkg.dependencies || {};
56
- if (!pkg.dependencies['@titan/native']) {
57
- pkg.dependencies['@titan/native'] = "latest";
58
- modified = true;
59
- }
60
- if (!pkg.dependencies['@titan/route']) {
61
- pkg.dependencies['@titan/route'] = "latest";
62
- modified = true;
63
- }
64
-
65
- if (modified) {
66
- fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2));
67
- }
68
- } catch (e) {
69
- console.log(` ⚠️ Failed to update package.json automatically. Please do it manually.`);
70
- }
71
- }
72
-
73
- // 4. Synchronize Dockerfile and other common files
74
- try {
75
- const commonDir = path.resolve(__dirname, '..', '..', '..', '..', 'templates', 'common');
76
- if (fs.existsSync(commonDir)) {
77
- const filesToSync = [
78
- ['Dockerfile', 'Dockerfile'],
79
- ['_dockerignore', '.dockerignore'],
80
- ['_gitignore', '.gitignore'],
81
- ['app/t.native.d.ts', 'app/t.native.d.ts'],
82
- ['app/t.native.js', 'app/t.native.js']
83
- ];
84
-
85
- for (const [srcRel, destRel] of filesToSync) {
86
- const src = path.join(commonDir, srcRel);
87
- const dest = path.join(root, destRel);
88
- if (fs.existsSync(src)) {
89
- // Create parent dir if needed
90
- const parent = path.dirname(dest);
91
- if (!fs.existsSync(parent)) {
92
- fs.mkdirSync(parent, { recursive: true });
93
- }
94
- fs.copyFileSync(src, dest);
95
- }
96
- }
97
- console.log(` Synchronized Dockerfiles and native definitions.`);
98
- }
99
- } catch (e) {
100
- console.log(` ⚠️ Failed to synchronize common template files.`);
101
- }
102
-
103
- console.log(`\n🎉 Migration complete!`);
104
- console.log(` Please run 'npm install' to fetch the new dependencies.`);
105
- console.log(` Then run 'titan dev' to start your application.\n`);
106
- }
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import { fileURLToPath } from 'url';
4
+
5
+ const __filename = fileURLToPath(import.meta.url);
6
+ const __dirname = path.dirname(__filename);
7
+
8
+ export async function migrateCommand() {
9
+ const root = process.cwd();
10
+ console.log(`\n🔍 Checking project for legacy Titan architecture...`);
11
+
12
+ const serverDir = path.join(root, 'server');
13
+ const titanDir = path.join(root, 'titan');
14
+ const pkgPath = path.join(root, 'package.json');
15
+
16
+ if (!fs.existsSync(serverDir) && !fs.existsSync(titanDir)) {
17
+ console.log(`✅ This project is already using the modern Titan runtime architecture.`);
18
+ return;
19
+ }
20
+
21
+ console.log(`\n⚠️ Legacy server architecture detected. Migrating to runtime-first model...`);
22
+
23
+ // 1. Delete server/
24
+ if (fs.existsSync(serverDir)) {
25
+ console.log(` Deleting legacy server/ folder...`);
26
+ fs.rmSync(serverDir, { recursive: true, force: true });
27
+ }
28
+
29
+ // 2. Delete titan/ folder
30
+ if (fs.existsSync(titanDir)) {
31
+ console.log(` Deleting legacy titan/ runtime folder...`);
32
+ fs.rmSync(titanDir, { recursive: true, force: true });
33
+ }
34
+
35
+ // 3. Update package.json
36
+ if (fs.existsSync(pkgPath)) {
37
+ console.log(` Updating package.json...`);
38
+ try {
39
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
40
+ let modified = false;
41
+
42
+ // Update scripts
43
+ if (pkg.scripts) {
44
+ if (pkg.scripts.build && pkg.scripts.build.includes('cd server')) {
45
+ pkg.scripts.build = "titan build";
46
+ modified = true;
47
+ }
48
+ if (pkg.scripts.start && pkg.scripts.start.includes('cd server')) {
49
+ pkg.scripts.start = "titan start";
50
+ modified = true;
51
+ }
52
+ if (pkg.scripts.dev && pkg.scripts.dev.includes('titan/dev.js')) {
53
+ pkg.scripts.dev = "titan dev";
54
+ modified = true;
55
+ }
56
+ }
57
+
58
+ // Add / fix dependencies — ensure correct @titanpl/ scope
59
+ pkg.dependencies = pkg.dependencies || {};
60
+
61
+ // Remove any stale old-scope packages (@titan/ typo) that may exist
62
+ const stalePackages = ['@titanp/native', '@titan/route', '@titan/cli', '@titan/packet'];
63
+ for (const stale of stalePackages) {
64
+ if (pkg.dependencies[stale]) {
65
+ delete pkg.dependencies[stale];
66
+ modified = true;
67
+ }
68
+ }
69
+
70
+ const requiredDeps = {
71
+ '@titanpl/cli': 'latest',
72
+ '@titanpl/route': 'latest',
73
+ '@titanpl/native': 'latest',
74
+ '@titanpl/packet': 'latest',
75
+ };
76
+ for (const [dep, version] of Object.entries(requiredDeps)) {
77
+ if (!pkg.dependencies[dep]) {
78
+ pkg.dependencies[dep] = version;
79
+ modified = true;
80
+ }
81
+ }
82
+
83
+ if (modified) {
84
+ fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2));
85
+ }
86
+ } catch (e) {
87
+ console.log(` ⚠️ Failed to update package.json automatically. Please do it manually.`);
88
+ }
89
+ }
90
+
91
+ // 4. Synchronize Dockerfile and other common files
92
+ try {
93
+ const commonDir = path.resolve(__dirname, '..', '..', '..', '..', 'templates', 'common');
94
+ if (fs.existsSync(commonDir)) {
95
+ const filesToSync = [
96
+ ['Dockerfile', 'Dockerfile'],
97
+ ['_dockerignore', '.dockerignore'],
98
+ ['_gitignore', '.gitignore'],
99
+ ['app/t.native.d.ts', 'app/t.native.d.ts'],
100
+ ['app/t.native.js', 'app/t.native.js']
101
+ ];
102
+
103
+ for (const [srcRel, destRel] of filesToSync) {
104
+ const src = path.join(commonDir, srcRel);
105
+ const dest = path.join(root, destRel);
106
+ if (fs.existsSync(src)) {
107
+ // Create parent dir if needed
108
+ const parent = path.dirname(dest);
109
+ if (!fs.existsSync(parent)) {
110
+ fs.mkdirSync(parent, { recursive: true });
111
+ }
112
+ fs.copyFileSync(src, dest);
113
+ }
114
+ }
115
+ console.log(` Synchronized Dockerfiles and native definitions.`);
116
+ }
117
+ } catch (e) {
118
+ console.log(` ⚠️ Failed to synchronize common template files.`);
119
+ }
120
+
121
+ console.log(`\n🎉 Migration complete!`);
122
+ console.log(` Please run 'npm install' to fetch the new dependencies.`);
123
+ console.log(` Then run 'titan dev' to start your application.\n`);
124
+ }
@@ -1,5 +1,5 @@
1
- import { startEngine } from "../engine.js";
2
-
3
- export function startCommand() {
4
- startEngine(false);
1
+ import { startEngine } from "../engine.js";
2
+
3
+ export function startCommand() {
4
+ startEngine(false);
5
5
  }