@ifecodes/backend-template 1.1.1 → 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 +32 -5
- package/bin/lib/service-setup.js +8 -0
- 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,13 +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
|
+
});
|
|
139
145
|
|
|
140
146
|
// Handle cancelled prompts (user pressed Ctrl+C or closed the prompt)
|
|
141
|
-
if
|
|
147
|
+
// Check if user cancelled the prompt (Ctrl+C) vs just didn't enter anything
|
|
148
|
+
if (!res) {
|
|
142
149
|
console.log(pc.yellow("\n❌ Operation cancelled by user."));
|
|
143
150
|
process.exit(0);
|
|
144
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
|
+
}
|
|
145
167
|
|
|
146
168
|
// Set defaults for CI/non-interactive mode
|
|
147
169
|
if (isCI) {
|
|
@@ -161,6 +183,11 @@ export const getProjectConfig = async () => {
|
|
|
161
183
|
res.projectType = res.projectType || "monolith";
|
|
162
184
|
}
|
|
163
185
|
}
|
|
186
|
+
|
|
187
|
+
// Ensure we have a project name (fallback if somehow missed)
|
|
188
|
+
if (!res.name && !isInMicroserviceProject) {
|
|
189
|
+
res.name = "my-backend";
|
|
190
|
+
}
|
|
164
191
|
|
|
165
192
|
let sanitizedName, target, isExistingProject, mode;
|
|
166
193
|
|
package/bin/lib/service-setup.js
CHANGED
|
@@ -350,6 +350,14 @@ await connectDB();`
|
|
|
350
350
|
finalPackageJson.keywords = res.keywords.split(',').map(k => k.trim()).filter(Boolean);
|
|
351
351
|
}
|
|
352
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
|
+
|
|
353
361
|
fs.writeFileSync(
|
|
354
362
|
packageJsonPath,
|
|
355
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
|
|