frontend-hamroun 1.2.16 → 1.2.18
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 +4 -0
- package/bin/cli.js +691 -0
- package/dist/component.d.ts +1 -1
- package/dist/context.d.ts +4 -3
- package/dist/index.client.d.ts +11 -0
- package/dist/index.d.ts +9 -89
- package/dist/index.js +396 -67
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +392 -0
- package/dist/index.mjs.map +1 -0
- package/dist/jsx-runtime/jsx-runtime.d.ts +0 -1
- package/dist/jsx-runtime.d.ts +1 -1
- package/dist/renderer.d.ts +0 -10
- package/dist/server-renderer.d.ts +0 -3
- package/dist/server-types.d.ts +42 -0
- package/package.json +74 -41
- package/templates/basic-app/index.html +6 -6
- package/templates/basic-app/package.json +18 -7
- package/templates/basic-app/postcss.config.js +0 -1
- package/templates/basic-app/src/main.tsx +1 -10
- package/templates/basic-app/tailwind.config.js +2 -23
- package/templates/basic-app/tsconfig.json +4 -17
- package/templates/basic-app/vite.config.ts +3 -54
- package/templates/fullstack-app/api/hello.ts +18 -0
- package/templates/fullstack-app/api/users/[id].ts +73 -0
- package/templates/fullstack-app/api/users/index.ts +32 -0
- package/templates/fullstack-app/package.json +31 -0
- package/templates/fullstack-app/server.ts +46 -0
- package/templates/fullstack-app/src/pages/index.tsx +59 -0
- package/templates/ssr-template/vite.config.ts +1 -11
- package/bin/cli.cjs +0 -16
- package/bin/cli.mjs +0 -237
- package/dist/backend/api-utils.d.ts +0 -38
- package/dist/backend/api-utils.js +0 -135
- package/dist/backend/auth.d.ts +0 -134
- package/dist/backend/auth.js +0 -387
- package/dist/backend/database.d.ts +0 -27
- package/dist/backend/database.js +0 -91
- package/dist/backend/model.d.ts +0 -43
- package/dist/backend/model.js +0 -178
- package/dist/backend/router.d.ts +0 -27
- package/dist/backend/router.js +0 -137
- package/dist/backend/server.d.ts +0 -19
- package/dist/backend/server.js +0 -268
- package/dist/backend/types.d.ts +0 -217
- package/dist/backend/types.js +0 -1
- package/dist/batch.js +0 -22
- package/dist/cli/index.d.ts +0 -2
- package/dist/cli/index.js +0 -215
- package/dist/component.js +0 -84
- package/dist/components/Counter.js +0 -2
- package/dist/context.js +0 -18
- package/dist/frontend-hamroun.es.js +0 -1378
- package/dist/frontend-hamroun.umd.js +0 -66
- package/dist/hooks.js +0 -164
- package/dist/jsx-runtime/index.d.ts +0 -11
- package/dist/jsx-runtime/index.js +0 -19
- package/dist/jsx-runtime/jsx-dev-runtime.js +0 -1
- package/dist/jsx-runtime/jsx-runtime.js +0 -95
- package/dist/jsx-runtime.js +0 -192
- package/dist/renderer.js +0 -51
- package/dist/server-renderer.js +0 -102
- package/dist/types.js +0 -1
- package/dist/vdom.js +0 -27
- package/scripts/build-cli.js +0 -1199
- package/scripts/generate.js +0 -134
- package/src/backend/api-utils.ts +0 -178
- package/src/backend/auth.ts +0 -544
- package/src/backend/database.ts +0 -104
- package/src/backend/model.ts +0 -198
- package/src/backend/router.ts +0 -176
- package/src/backend/server.ts +0 -330
- package/src/backend/types.ts +0 -257
- package/src/batch.ts +0 -24
- package/src/cli/index.js +0 -554
- package/src/cli/index.ts +0 -257
- package/src/component.ts +0 -98
- package/src/components/Counter.tsx +0 -4
- package/src/context.ts +0 -29
- package/src/hooks.ts +0 -211
- package/src/index.ts +0 -144
- package/src/jsx-runtime/index.ts +0 -27
- package/src/jsx-runtime/jsx-dev-runtime.ts +0 -0
- package/src/jsx-runtime/jsx-runtime.ts +0 -104
- package/src/jsx-runtime.ts +0 -226
- package/src/renderer.ts +0 -55
- package/src/server-renderer.ts +0 -114
- package/src/shims.d.ts +0 -20
- package/src/types/bcrypt.d.ts +0 -30
- package/src/types/jsonwebtoken.d.ts +0 -55
- package/src/types.d.ts +0 -26
- package/src/types.ts +0 -21
- package/src/vdom.ts +0 -34
- package/templates/basic/.eslintignore +0 -5
- package/templates/basic/.eslintrc.json +0 -25
- package/templates/basic/docs/rapport_pfe.aux +0 -27
- package/templates/basic/docs/rapport_pfe.log +0 -399
- package/templates/basic/docs/rapport_pfe.out +0 -10
- package/templates/basic/docs/rapport_pfe.pdf +0 -0
- package/templates/basic/docs/rapport_pfe.tex +0 -68
- package/templates/basic/docs/rapport_pfe.toc +0 -14
- package/templates/basic/index.html +0 -12
- package/templates/basic/jsconfig.json +0 -14
- package/templates/basic/package.json +0 -18
- package/templates/basic/postcss.config.js +0 -7
- package/templates/basic/src/App.js +0 -105
- package/templates/basic/src/App.tsx +0 -65
- package/templates/basic/src/api.ts +0 -58
- package/templates/basic/src/components/Counter.tsx +0 -26
- package/templates/basic/src/components/Header.tsx +0 -9
- package/templates/basic/src/components/TodoList.tsx +0 -90
- package/templates/basic/src/main.css +0 -3
- package/templates/basic/src/main.js +0 -11
- package/templates/basic/src/main.ts +0 -20
- package/templates/basic/src/main.tsx +0 -144
- package/templates/basic/src/server.ts +0 -99
- package/templates/basic/tailwind.config.js +0 -32
- package/templates/basic/tsconfig.json +0 -20
- package/templates/basic/tsconfig.node.json +0 -10
- package/templates/basic/vite.config.js +0 -18
- package/templates/basic/vite.config.ts +0 -86
- package/templates/basic-app/src/App.js +0 -105
- package/templates/basic-app/src/App.tsx +0 -143
- package/templates/basic-app/src/api.ts +0 -58
- package/templates/basic-app/src/components/Counter.tsx +0 -26
- package/templates/basic-app/src/components/Header.tsx +0 -9
- package/templates/basic-app/src/components/TodoList.tsx +0 -90
- package/templates/basic-app/src/main.js +0 -10
- package/templates/basic-app/src/main.ts +0 -21
- package/templates/basic-app/src/react/index.ts +0 -35
- package/templates/basic-app/src/react/jsx-dev-runtime.ts +0 -13
- package/templates/basic-app/src/react/jsx-runtime.ts +0 -12
- package/templates/basic-app/src/server.ts +0 -99
- package/templates/basic-app/src/shims.ts +0 -9
- package/templates/basic-app/tsconfig.node.json +0 -10
- package/templates/basic-app/vite.config.js +0 -22
- package/templates/full-stack/.env.example +0 -11
- package/templates/full-stack/README.md +0 -51
- package/templates/full-stack/index.html +0 -12
- package/templates/full-stack/jsconfig.json +0 -14
- package/templates/full-stack/package.json +0 -21
- package/templates/full-stack/src/App.js +0 -105
- package/templates/full-stack/src/client/App.tsx +0 -50
- package/templates/full-stack/src/client/components/Header.tsx +0 -42
- package/templates/full-stack/src/client/components/UserList.tsx +0 -29
- package/templates/full-stack/src/client/main.tsx +0 -5
- package/templates/full-stack/src/main.css +0 -3
- package/templates/full-stack/src/main.js +0 -11
- package/templates/full-stack/src/main.ts +0 -20
- package/templates/full-stack/src/server/index.ts +0 -99
- package/templates/full-stack/src/server/routes/auth.ts +0 -39
- package/templates/full-stack/src/server/routes/users.ts +0 -48
- package/templates/full-stack/src/shims.ts +0 -9
- package/templates/full-stack/tsconfig.json +0 -20
- package/templates/full-stack/tsconfig.node.json +0 -10
- package/templates/full-stack/tsconfig.server.json +0 -15
- package/templates/full-stack/vite.config.js +0 -18
- package/templates/full-stack/vite.config.ts +0 -85
package/README.md
CHANGED
package/bin/cli.js
ADDED
@@ -0,0 +1,691 @@
|
|
1
|
+
#!/usr/bin/env node
|
2
|
+
|
3
|
+
import { Command } from 'commander';
|
4
|
+
import inquirer from 'inquirer';
|
5
|
+
import fs from 'fs-extra';
|
6
|
+
import path from 'path';
|
7
|
+
import { fileURLToPath } from 'url';
|
8
|
+
import chalk from 'chalk';
|
9
|
+
import { createSpinner } from 'nanospinner';
|
10
|
+
|
11
|
+
const __filename = fileURLToPath(import.meta.url);
|
12
|
+
const __dirname = path.dirname(__filename);
|
13
|
+
|
14
|
+
// Component templates
|
15
|
+
const FUNCTION_COMPONENT_TEMPLATE = (name) => `import { useState, useEffect } from 'frontend-hamroun';
|
16
|
+
|
17
|
+
export function ${name}(props) {
|
18
|
+
// State hooks
|
19
|
+
const [state, setState] = useState(null);
|
20
|
+
|
21
|
+
// Effect hooks
|
22
|
+
useEffect(() => {
|
23
|
+
// Component mounted
|
24
|
+
return () => {
|
25
|
+
// Component will unmount
|
26
|
+
};
|
27
|
+
}, []);
|
28
|
+
|
29
|
+
return (
|
30
|
+
<div className="${name.toLowerCase()}">
|
31
|
+
<h2>${name} Component</h2>
|
32
|
+
{/* Your JSX here */}
|
33
|
+
</div>
|
34
|
+
);
|
35
|
+
}
|
36
|
+
`;
|
37
|
+
|
38
|
+
const CSS_TEMPLATE = (name) => `.${name.toLowerCase()} {
|
39
|
+
display: flex;
|
40
|
+
flex-direction: column;
|
41
|
+
padding: 1rem;
|
42
|
+
margin: 0.5rem;
|
43
|
+
border-radius: 4px;
|
44
|
+
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
45
|
+
}
|
46
|
+
`;
|
47
|
+
|
48
|
+
const TEST_TEMPLATE = (name) => `import { render, screen } from '@testing-library/frontend-hamroun';
|
49
|
+
import { ${name} } from './${name}';
|
50
|
+
|
51
|
+
describe('${name} Component', () => {
|
52
|
+
test('renders correctly', () => {
|
53
|
+
render(<${name} />);
|
54
|
+
const element = screen.getByText('${name} Component');
|
55
|
+
expect(element).toBeInTheDocument();
|
56
|
+
});
|
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
|
+
|
76
|
+
# Build the application
|
77
|
+
RUN npm run build
|
78
|
+
|
79
|
+
# Stage 2: Serve the application
|
80
|
+
FROM nginx:alpine
|
81
|
+
|
82
|
+
# Copy the build output from the previous stage
|
83
|
+
COPY --from=build /app/dist /usr/share/nginx/html
|
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
|
114
|
+
|
115
|
+
# Copy package files and install production dependencies only
|
116
|
+
COPY package.json package-lock.json ./
|
117
|
+
RUN npm ci --production
|
118
|
+
|
119
|
+
# Copy build artifacts
|
120
|
+
COPY --from=build /app/dist ./dist
|
121
|
+
COPY --from=build /app/server ./server
|
122
|
+
|
123
|
+
# Expose port 3000
|
124
|
+
EXPOSE 3000
|
125
|
+
|
126
|
+
# Start the server
|
127
|
+
CMD ["node", "server/index.js"]
|
128
|
+
`;
|
129
|
+
|
130
|
+
async function init() {
|
131
|
+
const program = new Command();
|
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
|
+
}
|
185
|
+
|
186
|
+
async function interactiveMode() {
|
187
|
+
const { action } = await inquirer.prompt([{
|
188
|
+
type: 'list',
|
189
|
+
name: 'action',
|
190
|
+
message: 'What would you like to do?',
|
191
|
+
choices: [
|
192
|
+
{ name: 'Create a new project', value: 'create' },
|
193
|
+
{ name: 'Generate a component', value: 'generate' },
|
194
|
+
{ name: 'Generate an API route', value: 'api' },
|
195
|
+
{ name: 'Add Dockerfile to project', value: 'docker' }
|
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);
|
243
|
+
}
|
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
|
+
|
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
|
+
const spinner = createSpinner('Creating project...').start();
|
272
|
+
|
273
|
+
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
|
+
await fs.ensureDir(targetDir);
|
280
|
+
|
281
|
+
// Copy template files
|
282
|
+
await fs.copy(templateDir, targetDir);
|
283
|
+
|
284
|
+
// Update package.json
|
285
|
+
const pkgPath = path.join(targetDir, 'package.json');
|
286
|
+
const pkg = await fs.readJson(pkgPath);
|
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!` });
|
296
|
+
|
297
|
+
// Show next steps
|
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'));
|
305
|
+
|
306
|
+
} catch (error) {
|
307
|
+
spinner.error({ text: 'Failed to create project' });
|
308
|
+
console.error(chalk.red(error));
|
309
|
+
process.exit(1);
|
310
|
+
}
|
311
|
+
}
|
312
|
+
|
313
|
+
async function generateComponent(name, directory) {
|
314
|
+
const spinner = createSpinner(`Generating ${name} component...`).start();
|
315
|
+
|
316
|
+
try {
|
317
|
+
const targetDir = path.join(process.cwd(), directory, name);
|
318
|
+
|
319
|
+
// Create component directory
|
320
|
+
await fs.ensureDir(targetDir);
|
321
|
+
|
322
|
+
// Create component files
|
323
|
+
await fs.writeFile(
|
324
|
+
path.join(targetDir, `${name}.jsx`),
|
325
|
+
FUNCTION_COMPONENT_TEMPLATE(name)
|
326
|
+
);
|
327
|
+
|
328
|
+
await fs.writeFile(
|
329
|
+
path.join(targetDir, `${name}.css`),
|
330
|
+
CSS_TEMPLATE(name)
|
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);
|
355
|
+
}
|
356
|
+
}
|
357
|
+
|
358
|
+
async function generateApiRoute(name, directory) {
|
359
|
+
const spinner = createSpinner(`Generating ${name} API route...`).start();
|
360
|
+
|
361
|
+
try {
|
362
|
+
const routePath = name.includes('/') ? name : name;
|
363
|
+
const routeDir = path.dirname(path.join(process.cwd(), directory, routePath));
|
364
|
+
const routeFileName = path.basename(routePath);
|
365
|
+
const targetPath = path.join(routeDir, `${routeFileName}.ts`);
|
366
|
+
|
367
|
+
// Create directory structure
|
368
|
+
await fs.ensureDir(routeDir);
|
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'));
|
387
|
+
}
|
388
|
+
|
389
|
+
// Check and update package.json to add server dependencies
|
390
|
+
try {
|
391
|
+
const pkgPath = path.join(process.cwd(), 'package.json');
|
392
|
+
if (await fs.pathExists(pkgPath)) {
|
393
|
+
const pkg = await fs.readJson(pkgPath);
|
394
|
+
|
395
|
+
// Check if we need to add server dependencies
|
396
|
+
let needsUpdate = false;
|
397
|
+
const serverDeps = {
|
398
|
+
"express": "^4.18.2",
|
399
|
+
"cors": "^2.8.5",
|
400
|
+
"mongodb": "^5.7.0", // Add MongoDB support
|
401
|
+
"jsonwebtoken": "^9.0.2", // Add JWT support for auth
|
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));
|
446
|
+
}
|
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);
|
458
|
+
}
|
459
|
+
}
|
460
|
+
|
461
|
+
// Add a server template for Express
|
462
|
+
const SERVER_TEMPLATE = `import { server } from 'frontend-hamroun';
|
463
|
+
|
464
|
+
async function startServer() {
|
465
|
+
try {
|
466
|
+
// Dynamically import server module
|
467
|
+
const { Server } = await server.getServer();
|
468
|
+
|
469
|
+
// Create and configure the server
|
470
|
+
const app = new Server({
|
471
|
+
port: process.env.PORT ? parseInt(process.env.PORT) : 3000,
|
472
|
+
apiDir: './api',
|
473
|
+
staticDir: './public',
|
474
|
+
|
475
|
+
// Enable CORS
|
476
|
+
enableCors: true,
|
477
|
+
corsOptions: {
|
478
|
+
origin: process.env.CORS_ORIGIN || '*', // Set to specific domain in production
|
479
|
+
methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'],
|
480
|
+
allowedHeaders: ['Content-Type', 'Authorization']
|
481
|
+
},
|
482
|
+
|
483
|
+
// Uncomment to enable database
|
484
|
+
/*
|
485
|
+
db: {
|
486
|
+
// MongoDB
|
487
|
+
url: process.env.MONGODB_URL || 'mongodb://localhost:27017/my_app',
|
488
|
+
type: 'mongodb'
|
489
|
+
|
490
|
+
// MySQL
|
491
|
+
// url: process.env.MYSQL_URL || 'mysql://user:password@localhost:3306/my_db',
|
492
|
+
// type: 'mysql'
|
493
|
+
|
494
|
+
// PostgreSQL
|
495
|
+
// url: process.env.POSTGRES_URL || 'postgres://user:password@localhost:5432/my_db',
|
496
|
+
// type: 'postgres'
|
497
|
+
},
|
498
|
+
*/
|
499
|
+
|
500
|
+
// Uncomment to enable authentication
|
501
|
+
/*
|
502
|
+
auth: {
|
503
|
+
secret: process.env.JWT_SECRET || 'your-secret-key',
|
504
|
+
expiresIn: '7d'
|
505
|
+
}
|
506
|
+
*/
|
507
|
+
});
|
508
|
+
|
509
|
+
// Connect to database if configured
|
510
|
+
if (app.getDatabase()) {
|
511
|
+
await app.getDatabase().connect();
|
512
|
+
console.log('Connected to database');
|
513
|
+
}
|
514
|
+
|
515
|
+
// Start the server
|
516
|
+
await app.start();
|
517
|
+
|
518
|
+
console.log('Server running at http://localhost:' +
|
519
|
+
(process.env.PORT || 3000));
|
520
|
+
|
521
|
+
// Handle graceful shutdown
|
522
|
+
process.on('SIGINT', async () => {
|
523
|
+
console.log('Shutting down server...');
|
524
|
+
if (app.getDatabase()) {
|
525
|
+
await app.getDatabase().disconnect();
|
526
|
+
console.log('Database connection closed');
|
527
|
+
}
|
528
|
+
await app.stop();
|
529
|
+
console.log('Server stopped');
|
530
|
+
process.exit(0);
|
531
|
+
});
|
532
|
+
} catch (error) {
|
533
|
+
console.error('Failed to start server:', error);
|
534
|
+
process.exit(1);
|
535
|
+
}
|
536
|
+
}
|
537
|
+
|
538
|
+
startServer();
|
539
|
+
`;
|
540
|
+
|
541
|
+
async function addDockerfile(isSSR) {
|
542
|
+
const spinner = createSpinner('Adding Dockerfile...').start();
|
543
|
+
|
544
|
+
try {
|
545
|
+
const dockerContent = isSSR ? SSR_DOCKERFILE_TEMPLATE : DOCKERFILE_TEMPLATE;
|
546
|
+
const targetPath = path.join(process.cwd(), 'Dockerfile');
|
547
|
+
|
548
|
+
// Check if Dockerfile already exists
|
549
|
+
if (await fs.pathExists(targetPath)) {
|
550
|
+
spinner.stop();
|
551
|
+
const { overwrite } = await inquirer.prompt([{
|
552
|
+
type: 'confirm',
|
553
|
+
name: 'overwrite',
|
554
|
+
message: 'Dockerfile already exists. Overwrite?',
|
555
|
+
default: false
|
556
|
+
}]);
|
557
|
+
|
558
|
+
if (!overwrite) {
|
559
|
+
console.log(chalk.yellow('Operation cancelled.'));
|
560
|
+
return;
|
561
|
+
}
|
562
|
+
|
563
|
+
spinner.start();
|
564
|
+
}
|
565
|
+
|
566
|
+
// Write Dockerfile
|
567
|
+
await fs.writeFile(targetPath, dockerContent);
|
568
|
+
|
569
|
+
spinner.success({ text: 'Dockerfile added successfully!' });
|
570
|
+
|
571
|
+
console.log('\nTo build and run Docker image:');
|
572
|
+
console.log(chalk.cyan(' docker build -t my-app .'));
|
573
|
+
console.log(chalk.cyan(' docker run -p 3000:' + (isSSR ? '3000' : '80') + ' my-app'));
|
574
|
+
|
575
|
+
} catch (error) {
|
576
|
+
spinner.error({ text: 'Failed to add Dockerfile' });
|
577
|
+
console.error(chalk.red(error));
|
578
|
+
process.exit(1);
|
579
|
+
}
|
580
|
+
}
|
581
|
+
|
582
|
+
// Add API route templates
|
583
|
+
const API_ROUTE_TEMPLATE = `import { Request, Response } from 'express';
|
584
|
+
|
585
|
+
export const get = (req: Request, res: Response) => {
|
586
|
+
res.json({
|
587
|
+
message: 'This is a GET endpoint',
|
588
|
+
query: req.query,
|
589
|
+
timestamp: new Date().toISOString()
|
590
|
+
});
|
591
|
+
};
|
592
|
+
|
593
|
+
export const post = (req: Request, res: Response) => {
|
594
|
+
res.json({
|
595
|
+
message: 'This is a POST endpoint',
|
596
|
+
body: req.body,
|
597
|
+
timestamp: new Date().toISOString()
|
598
|
+
});
|
599
|
+
};
|
600
|
+
|
601
|
+
export const put = (req: Request, res: Response) => {
|
602
|
+
res.json({
|
603
|
+
message: 'This is a PUT endpoint',
|
604
|
+
body: req.body,
|
605
|
+
timestamp: new Date().toISOString()
|
606
|
+
});
|
607
|
+
};
|
608
|
+
|
609
|
+
export const delete_ = (req: Request, res: Response) => {
|
610
|
+
res.json({
|
611
|
+
message: 'This is a DELETE endpoint',
|
612
|
+
timestamp: new Date().toISOString()
|
613
|
+
});
|
614
|
+
};
|
615
|
+
|
616
|
+
// You can add middleware that will be applied to all methods
|
617
|
+
export const middleware = [
|
618
|
+
// Example middleware
|
619
|
+
(req: Request, res: Response, next: Function) => {
|
620
|
+
console.log(\`\${req.method} \${req.url} - \${new Date().toISOString()}\`);
|
621
|
+
next();
|
622
|
+
}
|
623
|
+
];
|
624
|
+
`;
|
625
|
+
|
626
|
+
const DYNAMIC_API_ROUTE_TEMPLATE = `import { Request, Response } from 'express';
|
627
|
+
|
628
|
+
export const get = (req: Request, res: Response) => {
|
629
|
+
res.json({
|
630
|
+
message: 'This is a dynamic route GET endpoint',
|
631
|
+
params: req.params,
|
632
|
+
query: req.query,
|
633
|
+
timestamp: new Date().toISOString()
|
634
|
+
});
|
635
|
+
};
|
636
|
+
|
637
|
+
export const post = (req: Request, res: Response) => {
|
638
|
+
res.json({
|
639
|
+
message: 'This is a dynamic route POST endpoint',
|
640
|
+
params: req.params,
|
641
|
+
body: req.body,
|
642
|
+
timestamp: new Date().toISOString()
|
643
|
+
});
|
644
|
+
};
|
645
|
+
|
646
|
+
export const put = (req: Request, res: Response) => {
|
647
|
+
res.json({
|
648
|
+
message: 'This is a dynamic route PUT endpoint',
|
649
|
+
params: req.params,
|
650
|
+
body: req.body,
|
651
|
+
timestamp: new Date().toISOString()
|
652
|
+
});
|
653
|
+
};
|
654
|
+
|
655
|
+
export const delete_ = (req: Request, res: Response) => {
|
656
|
+
res.json({
|
657
|
+
message: 'This is a dynamic route DELETE endpoint',
|
658
|
+
params: req.params,
|
659
|
+
timestamp: new Date().toISOString()
|
660
|
+
});
|
661
|
+
};
|
662
|
+
|
663
|
+
// You can add middleware that will be applied to all methods
|
664
|
+
export const middleware = [
|
665
|
+
// Example middleware
|
666
|
+
(req: Request, res: Response, next: Function) => {
|
667
|
+
console.log(\`\${req.method} \${req.url} - \${new Date().toISOString()}\`);
|
668
|
+
next();
|
669
|
+
}
|
670
|
+
];
|
671
|
+
`;
|
672
|
+
|
673
|
+
// Add tsconfig.server.json template
|
674
|
+
const TSCONFIG_SERVER_TEMPLATE = `{
|
675
|
+
"compilerOptions": {
|
676
|
+
"target": "ES2020",
|
677
|
+
"module": "NodeNext",
|
678
|
+
"moduleResolution": "NodeNext",
|
679
|
+
"esModuleInterop": true,
|
680
|
+
"outDir": "./dist",
|
681
|
+
"declaration": true,
|
682
|
+
"sourceMap": true,
|
683
|
+
"noEmit": false,
|
684
|
+
"strict": true,
|
685
|
+
"skipLibCheck": true
|
686
|
+
},
|
687
|
+
"include": ["server.ts", "api/**/*.ts"],
|
688
|
+
"exclude": ["node_modules", "dist", "**/*.test.ts"]
|
689
|
+
}`;
|
690
|
+
|
691
|
+
init().catch(console.error);
|
package/dist/component.d.ts
CHANGED
@@ -8,7 +8,7 @@ export declare class Component {
|
|
8
8
|
setState(newState: any): Promise<void>;
|
9
9
|
private _replayEvents;
|
10
10
|
private _deepCloneWithEvents;
|
11
|
-
update(): Promise<
|
11
|
+
update(): Promise<HTMLElement | Text>;
|
12
12
|
private _updateElement;
|
13
13
|
render(): any;
|
14
14
|
}
|