frontend-hamroun 1.2.28 → 1.2.30
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 +515 -591
- package/package.json +1 -1
package/bin/cli.js
CHANGED
@@ -2,668 +2,592 @@
|
|
2
2
|
|
3
3
|
import { Command } from 'commander';
|
4
4
|
import inquirer from 'inquirer';
|
5
|
-
import fs from 'fs-extra';
|
6
|
-
import path from 'path';
|
7
|
-
import { fileURLToPath } from 'url';
|
8
5
|
import chalk from 'chalk';
|
9
6
|
import { createSpinner } from 'nanospinner';
|
7
|
+
import path from 'path';
|
8
|
+
import fs from 'fs-extra';
|
9
|
+
import { fileURLToPath } from 'url';
|
10
|
+
import { exec } from 'child_process';
|
11
|
+
import { promisify } from 'util';
|
10
12
|
|
13
|
+
// Convert to ESM-friendly __dirname equivalent
|
11
14
|
const __filename = fileURLToPath(import.meta.url);
|
12
15
|
const __dirname = path.dirname(__filename);
|
16
|
+
const execAsync = promisify(exec);
|
13
17
|
|
14
|
-
//
|
15
|
-
const
|
18
|
+
// CLI instance
|
19
|
+
const program = new Command();
|
16
20
|
|
17
|
-
|
18
|
-
|
19
|
-
|
21
|
+
// Create ASCII art banner
|
22
|
+
const banner = `
|
23
|
+
${chalk.blue('╔══════════════════════════════════════════════╗')}
|
24
|
+
${chalk.blue('║')} ${chalk.bold.cyan('Frontend Hamroun')} ${chalk.blue('║')}
|
25
|
+
${chalk.blue('║')} ${chalk.yellow('A lightweight full-stack JavaScript framework')} ${chalk.blue('║')}
|
26
|
+
${chalk.blue('╚══════════════════════════════════════════════╝')}
|
27
|
+
`;
|
20
28
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
};
|
27
|
-
}, []);
|
29
|
+
// Version and description
|
30
|
+
program
|
31
|
+
.name('frontend-hamroun')
|
32
|
+
.description('CLI for Frontend Hamroun - A lightweight full-stack JavaScript framework')
|
33
|
+
.version(JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'package.json'), 'utf8')).version);
|
28
34
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
);
|
35
|
-
}
|
36
|
-
`;
|
35
|
+
// Helper for creating visually consistent sections
|
36
|
+
const createSection = (title) => {
|
37
|
+
console.log('\n' + chalk.bold.cyan(`◆ ${title}`));
|
38
|
+
console.log(chalk.cyan('─'.repeat(50)) + '\n');
|
39
|
+
};
|
37
40
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
41
|
+
// Helper for checking dependencies
|
42
|
+
async function checkDependencies() {
|
43
|
+
const spinner = createSpinner('Checking dependencies...').start();
|
44
|
+
try {
|
45
|
+
await execAsync('npm --version');
|
46
|
+
spinner.success({ text: 'Dependencies verified' });
|
47
|
+
return true;
|
48
|
+
} catch (error) {
|
49
|
+
spinner.error({ text: 'Missing required dependencies' });
|
50
|
+
console.log(chalk.red('Error: Node.js and npm are required to use this tool.'));
|
51
|
+
return false;
|
52
|
+
}
|
45
53
|
}
|
46
|
-
`;
|
47
54
|
|
48
|
-
|
49
|
-
|
55
|
+
// Choose template interactively
|
56
|
+
async function chooseTemplate() {
|
57
|
+
const templatesPath = path.join(__dirname, '..', 'templates');
|
58
|
+
const templates = fs.readdirSync(templatesPath).filter(file =>
|
59
|
+
fs.statSync(path.join(templatesPath, file)).isDirectory()
|
60
|
+
);
|
50
61
|
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
62
|
+
const templateOptions = templates.map(template => {
|
63
|
+
let description = '';
|
64
|
+
switch (template) {
|
65
|
+
case 'basic-app':
|
66
|
+
description = 'Simple client-side application with minimal setup';
|
67
|
+
break;
|
68
|
+
case 'ssr-template':
|
69
|
+
description = 'Server-side rendering with hydration support';
|
70
|
+
break;
|
71
|
+
case 'fullstack-app':
|
72
|
+
description = 'Complete fullstack application with API routes';
|
73
|
+
break;
|
74
|
+
default:
|
75
|
+
description = 'Application template';
|
76
|
+
}
|
77
|
+
return {
|
78
|
+
name: `${chalk.bold(template)} - ${chalk.dim(description)}`,
|
79
|
+
value: template
|
80
|
+
};
|
56
81
|
});
|
57
|
-
});
|
58
|
-
`;
|
59
|
-
|
60
|
-
// Dockerfile templates
|
61
|
-
const DOCKERFILE_TEMPLATE = `# Stage 1: Build the application
|
62
|
-
FROM node:18-alpine as build
|
63
|
-
|
64
|
-
# Set working directory
|
65
|
-
WORKDIR /app
|
66
|
-
|
67
|
-
# Copy package files
|
68
|
-
COPY package.json package-lock.json ./
|
69
|
-
|
70
|
-
# Install dependencies
|
71
|
-
RUN npm ci
|
72
|
-
|
73
|
-
# Copy source files
|
74
|
-
COPY . .
|
75
82
|
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
# Expose port 80
|
86
|
-
EXPOSE 80
|
87
|
-
|
88
|
-
# Start nginx
|
89
|
-
CMD ["nginx", "-g", "daemon off;"]
|
90
|
-
`;
|
91
|
-
|
92
|
-
const SSR_DOCKERFILE_TEMPLATE = `# Stage 1: Build the application
|
93
|
-
FROM node:18-alpine as build
|
94
|
-
|
95
|
-
# Set working directory
|
96
|
-
WORKDIR /app
|
97
|
-
|
98
|
-
# Copy package files
|
99
|
-
COPY package.json package-lock.json ./
|
100
|
-
|
101
|
-
# Install dependencies
|
102
|
-
RUN npm ci
|
103
|
-
|
104
|
-
# Copy source files
|
105
|
-
COPY . .
|
106
|
-
|
107
|
-
# Build the application
|
108
|
-
RUN npm run build
|
109
|
-
|
110
|
-
# Stage 2: Run the server
|
111
|
-
FROM node:18-alpine
|
112
|
-
|
113
|
-
WORKDIR /app
|
83
|
+
const answers = await inquirer.prompt([
|
84
|
+
{
|
85
|
+
type: 'list',
|
86
|
+
name: 'template',
|
87
|
+
message: chalk.green('Select a project template:'),
|
88
|
+
choices: templateOptions,
|
89
|
+
loop: false
|
90
|
+
}
|
91
|
+
]);
|
114
92
|
|
115
|
-
|
116
|
-
|
117
|
-
RUN npm ci --production
|
93
|
+
return answers.template;
|
94
|
+
}
|
118
95
|
|
119
|
-
|
120
|
-
|
121
|
-
|
96
|
+
// Create a new project
|
97
|
+
async function createProject(projectName, options) {
|
98
|
+
console.log(banner);
|
99
|
+
createSection('Project Setup');
|
100
|
+
|
101
|
+
if (!projectName) {
|
102
|
+
const answers = await inquirer.prompt([
|
103
|
+
{
|
104
|
+
type: 'input',
|
105
|
+
name: 'projectName',
|
106
|
+
message: chalk.green('What is your project name?'),
|
107
|
+
default: 'my-frontend-app',
|
108
|
+
validate: input =>
|
109
|
+
/^[a-z0-9-_]+$/.test(input)
|
110
|
+
? true
|
111
|
+
: 'Project name can only contain lowercase letters, numbers, hyphens, and underscores'
|
112
|
+
}
|
113
|
+
]);
|
114
|
+
projectName = answers.projectName;
|
115
|
+
}
|
122
116
|
|
123
|
-
|
124
|
-
EXPOSE 3000
|
117
|
+
if (!await checkDependencies()) return;
|
125
118
|
|
126
|
-
|
127
|
-
CMD ["node", "server/index.js"]
|
128
|
-
`;
|
119
|
+
let template = options.template || await chooseTemplate();
|
129
120
|
|
130
|
-
|
131
|
-
const
|
132
|
-
|
133
|
-
program
|
134
|
-
.name('frontend-hamroun')
|
135
|
-
.description('CLI for Frontend Hamroun framework')
|
136
|
-
.version('1.0.0');
|
137
|
-
|
138
|
-
// Create new project
|
139
|
-
program
|
140
|
-
.command('create')
|
141
|
-
.description('Create a new Frontend Hamroun application')
|
142
|
-
.argument('[name]', 'Project name')
|
143
|
-
.action(async (name) => {
|
144
|
-
const projectName = name || await askProjectName();
|
145
|
-
await createProject(projectName);
|
146
|
-
});
|
147
|
-
|
148
|
-
// Generate component
|
149
|
-
program
|
150
|
-
.command('generate')
|
151
|
-
.alias('g')
|
152
|
-
.description('Generate a new component')
|
153
|
-
.argument('<name>', 'Component name')
|
154
|
-
.option('-d, --directory <directory>', 'Target directory', './src/components')
|
155
|
-
.action(async (name, options) => {
|
156
|
-
await generateComponent(name, options.directory);
|
157
|
-
});
|
158
|
-
|
159
|
-
// Add Dockerfile
|
160
|
-
program
|
161
|
-
.command('docker')
|
162
|
-
.description('Add Dockerfile to project')
|
163
|
-
.option('-s, --ssr', 'Use SSR-compatible Dockerfile')
|
164
|
-
.action(async (options) => {
|
165
|
-
await addDockerfile(options.ssr);
|
166
|
-
});
|
167
|
-
|
168
|
-
// Add generate API route command
|
169
|
-
program
|
170
|
-
.command('api')
|
171
|
-
.description('Generate a new API route')
|
172
|
-
.argument('<name>', 'API route name (e.g., users or users/[id])')
|
173
|
-
.option('-d, --directory <directory>', 'Target directory', './api')
|
174
|
-
.action(async (name, options) => {
|
175
|
-
await generateApiRoute(name, options.directory);
|
176
|
-
});
|
177
|
-
|
178
|
-
// Interactive mode if no command provided
|
179
|
-
if (process.argv.length <= 2) {
|
180
|
-
await interactiveMode();
|
181
|
-
} else {
|
182
|
-
program.parse();
|
183
|
-
}
|
184
|
-
}
|
121
|
+
const targetDir = path.resolve(projectName);
|
122
|
+
const templateDir = path.join(__dirname, '..', 'templates', template);
|
185
123
|
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
if (action === 'create') {
|
200
|
-
const projectName = await askProjectName();
|
201
|
-
await createProject(projectName);
|
202
|
-
} else if (action === 'generate') {
|
203
|
-
const { name } = await inquirer.prompt([{
|
204
|
-
type: 'input',
|
205
|
-
name: 'name',
|
206
|
-
message: 'Component name:',
|
207
|
-
validate: (input) => input ? true : 'Component name is required'
|
208
|
-
}]);
|
209
|
-
|
210
|
-
const { directory } = await inquirer.prompt([{
|
211
|
-
type: 'input',
|
212
|
-
name: 'directory',
|
213
|
-
message: 'Target directory:',
|
214
|
-
default: './src/components'
|
215
|
-
}]);
|
216
|
-
|
217
|
-
await generateComponent(name, directory);
|
218
|
-
} else if (action === 'api') {
|
219
|
-
const { name } = await inquirer.prompt([{
|
220
|
-
type: 'input',
|
221
|
-
name: 'name',
|
222
|
-
message: 'API route name:',
|
223
|
-
validate: (input) => input ? true : 'API route name is required'
|
224
|
-
}]);
|
225
|
-
|
226
|
-
const { directory } = await inquirer.prompt([{
|
227
|
-
type: 'input',
|
228
|
-
name: 'directory',
|
229
|
-
message: 'Target directory:',
|
230
|
-
default: './api'
|
231
|
-
}]);
|
232
|
-
|
233
|
-
await generateApiRoute(name, directory);
|
234
|
-
} else if (action === 'docker') {
|
235
|
-
const { isSSR } = await inquirer.prompt([{
|
236
|
-
type: 'confirm',
|
237
|
-
name: 'isSSR',
|
238
|
-
message: 'Is this a server-side rendered app?',
|
239
|
-
default: false
|
240
|
-
}]);
|
241
|
-
|
242
|
-
await addDockerfile(isSSR);
|
124
|
+
if (fs.existsSync(targetDir)) {
|
125
|
+
const answers = await inquirer.prompt([
|
126
|
+
{
|
127
|
+
type: 'confirm',
|
128
|
+
name: 'overwrite',
|
129
|
+
message: chalk.yellow(`Directory ${projectName} already exists. Overwrite?`),
|
130
|
+
default: false
|
131
|
+
}
|
132
|
+
]);
|
133
|
+
if (!answers.overwrite) {
|
134
|
+
console.log(chalk.red('✖ Operation cancelled'));
|
135
|
+
return;
|
136
|
+
}
|
243
137
|
}
|
244
|
-
}
|
245
|
-
|
246
|
-
async function askProjectName() {
|
247
|
-
const { projectName } = await inquirer.prompt([{
|
248
|
-
type: 'input',
|
249
|
-
name: 'projectName',
|
250
|
-
message: 'What is your project named?',
|
251
|
-
default: 'my-frontend-app'
|
252
|
-
}]);
|
253
|
-
return projectName;
|
254
|
-
}
|
255
138
|
|
256
|
-
async function askProjectType() {
|
257
|
-
const { template } = await inquirer.prompt([{
|
258
|
-
type: 'list',
|
259
|
-
name: 'template',
|
260
|
-
message: 'Select project type:',
|
261
|
-
choices: [
|
262
|
-
{ name: 'Client Side App', value: 'basic-app' },
|
263
|
-
{ name: 'Server Side Rendered App', value: 'ssr-template' },
|
264
|
-
{ name: 'Full Stack App', value: 'fullstack-app' }
|
265
|
-
]
|
266
|
-
}]);
|
267
|
-
return template;
|
268
|
-
}
|
269
|
-
|
270
|
-
async function createProject(projectName) {
|
271
139
|
const spinner = createSpinner('Creating project...').start();
|
272
|
-
|
273
140
|
try {
|
274
|
-
const template = await askProjectType();
|
275
|
-
const templateDir = path.join(__dirname, '..', 'templates', template);
|
276
|
-
const targetDir = path.join(process.cwd(), projectName);
|
277
|
-
|
278
|
-
// Create project directory
|
279
141
|
await fs.ensureDir(targetDir);
|
142
|
+
await fs.copy(templateDir, targetDir, { overwrite: true });
|
280
143
|
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
pkg.name = projectName;
|
288
|
-
await fs.writeJson(pkgPath, pkg, { spaces: 2 });
|
289
|
-
|
290
|
-
// Automatically add Dockerfile
|
291
|
-
const isSSR = template === 'ssr-template';
|
292
|
-
const dockerContent = isSSR ? SSR_DOCKERFILE_TEMPLATE : DOCKERFILE_TEMPLATE;
|
293
|
-
await fs.writeFile(path.join(targetDir, 'Dockerfile'), dockerContent);
|
294
|
-
|
295
|
-
spinner.success({ text: `Project ${chalk.green(projectName)} created successfully with Dockerfile!` });
|
144
|
+
const pkgJsonPath = path.join(targetDir, 'package.json');
|
145
|
+
if (fs.existsSync(pkgJsonPath)) {
|
146
|
+
const pkgJson = await fs.readJson(pkgJsonPath);
|
147
|
+
pkgJson.name = projectName;
|
148
|
+
await fs.writeJson(pkgJsonPath, pkgJson, { spaces: 2 });
|
149
|
+
}
|
296
150
|
|
297
|
-
|
298
|
-
console.log('\nNext steps:');
|
299
|
-
console.log(chalk.cyan(` cd ${projectName}`));
|
300
|
-
console.log(chalk.cyan(' npm install'));
|
301
|
-
console.log(chalk.cyan(' npm run dev'));
|
302
|
-
console.log(chalk.yellow('\nTo build Docker image:'));
|
303
|
-
console.log(chalk.cyan(' docker build -t my-app .'));
|
304
|
-
console.log(chalk.cyan(' docker run -p 3000:' + (isSSR ? '3000' : '80') + ' my-app'));
|
151
|
+
spinner.success({ text: `Project created successfully at ${chalk.green(targetDir)}` });
|
305
152
|
|
153
|
+
createSection('Next Steps');
|
154
|
+
console.log(`${chalk.bold.green('✓')} Run the following commands to get started:\n`);
|
155
|
+
console.log(` ${chalk.cyan('cd')} ${projectName}`);
|
156
|
+
console.log(` ${chalk.cyan('npm install')}`);
|
157
|
+
console.log(` ${chalk.cyan('npm run dev')}\n`);
|
306
158
|
} catch (error) {
|
307
159
|
spinner.error({ text: 'Failed to create project' });
|
308
|
-
console.error(chalk.red(error)
|
309
|
-
process.exit(1);
|
160
|
+
console.error(chalk.red('Error: ') + error.message);
|
310
161
|
}
|
311
162
|
}
|
312
163
|
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
await
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
);
|
332
|
-
|
333
|
-
await fs.writeFile(
|
334
|
-
path.join(targetDir, `${name}.test.jsx`),
|
335
|
-
TEST_TEMPLATE(name)
|
336
|
-
);
|
337
|
-
|
338
|
-
await fs.writeFile(
|
339
|
-
path.join(targetDir, 'index.js'),
|
340
|
-
`export { ${name} } from './${name}';\n`
|
341
|
-
);
|
342
|
-
|
343
|
-
spinner.success({ text: `Component ${chalk.green(name)} generated successfully!` });
|
344
|
-
|
345
|
-
console.log('\nFiles created:');
|
346
|
-
console.log(chalk.cyan(` ${path.join(directory, name, `${name}.jsx`)}`));
|
347
|
-
console.log(chalk.cyan(` ${path.join(directory, name, `${name}.css`)}`));
|
348
|
-
console.log(chalk.cyan(` ${path.join(directory, name, `${name}.test.jsx`)}`));
|
349
|
-
console.log(chalk.cyan(` ${path.join(directory, name, 'index.js')}`));
|
350
|
-
|
351
|
-
} catch (error) {
|
352
|
-
spinner.error({ text: 'Failed to generate component' });
|
353
|
-
console.error(chalk.red(error));
|
354
|
-
process.exit(1);
|
164
|
+
// Add component to existing project
|
165
|
+
async function addComponent(componentName, options) {
|
166
|
+
console.log(banner);
|
167
|
+
createSection('Create Component');
|
168
|
+
|
169
|
+
// Validate component name
|
170
|
+
if (!componentName) {
|
171
|
+
const answers = await inquirer.prompt([
|
172
|
+
{
|
173
|
+
type: 'input',
|
174
|
+
name: 'componentName',
|
175
|
+
message: 'What is your component name?',
|
176
|
+
validate: input => /^[A-Z][A-Za-z0-9]*$/.test(input)
|
177
|
+
? true
|
178
|
+
: 'Component name must start with uppercase letter and only contain alphanumeric characters'
|
179
|
+
}
|
180
|
+
]);
|
181
|
+
componentName = answers.componentName;
|
355
182
|
}
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
// Create API route file
|
371
|
-
const isDynamic = routeFileName.startsWith('[') && routeFileName.endsWith(']');
|
372
|
-
const template = isDynamic ? DYNAMIC_API_ROUTE_TEMPLATE : API_ROUTE_TEMPLATE;
|
373
|
-
|
374
|
-
await fs.writeFile(targetPath, template);
|
375
|
-
|
376
|
-
// Check if server.ts file exists, if not create it
|
377
|
-
const serverFilePath = path.join(process.cwd(), 'server.ts');
|
378
|
-
if (!await fs.pathExists(serverFilePath)) {
|
379
|
-
await fs.writeFile(serverFilePath, SERVER_TEMPLATE);
|
380
|
-
console.log(chalk.green('\nCreated server.ts file with Express setup'));
|
381
|
-
|
382
|
-
// Create tsconfig.server.json if it doesn't exist
|
383
|
-
const tsconfigPath = path.join(process.cwd(), 'tsconfig.server.json');
|
384
|
-
if (!await fs.pathExists(tsconfigPath)) {
|
385
|
-
await fs.writeFile(tsconfigPath, TSCONFIG_SERVER_TEMPLATE);
|
386
|
-
console.log(chalk.green('Created tsconfig.server.json for server-side TypeScript'));
|
183
|
+
|
184
|
+
// Determine file extension preference
|
185
|
+
let extension = options.typescript ? '.tsx' : options.jsx ? '.jsx' : null;
|
186
|
+
|
187
|
+
if (!extension) {
|
188
|
+
const answers = await inquirer.prompt([
|
189
|
+
{
|
190
|
+
type: 'list',
|
191
|
+
name: 'extension',
|
192
|
+
message: 'Select file type:',
|
193
|
+
choices: [
|
194
|
+
{ name: 'TypeScript (.tsx)', value: '.tsx' },
|
195
|
+
{ name: 'JavaScript (.jsx)', value: '.jsx' }
|
196
|
+
]
|
387
197
|
}
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
"bcryptjs": "^2.4.3" // Add password hashing support
|
403
|
-
};
|
404
|
-
|
405
|
-
const devDeps = {
|
406
|
-
"@types/express": "^4.17.17",
|
407
|
-
"@types/cors": "^2.8.13",
|
408
|
-
"@types/mongodb": "^4.0.7",
|
409
|
-
"@types/jsonwebtoken": "^9.0.3",
|
410
|
-
"@types/bcryptjs": "^2.4.4"
|
411
|
-
};
|
412
|
-
|
413
|
-
// Add dependencies if needed
|
414
|
-
pkg.dependencies = pkg.dependencies || {};
|
415
|
-
for (const [dep, version] of Object.entries(serverDeps)) {
|
416
|
-
if (!pkg.dependencies[dep]) {
|
417
|
-
pkg.dependencies[dep] = version;
|
418
|
-
needsUpdate = true;
|
419
|
-
}
|
420
|
-
}
|
421
|
-
|
422
|
-
// Add dev dependencies if needed
|
423
|
-
pkg.devDependencies = pkg.devDependencies || {};
|
424
|
-
for (const [dep, version] of Object.entries(devDeps)) {
|
425
|
-
if (!pkg.devDependencies[dep]) {
|
426
|
-
pkg.devDependencies[dep] = version;
|
427
|
-
needsUpdate = true;
|
428
|
-
}
|
429
|
-
}
|
430
|
-
|
431
|
-
// Add start script if it doesn't exist
|
432
|
-
pkg.scripts = pkg.scripts || {};
|
433
|
-
if (!pkg.scripts.start) {
|
434
|
-
pkg.scripts.start = "node server.js";
|
435
|
-
needsUpdate = true;
|
436
|
-
}
|
437
|
-
|
438
|
-
// Save changes if needed
|
439
|
-
if (needsUpdate) {
|
440
|
-
await fs.writeJson(pkgPath, pkg, { spaces: 2 });
|
441
|
-
console.log(chalk.green('Updated package.json with server dependencies'));
|
442
|
-
}
|
443
|
-
}
|
444
|
-
} catch (error) {
|
445
|
-
console.warn(chalk.yellow('Could not update package.json:', error.message));
|
198
|
+
]);
|
199
|
+
extension = answers.extension;
|
200
|
+
}
|
201
|
+
|
202
|
+
// Determine component directory
|
203
|
+
let componentPath = options.path || 'src/components';
|
204
|
+
if (!options.path) {
|
205
|
+
const answers = await inquirer.prompt([
|
206
|
+
{
|
207
|
+
type: 'input',
|
208
|
+
name: 'path',
|
209
|
+
message: 'Where do you want to create this component?',
|
210
|
+
default: 'src/components',
|
211
|
+
validate: input => /^[a-zA-Z0-9-_/\\]+$/.test(input) ? true : 'Path can only contain letters, numbers, slashes, hyphens and underscores'
|
446
212
|
}
|
447
|
-
|
448
|
-
|
449
|
-
spinner.success({ text: `API route ${chalk.green(name)} generated successfully!` });
|
450
|
-
|
451
|
-
console.log('\nFile created:');
|
452
|
-
console.log(chalk.cyan(` ${path.join(directory, routePath)}.ts`));
|
453
|
-
|
454
|
-
} catch (error) {
|
455
|
-
spinner.error({ text: 'Failed to generate API route' });
|
456
|
-
console.error(chalk.red(error));
|
457
|
-
process.exit(1);
|
213
|
+
]);
|
214
|
+
componentPath = answers.path;
|
458
215
|
}
|
216
|
+
|
217
|
+
// Create component content based on type
|
218
|
+
const fullPath = path.join(process.cwd(), componentPath, `${componentName}${extension}`);
|
219
|
+
const dirPath = path.dirname(fullPath);
|
220
|
+
|
221
|
+
// Sample template
|
222
|
+
const componentTemplate = extension === '.tsx'
|
223
|
+
? `import { jsx } from 'frontend-hamroun';
|
224
|
+
|
225
|
+
interface ${componentName}Props {
|
226
|
+
title?: string;
|
227
|
+
children?: any;
|
459
228
|
}
|
460
229
|
|
461
|
-
|
462
|
-
|
463
|
-
|
464
|
-
|
230
|
+
export default function ${componentName}({ title, children }: ${componentName}Props) {
|
231
|
+
return (
|
232
|
+
<div className="component">
|
233
|
+
{title && <h2>{title}</h2>}
|
234
|
+
<div className="content">
|
235
|
+
{children}
|
236
|
+
</div>
|
237
|
+
</div>
|
238
|
+
);
|
239
|
+
}
|
240
|
+
`
|
241
|
+
: `import { jsx } from 'frontend-hamroun';
|
242
|
+
|
243
|
+
export default function ${componentName}({ title, children }) {
|
244
|
+
return (
|
245
|
+
<div className="component">
|
246
|
+
{title && <h2>{title}</h2>}
|
247
|
+
<div className="content">
|
248
|
+
{children}
|
249
|
+
</div>
|
250
|
+
</div>
|
251
|
+
);
|
252
|
+
}
|
253
|
+
`;
|
465
254
|
|
466
|
-
|
255
|
+
// Create the file
|
256
|
+
const spinner = createSpinner(`Creating ${componentName} component...`).start();
|
467
257
|
try {
|
468
|
-
|
469
|
-
|
470
|
-
|
471
|
-
// Create and configure the server
|
472
|
-
const app = new Server({
|
473
|
-
port: process.env.PORT ? parseInt(process.env.PORT) : 3000,
|
474
|
-
apiDir: './api',
|
475
|
-
pagesDir: './src/pages', // For SSR pages
|
476
|
-
staticDir: './public',
|
477
|
-
|
478
|
-
// Enable CORS
|
479
|
-
enableCors: true,
|
480
|
-
corsOptions: {
|
481
|
-
origin: process.env.CORS_ORIGIN || '*',
|
482
|
-
methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'],
|
483
|
-
allowedHeaders: ['Content-Type', 'Authorization']
|
484
|
-
}
|
485
|
-
});
|
486
|
-
|
487
|
-
// Connect to database if configured
|
488
|
-
if (app.getDatabase()) {
|
489
|
-
await app.getDatabase().connect();
|
490
|
-
console.log('Connected to database');
|
491
|
-
}
|
492
|
-
|
493
|
-
// Start the server
|
494
|
-
await app.start();
|
495
|
-
|
496
|
-
console.log('Server running at http://localhost:' +
|
497
|
-
(process.env.PORT || 3000));
|
498
|
-
|
499
|
-
// Handle graceful shutdown
|
500
|
-
process.on('SIGINT', async () => {
|
501
|
-
console.log('Shutting down server...');
|
502
|
-
if (app.getDatabase()) {
|
503
|
-
await app.getDatabase().disconnect();
|
504
|
-
console.log('Database connection closed');
|
505
|
-
}
|
506
|
-
await app.stop();
|
507
|
-
console.log('Server stopped');
|
508
|
-
process.exit(0);
|
509
|
-
});
|
258
|
+
await fs.ensureDir(dirPath);
|
259
|
+
await fs.writeFile(fullPath, componentTemplate);
|
260
|
+
spinner.success({ text: `Component created at ${chalk.green(fullPath)}` });
|
510
261
|
} catch (error) {
|
511
|
-
|
512
|
-
|
262
|
+
spinner.error({ text: 'Failed to create component' });
|
263
|
+
console.error(chalk.red('Error: ') + error.message);
|
513
264
|
}
|
514
265
|
}
|
515
266
|
|
516
|
-
|
517
|
-
|
518
|
-
|
519
|
-
|
520
|
-
const spinner = createSpinner('Adding Dockerfile...').start();
|
267
|
+
// Create a page component
|
268
|
+
async function addPage(pageName, options) {
|
269
|
+
console.log(banner);
|
270
|
+
createSection('Create Page');
|
521
271
|
|
522
|
-
|
523
|
-
|
524
|
-
const
|
525
|
-
|
526
|
-
|
527
|
-
|
528
|
-
|
529
|
-
|
530
|
-
|
531
|
-
|
532
|
-
message: 'Dockerfile already exists. Overwrite?',
|
533
|
-
default: false
|
534
|
-
}]);
|
535
|
-
|
536
|
-
if (!overwrite) {
|
537
|
-
console.log(chalk.yellow('Operation cancelled.'));
|
538
|
-
return;
|
272
|
+
// Validate page name
|
273
|
+
if (!pageName) {
|
274
|
+
const answers = await inquirer.prompt([
|
275
|
+
{
|
276
|
+
type: 'input',
|
277
|
+
name: 'pageName',
|
278
|
+
message: 'What is your page name?',
|
279
|
+
validate: input => /^[a-zA-Z0-9-_]+$/.test(input)
|
280
|
+
? true
|
281
|
+
: 'Page name can only contain alphanumeric characters, hyphens and underscores'
|
539
282
|
}
|
540
|
-
|
541
|
-
|
542
|
-
}
|
543
|
-
|
544
|
-
// Write Dockerfile
|
545
|
-
await fs.writeFile(targetPath, dockerContent);
|
546
|
-
|
547
|
-
spinner.success({ text: 'Dockerfile added successfully!' });
|
548
|
-
|
549
|
-
console.log('\nTo build and run Docker image:');
|
550
|
-
console.log(chalk.cyan(' docker build -t my-app .'));
|
551
|
-
console.log(chalk.cyan(' docker run -p 3000:' + (isSSR ? '3000' : '80') + ' my-app'));
|
552
|
-
|
553
|
-
} catch (error) {
|
554
|
-
spinner.error({ text: 'Failed to add Dockerfile' });
|
555
|
-
console.error(chalk.red(error));
|
556
|
-
process.exit(1);
|
283
|
+
]);
|
284
|
+
pageName = answers.pageName;
|
557
285
|
}
|
558
|
-
|
286
|
+
|
287
|
+
// Format page name to be PascalCase for the component name
|
288
|
+
const pageComponentName = pageName
|
289
|
+
.split('-')
|
290
|
+
.map(part => part.charAt(0).toUpperCase() + part.slice(1))
|
291
|
+
.join('') + 'Page';
|
292
|
+
|
293
|
+
// Determine file extension preference
|
294
|
+
let extension = options.typescript ? '.tsx' : options.jsx ? '.jsx' : null;
|
295
|
+
|
296
|
+
if (!extension) {
|
297
|
+
const answers = await inquirer.prompt([
|
298
|
+
{
|
299
|
+
type: 'list',
|
300
|
+
name: 'extension',
|
301
|
+
message: 'Select file type:',
|
302
|
+
choices: [
|
303
|
+
{ name: 'TypeScript (.tsx)', value: '.tsx' },
|
304
|
+
{ name: 'JavaScript (.jsx)', value: '.jsx' }
|
305
|
+
]
|
306
|
+
}
|
307
|
+
]);
|
308
|
+
extension = answers.extension;
|
309
|
+
}
|
310
|
+
|
311
|
+
// Create page file path
|
312
|
+
const pagePath = options.path || 'src/pages';
|
313
|
+
|
314
|
+
// For index page, use the directory itself
|
315
|
+
const fileName = pageName === 'index' ? 'index' : pageName;
|
316
|
+
const fullPath = path.join(process.cwd(), pagePath, `${fileName}${extension}`);
|
317
|
+
const dirPath = path.dirname(fullPath);
|
318
|
+
|
319
|
+
// Page template
|
320
|
+
const pageTemplate = extension === '.tsx'
|
321
|
+
? `import { jsx } from 'frontend-hamroun';
|
322
|
+
import Layout from '../components/Layout';
|
559
323
|
|
560
|
-
|
561
|
-
|
324
|
+
interface ${pageComponentName}Props {
|
325
|
+
initialState?: any;
|
326
|
+
}
|
562
327
|
|
563
|
-
|
564
|
-
|
565
|
-
|
566
|
-
|
567
|
-
|
568
|
-
|
328
|
+
const ${pageComponentName}: React.FC<${pageComponentName}Props> = ({ initialState }) => {
|
329
|
+
return (
|
330
|
+
<Layout title="${pageName.split('-').map(word => word.charAt(0).toUpperCase() + word.slice(1)).join(' ')}">
|
331
|
+
<div className="page">
|
332
|
+
<h1>${pageName.split('-').map(word => word.charAt(0).toUpperCase() + word.slice(1)).join(' ')}</h1>
|
333
|
+
<p>Welcome to this page!</p>
|
334
|
+
</div>
|
335
|
+
</Layout>
|
336
|
+
);
|
569
337
|
};
|
570
338
|
|
571
|
-
|
572
|
-
|
573
|
-
|
574
|
-
|
575
|
-
|
576
|
-
}
|
339
|
+
// Optional: Add data fetching for SSR support
|
340
|
+
${pageComponentName}.getInitialData = async () => {
|
341
|
+
// You can fetch data for server-side rendering here
|
342
|
+
return {
|
343
|
+
// Your data here
|
344
|
+
};
|
577
345
|
};
|
578
346
|
|
579
|
-
export
|
580
|
-
|
581
|
-
|
582
|
-
|
583
|
-
|
584
|
-
|
347
|
+
export default ${pageComponentName};
|
348
|
+
`
|
349
|
+
: `import { jsx } from 'frontend-hamroun';
|
350
|
+
import Layout from '../components/Layout';
|
351
|
+
|
352
|
+
const ${pageComponentName} = ({ initialState }) => {
|
353
|
+
return (
|
354
|
+
<Layout title="${pageName.split('-').map(word => word.charAt(0).toUpperCase() + word.slice(1)).join(' ')}">
|
355
|
+
<div className="page">
|
356
|
+
<h1>${pageName.split('-').map(word => word.charAt(0).toUpperCase() + word.slice(1)).join(' ')}</h1>
|
357
|
+
<p>Welcome to this page!</p>
|
358
|
+
</div>
|
359
|
+
</Layout>
|
360
|
+
);
|
585
361
|
};
|
586
362
|
|
587
|
-
|
588
|
-
|
589
|
-
|
590
|
-
|
591
|
-
|
363
|
+
// Optional: Add data fetching for SSR support
|
364
|
+
${pageComponentName}.getInitialData = async () => {
|
365
|
+
// You can fetch data for server-side rendering here
|
366
|
+
return {
|
367
|
+
// Your data here
|
368
|
+
};
|
592
369
|
};
|
593
370
|
|
594
|
-
|
595
|
-
export const middleware = [
|
596
|
-
// Example middleware
|
597
|
-
(req: Request, res: Response, next: Function) => {
|
598
|
-
console.log(\`\${req.method} \${req.url} - \${new Date().toISOString()}\`);
|
599
|
-
next();
|
600
|
-
}
|
601
|
-
];
|
371
|
+
export default ${pageComponentName};
|
602
372
|
`;
|
603
373
|
|
604
|
-
|
374
|
+
// Create the file
|
375
|
+
const spinner = createSpinner(`Creating ${pageName} page...`).start();
|
376
|
+
try {
|
377
|
+
await fs.ensureDir(dirPath);
|
378
|
+
await fs.writeFile(fullPath, pageTemplate);
|
379
|
+
spinner.success({ text: `Page created at ${chalk.green(fullPath)}` });
|
380
|
+
} catch (error) {
|
381
|
+
spinner.error({ text: 'Failed to create page' });
|
382
|
+
console.error(chalk.red('Error: ') + error.message);
|
383
|
+
}
|
384
|
+
}
|
605
385
|
|
606
|
-
|
386
|
+
// Add API route
|
387
|
+
async function addApiRoute(routeName, options) {
|
388
|
+
console.log(banner);
|
389
|
+
createSection('Create API Route');
|
390
|
+
|
391
|
+
// Validate route name
|
392
|
+
if (!routeName) {
|
393
|
+
const answers = await inquirer.prompt([
|
394
|
+
{
|
395
|
+
type: 'input',
|
396
|
+
name: 'routeName',
|
397
|
+
message: 'What is your API route name?',
|
398
|
+
validate: input => /^[a-zA-Z0-9-_/[\]]+$/.test(input)
|
399
|
+
? true
|
400
|
+
: 'Route name can only contain alphanumeric characters, hyphens, underscores, slashes, and square brackets for dynamic parameters ([id])'
|
401
|
+
}
|
402
|
+
]);
|
403
|
+
routeName = answers.routeName;
|
404
|
+
}
|
405
|
+
|
406
|
+
// Determine file extension preference
|
407
|
+
let extension = options.typescript ? '.ts' : '.js';
|
408
|
+
|
409
|
+
if (!options.typescript && !options.javascript) {
|
410
|
+
const answers = await inquirer.prompt([
|
411
|
+
{
|
412
|
+
type: 'list',
|
413
|
+
name: 'extension',
|
414
|
+
message: 'Select file type:',
|
415
|
+
choices: [
|
416
|
+
{ name: 'TypeScript (.ts)', value: '.ts' },
|
417
|
+
{ name: 'JavaScript (.js)', value: '.js' }
|
418
|
+
]
|
419
|
+
}
|
420
|
+
]);
|
421
|
+
extension = answers.extension;
|
422
|
+
}
|
423
|
+
|
424
|
+
// Choose HTTP methods to implement
|
425
|
+
let methods = options.methods ? options.methods.split(',') : null;
|
426
|
+
|
427
|
+
if (!methods) {
|
428
|
+
const answers = await inquirer.prompt([
|
429
|
+
{
|
430
|
+
type: 'checkbox',
|
431
|
+
name: 'methods',
|
432
|
+
message: 'Select HTTP methods to implement:',
|
433
|
+
choices: [
|
434
|
+
{ name: 'GET', value: 'get', checked: true },
|
435
|
+
{ name: 'POST', value: 'post' },
|
436
|
+
{ name: 'PUT', value: 'put' },
|
437
|
+
{ name: 'DELETE', value: 'delete' },
|
438
|
+
{ name: 'PATCH', value: 'patch' }
|
439
|
+
],
|
440
|
+
validate: input => input.length > 0 ? true : 'Please select at least one method'
|
441
|
+
}
|
442
|
+
]);
|
443
|
+
methods = answers.methods;
|
444
|
+
}
|
445
|
+
|
446
|
+
// Create API route path
|
447
|
+
const routePath = options.path || 'api';
|
448
|
+
|
449
|
+
// Determine filename from route name
|
450
|
+
// If last segment includes parameters, handle specially
|
451
|
+
const segments = routeName.split('/').filter(Boolean);
|
452
|
+
let directory = '';
|
453
|
+
let filename = 'index';
|
454
|
+
|
455
|
+
if (segments.length > 0) {
|
456
|
+
if (segments.length === 1) {
|
457
|
+
filename = segments[0];
|
458
|
+
} else {
|
459
|
+
directory = segments.slice(0, -1).join('/');
|
460
|
+
filename = segments[segments.length - 1];
|
461
|
+
}
|
462
|
+
}
|
463
|
+
|
464
|
+
const fullPath = path.join(process.cwd(), routePath, directory, `${filename}${extension}`);
|
465
|
+
const dirPath = path.dirname(fullPath);
|
466
|
+
|
467
|
+
// API route template
|
468
|
+
let apiTemplate = extension === '.ts'
|
469
|
+
? `import { Request, Response } from 'express';\n\n`
|
470
|
+
: '';
|
471
|
+
|
472
|
+
// Add method handlers
|
473
|
+
methods.forEach(method => {
|
474
|
+
if (method === 'delete' && extension === '.js') {
|
475
|
+
// In JS, "delete" is a reserved keyword, so we name it "delete_"
|
476
|
+
apiTemplate += `export const delete_ = (req, res) => {
|
607
477
|
res.json({
|
608
|
-
message: '
|
609
|
-
params: req.params,
|
610
|
-
query: req.query,
|
478
|
+
message: 'DELETE ${routeName} endpoint',
|
611
479
|
timestamp: new Date().toISOString()
|
612
480
|
});
|
613
|
-
}
|
614
|
-
|
615
|
-
|
481
|
+
};\n\n`;
|
482
|
+
} else {
|
483
|
+
apiTemplate += extension === '.ts'
|
484
|
+
? `export const ${method} = (req: Request, res: Response) => {
|
616
485
|
res.json({
|
617
|
-
message: '
|
618
|
-
params: req.params,
|
619
|
-
body: req.body,
|
486
|
+
message: '${method.toUpperCase()} ${routeName} endpoint',
|
620
487
|
timestamp: new Date().toISOString()
|
621
488
|
});
|
622
|
-
}
|
623
|
-
|
624
|
-
export const put = (req: Request, res: Response) => {
|
489
|
+
};\n\n`
|
490
|
+
: `export const ${method} = (req, res) => {
|
625
491
|
res.json({
|
626
|
-
message: '
|
627
|
-
params: req.params,
|
628
|
-
body: req.body,
|
492
|
+
message: '${method.toUpperCase()} ${routeName} endpoint',
|
629
493
|
timestamp: new Date().toISOString()
|
630
494
|
});
|
631
|
-
}
|
632
|
-
|
633
|
-
export const delete_ = (req: Request, res: Response) => {
|
634
|
-
res.json({
|
635
|
-
message: 'This is a dynamic route DELETE endpoint',
|
636
|
-
params: req.params,
|
637
|
-
timestamp: new Date().toISOString()
|
495
|
+
};\n\n`;
|
496
|
+
}
|
638
497
|
});
|
639
|
-
|
640
|
-
|
641
|
-
|
642
|
-
|
643
|
-
|
644
|
-
|
645
|
-
|
646
|
-
|
498
|
+
|
499
|
+
// Create the file
|
500
|
+
const spinner = createSpinner(`Creating ${routeName} API route...`).start();
|
501
|
+
try {
|
502
|
+
await fs.ensureDir(dirPath);
|
503
|
+
await fs.writeFile(fullPath, apiTemplate);
|
504
|
+
spinner.success({ text: `API route created at ${chalk.green(fullPath)}` });
|
505
|
+
|
506
|
+
createSection('API Route Information');
|
507
|
+
console.log(`${chalk.bold.green('✓')} Your API route is accessible at:`);
|
508
|
+
console.log(` ${chalk.cyan(`/api/${routeName}`)}\n`);
|
509
|
+
|
510
|
+
// For dynamic routes, provide example
|
511
|
+
if (routeName.includes('[') && routeName.includes(']')) {
|
512
|
+
const exampleRoute = routeName.replace(/\[([^\]]+)\]/g, 'value');
|
513
|
+
console.log(`${chalk.bold.yellow('ℹ')} For dynamic parameters, access using:`);
|
514
|
+
console.log(` ${chalk.cyan(`/api/${exampleRoute}`)}\n`);
|
515
|
+
console.log(`${chalk.bold.yellow('ℹ')} Access parameters in your handler with:`);
|
516
|
+
console.log(` ${chalk.cyan('req.params.paramName')}\n`);
|
517
|
+
}
|
518
|
+
} catch (error) {
|
519
|
+
spinner.error({ text: 'Failed to create API route' });
|
520
|
+
console.error(chalk.red('Error: ') + error.message);
|
647
521
|
}
|
648
|
-
|
649
|
-
|
522
|
+
}
|
523
|
+
|
524
|
+
// Display dev tools and tips
|
525
|
+
function showDevTools() {
|
526
|
+
console.log(banner);
|
527
|
+
createSection('Development Tools');
|
528
|
+
|
529
|
+
console.log(chalk.bold('Available Commands:') + '\n');
|
530
|
+
console.log(`${chalk.green('•')} ${chalk.bold('npm run dev')} - Start development server`);
|
531
|
+
console.log(`${chalk.green('•')} ${chalk.bold('npm run build')} - Create production build`);
|
532
|
+
console.log(`${chalk.green('•')} ${chalk.bold('npm start')} - Run production server\n`);
|
533
|
+
|
534
|
+
console.log(chalk.bold('Useful Tips:') + '\n');
|
535
|
+
console.log(`${chalk.blue('1.')} Use ${chalk.cyan('frontend-hamroun create')} to scaffold a new project`);
|
536
|
+
console.log(`${chalk.blue('2.')} Add components with ${chalk.cyan('frontend-hamroun add:component')}`);
|
537
|
+
console.log(`${chalk.blue('3.')} Create pages with ${chalk.cyan('frontend-hamroun add:page')}`);
|
538
|
+
console.log(`${chalk.blue('4.')} Add API routes with ${chalk.cyan('frontend-hamroun add:api')}\n`);
|
539
|
+
|
540
|
+
console.log(chalk.bold('Project Structure:') + '\n');
|
541
|
+
console.log(`${chalk.yellow('src/')}`);
|
542
|
+
console.log(` ${chalk.yellow('├── components/')} - Reusable UI components`);
|
543
|
+
console.log(` ${chalk.yellow('├── pages/')} - File-based route components`);
|
544
|
+
console.log(` ${chalk.yellow('├── styles/')} - CSS and styling files`);
|
545
|
+
console.log(` ${chalk.yellow('└── utils/')} - Helper functions and utilities`);
|
546
|
+
console.log(`${chalk.yellow('api/')} - API route handlers`);
|
547
|
+
console.log(`${chalk.yellow('public/')} - Static assets\n`);
|
548
|
+
}
|
549
|
+
|
550
|
+
// Register commands
|
551
|
+
program
|
552
|
+
.command('create [name]')
|
553
|
+
.description('Create a new Frontend Hamroun project')
|
554
|
+
.option('-t, --template <template>', 'Specify template (basic-app, ssr-template, fullstack-app)')
|
555
|
+
.action(createProject);
|
556
|
+
|
557
|
+
program
|
558
|
+
.command('add:component [name]')
|
559
|
+
.description('Create a new component')
|
560
|
+
.option('-p, --path <path>', 'Path where the component should be created')
|
561
|
+
.option('-ts, --typescript', 'Use TypeScript')
|
562
|
+
.option('-jsx, --jsx', 'Use JSX')
|
563
|
+
.action(addComponent);
|
564
|
+
|
565
|
+
program
|
566
|
+
.command('add:page [name]')
|
567
|
+
.description('Create a new page')
|
568
|
+
.option('-p, --path <path>', 'Path where the page should be created')
|
569
|
+
.option('-ts, --typescript', 'Use TypeScript')
|
570
|
+
.option('-jsx, --jsx', 'Use JSX')
|
571
|
+
.action(addPage);
|
572
|
+
|
573
|
+
program
|
574
|
+
.command('add:api [name]')
|
575
|
+
.description('Create a new API route')
|
576
|
+
.option('-p, --path <path>', 'Path where the API route should be created')
|
577
|
+
.option('-ts, --typescript', 'Use TypeScript')
|
578
|
+
.option('-js, --javascript', 'Use JavaScript')
|
579
|
+
.option('-m, --methods <methods>', 'HTTP methods to implement (comma-separated: get,post,put,delete,patch)')
|
580
|
+
.action(addApiRoute);
|
581
|
+
|
582
|
+
program
|
583
|
+
.command('dev:tools')
|
584
|
+
.description('Show development tools and tips')
|
585
|
+
.action(showDevTools);
|
586
|
+
|
587
|
+
// Default command when no arguments
|
588
|
+
if (process.argv.length <= 2) {
|
589
|
+
console.log(banner);
|
590
|
+
program.help();
|
591
|
+
}
|
650
592
|
|
651
|
-
|
652
|
-
const TSCONFIG_SERVER_TEMPLATE = `{
|
653
|
-
"compilerOptions": {
|
654
|
-
"target": "ES2020",
|
655
|
-
"module": "NodeNext",
|
656
|
-
"moduleResolution": "NodeNext",
|
657
|
-
"esModuleInterop": true,
|
658
|
-
"outDir": "./dist",
|
659
|
-
"declaration": true,
|
660
|
-
"sourceMap": true,
|
661
|
-
"noEmit": false,
|
662
|
-
"strict": true,
|
663
|
-
"skipLibCheck": true
|
664
|
-
},
|
665
|
-
"include": ["server.ts", "api/**/*.ts"],
|
666
|
-
"exclude": ["node_modules", "dist", "**/*.test.ts"]
|
667
|
-
}`;
|
668
|
-
|
669
|
-
init().catch(console.error);
|
593
|
+
program.parse();
|