servcraft 0.1.6 → 0.2.0
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/README.md +51 -4
- package/ROADMAP.md +327 -0
- package/dist/cli/index.cjs +3039 -222
- package/dist/cli/index.cjs.map +1 -1
- package/dist/cli/index.js +3058 -220
- package/dist/cli/index.js.map +1 -1
- package/package.json +2 -2
- package/src/cli/commands/add-module.ts +37 -36
- package/src/cli/commands/doctor.ts +8 -0
- package/src/cli/commands/generate.ts +43 -1
- package/src/cli/commands/init.ts +470 -75
- package/src/cli/commands/list.ts +274 -0
- package/src/cli/commands/remove.ts +102 -0
- package/src/cli/index.ts +16 -0
- package/src/cli/utils/dry-run.ts +155 -0
- package/src/cli/utils/error-handler.ts +184 -0
- package/src/cli/utils/helpers.ts +13 -0
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
/* eslint-disable no-console */
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
|
|
4
|
+
export interface ErrorSuggestion {
|
|
5
|
+
message: string;
|
|
6
|
+
suggestions: string[];
|
|
7
|
+
docsLink?: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export class ServCraftError extends Error {
|
|
11
|
+
suggestions: string[];
|
|
12
|
+
docsLink?: string;
|
|
13
|
+
|
|
14
|
+
constructor(message: string, suggestions: string[] = [], docsLink?: string) {
|
|
15
|
+
super(message);
|
|
16
|
+
this.name = 'ServCraftError';
|
|
17
|
+
this.suggestions = suggestions;
|
|
18
|
+
this.docsLink = docsLink;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Common error types with suggestions
|
|
23
|
+
export const ErrorTypes = {
|
|
24
|
+
MODULE_NOT_FOUND: (moduleName: string): ServCraftError =>
|
|
25
|
+
new ServCraftError(
|
|
26
|
+
`Module "${moduleName}" not found`,
|
|
27
|
+
[
|
|
28
|
+
`Run ${chalk.cyan('servcraft list')} to see available modules`,
|
|
29
|
+
`Check the spelling of the module name`,
|
|
30
|
+
`Visit ${chalk.blue('https://github.com/Le-Sourcier/servcraft#modules')} for module list`,
|
|
31
|
+
],
|
|
32
|
+
'https://github.com/Le-Sourcier/servcraft#add-pre-built-modules'
|
|
33
|
+
),
|
|
34
|
+
|
|
35
|
+
MODULE_ALREADY_EXISTS: (moduleName: string): ServCraftError =>
|
|
36
|
+
new ServCraftError(`Module "${moduleName}" already exists`, [
|
|
37
|
+
`Use ${chalk.cyan('servcraft add ' + moduleName + ' --force')} to overwrite`,
|
|
38
|
+
`Use ${chalk.cyan('servcraft add ' + moduleName + ' --update')} to update`,
|
|
39
|
+
`Use ${chalk.cyan('servcraft add ' + moduleName + ' --skip-existing')} to skip`,
|
|
40
|
+
]),
|
|
41
|
+
|
|
42
|
+
NOT_IN_PROJECT: (): ServCraftError =>
|
|
43
|
+
new ServCraftError(
|
|
44
|
+
'Not in a ServCraft project directory',
|
|
45
|
+
[
|
|
46
|
+
`Run ${chalk.cyan('servcraft init')} to create a new project`,
|
|
47
|
+
`Navigate to your ServCraft project directory`,
|
|
48
|
+
`Check if ${chalk.yellow('package.json')} exists`,
|
|
49
|
+
],
|
|
50
|
+
'https://github.com/Le-Sourcier/servcraft#initialize-project'
|
|
51
|
+
),
|
|
52
|
+
|
|
53
|
+
FILE_ALREADY_EXISTS: (fileName: string): ServCraftError =>
|
|
54
|
+
new ServCraftError(`File "${fileName}" already exists`, [
|
|
55
|
+
`Use ${chalk.cyan('--force')} flag to overwrite`,
|
|
56
|
+
`Choose a different name`,
|
|
57
|
+
`Delete the existing file first`,
|
|
58
|
+
]),
|
|
59
|
+
|
|
60
|
+
INVALID_DATABASE: (database: string): ServCraftError =>
|
|
61
|
+
new ServCraftError(`Invalid database type: "${database}"`, [
|
|
62
|
+
`Valid options: ${chalk.cyan('postgresql, mysql, sqlite, mongodb, none')}`,
|
|
63
|
+
`Use ${chalk.cyan('servcraft init --db postgresql')} for PostgreSQL`,
|
|
64
|
+
]),
|
|
65
|
+
|
|
66
|
+
INVALID_VALIDATOR: (validator: string): ServCraftError =>
|
|
67
|
+
new ServCraftError(`Invalid validator type: "${validator}"`, [
|
|
68
|
+
`Valid options: ${chalk.cyan('zod, joi, yup')}`,
|
|
69
|
+
`Default is ${chalk.cyan('zod')}`,
|
|
70
|
+
]),
|
|
71
|
+
|
|
72
|
+
MISSING_DEPENDENCY: (dependency: string, command: string): ServCraftError =>
|
|
73
|
+
new ServCraftError(`Missing dependency: "${dependency}"`, [
|
|
74
|
+
`Run ${chalk.cyan(command)} to install`,
|
|
75
|
+
`Check your ${chalk.yellow('package.json')}`,
|
|
76
|
+
]),
|
|
77
|
+
|
|
78
|
+
INVALID_FIELD_FORMAT: (field: string): ServCraftError =>
|
|
79
|
+
new ServCraftError(`Invalid field format: "${field}"`, [
|
|
80
|
+
`Expected format: ${chalk.cyan('name:type')}`,
|
|
81
|
+
`Example: ${chalk.cyan('name:string age:number isActive:boolean')}`,
|
|
82
|
+
`Supported types: string, number, boolean, date`,
|
|
83
|
+
]),
|
|
84
|
+
|
|
85
|
+
GIT_NOT_INITIALIZED: (): ServCraftError =>
|
|
86
|
+
new ServCraftError('Git repository not initialized', [
|
|
87
|
+
`Run ${chalk.cyan('git init')} to initialize git`,
|
|
88
|
+
`This is required for some ServCraft features`,
|
|
89
|
+
]),
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
// Display error with suggestions
|
|
93
|
+
export function displayError(error: Error | ServCraftError): void {
|
|
94
|
+
console.error('\n' + chalk.red.bold('✗ Error: ') + chalk.red(error.message));
|
|
95
|
+
|
|
96
|
+
if (error instanceof ServCraftError) {
|
|
97
|
+
if (error.suggestions.length > 0) {
|
|
98
|
+
console.log('\n' + chalk.yellow.bold('💡 Suggestions:'));
|
|
99
|
+
error.suggestions.forEach((suggestion) => {
|
|
100
|
+
console.log(chalk.yellow(' • ') + suggestion);
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (error.docsLink) {
|
|
105
|
+
console.log(
|
|
106
|
+
'\n' + chalk.blue.bold('📚 Documentation: ') + chalk.blue.underline(error.docsLink)
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
console.log(); // Empty line for spacing
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Handle common Node.js errors
|
|
115
|
+
export function handleSystemError(err: NodeJS.ErrnoException): ServCraftError {
|
|
116
|
+
switch (err.code) {
|
|
117
|
+
case 'ENOENT':
|
|
118
|
+
return new ServCraftError(`File or directory not found: ${err.path}`, [
|
|
119
|
+
`Check if the path exists`,
|
|
120
|
+
`Create the directory first`,
|
|
121
|
+
]);
|
|
122
|
+
|
|
123
|
+
case 'EACCES':
|
|
124
|
+
case 'EPERM':
|
|
125
|
+
return new ServCraftError(`Permission denied: ${err.path}`, [
|
|
126
|
+
`Check file permissions`,
|
|
127
|
+
`Try running with elevated privileges (not recommended)`,
|
|
128
|
+
`Change ownership of the directory`,
|
|
129
|
+
]);
|
|
130
|
+
|
|
131
|
+
case 'EEXIST':
|
|
132
|
+
return new ServCraftError(`File or directory already exists: ${err.path}`, [
|
|
133
|
+
`Use a different name`,
|
|
134
|
+
`Remove the existing file first`,
|
|
135
|
+
`Use ${chalk.cyan('--force')} to overwrite`,
|
|
136
|
+
]);
|
|
137
|
+
|
|
138
|
+
case 'ENOTDIR':
|
|
139
|
+
return new ServCraftError(`Not a directory: ${err.path}`, [
|
|
140
|
+
`Check the path`,
|
|
141
|
+
`A file exists where a directory is expected`,
|
|
142
|
+
]);
|
|
143
|
+
|
|
144
|
+
case 'EISDIR':
|
|
145
|
+
return new ServCraftError(`Is a directory: ${err.path}`, [
|
|
146
|
+
`Cannot perform this operation on a directory`,
|
|
147
|
+
`Did you mean to target a file?`,
|
|
148
|
+
]);
|
|
149
|
+
|
|
150
|
+
default:
|
|
151
|
+
return new ServCraftError(err.message, [
|
|
152
|
+
`Check system error code: ${err.code}`,
|
|
153
|
+
`Review the error details above`,
|
|
154
|
+
]);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Validate project structure
|
|
159
|
+
export function validateProject(): ServCraftError | null {
|
|
160
|
+
try {
|
|
161
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
162
|
+
const fs = require('fs');
|
|
163
|
+
|
|
164
|
+
if (!fs.existsSync('package.json')) {
|
|
165
|
+
return ErrorTypes.NOT_IN_PROJECT();
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const packageJson = JSON.parse(fs.readFileSync('package.json', 'utf-8'));
|
|
169
|
+
|
|
170
|
+
if (!packageJson.dependencies?.fastify) {
|
|
171
|
+
return new ServCraftError('This does not appear to be a ServCraft project', [
|
|
172
|
+
`ServCraft projects require Fastify`,
|
|
173
|
+
`Run ${chalk.cyan('servcraft init')} to create a new project`,
|
|
174
|
+
]);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return null;
|
|
178
|
+
} catch {
|
|
179
|
+
return new ServCraftError('Failed to validate project', [
|
|
180
|
+
`Ensure you are in the project root directory`,
|
|
181
|
+
`Check if ${chalk.yellow('package.json')} is valid`,
|
|
182
|
+
]);
|
|
183
|
+
}
|
|
184
|
+
}
|
package/src/cli/utils/helpers.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import fs from 'fs/promises';
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import chalk from 'chalk';
|
|
4
|
+
import { DryRunManager } from './dry-run.js';
|
|
4
5
|
|
|
5
6
|
export function toPascalCase(str: string): string {
|
|
6
7
|
return str
|
|
@@ -52,6 +53,18 @@ export async function ensureDir(dirPath: string): Promise<void> {
|
|
|
52
53
|
}
|
|
53
54
|
|
|
54
55
|
export async function writeFile(filePath: string, content: string): Promise<void> {
|
|
56
|
+
const dryRun = DryRunManager.getInstance();
|
|
57
|
+
|
|
58
|
+
if (dryRun.isEnabled()) {
|
|
59
|
+
dryRun.addOperation({
|
|
60
|
+
type: 'create',
|
|
61
|
+
path: dryRun.relativePath(filePath),
|
|
62
|
+
content,
|
|
63
|
+
size: content.length,
|
|
64
|
+
});
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
|
|
55
68
|
await ensureDir(path.dirname(filePath));
|
|
56
69
|
await fs.writeFile(filePath, content, 'utf-8');
|
|
57
70
|
}
|