servcraft 0.4.8 → 0.4.9
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/ROADMAP.md +2 -1
- package/dist/cli/index.cjs +102 -24
- package/dist/cli/index.cjs.map +1 -1
- package/dist/cli/index.js +102 -24
- package/dist/cli/index.js.map +1 -1
- package/package.json +1 -1
- package/src/cli/commands/add-module.ts +111 -6
- package/src/cli/commands/init.ts +77 -20
package/package.json
CHANGED
|
@@ -681,7 +681,13 @@ async function findServercraftModules(): Promise<string | null> {
|
|
|
681
681
|
/**
|
|
682
682
|
* Helper: Generate module files - copies from servcraft package modules
|
|
683
683
|
*/
|
|
684
|
-
async function generateModuleFiles(
|
|
684
|
+
async function generateModuleFiles(
|
|
685
|
+
moduleName: string,
|
|
686
|
+
moduleDir: string,
|
|
687
|
+
language: 'typescript' | 'javascript' = 'typescript',
|
|
688
|
+
moduleSystem: 'esm' | 'commonjs' = 'esm',
|
|
689
|
+
fileExtension: 'js' | 'cjs' | 'ts' = 'ts'
|
|
690
|
+
): Promise<void> {
|
|
685
691
|
// Map module names to their directory names in servcraft
|
|
686
692
|
const moduleNameMap: Record<string, string> = {
|
|
687
693
|
users: 'user',
|
|
@@ -701,7 +707,8 @@ async function generateModuleFiles(moduleName: string, moduleDir: string): Promi
|
|
|
701
707
|
|
|
702
708
|
if (await fileExists(sourceModuleDir)) {
|
|
703
709
|
// Copy from servcraft package
|
|
704
|
-
|
|
710
|
+
const jsExt = language === 'javascript' ? (fileExtension as 'js' | 'cjs') : 'js';
|
|
711
|
+
await copyModuleFromSource(sourceModuleDir, moduleDir, language, moduleSystem, jsExt);
|
|
705
712
|
return;
|
|
706
713
|
}
|
|
707
714
|
}
|
|
@@ -731,21 +738,119 @@ async function generateModuleFiles(moduleName: string, moduleDir: string): Promi
|
|
|
731
738
|
}
|
|
732
739
|
}
|
|
733
740
|
|
|
741
|
+
/**
|
|
742
|
+
* Helper: Convert ESM syntax to CommonJS
|
|
743
|
+
*/
|
|
744
|
+
function convertESMtoCommonJS(content: string): string {
|
|
745
|
+
return (
|
|
746
|
+
content
|
|
747
|
+
// Convert export class/function/const/let/var
|
|
748
|
+
.replace(/^export\s+class\s+/gm, 'class ')
|
|
749
|
+
.replace(/^export\s+function\s+/gm, 'function ')
|
|
750
|
+
.replace(/^export\s+const\s+/gm, 'const ')
|
|
751
|
+
.replace(/^export\s+let\s+/gm, 'let ')
|
|
752
|
+
.replace(/^export\s+var\s+/gm, 'var ')
|
|
753
|
+
// Convert named imports: import { a, b } from 'module'
|
|
754
|
+
.replace(
|
|
755
|
+
/import\s*\{\s*([^}]+)\s*\}\s*from\s*['"]([^'"]+)['"]/g,
|
|
756
|
+
"const { $1 } = require('$2')"
|
|
757
|
+
)
|
|
758
|
+
// Convert default imports: import name from 'module'
|
|
759
|
+
.replace(/import\s+(\w+)\s+from\s*['"]([^'"]+)['"]/g, "const $1 = require('$2')")
|
|
760
|
+
// Convert mixed imports: import name, { a, b } from 'module'
|
|
761
|
+
.replace(
|
|
762
|
+
/import\s+(\w+)\s*,\s*\{\s*([^}]+)\s*\}\s*from\s*['"]([^'"]+)['"]/g,
|
|
763
|
+
"const $1 = require('$3');\nconst { $2 } = require('$3')"
|
|
764
|
+
)
|
|
765
|
+
// Add module.exports at the end for classes and main exports
|
|
766
|
+
.replace(/^(class\s+(\w+)\s+\{[\s\S]*?\n\})/gm, '$1\nmodule.exports.$2 = $2;')
|
|
767
|
+
// Handle export { ... }
|
|
768
|
+
.replace(/export\s*\{\s*([^}]+)\s*\}/g, (match, exports) => {
|
|
769
|
+
const items = exports
|
|
770
|
+
.split(',')
|
|
771
|
+
.map((item: string) => item.trim())
|
|
772
|
+
.filter(Boolean);
|
|
773
|
+
return items.map((item: string) => `module.exports.${item} = ${item};`).join('\n');
|
|
774
|
+
})
|
|
775
|
+
);
|
|
776
|
+
}
|
|
777
|
+
|
|
734
778
|
/**
|
|
735
779
|
* Helper: Copy module from source directory
|
|
736
780
|
*/
|
|
737
|
-
async function copyModuleFromSource(
|
|
781
|
+
async function copyModuleFromSource(
|
|
782
|
+
sourceDir: string,
|
|
783
|
+
targetDir: string,
|
|
784
|
+
language: 'typescript' | 'javascript' = 'typescript',
|
|
785
|
+
moduleSystem: 'esm' | 'commonjs' = 'esm',
|
|
786
|
+
fileExtension: 'js' | 'cjs' = 'js'
|
|
787
|
+
): Promise<void> {
|
|
738
788
|
const entries = await fs.readdir(sourceDir, { withFileTypes: true });
|
|
739
789
|
|
|
740
790
|
for (const entry of entries) {
|
|
741
791
|
const sourcePath = path.join(sourceDir, entry.name);
|
|
742
|
-
|
|
792
|
+
let targetPath = path.join(targetDir, entry.name);
|
|
743
793
|
|
|
744
794
|
if (entry.isDirectory()) {
|
|
745
795
|
await fs.mkdir(targetPath, { recursive: true });
|
|
746
|
-
await copyModuleFromSource(sourcePath, targetPath);
|
|
796
|
+
await copyModuleFromSource(sourcePath, targetPath, language, moduleSystem, fileExtension);
|
|
747
797
|
} else {
|
|
748
|
-
|
|
798
|
+
// Convert file extension if generating JavaScript
|
|
799
|
+
if (language === 'javascript' && entry.name.endsWith('.ts')) {
|
|
800
|
+
const ext = `.${fileExtension}`;
|
|
801
|
+
targetPath = targetPath.replace(/\.ts$/, ext);
|
|
802
|
+
|
|
803
|
+
// Read, convert, and write the file
|
|
804
|
+
let content = await fs.readFile(sourcePath, 'utf-8');
|
|
805
|
+
|
|
806
|
+
// Basic TypeScript to JavaScript conversion
|
|
807
|
+
// Apply replacements multiple times to catch nested patterns
|
|
808
|
+
for (let i = 0; i < 3; i++) {
|
|
809
|
+
content = content
|
|
810
|
+
// Remove import type statements
|
|
811
|
+
.replace(/import\s+type\s+\{[^}]+\}\s+from\s+['"][^'"]+['"];?\s*\n/g, '')
|
|
812
|
+
// Remove type imports from regular imports
|
|
813
|
+
.replace(/import\s+\{([^}]+)\}\s+from/g, (match, imports) => {
|
|
814
|
+
const filtered = imports
|
|
815
|
+
.split(',')
|
|
816
|
+
.map((imp: string) => imp.trim())
|
|
817
|
+
.filter((imp: string) => !imp.startsWith('type '))
|
|
818
|
+
.join(', ');
|
|
819
|
+
return filtered ? `import {${filtered}} from` : '';
|
|
820
|
+
})
|
|
821
|
+
// Update import paths
|
|
822
|
+
.replace(/from\s+['"](.+?)\.js['"]/g, `from '$1${ext}'`)
|
|
823
|
+
// Remove 'private', 'public', 'protected', 'readonly' keywords
|
|
824
|
+
.replace(/\b(private|public|protected|readonly)\s+/g, '')
|
|
825
|
+
// Remove type annotations from function parameters and variables (multiple passes)
|
|
826
|
+
.replace(
|
|
827
|
+
/:\s*[A-Z]\w+(<[^>]+>)?(\[\])?(\s*[|&]\s*[A-Z]\w+(<[^>]+>)?(\[\])?)*(?=[,)\s=\n])/g,
|
|
828
|
+
''
|
|
829
|
+
)
|
|
830
|
+
.replace(/(\w+)\s*:\s*[^,)=\n]+([,)])/g, '$1$2')
|
|
831
|
+
.replace(/(\w+)\s*:\s*[^=\n{]+(\s*=)/g, '$1$2')
|
|
832
|
+
// Remove return type annotations
|
|
833
|
+
.replace(/\)\s*:\s*[^{=\n]+\s*([{=])/g, ') $1')
|
|
834
|
+
// Remove interface and type definitions
|
|
835
|
+
.replace(/^export\s+(interface|type)\s+[^;]+;?\s*$/gm, '')
|
|
836
|
+
.replace(/^(interface|type)\s+[^;]+;?\s*$/gm, '')
|
|
837
|
+
// Remove type assertions (as Type)
|
|
838
|
+
.replace(/\s+as\s+\w+/g, '')
|
|
839
|
+
// Remove generic type parameters
|
|
840
|
+
.replace(/<[A-Z][\w,\s<>[\]|&]*>/g, '');
|
|
841
|
+
}
|
|
842
|
+
// Final cleanup
|
|
843
|
+
content = content.replace(/^\s*\n/gm, '');
|
|
844
|
+
|
|
845
|
+
// Convert to CommonJS if needed
|
|
846
|
+
if (moduleSystem === 'commonjs') {
|
|
847
|
+
content = convertESMtoCommonJS(content);
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
await fs.writeFile(targetPath, content, 'utf-8');
|
|
851
|
+
} else {
|
|
852
|
+
await fs.copyFile(sourcePath, targetPath);
|
|
853
|
+
}
|
|
749
854
|
}
|
|
750
855
|
}
|
|
751
856
|
}
|
package/src/cli/commands/init.ts
CHANGED
|
@@ -13,6 +13,7 @@ interface InitOptions {
|
|
|
13
13
|
name: string;
|
|
14
14
|
language: 'typescript' | 'javascript';
|
|
15
15
|
moduleSystem: 'esm' | 'commonjs';
|
|
16
|
+
fileExtension: 'js' | 'cjs' | 'ts';
|
|
16
17
|
database: 'postgresql' | 'mysql' | 'sqlite' | 'mongodb' | 'none';
|
|
17
18
|
orm: 'prisma' | 'mongoose' | 'none';
|
|
18
19
|
validator: 'zod' | 'joi' | 'yup';
|
|
@@ -55,26 +56,25 @@ export const initCommand = new Command('init')
|
|
|
55
56
|
console.log(chalk.cyan('│') + ' ' + chalk.cyan('│'));
|
|
56
57
|
console.log(
|
|
57
58
|
chalk.cyan('│') +
|
|
58
|
-
' ' +
|
|
59
59
|
chalk.bold.white('🚀 Servcraft') +
|
|
60
60
|
chalk.gray(' - Project Generator') +
|
|
61
|
-
'
|
|
61
|
+
' ' +
|
|
62
62
|
chalk.cyan('│')
|
|
63
63
|
);
|
|
64
64
|
console.log(
|
|
65
65
|
chalk.cyan('│') +
|
|
66
|
-
'
|
|
66
|
+
' ' +
|
|
67
67
|
chalk.gray('by ') +
|
|
68
68
|
chalk.blue('Yao Logan') +
|
|
69
69
|
chalk.gray(' (@Le-Sourcier)') +
|
|
70
|
-
'
|
|
70
|
+
' ' +
|
|
71
71
|
chalk.cyan('│')
|
|
72
72
|
);
|
|
73
73
|
console.log(
|
|
74
74
|
chalk.cyan('│') +
|
|
75
|
-
'
|
|
75
|
+
' ' +
|
|
76
76
|
chalk.bgBlue.white(' in/yao-logan ') +
|
|
77
|
-
'
|
|
77
|
+
' ' +
|
|
78
78
|
chalk.cyan('│')
|
|
79
79
|
);
|
|
80
80
|
console.log(chalk.cyan('│') + ' ' + chalk.cyan('│'));
|
|
@@ -85,17 +85,28 @@ export const initCommand = new Command('init')
|
|
|
85
85
|
|
|
86
86
|
if (cmdOptions?.yes) {
|
|
87
87
|
const db = (cmdOptions.db as InitOptions['database']) || 'postgresql';
|
|
88
|
+
const language = cmdOptions.javascript ? 'javascript' : 'typescript';
|
|
89
|
+
const moduleSystem = cmdOptions.commonjs ? 'commonjs' : 'esm';
|
|
90
|
+
|
|
91
|
+
// Determine file extension based on language and module system
|
|
92
|
+
let fileExtension: 'js' | 'cjs' | 'ts' = 'ts';
|
|
93
|
+
if (language === 'javascript') {
|
|
94
|
+
fileExtension = moduleSystem === 'commonjs' ? 'cjs' : 'js';
|
|
95
|
+
}
|
|
96
|
+
|
|
88
97
|
options = {
|
|
89
98
|
name: name || 'my-servcraft-app',
|
|
90
|
-
language
|
|
91
|
-
moduleSystem
|
|
99
|
+
language,
|
|
100
|
+
moduleSystem,
|
|
101
|
+
fileExtension,
|
|
92
102
|
database: db,
|
|
93
103
|
orm: db === 'mongodb' ? 'mongoose' : db === 'none' ? 'none' : 'prisma',
|
|
94
104
|
validator: 'zod',
|
|
95
105
|
features: ['auth', 'users', 'email'],
|
|
96
106
|
};
|
|
97
107
|
} else {
|
|
98
|
-
|
|
108
|
+
// Step 1: Basic project info and language
|
|
109
|
+
const basicAnswers = await inquirer.prompt([
|
|
99
110
|
{
|
|
100
111
|
type: 'input',
|
|
101
112
|
name: 'name',
|
|
@@ -118,16 +129,48 @@ export const initCommand = new Command('init')
|
|
|
118
129
|
],
|
|
119
130
|
default: 'typescript',
|
|
120
131
|
},
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
132
|
+
]);
|
|
133
|
+
|
|
134
|
+
// Step 2: Module system and file extension (only for JavaScript)
|
|
135
|
+
let moduleSystem: 'esm' | 'commonjs' = 'esm';
|
|
136
|
+
let fileExtension: 'js' | 'cjs' | 'ts' = 'ts';
|
|
137
|
+
|
|
138
|
+
if (basicAnswers.language === 'javascript') {
|
|
139
|
+
const jsConfigAnswers = await inquirer.prompt([
|
|
140
|
+
{
|
|
141
|
+
type: 'list',
|
|
142
|
+
name: 'moduleSystem',
|
|
143
|
+
message: '📦 Select module syntax:',
|
|
144
|
+
choices: [
|
|
145
|
+
{ name: '✨ ESM (import/export)', value: 'esm' },
|
|
146
|
+
{ name: ' CommonJS (require/module.exports)', value: 'commonjs' },
|
|
147
|
+
],
|
|
148
|
+
default: 'esm',
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
type: 'list',
|
|
152
|
+
name: 'fileExtension',
|
|
153
|
+
message: '📄 Select file extension:',
|
|
154
|
+
choices: (answers: { moduleSystem: string }) => {
|
|
155
|
+
if (answers.moduleSystem === 'esm') {
|
|
156
|
+
return [{ name: '✨ .js (standard for ESM)', value: 'js' }];
|
|
157
|
+
} else {
|
|
158
|
+
return [
|
|
159
|
+
{ name: '✨ .cjs (explicit CommonJS)', value: 'cjs' },
|
|
160
|
+
{ name: ' .js (CommonJS in .js)', value: 'js' },
|
|
161
|
+
];
|
|
162
|
+
}
|
|
163
|
+
},
|
|
164
|
+
default: (answers: { moduleSystem: string }) =>
|
|
165
|
+
answers.moduleSystem === 'esm' ? 'js' : 'cjs',
|
|
166
|
+
},
|
|
167
|
+
]);
|
|
168
|
+
moduleSystem = jsConfigAnswers.moduleSystem;
|
|
169
|
+
fileExtension = jsConfigAnswers.fileExtension;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Step 3: Database, validator, and features
|
|
173
|
+
const restAnswers = await inquirer.prompt([
|
|
131
174
|
{
|
|
132
175
|
type: 'list',
|
|
133
176
|
name: 'database',
|
|
@@ -167,6 +210,14 @@ export const initCommand = new Command('init')
|
|
|
167
210
|
},
|
|
168
211
|
]);
|
|
169
212
|
|
|
213
|
+
// Combine all answers
|
|
214
|
+
const answers = {
|
|
215
|
+
...basicAnswers,
|
|
216
|
+
moduleSystem,
|
|
217
|
+
fileExtension,
|
|
218
|
+
...restAnswers,
|
|
219
|
+
};
|
|
220
|
+
|
|
170
221
|
// Auto-determine ORM based on database choice
|
|
171
222
|
const db = answers.database as InitOptions['database'];
|
|
172
223
|
options = {
|
|
@@ -315,7 +366,13 @@ export const initCommand = new Command('init')
|
|
|
315
366
|
moduleSpinner.text = `Installing ${feature} module...`;
|
|
316
367
|
const moduleDir = path.join(projectDir, 'src/modules', feature);
|
|
317
368
|
await ensureDir(moduleDir);
|
|
318
|
-
await generateModuleFiles(
|
|
369
|
+
await generateModuleFiles(
|
|
370
|
+
feature,
|
|
371
|
+
moduleDir,
|
|
372
|
+
options.language,
|
|
373
|
+
options.moduleSystem,
|
|
374
|
+
options.fileExtension
|
|
375
|
+
);
|
|
319
376
|
}
|
|
320
377
|
moduleSpinner.succeed(`${options.features.length} module(s) installed!`);
|
|
321
378
|
} catch (err) {
|