servcraft 0.4.6 → 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 +4 -1
- package/dist/cli/index.cjs +4476 -4383
- package/dist/cli/index.cjs.map +1 -1
- package/dist/cli/index.js +4458 -4365
- package/dist/cli/index.js.map +1 -1
- package/package.json +1 -1
- package/src/cli/commands/add-module.ts +114 -6
- package/src/cli/commands/init.ts +96 -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
|
}
|
|
@@ -926,3 +1031,6 @@ async function performSmartMerge(
|
|
|
926
1031
|
|
|
927
1032
|
InteractivePrompt.showMergeSummary(stats);
|
|
928
1033
|
}
|
|
1034
|
+
|
|
1035
|
+
// Export helper functions for use in other commands
|
|
1036
|
+
export { generateModuleFiles, EnvManager, TemplateManager };
|
package/src/cli/commands/init.ts
CHANGED
|
@@ -7,11 +7,13 @@ import chalk from 'chalk';
|
|
|
7
7
|
import { execSync } from 'child_process';
|
|
8
8
|
import { ensureDir, writeFile, error, warn } from '../utils/helpers.js';
|
|
9
9
|
import { DryRunManager } from '../utils/dry-run.js';
|
|
10
|
+
import { generateModuleFiles } from './add-module.js';
|
|
10
11
|
|
|
11
12
|
interface InitOptions {
|
|
12
13
|
name: string;
|
|
13
14
|
language: 'typescript' | 'javascript';
|
|
14
15
|
moduleSystem: 'esm' | 'commonjs';
|
|
16
|
+
fileExtension: 'js' | 'cjs' | 'ts';
|
|
15
17
|
database: 'postgresql' | 'mysql' | 'sqlite' | 'mongodb' | 'none';
|
|
16
18
|
orm: 'prisma' | 'mongoose' | 'none';
|
|
17
19
|
validator: 'zod' | 'joi' | 'yup';
|
|
@@ -54,26 +56,25 @@ export const initCommand = new Command('init')
|
|
|
54
56
|
console.log(chalk.cyan('│') + ' ' + chalk.cyan('│'));
|
|
55
57
|
console.log(
|
|
56
58
|
chalk.cyan('│') +
|
|
57
|
-
' ' +
|
|
58
59
|
chalk.bold.white('🚀 Servcraft') +
|
|
59
60
|
chalk.gray(' - Project Generator') +
|
|
60
|
-
'
|
|
61
|
+
' ' +
|
|
61
62
|
chalk.cyan('│')
|
|
62
63
|
);
|
|
63
64
|
console.log(
|
|
64
65
|
chalk.cyan('│') +
|
|
65
|
-
'
|
|
66
|
+
' ' +
|
|
66
67
|
chalk.gray('by ') +
|
|
67
68
|
chalk.blue('Yao Logan') +
|
|
68
69
|
chalk.gray(' (@Le-Sourcier)') +
|
|
69
|
-
'
|
|
70
|
+
' ' +
|
|
70
71
|
chalk.cyan('│')
|
|
71
72
|
);
|
|
72
73
|
console.log(
|
|
73
74
|
chalk.cyan('│') +
|
|
74
|
-
'
|
|
75
|
+
' ' +
|
|
75
76
|
chalk.bgBlue.white(' in/yao-logan ') +
|
|
76
|
-
'
|
|
77
|
+
' ' +
|
|
77
78
|
chalk.cyan('│')
|
|
78
79
|
);
|
|
79
80
|
console.log(chalk.cyan('│') + ' ' + chalk.cyan('│'));
|
|
@@ -84,17 +85,28 @@ export const initCommand = new Command('init')
|
|
|
84
85
|
|
|
85
86
|
if (cmdOptions?.yes) {
|
|
86
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
|
+
|
|
87
97
|
options = {
|
|
88
98
|
name: name || 'my-servcraft-app',
|
|
89
|
-
language
|
|
90
|
-
moduleSystem
|
|
99
|
+
language,
|
|
100
|
+
moduleSystem,
|
|
101
|
+
fileExtension,
|
|
91
102
|
database: db,
|
|
92
103
|
orm: db === 'mongodb' ? 'mongoose' : db === 'none' ? 'none' : 'prisma',
|
|
93
104
|
validator: 'zod',
|
|
94
105
|
features: ['auth', 'users', 'email'],
|
|
95
106
|
};
|
|
96
107
|
} else {
|
|
97
|
-
|
|
108
|
+
// Step 1: Basic project info and language
|
|
109
|
+
const basicAnswers = await inquirer.prompt([
|
|
98
110
|
{
|
|
99
111
|
type: 'input',
|
|
100
112
|
name: 'name',
|
|
@@ -117,16 +129,48 @@ export const initCommand = new Command('init')
|
|
|
117
129
|
],
|
|
118
130
|
default: 'typescript',
|
|
119
131
|
},
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
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([
|
|
130
174
|
{
|
|
131
175
|
type: 'list',
|
|
132
176
|
name: 'database',
|
|
@@ -161,11 +205,19 @@ export const initCommand = new Command('init')
|
|
|
161
205
|
{ name: 'Email Service', value: 'email', checked: true },
|
|
162
206
|
{ name: 'Audit Logs', value: 'audit', checked: false },
|
|
163
207
|
{ name: 'File Upload', value: 'upload', checked: false },
|
|
164
|
-
{ name: 'Redis Cache', value: '
|
|
208
|
+
{ name: 'Redis Cache', value: 'cache', checked: false },
|
|
165
209
|
],
|
|
166
210
|
},
|
|
167
211
|
]);
|
|
168
212
|
|
|
213
|
+
// Combine all answers
|
|
214
|
+
const answers = {
|
|
215
|
+
...basicAnswers,
|
|
216
|
+
moduleSystem,
|
|
217
|
+
fileExtension,
|
|
218
|
+
...restAnswers,
|
|
219
|
+
};
|
|
220
|
+
|
|
169
221
|
// Auto-determine ORM based on database choice
|
|
170
222
|
const db = answers.database as InitOptions['database'];
|
|
171
223
|
options = {
|
|
@@ -305,6 +357,30 @@ export const initCommand = new Command('init')
|
|
|
305
357
|
|
|
306
358
|
spinner.succeed('Project files generated!');
|
|
307
359
|
|
|
360
|
+
// Install selected feature modules
|
|
361
|
+
if (options.features && options.features.length > 0 && !cmdOptions?.dryRun) {
|
|
362
|
+
const moduleSpinner = ora('Installing selected modules...').start();
|
|
363
|
+
|
|
364
|
+
try {
|
|
365
|
+
for (const feature of options.features) {
|
|
366
|
+
moduleSpinner.text = `Installing ${feature} module...`;
|
|
367
|
+
const moduleDir = path.join(projectDir, 'src/modules', feature);
|
|
368
|
+
await ensureDir(moduleDir);
|
|
369
|
+
await generateModuleFiles(
|
|
370
|
+
feature,
|
|
371
|
+
moduleDir,
|
|
372
|
+
options.language,
|
|
373
|
+
options.moduleSystem,
|
|
374
|
+
options.fileExtension
|
|
375
|
+
);
|
|
376
|
+
}
|
|
377
|
+
moduleSpinner.succeed(`${options.features.length} module(s) installed!`);
|
|
378
|
+
} catch (err) {
|
|
379
|
+
moduleSpinner.fail('Failed to install some modules');
|
|
380
|
+
warn(` Error: ${err instanceof Error ? err.message : 'Unknown error'}`);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
308
384
|
// Install dependencies (skip in dry-run mode)
|
|
309
385
|
if (!cmdOptions?.dryRun) {
|
|
310
386
|
const installSpinner = ora('Installing dependencies...').start();
|