@ifecodes/backend-template 1.1.0 → 1.1.3
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/bin/cli.js +22 -93
- package/bin/lib/prompts.js +37 -4
- package/bin/lib/service-setup.js +36 -12
- package/bin/lib/ts-to-js.js +242 -23
- package/package.json +1 -1
- package/template/base/src/utils/http-error.ts +0 -2
- package/template/base/src/utils/logger.ts +16 -6
package/bin/cli.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
1
|
+
#!/usr/bin/env node
|
|
2
2
|
import fs from "fs";
|
|
3
3
|
import path from "path";
|
|
4
4
|
import { execSync } from "child_process";
|
|
@@ -7,7 +7,7 @@ import pc from "picocolors";
|
|
|
7
7
|
import { getProjectConfig } from "./lib/prompts.js";
|
|
8
8
|
import { setupService } from "./lib/service-setup.js";
|
|
9
9
|
import { generateReadme } from "./lib/readme-generator.js";
|
|
10
|
-
import { stripTypeScript, getJavaScriptScripts, getJavaScriptDependencies } from "./lib/ts-to-js.js";
|
|
10
|
+
import { stripTypeScript, getJavaScriptScripts, getJavaScriptDependencies, transformToJavaScript, transformDirectory } from "./lib/ts-to-js.js";
|
|
11
11
|
import {
|
|
12
12
|
generateDockerCompose,
|
|
13
13
|
generatePm2Config,
|
|
@@ -67,97 +67,6 @@ if (!isInMicroserviceProject && config.projectType === "microservice") {
|
|
|
67
67
|
console.log(`\n${pc.cyan("🏗️ Adding service:")} ${pc.bold(servicesToCreate[0])}...\n`);
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
-
// Helper function to transform TypeScript project to JavaScript
|
|
71
|
-
function transformToJavaScript(projectRoot) {
|
|
72
|
-
// Recursively find and transform all .ts files
|
|
73
|
-
function transformDirectory(dir) {
|
|
74
|
-
const items = fs.readdirSync(dir);
|
|
75
|
-
|
|
76
|
-
for (const item of items) {
|
|
77
|
-
const fullPath = path.join(dir, item);
|
|
78
|
-
const stat = fs.statSync(fullPath);
|
|
79
|
-
|
|
80
|
-
if (stat.isDirectory()) {
|
|
81
|
-
// Skip node_modules and dist
|
|
82
|
-
if (item !== 'node_modules' && item !== 'dist') {
|
|
83
|
-
transformDirectory(fullPath);
|
|
84
|
-
}
|
|
85
|
-
} else if (item.endsWith('.ts') && !item.endsWith('.d.ts')) {
|
|
86
|
-
// Transform TypeScript file to JavaScript
|
|
87
|
-
const content = fs.readFileSync(fullPath, 'utf8');
|
|
88
|
-
const jsContent = stripTypeScript(content);
|
|
89
|
-
const jsPath = fullPath.replace(/\.ts$/, '.js');
|
|
90
|
-
|
|
91
|
-
fs.writeFileSync(jsPath, jsContent);
|
|
92
|
-
fs.unlinkSync(fullPath); // Remove original .ts file
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
transformDirectory(path.join(projectRoot, 'src'));
|
|
98
|
-
|
|
99
|
-
// Remove TypeScript config file
|
|
100
|
-
const tsconfigPath = path.join(projectRoot, 'tsconfig.json');
|
|
101
|
-
if (fs.existsSync(tsconfigPath)) {
|
|
102
|
-
fs.unlinkSync(tsconfigPath);
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
// Remove .eslintrc.json and replace with JS version
|
|
106
|
-
const eslintrcPath = path.join(projectRoot, '.eslintrc.json');
|
|
107
|
-
if (fs.existsSync(eslintrcPath)) {
|
|
108
|
-
fs.unlinkSync(eslintrcPath);
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
// Update package.json
|
|
112
|
-
const packageJsonPath = path.join(projectRoot, 'package.json');
|
|
113
|
-
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
|
114
|
-
|
|
115
|
-
// Update scripts
|
|
116
|
-
packageJson.scripts = {
|
|
117
|
-
...packageJson.scripts,
|
|
118
|
-
...getJavaScriptScripts()
|
|
119
|
-
};
|
|
120
|
-
|
|
121
|
-
// Update type
|
|
122
|
-
packageJson.type = "module";
|
|
123
|
-
|
|
124
|
-
// Remove main field
|
|
125
|
-
delete packageJson.main;
|
|
126
|
-
|
|
127
|
-
// Update dependencies
|
|
128
|
-
const { dependencies, devDependencies } = getJavaScriptDependencies(
|
|
129
|
-
packageJson.dependencies,
|
|
130
|
-
packageJson.devDependencies
|
|
131
|
-
);
|
|
132
|
-
|
|
133
|
-
packageJson.dependencies = dependencies;
|
|
134
|
-
packageJson.devDependencies = devDependencies;
|
|
135
|
-
|
|
136
|
-
// Remove _moduleAliases (not needed for ES modules with import maps)
|
|
137
|
-
delete packageJson._moduleAliases;
|
|
138
|
-
|
|
139
|
-
fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2) + '\n');
|
|
140
|
-
|
|
141
|
-
// Create simple .eslintrc.json for JavaScript
|
|
142
|
-
const eslintConfig = {
|
|
143
|
-
env: {
|
|
144
|
-
node: true,
|
|
145
|
-
es2021: true
|
|
146
|
-
},
|
|
147
|
-
extends: ["eslint:recommended", "prettier"],
|
|
148
|
-
parserOptions: {
|
|
149
|
-
ecmaVersion: "latest",
|
|
150
|
-
sourceType: "module"
|
|
151
|
-
},
|
|
152
|
-
rules: {}
|
|
153
|
-
};
|
|
154
|
-
|
|
155
|
-
fs.writeFileSync(
|
|
156
|
-
path.join(projectRoot, '.eslintrc.json'),
|
|
157
|
-
JSON.stringify(eslintConfig, null, 2) + '\n'
|
|
158
|
-
);
|
|
159
|
-
}
|
|
160
|
-
|
|
161
70
|
// Process services
|
|
162
71
|
if (isInMicroserviceProject || config.projectType === "microservice") {
|
|
163
72
|
// Create shared folder for config and utils (only once)
|
|
@@ -247,6 +156,26 @@ if (isInMicroserviceProject || config.projectType === "microservice") {
|
|
|
247
156
|
// Store for later use
|
|
248
157
|
config.allInstallsSucceeded = allInstallsSucceeded;
|
|
249
158
|
|
|
159
|
+
// Transform to JavaScript if selected (for microservices)
|
|
160
|
+
if (config.language === "javascript") {
|
|
161
|
+
console.log(`\n${pc.cyan("⚙️ Converting microservices to JavaScript...")}\n`);
|
|
162
|
+
|
|
163
|
+
// Transform shared folder
|
|
164
|
+
const sharedDir = path.join(target, "shared");
|
|
165
|
+
if (fs.existsSync(sharedDir)) {
|
|
166
|
+
transformDirectory(sharedDir);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Transform each service
|
|
170
|
+
for (const serviceName of allServices) {
|
|
171
|
+
const serviceRoot = path.join(target, "services", serviceName);
|
|
172
|
+
console.log(pc.dim(` Transforming ${serviceName}...`));
|
|
173
|
+
transformToJavaScript(serviceRoot);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
console.log(pc.green("✓ JavaScript transformation complete\n"));
|
|
177
|
+
}
|
|
178
|
+
|
|
250
179
|
if (mode === "docker") {
|
|
251
180
|
generateDockerCompose(target, allServices);
|
|
252
181
|
copyDockerfile(target, servicesToCreate);
|
package/bin/lib/prompts.js
CHANGED
|
@@ -59,19 +59,19 @@ export const getProjectConfig = async () => {
|
|
|
59
59
|
initial: 0,
|
|
60
60
|
},
|
|
61
61
|
{
|
|
62
|
-
type: isInMicroserviceProject ||
|
|
62
|
+
type: isInMicroserviceProject || isCI ? null : "text",
|
|
63
63
|
name: "description",
|
|
64
64
|
message: pc.cyan("Project description") + pc.dim(" (optional)"),
|
|
65
65
|
initial: "",
|
|
66
66
|
},
|
|
67
67
|
{
|
|
68
|
-
type: isInMicroserviceProject ||
|
|
68
|
+
type: isInMicroserviceProject || isCI ? null : "text",
|
|
69
69
|
name: "author",
|
|
70
70
|
message: pc.cyan("Author") + pc.dim(" (optional)"),
|
|
71
71
|
initial: "",
|
|
72
72
|
},
|
|
73
73
|
{
|
|
74
|
-
type: isInMicroserviceProject ||
|
|
74
|
+
type: isInMicroserviceProject || isCI ? null : "text",
|
|
75
75
|
name: "keywords",
|
|
76
76
|
message: pc.cyan("Keywords") + pc.dim(" (comma-separated, optional)"),
|
|
77
77
|
initial: "",
|
|
@@ -135,7 +135,35 @@ export const getProjectConfig = async () => {
|
|
|
135
135
|
{ title: pc.blue("CORS"), value: "cors" },
|
|
136
136
|
],
|
|
137
137
|
},
|
|
138
|
-
]
|
|
138
|
+
],
|
|
139
|
+
{
|
|
140
|
+
onCancel: () => {
|
|
141
|
+
console.log(pc.yellow("\n❌ Operation cancelled by user."));
|
|
142
|
+
process.exit(0);
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
// Handle cancelled prompts (user pressed Ctrl+C or closed the prompt)
|
|
147
|
+
// Check if user cancelled the prompt (Ctrl+C) vs just didn't enter anything
|
|
148
|
+
if (!res) {
|
|
149
|
+
console.log(pc.yellow("\n❌ Operation cancelled by user."));
|
|
150
|
+
process.exit(0);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Check if critical fields are missing (indicates cancellation mid-prompt)
|
|
154
|
+
if (!isInMicroserviceProject && !isCI) {
|
|
155
|
+
// For new projects, we need language and projectType
|
|
156
|
+
if (!res.language || (!res.projectType && !cliProjectType)) {
|
|
157
|
+
console.log(pc.yellow("\n❌ Operation cancelled by user."));
|
|
158
|
+
process.exit(0);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// If the response is empty but we expected prompts, something went wrong
|
|
163
|
+
if (Object.keys(res).length === 0 && !isInMicroserviceProject && !hasCliArgs && !isCI) {
|
|
164
|
+
console.log(pc.red("\n❌ No inputs received. Please try again."));
|
|
165
|
+
process.exit(1);
|
|
166
|
+
}
|
|
139
167
|
|
|
140
168
|
// Set defaults for CI/non-interactive mode
|
|
141
169
|
if (isCI) {
|
|
@@ -155,6 +183,11 @@ export const getProjectConfig = async () => {
|
|
|
155
183
|
res.projectType = res.projectType || "monolith";
|
|
156
184
|
}
|
|
157
185
|
}
|
|
186
|
+
|
|
187
|
+
// Ensure we have a project name (fallback if somehow missed)
|
|
188
|
+
if (!res.name && !isInMicroserviceProject) {
|
|
189
|
+
res.name = "my-backend";
|
|
190
|
+
}
|
|
158
191
|
|
|
159
192
|
let sanitizedName, target, isExistingProject, mode;
|
|
160
193
|
|
package/bin/lib/service-setup.js
CHANGED
|
@@ -5,6 +5,19 @@ import pc from "picocolors";
|
|
|
5
5
|
import { execSync } from "child_process";
|
|
6
6
|
import { fileURLToPath } from "url";
|
|
7
7
|
|
|
8
|
+
// Helper function to get the correct file extension (.ts or .js)
|
|
9
|
+
function getFileExtension(dir) {
|
|
10
|
+
// Check if .ts files exist, otherwise use .js
|
|
11
|
+
const sampleFiles = ['src/app.ts', 'src/server.ts', 'src/routes.ts'];
|
|
12
|
+
for (const file of sampleFiles) {
|
|
13
|
+
const tsPath = path.join(dir, file);
|
|
14
|
+
if (fs.existsSync(tsPath)) return 'ts';
|
|
15
|
+
const jsPath = path.join(dir, file.replace('.ts', '.js'));
|
|
16
|
+
if (fs.existsSync(jsPath)) return 'js';
|
|
17
|
+
}
|
|
18
|
+
return 'ts'; // default to ts
|
|
19
|
+
}
|
|
20
|
+
|
|
8
21
|
export const setupService = async (
|
|
9
22
|
res,
|
|
10
23
|
serviceName,
|
|
@@ -18,6 +31,9 @@ export const setupService = async (
|
|
|
18
31
|
let devDeps = [];
|
|
19
32
|
let v1Imports = [];
|
|
20
33
|
let v1Routes = [];
|
|
34
|
+
|
|
35
|
+
// Detect file extension (ts or js)
|
|
36
|
+
const ext = getFileExtension(serviceRoot);
|
|
21
37
|
|
|
22
38
|
// Special handling for gateway service
|
|
23
39
|
if (serviceName === "gateway") {
|
|
@@ -25,8 +41,8 @@ export const setupService = async (
|
|
|
25
41
|
deps.push(...gatewayModule.gatewayDeps);
|
|
26
42
|
|
|
27
43
|
// Copy gateway-specific files
|
|
28
|
-
const gatewayAppPath = path.join(serviceRoot,
|
|
29
|
-
const gatewayServerPath = path.join(serviceRoot,
|
|
44
|
+
const gatewayAppPath = path.join(serviceRoot, `src/app.${ext}`);
|
|
45
|
+
const gatewayServerPath = path.join(serviceRoot, `src/server.${ext}`);
|
|
30
46
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
31
47
|
|
|
32
48
|
const gatewayAppContent = fs.readFileSync(
|
|
@@ -46,7 +62,7 @@ export const setupService = async (
|
|
|
46
62
|
fs.writeFileSync(gatewayServerPath, gatewayServerContent);
|
|
47
63
|
|
|
48
64
|
// Remove unnecessary files for gateway
|
|
49
|
-
const routesPath = path.join(serviceRoot,
|
|
65
|
+
const routesPath = path.join(serviceRoot, `src/routes.${ext}`);
|
|
50
66
|
const modulesPath = path.join(serviceRoot, "src/modules");
|
|
51
67
|
const middlewaresPath = path.join(serviceRoot, "src/middlewares");
|
|
52
68
|
|
|
@@ -125,15 +141,15 @@ export const setupService = async (
|
|
|
125
141
|
v1Routes.push(baseAuth.middleware);
|
|
126
142
|
}
|
|
127
143
|
|
|
128
|
-
// Update app
|
|
129
|
-
const appPath = path.join(serviceRoot,
|
|
144
|
+
// Update app file
|
|
145
|
+
const appPath = path.join(serviceRoot, `src/app.${ext}`);
|
|
130
146
|
let content = fs.readFileSync(appPath, "utf8");
|
|
131
147
|
content = content.replace("/*__IMPORTS__*/", imports.join("\n"));
|
|
132
148
|
content = content.replace("/*__MIDDLEWARE__*/", middlewares.join("\n"));
|
|
133
149
|
fs.writeFileSync(appPath, content);
|
|
134
150
|
|
|
135
151
|
// Update root endpoint middleware with project info
|
|
136
|
-
const rootMiddlewarePath = path.join(serviceRoot,
|
|
152
|
+
const rootMiddlewarePath = path.join(serviceRoot, `src/middlewares/root.middleware.${ext}`);
|
|
137
153
|
if (fs.existsSync(rootMiddlewarePath)) {
|
|
138
154
|
let rootContent = fs.readFileSync(rootMiddlewarePath, "utf8");
|
|
139
155
|
rootContent = rootContent.replace("/*__PROJECT_NAME__*/", serviceName || res.sanitizedName);
|
|
@@ -152,9 +168,9 @@ export const setupService = async (
|
|
|
152
168
|
fs.writeFileSync(rootMiddlewarePath, rootContent);
|
|
153
169
|
}
|
|
154
170
|
|
|
155
|
-
// Update v1 index
|
|
171
|
+
// Update v1 index file if needed
|
|
156
172
|
if (v1Imports.length || v1Routes.length) {
|
|
157
|
-
const v1IndexPath = path.join(serviceRoot,
|
|
173
|
+
const v1IndexPath = path.join(serviceRoot, `src/modules/v1/index.${ext}`);
|
|
158
174
|
let v1Content = fs.readFileSync(v1IndexPath, "utf8");
|
|
159
175
|
|
|
160
176
|
const lastImportIndex = v1Content.lastIndexOf("import");
|
|
@@ -175,8 +191,8 @@ export const setupService = async (
|
|
|
175
191
|
fs.writeFileSync(v1IndexPath, v1Content);
|
|
176
192
|
}
|
|
177
193
|
|
|
178
|
-
// Update env
|
|
179
|
-
const envPath = path.join(serviceRoot,
|
|
194
|
+
// Update env file to conditionally include ALLOWED_ORIGIN and MONGO_URI
|
|
195
|
+
const envPath = path.join(serviceRoot, `src/config/env.${ext}`);
|
|
180
196
|
if (fs.existsSync(envPath)) {
|
|
181
197
|
let envContent = fs.readFileSync(envPath, "utf8");
|
|
182
198
|
|
|
@@ -220,8 +236,8 @@ export const setupService = async (
|
|
|
220
236
|
fs.writeFileSync(envPath, envContent);
|
|
221
237
|
}
|
|
222
238
|
|
|
223
|
-
// Update server
|
|
224
|
-
const serverPath = path.join(serviceRoot,
|
|
239
|
+
// Update server file to connect to DB if auth is enabled
|
|
240
|
+
const serverPath = path.join(serviceRoot, `src/server.${ext}`);
|
|
225
241
|
if (fs.existsSync(serverPath)) {
|
|
226
242
|
let serverContent = fs.readFileSync(serverPath, "utf8");
|
|
227
243
|
|
|
@@ -334,6 +350,14 @@ await connectDB();`
|
|
|
334
350
|
finalPackageJson.keywords = res.keywords.split(',').map(k => k.trim()).filter(Boolean);
|
|
335
351
|
}
|
|
336
352
|
|
|
353
|
+
// Add Node.js native path aliasing for JavaScript projects
|
|
354
|
+
// This replaces TypeScript's tsconfig paths with Node's native imports field
|
|
355
|
+
if (res.language === 'javascript') {
|
|
356
|
+
finalPackageJson.imports = {
|
|
357
|
+
"#/*": "./src/*"
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
|
|
337
361
|
fs.writeFileSync(
|
|
338
362
|
packageJsonPath,
|
|
339
363
|
JSON.stringify(finalPackageJson, null, 2) + "\n"
|
package/bin/lib/ts-to-js.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
|
-
* Strip TypeScript type annotations from code
|
|
7
|
+
* Strip TypeScript type annotations from code and fix imports for JavaScript
|
|
8
8
|
* @param {string} content - TypeScript file content
|
|
9
9
|
* @returns {string} - JavaScript content
|
|
10
10
|
*/
|
|
@@ -20,42 +20,114 @@ export function stripTypeScript(content) {
|
|
|
20
20
|
return match.replace(/,?\s*type\s+\w+/g, '').replace(/\{\s*,/, '{').replace(/,\s*\}/, '}');
|
|
21
21
|
});
|
|
22
22
|
|
|
23
|
+
// Remove imports from express types (Request, Response, NextFunction, etc.)
|
|
24
|
+
jsContent = jsContent.replace(/import\s*\{\s*Request\s*,\s*Response\s*,\s*NextFunction\s*\}\s*from\s*["']express["'];?\s*\n/gm, '');
|
|
25
|
+
jsContent = jsContent.replace(/import\s*\{\s*Request\s*,\s*Response\s*\}\s*from\s*["']express["'];?\s*\n/gm, '');
|
|
26
|
+
jsContent = jsContent.replace(/import\s*\{\s*Response\s*,\s*Request\s*\}\s*from\s*["']express["'];?\s*\n/gm, '');
|
|
27
|
+
jsContent = jsContent.replace(/import\s*\{\s*Request\s*\}\s*from\s*["']express["'];?\s*\n/gm, '');
|
|
28
|
+
jsContent = jsContent.replace(/import\s*\{\s*Response\s*\}\s*from\s*["']express["'];?\s*\n/gm, '');
|
|
29
|
+
jsContent = jsContent.replace(/import\s*\{\s*NextFunction\s*\}\s*from\s*["']express["'];?\s*\n/gm, '');
|
|
30
|
+
|
|
23
31
|
// Remove interface declarations
|
|
24
|
-
|
|
25
|
-
|
|
32
|
+
// CRITICAL: Must have 'interface' keyword explicitly (not 'const' or other keywords)
|
|
33
|
+
// Single-line interfaces
|
|
34
|
+
jsContent = jsContent.replace(/^export\s+interface\s+\w+(\s+extends\s+[\w,\s]+)?\s*\{[^}]*\}\s*;?\s*$/gm, '');
|
|
35
|
+
jsContent = jsContent.replace(/^interface\s+\w+(\s+extends\s+[\w,\s]+)?\s*\{[^}]*\}\s*;?\s*$/gm, '');
|
|
36
|
+
|
|
37
|
+
// Multi-line interfaces - must have a line break after the interface name/extends before {
|
|
38
|
+
// This prevents matching regular object literals
|
|
39
|
+
jsContent = jsContent.replace(/^export\s+interface\s+\w+(\s+extends\s+[\w,\s<>]+)?\s*\{[\s\S]*?^}/gm, '');
|
|
40
|
+
jsContent = jsContent.replace(/^interface\s+\w+(\s+extends\s+[\w,\s<>]+)?\s*\{[\s\S]*?^}/gm, '');
|
|
26
41
|
|
|
27
42
|
// Remove type aliases
|
|
28
43
|
jsContent = jsContent.replace(/^export\s+type\s+\w+\s*=\s*[^;]+;\s*$/gm, '');
|
|
29
44
|
jsContent = jsContent.replace(/^type\s+\w+\s*=\s*[^;]+;\s*$/gm, '');
|
|
45
|
+
|
|
46
|
+
// Remove class property type annotations: public/private/protected status: number;
|
|
47
|
+
// CRITICAL: Only match single-line class properties (use [^\n;=]+ instead of [^;=]+)
|
|
48
|
+
jsContent = jsContent.replace(/^\s*(public|private|protected)?\s+(\w+)\s*:\s*[^\n;=]+;/gm, '');
|
|
49
|
+
|
|
50
|
+
// Remove visibility modifiers and types from constructor parameters
|
|
51
|
+
jsContent = jsContent.replace(/constructor\s*\(\s*([^)]*)\)\s*\{/g, (match, params) => {
|
|
52
|
+
if (!params.trim()) return 'constructor() {';
|
|
53
|
+
const cleanParams = params
|
|
54
|
+
.split(',')
|
|
55
|
+
.map(p => {
|
|
56
|
+
// Remove visibility modifiers
|
|
57
|
+
let cleaned = p.replace(/^\s*(public|private|protected)\s+/, '');
|
|
58
|
+
// Remove type annotation (everything after :)
|
|
59
|
+
cleaned = cleaned.replace(/:\s*[^=,]+/, '');
|
|
60
|
+
return cleaned.trim();
|
|
61
|
+
})
|
|
62
|
+
.filter(p => p)
|
|
63
|
+
.join(', ');
|
|
64
|
+
return `constructor(${cleanParams}) {`;
|
|
65
|
+
});
|
|
30
66
|
|
|
31
|
-
// Remove
|
|
32
|
-
|
|
33
|
-
|
|
67
|
+
// Remove ! non-null assertions FIRST (before other transformations)
|
|
68
|
+
// Only remove ! that appears after identifiers, closing brackets, or closing parentheses
|
|
69
|
+
// Be very conservative to avoid breaking other syntax
|
|
70
|
+
jsContent = jsContent.replace(/([a-zA-Z0-9_\])])!/g, '$1');
|
|
34
71
|
|
|
35
|
-
// Remove
|
|
36
|
-
|
|
37
|
-
jsContent = jsContent.replace(
|
|
72
|
+
// Remove function parameter types - IMPROVED VERSION
|
|
73
|
+
// Handle spread parameters: ...args: Type[] => ...args
|
|
74
|
+
jsContent = jsContent.replace(/(\.\.\.\s*\w+)\s*:\s*[^,)]+/g, '$1');
|
|
38
75
|
|
|
39
|
-
//
|
|
40
|
-
|
|
76
|
+
// Handle regular parameters with more precision
|
|
77
|
+
// Strategy: Clean type annotations from parameter lists while avoiding object literals in function CALLS
|
|
41
78
|
|
|
42
|
-
//
|
|
43
|
-
jsContent = jsContent.replace(/function\s
|
|
79
|
+
// 1. Function declarations: function name(params) {
|
|
80
|
+
jsContent = jsContent.replace(/function\s+\w+\s*\(([^)]*)\)/g, (match, params) => {
|
|
81
|
+
if (!params.includes(':')) return match;
|
|
82
|
+
// Skip if it has object literals (look for : followed by value, not type)
|
|
83
|
+
// Object literals have patterns like {key: value} while types have param: Type
|
|
84
|
+
const cleaned = params.split(',').map(p => p.replace(/:\s*[^,=)]+(?=[,)]|$)/, '')).join(',');
|
|
85
|
+
return match.replace(params, cleaned);
|
|
86
|
+
});
|
|
44
87
|
|
|
45
|
-
//
|
|
46
|
-
|
|
88
|
+
// 2. Arrow functions: (params) => or (params) => {
|
|
89
|
+
// Be more specific: only skip if we see actual object literal patterns {key:value}
|
|
90
|
+
jsContent = jsContent.replace(/\(([^)]+)\)\s*=>/g, (match, params) => {
|
|
91
|
+
if (!params.includes(':')) return match;
|
|
92
|
+
|
|
93
|
+
// Check if this looks like an object literal: has both { and } with content between
|
|
94
|
+
if (/\{[^}]+\}/.test(params)) return match;
|
|
95
|
+
|
|
96
|
+
const cleaned = params.split(',').map(p => p.replace(/:\s*[^,=)]+(?=[,)]|$)/, '')).join(',');
|
|
97
|
+
return `(${cleaned}) =>`;
|
|
98
|
+
});
|
|
47
99
|
|
|
48
|
-
//
|
|
49
|
-
jsContent = jsContent.replace(
|
|
100
|
+
// 3. Method definitions in classes/objects: methodName(params) {
|
|
101
|
+
jsContent = jsContent.replace(/(\w+)\s*\(([^)]*)\)\s*\{/g, (match, name, params) => {
|
|
102
|
+
if (!params.includes(':')) return match;
|
|
103
|
+
// Skip if it looks like object destructuring in params
|
|
104
|
+
if (/\{[^}]+\}/.test(params)) return match;
|
|
105
|
+
const cleaned = params.split(',').map(p => p.replace(/:\s*[^,=)]+(?=[,)]|$)/, '')).join(',');
|
|
106
|
+
return `${name}(${cleaned}) {`;
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
// Remove return type annotations - IMPROVED VERSION
|
|
110
|
+
// Match ): Type => but only consume the type part
|
|
111
|
+
jsContent = jsContent.replace(/\)\s*:\s*[^{=>]+(\s*=>)/g, ')$1');
|
|
112
|
+
jsContent = jsContent.replace(/\)\s*:\s*[^{=>]+(\s*\{)/g, ')$1');
|
|
113
|
+
|
|
114
|
+
// Remove function parameter types in function declarations
|
|
115
|
+
jsContent = jsContent.replace(/function\s+(\w+)\s*:\s*[^=]+/g, 'function $1');
|
|
50
116
|
|
|
51
|
-
// Remove
|
|
52
|
-
jsContent = jsContent.replace(
|
|
117
|
+
// Remove as type assertions (including 'as const')
|
|
118
|
+
jsContent = jsContent.replace(/\s+as\s+(const|any|unknown|readonly|\w+)/g, '');
|
|
53
119
|
|
|
54
|
-
// Remove
|
|
55
|
-
|
|
120
|
+
// Remove satisfies operators
|
|
121
|
+
jsContent = jsContent.replace(/\s+satisfies\s+[^;,\n]+/g, '');
|
|
56
122
|
|
|
57
|
-
// Remove
|
|
58
|
-
jsContent = jsContent.replace(/<[^>]
|
|
123
|
+
// Remove angle bracket type assertions: <Type>value => value (but not JSX)
|
|
124
|
+
jsContent = jsContent.replace(/<[\w\[\]]+>(?=[^<]*[^>])/g, '');
|
|
125
|
+
|
|
126
|
+
// Remove optional property markers: property?: Type => property
|
|
127
|
+
jsContent = jsContent.replace(/(\w+)\?\s*:/g, '$1:');
|
|
128
|
+
|
|
129
|
+
// Remove generic type parameters from class/function declarations
|
|
130
|
+
jsContent = jsContent.replace(/(class|function|interface)\s+(\w+)\s*<[^>]+>/g, '$1 $2');
|
|
59
131
|
|
|
60
132
|
// Remove enum declarations (convert to object)
|
|
61
133
|
jsContent = jsContent.replace(/enum\s+(\w+)\s*\{([^}]+)\}/g, (match, name, body) => {
|
|
@@ -67,6 +139,30 @@ export function stripTypeScript(content) {
|
|
|
67
139
|
return `const ${name} = {\n${obj}\n}`;
|
|
68
140
|
});
|
|
69
141
|
|
|
142
|
+
// Fix import paths for Node.js ESM
|
|
143
|
+
// 1. Replace TypeScript alias @/ with Node.js native #/
|
|
144
|
+
jsContent = jsContent.replace(/from\s+["']@\//g, 'from "#/');
|
|
145
|
+
jsContent = jsContent.replace(/import\s*\(\s*["']@\//g, 'import("#/');
|
|
146
|
+
jsContent = jsContent.replace(/require\s*\(\s*["']@\//g, 'require("#/');
|
|
147
|
+
|
|
148
|
+
// 2. Add .js extension to #/ imports (they're now pointing to .js files)
|
|
149
|
+
jsContent = jsContent.replace(/from\s+["']#\/([^"']+)["']/g, (match, path) => {
|
|
150
|
+
// Don't add .js if it already has an extension or is a directory import
|
|
151
|
+
if (path.endsWith('.js') || path.endsWith('.json') || path.endsWith('/')) {
|
|
152
|
+
return match;
|
|
153
|
+
}
|
|
154
|
+
return `from "#/${path}.js"`;
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
// 3. Add .js extension to relative imports
|
|
158
|
+
jsContent = jsContent.replace(/from\s+["'](\.\.?\/[^"']+)["']/g, (match, path) => {
|
|
159
|
+
// Don't add .js if it already has an extension
|
|
160
|
+
if (path.match(/\.\w+$/)) {
|
|
161
|
+
return match;
|
|
162
|
+
}
|
|
163
|
+
return `from "${path}.js"`;
|
|
164
|
+
});
|
|
165
|
+
|
|
70
166
|
// Remove multiple consecutive blank lines
|
|
71
167
|
jsContent = jsContent.replace(/\n\s*\n\s*\n/g, '\n\n');
|
|
72
168
|
|
|
@@ -121,3 +217,126 @@ export function getJavaScriptDependencies(originalDeps, originalDevDeps) {
|
|
|
121
217
|
devDependencies: devDeps
|
|
122
218
|
};
|
|
123
219
|
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Get package.json imports field for Node.js native path aliasing
|
|
223
|
+
* This replaces TypeScript's tsconfig paths with Node's native imports
|
|
224
|
+
* @returns {Object} - imports configuration
|
|
225
|
+
*/
|
|
226
|
+
export function getNodeImportsConfig() {
|
|
227
|
+
return {
|
|
228
|
+
"#/*": "./src/*"
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
import fs from 'fs';
|
|
233
|
+
import path from 'path';
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Transform a single TypeScript file to JavaScript
|
|
237
|
+
* @param {string} filePath - Path to .ts file
|
|
238
|
+
*/
|
|
239
|
+
function transformFile(filePath) {
|
|
240
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
241
|
+
const jsContent = stripTypeScript(content);
|
|
242
|
+
|
|
243
|
+
// Write to .js file
|
|
244
|
+
const jsFilePath = filePath.replace(/\.ts$/, '.js');
|
|
245
|
+
fs.writeFileSync(jsFilePath, jsContent);
|
|
246
|
+
|
|
247
|
+
// Remove original .ts file
|
|
248
|
+
fs.unlinkSync(filePath);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Recursively transform all .ts files in a directory to .js
|
|
253
|
+
* @param {string} dir - Directory path
|
|
254
|
+
*/
|
|
255
|
+
export function transformDirectory(dir) {
|
|
256
|
+
if (!fs.existsSync(dir)) return;
|
|
257
|
+
|
|
258
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
259
|
+
|
|
260
|
+
for (const entry of entries) {
|
|
261
|
+
const fullPath = path.join(dir, entry.name);
|
|
262
|
+
|
|
263
|
+
if (entry.isDirectory()) {
|
|
264
|
+
// Skip node_modules and other non-source directories
|
|
265
|
+
if (entry.name === 'node_modules' || entry.name === '.git') {
|
|
266
|
+
continue;
|
|
267
|
+
}
|
|
268
|
+
transformDirectory(fullPath);
|
|
269
|
+
} else if (entry.isFile() && entry.name.endsWith('.ts')) {
|
|
270
|
+
transformFile(fullPath);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Main function to transform a TypeScript project to JavaScript
|
|
277
|
+
* @param {string} targetDir - Root directory of the project
|
|
278
|
+
*/
|
|
279
|
+
export function transformToJavaScript(targetDir) {
|
|
280
|
+
// Transform all .ts files to .js
|
|
281
|
+
const srcDir = path.join(targetDir, 'src');
|
|
282
|
+
if (fs.existsSync(srcDir)) {
|
|
283
|
+
transformDirectory(srcDir);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// Update package.json
|
|
287
|
+
const packageJsonPath = path.join(targetDir, 'package.json');
|
|
288
|
+
if (fs.existsSync(packageJsonPath)) {
|
|
289
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
|
|
290
|
+
|
|
291
|
+
// Get JavaScript dependencies
|
|
292
|
+
const { dependencies, devDependencies } = getJavaScriptDependencies(
|
|
293
|
+
packageJson.dependencies || {},
|
|
294
|
+
packageJson.devDependencies || {}
|
|
295
|
+
);
|
|
296
|
+
|
|
297
|
+
// Update package.json
|
|
298
|
+
packageJson.dependencies = dependencies;
|
|
299
|
+
packageJson.devDependencies = devDependencies;
|
|
300
|
+
packageJson.scripts = getJavaScriptScripts();
|
|
301
|
+
|
|
302
|
+
// Add Node.js native imports for path aliasing
|
|
303
|
+
packageJson.imports = getNodeImportsConfig();
|
|
304
|
+
|
|
305
|
+
// Ensure type: module is set
|
|
306
|
+
packageJson.type = 'module';
|
|
307
|
+
|
|
308
|
+
fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2) + '\n');
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// Remove tsconfig.json if it exists
|
|
312
|
+
const tsconfigPath = path.join(targetDir, 'tsconfig.json');
|
|
313
|
+
if (fs.existsSync(tsconfigPath)) {
|
|
314
|
+
fs.unlinkSync(tsconfigPath);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// Update .prettierrc if it exists to use .js instead of .ts
|
|
318
|
+
const prettierrcPath = path.join(targetDir, '.prettierrc');
|
|
319
|
+
if (fs.existsSync(prettierrcPath)) {
|
|
320
|
+
let prettierConfig = fs.readFileSync(prettierrcPath, 'utf-8');
|
|
321
|
+
prettierConfig = prettierConfig.replace(/\.ts/g, '.js');
|
|
322
|
+
fs.writeFileSync(prettierrcPath, prettierConfig);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// Update eslint.config.js if it exists
|
|
326
|
+
const eslintConfigPath = path.join(targetDir, 'eslint.config.js');
|
|
327
|
+
if (fs.existsSync(eslintConfigPath)) {
|
|
328
|
+
let eslintConfig = fs.readFileSync(eslintConfigPath, 'utf-8');
|
|
329
|
+
|
|
330
|
+
// Remove TypeScript parser and plugin
|
|
331
|
+
eslintConfig = eslintConfig.replace(/@typescript-eslint\/parser/g, '');
|
|
332
|
+
eslintConfig = eslintConfig.replace(/@typescript-eslint\/eslint-plugin/g, '');
|
|
333
|
+
eslintConfig = eslintConfig.replace(/tseslint\./g, '');
|
|
334
|
+
eslintConfig = eslintConfig.replace(/import tseslint[^\n]+\n/g, '');
|
|
335
|
+
|
|
336
|
+
// Update file patterns
|
|
337
|
+
eslintConfig = eslintConfig.replace(/\*\*\/\*\.ts/g, '**/*.js');
|
|
338
|
+
|
|
339
|
+
fs.writeFileSync(eslintConfigPath, eslintConfig);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
package/package.json
CHANGED
|
@@ -1,7 +1,17 @@
|
|
|
1
1
|
import { ENV } from "@/config";
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
// ANSI color codes for terminal output
|
|
4
|
+
const colors = {
|
|
5
|
+
reset: "\x1b[0m",
|
|
6
|
+
blue: "\x1b[34m",
|
|
7
|
+
cyan: "\x1b[36m",
|
|
8
|
+
yellow: "\x1b[33m",
|
|
9
|
+
red: "\x1b[31m",
|
|
10
|
+
bold: "\x1b[1m",
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
function format(tag: string, color: string) {
|
|
14
|
+
return `${color}${colors.bold}[${tag}]${colors.reset}`;
|
|
5
15
|
}
|
|
6
16
|
|
|
7
17
|
function getEnvironment() {
|
|
@@ -13,22 +23,22 @@ function getEnvironment() {
|
|
|
13
23
|
const logger = {
|
|
14
24
|
log(tag: string, ...args: unknown[]) {
|
|
15
25
|
if (getEnvironment() !== "development") return;
|
|
16
|
-
console.log(
|
|
26
|
+
console.log(format(tag, colors.blue), ...args);
|
|
17
27
|
},
|
|
18
28
|
|
|
19
29
|
info(tag: string, ...args: unknown[]) {
|
|
20
30
|
if (getEnvironment() !== "development") return;
|
|
21
|
-
console.info(
|
|
31
|
+
console.info(format(tag, colors.cyan), ...args);
|
|
22
32
|
},
|
|
23
33
|
|
|
24
34
|
warn(tag: string, ...args: unknown[]) {
|
|
25
35
|
if (getEnvironment() !== "development") return;
|
|
26
|
-
console.warn(
|
|
36
|
+
console.warn(format(tag, colors.yellow), ...args);
|
|
27
37
|
},
|
|
28
38
|
|
|
29
39
|
error(tag: string, ...args: unknown[]) {
|
|
30
40
|
if (getEnvironment() !== "development") return;
|
|
31
|
-
console.error(
|
|
41
|
+
console.error(format(tag, colors.red), ...args);
|
|
32
42
|
},
|
|
33
43
|
};
|
|
34
44
|
|