@coherent.js/cli 1.0.0-beta.5 → 1.0.0-beta.6
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/dist/index.js +876 -322
- package/package.json +1 -1
- package/dist/index.cjs +0 -2456
package/dist/index.cjs
DELETED
|
@@ -1,2456 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __create = Object.create;
|
|
3
|
-
var __defProp = Object.defineProperty;
|
|
4
|
-
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
-
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
-
var __export = (target, all) => {
|
|
9
|
-
for (var name in all)
|
|
10
|
-
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
-
};
|
|
12
|
-
var __copyProps = (to, from, except, desc) => {
|
|
13
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
-
for (let key of __getOwnPropNames(from))
|
|
15
|
-
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
-
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
-
}
|
|
18
|
-
return to;
|
|
19
|
-
};
|
|
20
|
-
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
-
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
-
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
-
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
-
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
-
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
-
mod
|
|
27
|
-
));
|
|
28
|
-
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
-
|
|
30
|
-
// src/index.js
|
|
31
|
-
var index_exports = {};
|
|
32
|
-
__export(index_exports, {
|
|
33
|
-
buildCommand: () => buildCommand,
|
|
34
|
-
createCLI: () => createCLI,
|
|
35
|
-
createCommand: () => createCommand,
|
|
36
|
-
devCommand: () => devCommand,
|
|
37
|
-
generateCommand: () => generateCommand
|
|
38
|
-
});
|
|
39
|
-
module.exports = __toCommonJS(index_exports);
|
|
40
|
-
var import_commander5 = require("commander");
|
|
41
|
-
var import_fs9 = require("fs");
|
|
42
|
-
var import_url2 = require("url");
|
|
43
|
-
var import_path9 = require("path");
|
|
44
|
-
var import_picocolors5 = __toESM(require("picocolors"), 1);
|
|
45
|
-
|
|
46
|
-
// src/commands/create.js
|
|
47
|
-
var import_commander = require("commander");
|
|
48
|
-
var import_prompts = __toESM(require("prompts"), 1);
|
|
49
|
-
var import_ora = __toESM(require("ora"), 1);
|
|
50
|
-
var import_picocolors = __toESM(require("picocolors"), 1);
|
|
51
|
-
var import_fs3 = require("fs");
|
|
52
|
-
var import_path3 = require("path");
|
|
53
|
-
|
|
54
|
-
// src/generators/project-scaffold.js
|
|
55
|
-
var import_fs = require("fs");
|
|
56
|
-
var import_path = require("path");
|
|
57
|
-
var import_child_process = require("child_process");
|
|
58
|
-
var import_url = require("url");
|
|
59
|
-
var import_meta = {};
|
|
60
|
-
var __filename = (0, import_url.fileURLToPath)(import_meta.url);
|
|
61
|
-
var __dirname = (0, import_path.dirname)(__filename);
|
|
62
|
-
async function scaffoldProject(projectPath, options) {
|
|
63
|
-
const { name, template, skipInstall, skipGit } = options;
|
|
64
|
-
const dirs = [
|
|
65
|
-
"src",
|
|
66
|
-
"src/components",
|
|
67
|
-
"src/pages",
|
|
68
|
-
"src/api",
|
|
69
|
-
"src/utils",
|
|
70
|
-
"public",
|
|
71
|
-
"tests"
|
|
72
|
-
];
|
|
73
|
-
dirs.forEach((dir) => {
|
|
74
|
-
(0, import_fs.mkdirSync)((0, import_path.join)(projectPath, dir), { recursive: true });
|
|
75
|
-
});
|
|
76
|
-
const packageJson = generatePackageJson(name, template);
|
|
77
|
-
(0, import_fs.writeFileSync)((0, import_path.join)(projectPath, "package.json"), JSON.stringify(packageJson, null, 2));
|
|
78
|
-
switch (template) {
|
|
79
|
-
case "basic":
|
|
80
|
-
await generateBasicTemplate(projectPath, name);
|
|
81
|
-
break;
|
|
82
|
-
case "fullstack":
|
|
83
|
-
console.warn("Fullstack template not yet implemented, falling back to basic");
|
|
84
|
-
await generateBasicTemplate(projectPath, name);
|
|
85
|
-
break;
|
|
86
|
-
case "express":
|
|
87
|
-
await generateExpressTemplate(projectPath, name);
|
|
88
|
-
break;
|
|
89
|
-
case "fastify":
|
|
90
|
-
console.warn("Fastify template not yet implemented, falling back to basic");
|
|
91
|
-
await generateBasicTemplate(projectPath, name);
|
|
92
|
-
break;
|
|
93
|
-
case "components":
|
|
94
|
-
console.warn("Components template not yet implemented, falling back to basic");
|
|
95
|
-
await generateBasicTemplate(projectPath, name);
|
|
96
|
-
break;
|
|
97
|
-
default:
|
|
98
|
-
await generateBasicTemplate(projectPath, name);
|
|
99
|
-
}
|
|
100
|
-
generateCommonFiles(projectPath, name);
|
|
101
|
-
if (!skipInstall) {
|
|
102
|
-
console.log("\u{1F4E6} Installing dependencies...");
|
|
103
|
-
try {
|
|
104
|
-
(0, import_child_process.execSync)("npm install", {
|
|
105
|
-
cwd: projectPath,
|
|
106
|
-
stdio: "inherit"
|
|
107
|
-
});
|
|
108
|
-
} catch {
|
|
109
|
-
console.warn("\u26A0\uFE0F Failed to install dependencies automatically");
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
if (!skipGit) {
|
|
113
|
-
try {
|
|
114
|
-
(0, import_child_process.execSync)("git init", { cwd: projectPath, stdio: "pipe" });
|
|
115
|
-
(0, import_child_process.execSync)("git add .", { cwd: projectPath, stdio: "pipe" });
|
|
116
|
-
(0, import_child_process.execSync)('git commit -m "Initial commit"', { cwd: projectPath, stdio: "pipe" });
|
|
117
|
-
} catch {
|
|
118
|
-
console.warn("\u26A0\uFE0F Failed to initialize git repository");
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
function generatePackageJson(name, template) {
|
|
123
|
-
const base = {
|
|
124
|
-
name,
|
|
125
|
-
version: "1.0.0",
|
|
126
|
-
description: "A Coherent.js application",
|
|
127
|
-
type: "module",
|
|
128
|
-
main: "src/index.js",
|
|
129
|
-
scripts: {
|
|
130
|
-
dev: "node src/index.js",
|
|
131
|
-
build: "coherent build",
|
|
132
|
-
start: "node src/index.js",
|
|
133
|
-
test: "node --test tests/*.test.js"
|
|
134
|
-
},
|
|
135
|
-
dependencies: {
|
|
136
|
-
"@coherentjs/core": "^1.0.1"
|
|
137
|
-
},
|
|
138
|
-
devDependencies: {
|
|
139
|
-
"@coherentjs/cli": "^1.0.1"
|
|
140
|
-
}
|
|
141
|
-
};
|
|
142
|
-
switch (template) {
|
|
143
|
-
case "express":
|
|
144
|
-
base.dependencies.express = "^4.18.2";
|
|
145
|
-
base.dependencies["@coherentjs/express"] = "^1.0.1";
|
|
146
|
-
break;
|
|
147
|
-
case "fastify":
|
|
148
|
-
base.dependencies.fastify = "^4.24.3";
|
|
149
|
-
base.dependencies["@coherentjs/fastify"] = "^1.0.1";
|
|
150
|
-
break;
|
|
151
|
-
case "fullstack":
|
|
152
|
-
base.dependencies["@coherentjs/api"] = "^1.0.1";
|
|
153
|
-
base.dependencies["@coherentjs/database"] = "^1.0.1";
|
|
154
|
-
base.dependencies.express = "^4.18.2";
|
|
155
|
-
base.dependencies["@coherentjs/express"] = "^1.0.1";
|
|
156
|
-
break;
|
|
157
|
-
}
|
|
158
|
-
return base;
|
|
159
|
-
}
|
|
160
|
-
async function generateBasicTemplate(projectPath, name) {
|
|
161
|
-
const indexJs = `/**
|
|
162
|
-
* ${name} - Coherent.js Application
|
|
163
|
-
*/
|
|
164
|
-
|
|
165
|
-
import { renderToString, createComponent } from '@coherentjs/core';
|
|
166
|
-
import { createServer } from 'http';
|
|
167
|
-
|
|
168
|
-
// Simple component
|
|
169
|
-
const App = createComponent(() => ({
|
|
170
|
-
html: {
|
|
171
|
-
children: [
|
|
172
|
-
{
|
|
173
|
-
head: {
|
|
174
|
-
children: [
|
|
175
|
-
{ title: { text: '${name}' } },
|
|
176
|
-
{
|
|
177
|
-
meta: {
|
|
178
|
-
name: 'viewport',
|
|
179
|
-
content: 'width=device-width, initial-scale=1.0'
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
]
|
|
183
|
-
}
|
|
184
|
-
},
|
|
185
|
-
{
|
|
186
|
-
body: {
|
|
187
|
-
children: [
|
|
188
|
-
{
|
|
189
|
-
div: {
|
|
190
|
-
className: 'container',
|
|
191
|
-
children: [
|
|
192
|
-
{ h1: { text: 'Welcome to ${name}!' } },
|
|
193
|
-
{
|
|
194
|
-
p: {
|
|
195
|
-
text: 'This is a Coherent.js application built with pure JavaScript objects.'
|
|
196
|
-
}
|
|
197
|
-
},
|
|
198
|
-
{
|
|
199
|
-
div: {
|
|
200
|
-
className: 'features',
|
|
201
|
-
children: [
|
|
202
|
-
{ h2: { text: 'Features:' } },
|
|
203
|
-
{
|
|
204
|
-
ul: {
|
|
205
|
-
children: [
|
|
206
|
-
{ li: { text: '\u26A1 Lightning fast SSR' } },
|
|
207
|
-
{ li: { text: '\u{1F3AF} Pure JavaScript objects' } },
|
|
208
|
-
{ li: { text: '\u{1F512} Built-in XSS protection' } },
|
|
209
|
-
{ li: { text: '\u{1F4E6} Minimal bundle size' } }
|
|
210
|
-
]
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
]
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
]
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
]
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
]
|
|
223
|
-
}
|
|
224
|
-
}));
|
|
225
|
-
|
|
226
|
-
// Create server
|
|
227
|
-
const server = createServer((req, res) => {
|
|
228
|
-
const html = renderToString(App());
|
|
229
|
-
|
|
230
|
-
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
231
|
-
res.end(html);
|
|
232
|
-
});
|
|
233
|
-
|
|
234
|
-
const PORT = process.env.PORT || 3000;
|
|
235
|
-
server.listen(PORT, () => {
|
|
236
|
-
console.log(\`\u{1F680} Server running at http://localhost:\${PORT}\`);
|
|
237
|
-
});`;
|
|
238
|
-
(0, import_fs.writeFileSync)((0, import_path.join)(projectPath, "src/index.js"), indexJs);
|
|
239
|
-
const buttonComponent = `import { createComponent } from '@coherentjs/core';
|
|
240
|
-
|
|
241
|
-
export const Button = createComponent(({ text = 'Click me', onClick, className = '' }) => ({
|
|
242
|
-
button: {
|
|
243
|
-
className: \`btn \${className}\`,
|
|
244
|
-
onclick: onClick,
|
|
245
|
-
text
|
|
246
|
-
}
|
|
247
|
-
}));`;
|
|
248
|
-
(0, import_fs.writeFileSync)((0, import_path.join)(projectPath, "src/components/Button.js"), buttonComponent);
|
|
249
|
-
}
|
|
250
|
-
async function generateExpressTemplate(projectPath, name) {
|
|
251
|
-
const indexJs = `/**
|
|
252
|
-
* ${name} - Express + Coherent.js Application
|
|
253
|
-
*/
|
|
254
|
-
|
|
255
|
-
import express from 'express';
|
|
256
|
-
import { setupCoherentExpress } from '@coherentjs/express';
|
|
257
|
-
import { renderToString, createComponent } from '@coherentjs/core';
|
|
258
|
-
|
|
259
|
-
const app = express();
|
|
260
|
-
|
|
261
|
-
// Setup Coherent.js with Express
|
|
262
|
-
setupCoherentExpress(app);
|
|
263
|
-
|
|
264
|
-
// Serve static files
|
|
265
|
-
app.use(express.static('public'));
|
|
266
|
-
|
|
267
|
-
// Home page component
|
|
268
|
-
const HomePage = createComponent(() => ({
|
|
269
|
-
html: {
|
|
270
|
-
children: [
|
|
271
|
-
{
|
|
272
|
-
head: {
|
|
273
|
-
children: [
|
|
274
|
-
{ title: { text: '${name}' } },
|
|
275
|
-
{
|
|
276
|
-
meta: {
|
|
277
|
-
name: 'viewport',
|
|
278
|
-
content: 'width=device-width, initial-scale=1.0'
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
]
|
|
282
|
-
}
|
|
283
|
-
},
|
|
284
|
-
{
|
|
285
|
-
body: {
|
|
286
|
-
children: [
|
|
287
|
-
{
|
|
288
|
-
div: {
|
|
289
|
-
className: 'container',
|
|
290
|
-
children: [
|
|
291
|
-
{ h1: { text: 'Express + Coherent.js' } },
|
|
292
|
-
{
|
|
293
|
-
p: {
|
|
294
|
-
text: 'Combining the power of Express.js with Coherent.js SSR!'
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
]
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
]
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
]
|
|
304
|
-
}
|
|
305
|
-
}));
|
|
306
|
-
|
|
307
|
-
// Routes
|
|
308
|
-
app.get('/', (req, res) => {
|
|
309
|
-
const html = renderToString(HomePage());
|
|
310
|
-
res.send(html);
|
|
311
|
-
});
|
|
312
|
-
|
|
313
|
-
const PORT = process.env.PORT || 3000;
|
|
314
|
-
app.listen(PORT, () => {
|
|
315
|
-
console.log(\`\u{1F680} Express server running at http://localhost:\${PORT}\`);
|
|
316
|
-
});`;
|
|
317
|
-
(0, import_fs.writeFileSync)((0, import_path.join)(projectPath, "src/index.js"), indexJs);
|
|
318
|
-
}
|
|
319
|
-
function generateCommonFiles(projectPath, name) {
|
|
320
|
-
const readme = `# ${name}
|
|
321
|
-
|
|
322
|
-
A Coherent.js application built with pure JavaScript objects.
|
|
323
|
-
|
|
324
|
-
## Getting Started
|
|
325
|
-
|
|
326
|
-
\`\`\`bash
|
|
327
|
-
# Install dependencies
|
|
328
|
-
npm install
|
|
329
|
-
|
|
330
|
-
# Start development server
|
|
331
|
-
npm run dev
|
|
332
|
-
|
|
333
|
-
# Build for production
|
|
334
|
-
npm run build
|
|
335
|
-
|
|
336
|
-
# Run tests
|
|
337
|
-
npm test
|
|
338
|
-
\`\`\`
|
|
339
|
-
|
|
340
|
-
## Project Structure
|
|
341
|
-
|
|
342
|
-
\`\`\`
|
|
343
|
-
src/
|
|
344
|
-
components/ # Reusable components
|
|
345
|
-
pages/ # Page components
|
|
346
|
-
api/ # API routes
|
|
347
|
-
utils/ # Utility functions
|
|
348
|
-
index.js # Main entry point
|
|
349
|
-
public/ # Static assets
|
|
350
|
-
tests/ # Test files
|
|
351
|
-
\`\`\`
|
|
352
|
-
|
|
353
|
-
## Learn More
|
|
354
|
-
|
|
355
|
-
- [Coherent.js Documentation](https://github.com/Tomdrouv1/coherent.js)
|
|
356
|
-
- [API Reference](https://github.com/Tomdrouv1/coherent.js/docs/api-reference.md)
|
|
357
|
-
|
|
358
|
-
## License
|
|
359
|
-
|
|
360
|
-
MIT
|
|
361
|
-
`;
|
|
362
|
-
(0, import_fs.writeFileSync)((0, import_path.join)(projectPath, "README.md"), readme);
|
|
363
|
-
const gitignore = `# Dependencies
|
|
364
|
-
node_modules/
|
|
365
|
-
npm-debug.log*
|
|
366
|
-
yarn-debug.log*
|
|
367
|
-
yarn-_error.log*
|
|
368
|
-
|
|
369
|
-
# Production builds
|
|
370
|
-
dist/
|
|
371
|
-
build/
|
|
372
|
-
|
|
373
|
-
# Environment files
|
|
374
|
-
.env
|
|
375
|
-
.env.local
|
|
376
|
-
.env.development.local
|
|
377
|
-
.env.test.local
|
|
378
|
-
.env.production.local
|
|
379
|
-
|
|
380
|
-
# Runtime data
|
|
381
|
-
pids
|
|
382
|
-
*.pid
|
|
383
|
-
*.seed
|
|
384
|
-
*.pid.lock
|
|
385
|
-
|
|
386
|
-
# Coverage directory used by tools like istanbul
|
|
387
|
-
coverage/
|
|
388
|
-
*.lcov
|
|
389
|
-
|
|
390
|
-
# nyc test coverage
|
|
391
|
-
.nyc_output
|
|
392
|
-
|
|
393
|
-
# IDE files
|
|
394
|
-
.vscode/
|
|
395
|
-
.idea/
|
|
396
|
-
*.swp
|
|
397
|
-
*.swo
|
|
398
|
-
*~
|
|
399
|
-
|
|
400
|
-
# OS generated files
|
|
401
|
-
.DS_Store
|
|
402
|
-
.DS_Store?
|
|
403
|
-
._*
|
|
404
|
-
.Spotlight-V100
|
|
405
|
-
.Trashes
|
|
406
|
-
ehthumbs.db
|
|
407
|
-
Thumbs.db
|
|
408
|
-
|
|
409
|
-
# Logs
|
|
410
|
-
logs
|
|
411
|
-
*.log
|
|
412
|
-
|
|
413
|
-
# Optional npm cache directory
|
|
414
|
-
.npm
|
|
415
|
-
|
|
416
|
-
# Optional REPL history
|
|
417
|
-
.node_repl_history
|
|
418
|
-
`;
|
|
419
|
-
(0, import_fs.writeFileSync)((0, import_path.join)(projectPath, ".gitignore"), gitignore);
|
|
420
|
-
const testFile = `import { test } from 'node:test';
|
|
421
|
-
import assert from 'node:assert';
|
|
422
|
-
import { renderToString } from '@coherentjs/core';
|
|
423
|
-
|
|
424
|
-
test('renders basic component', () => {
|
|
425
|
-
const component = {
|
|
426
|
-
div: {
|
|
427
|
-
text: 'Hello, World!'
|
|
428
|
-
}
|
|
429
|
-
};
|
|
430
|
-
|
|
431
|
-
const html = renderToString(component);
|
|
432
|
-
assert(html.includes('Hello, World!'));
|
|
433
|
-
});
|
|
434
|
-
`;
|
|
435
|
-
(0, import_fs.writeFileSync)((0, import_path.join)(projectPath, "tests/basic.test.js"), testFile);
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
// src/utils/validation.js
|
|
439
|
-
var import_fs2 = require("fs");
|
|
440
|
-
var import_path2 = require("path");
|
|
441
|
-
function validateProjectName(name) {
|
|
442
|
-
if (!name || typeof name !== "string" || name.trim().length === 0) {
|
|
443
|
-
return "Project name is required";
|
|
444
|
-
}
|
|
445
|
-
const trimmed = name.trim();
|
|
446
|
-
if (trimmed.length > 214) {
|
|
447
|
-
return "Project name must be less than 214 characters";
|
|
448
|
-
}
|
|
449
|
-
if (!/^[a-z0-9-_@./]+$/i.test(trimmed)) {
|
|
450
|
-
return "Project name can only contain letters, numbers, hyphens, underscores, dots, and slashes";
|
|
451
|
-
}
|
|
452
|
-
if (trimmed.startsWith(".") || trimmed.startsWith("_")) {
|
|
453
|
-
return "Project name cannot start with . or _";
|
|
454
|
-
}
|
|
455
|
-
const reserved = [
|
|
456
|
-
"node_modules",
|
|
457
|
-
"favicon.ico",
|
|
458
|
-
".git",
|
|
459
|
-
".gitignore",
|
|
460
|
-
".env",
|
|
461
|
-
"package.json",
|
|
462
|
-
"package-lock.json",
|
|
463
|
-
"yarn.lock",
|
|
464
|
-
"pnpm-lock.yaml",
|
|
465
|
-
"con",
|
|
466
|
-
"prn",
|
|
467
|
-
"aux",
|
|
468
|
-
"nul",
|
|
469
|
-
"com1",
|
|
470
|
-
"com2",
|
|
471
|
-
"com3",
|
|
472
|
-
"com4",
|
|
473
|
-
"com5",
|
|
474
|
-
"com6",
|
|
475
|
-
"com7",
|
|
476
|
-
"com8",
|
|
477
|
-
"com9",
|
|
478
|
-
"lpt1",
|
|
479
|
-
"lpt2",
|
|
480
|
-
"lpt3",
|
|
481
|
-
"lpt4",
|
|
482
|
-
"lpt5",
|
|
483
|
-
"lpt6",
|
|
484
|
-
"lpt7",
|
|
485
|
-
"lpt8",
|
|
486
|
-
"lpt9"
|
|
487
|
-
];
|
|
488
|
-
if (reserved.includes(trimmed.toLowerCase())) {
|
|
489
|
-
return `Project name "${trimmed}" is reserved`;
|
|
490
|
-
}
|
|
491
|
-
const projectPath = (0, import_path2.resolve)(trimmed);
|
|
492
|
-
if ((0, import_fs2.existsSync)(projectPath)) {
|
|
493
|
-
return `Directory "${trimmed}" already exists`;
|
|
494
|
-
}
|
|
495
|
-
return true;
|
|
496
|
-
}
|
|
497
|
-
function validateComponentName(name) {
|
|
498
|
-
if (!name || typeof name !== "string" || name.trim().length === 0) {
|
|
499
|
-
return "Name is required";
|
|
500
|
-
}
|
|
501
|
-
const trimmed = name.trim();
|
|
502
|
-
if (!/^[a-zA-Z][a-zA-Z0-9-_]*$/.test(trimmed)) {
|
|
503
|
-
return "Name must start with a letter and contain only letters, numbers, hyphens, and underscores";
|
|
504
|
-
}
|
|
505
|
-
if (trimmed.length > 100) {
|
|
506
|
-
return "Name must be less than 100 characters";
|
|
507
|
-
}
|
|
508
|
-
if (!/^[A-Z]/.test(trimmed)) {
|
|
509
|
-
return "Name should start with a capital letter (PascalCase)";
|
|
510
|
-
}
|
|
511
|
-
return true;
|
|
512
|
-
}
|
|
513
|
-
|
|
514
|
-
// src/commands/create.js
|
|
515
|
-
var createCommand = new import_commander.Command("create").description("Create a new Coherent.js project").argument("[name]", "project name").option("-t, --template <template>", "project template", "basic").option("--skip-install", "skip npm install").option("--skip-git", "skip git initialization").action(async (name, options) => {
|
|
516
|
-
let projectName = name;
|
|
517
|
-
if (!projectName) {
|
|
518
|
-
const response = await (0, import_prompts.default)({
|
|
519
|
-
type: "text",
|
|
520
|
-
name: "name",
|
|
521
|
-
message: "What is your project name?",
|
|
522
|
-
initial: "my-coherent-app",
|
|
523
|
-
validate: validateProjectName
|
|
524
|
-
});
|
|
525
|
-
if (!response.name) {
|
|
526
|
-
console.log(import_picocolors.default.yellow("\u{1F44B} Project creation cancelled"));
|
|
527
|
-
process.exit(0);
|
|
528
|
-
}
|
|
529
|
-
projectName = response.name;
|
|
530
|
-
}
|
|
531
|
-
const nameValidation = validateProjectName(projectName);
|
|
532
|
-
if (nameValidation !== true) {
|
|
533
|
-
console.error(import_picocolors.default.red("\u274C Invalid project name:"), nameValidation);
|
|
534
|
-
process.exit(1);
|
|
535
|
-
}
|
|
536
|
-
const projectPath = (0, import_path3.resolve)(projectName);
|
|
537
|
-
if ((0, import_fs3.existsSync)(projectPath)) {
|
|
538
|
-
console.error(import_picocolors.default.red("\u274C Directory already exists:"), projectName);
|
|
539
|
-
process.exit(1);
|
|
540
|
-
}
|
|
541
|
-
console.log();
|
|
542
|
-
console.log(import_picocolors.default.cyan("\u{1F680} Creating Coherent.js project..."));
|
|
543
|
-
console.log(import_picocolors.default.gray("\u{1F4C1} Project:"), import_picocolors.default.bold(projectName));
|
|
544
|
-
console.log(import_picocolors.default.gray("\u{1F4CD} Location:"), projectPath);
|
|
545
|
-
console.log();
|
|
546
|
-
let template = options.template;
|
|
547
|
-
if (!template || template === "basic") {
|
|
548
|
-
const response = await (0, import_prompts.default)({
|
|
549
|
-
type: "select",
|
|
550
|
-
name: "template",
|
|
551
|
-
message: "Which template would you like to use?",
|
|
552
|
-
choices: [
|
|
553
|
-
{ title: "\u{1F3C3}\u200D\u2642\uFE0F Basic App", value: "basic", description: "Simple Coherent.js app with routing" },
|
|
554
|
-
{ title: "\u{1F310} Full Stack", value: "fullstack", description: "API + SSR with database integration" },
|
|
555
|
-
{ title: "\u26A1 Express Integration", value: "express", description: "Coherent.js with Express.js" },
|
|
556
|
-
{ title: "\u{1F680} Fastify Integration", value: "fastify", description: "Coherent.js with Fastify" },
|
|
557
|
-
{ title: "\u{1F4F1} Component Library", value: "components", description: "Reusable component library" }
|
|
558
|
-
],
|
|
559
|
-
initial: 0
|
|
560
|
-
});
|
|
561
|
-
if (!response.template) {
|
|
562
|
-
console.log(import_picocolors.default.yellow("\u{1F44B} Project creation cancelled"));
|
|
563
|
-
process.exit(0);
|
|
564
|
-
}
|
|
565
|
-
template = response.template;
|
|
566
|
-
}
|
|
567
|
-
const spinner = (0, import_ora.default)("Scaffolding project...").start();
|
|
568
|
-
try {
|
|
569
|
-
(0, import_fs3.mkdirSync)(projectPath, { recursive: true });
|
|
570
|
-
await scaffoldProject(projectPath, {
|
|
571
|
-
name: projectName,
|
|
572
|
-
template,
|
|
573
|
-
skipInstall: options.skipInstall,
|
|
574
|
-
skipGit: options.skipGit
|
|
575
|
-
});
|
|
576
|
-
spinner.succeed("Project created successfully!");
|
|
577
|
-
console.log();
|
|
578
|
-
console.log(import_picocolors.default.green("\u2705 Project created successfully!"));
|
|
579
|
-
console.log();
|
|
580
|
-
console.log(import_picocolors.default.cyan("Next steps:"));
|
|
581
|
-
console.log(import_picocolors.default.gray(" cd"), import_picocolors.default.bold(projectName));
|
|
582
|
-
if (!options.skipInstall) {
|
|
583
|
-
console.log(import_picocolors.default.gray(" npm run dev"));
|
|
584
|
-
} else {
|
|
585
|
-
console.log(import_picocolors.default.gray(" npm install"));
|
|
586
|
-
console.log(import_picocolors.default.gray(" npm run dev"));
|
|
587
|
-
}
|
|
588
|
-
console.log();
|
|
589
|
-
console.log(import_picocolors.default.gray("Happy coding! \u{1F389}"));
|
|
590
|
-
} catch (_error) {
|
|
591
|
-
spinner.fail("Failed to create project");
|
|
592
|
-
console.error(import_picocolors.default.red("\u274C Error:"), _error.message);
|
|
593
|
-
process.exit(1);
|
|
594
|
-
}
|
|
595
|
-
});
|
|
596
|
-
|
|
597
|
-
// src/commands/generate.js
|
|
598
|
-
var import_commander2 = require("commander");
|
|
599
|
-
var import_prompts2 = __toESM(require("prompts"), 1);
|
|
600
|
-
var import_ora2 = __toESM(require("ora"), 1);
|
|
601
|
-
var import_picocolors2 = __toESM(require("picocolors"), 1);
|
|
602
|
-
|
|
603
|
-
// src/generators/component-generator.js
|
|
604
|
-
var import_fs4 = require("fs");
|
|
605
|
-
var import_path4 = require("path");
|
|
606
|
-
async function generateComponent(name, options = {}) {
|
|
607
|
-
const { path = "src/components", template = "basic", skipTest = false, skipStory = false } = options;
|
|
608
|
-
const componentName = toPascalCase(name);
|
|
609
|
-
const fileName = componentName;
|
|
610
|
-
const outputDir = (0, import_path4.join)(process.cwd(), path);
|
|
611
|
-
if (!(0, import_fs4.existsSync)(outputDir)) {
|
|
612
|
-
(0, import_fs4.mkdirSync)(outputDir, { recursive: true });
|
|
613
|
-
}
|
|
614
|
-
const files = [];
|
|
615
|
-
const nextSteps = [];
|
|
616
|
-
const componentPath = (0, import_path4.join)(outputDir, `${fileName}.js`);
|
|
617
|
-
const componentContent = generateComponentContent(componentName, template);
|
|
618
|
-
(0, import_fs4.writeFileSync)(componentPath, componentContent);
|
|
619
|
-
files.push(componentPath);
|
|
620
|
-
if (!skipTest) {
|
|
621
|
-
const testPath = (0, import_path4.join)(outputDir, `${fileName}.test.js`);
|
|
622
|
-
const testContent = generateTestContent(componentName);
|
|
623
|
-
(0, import_fs4.writeFileSync)(testPath, testContent);
|
|
624
|
-
files.push(testPath);
|
|
625
|
-
}
|
|
626
|
-
if (!skipStory) {
|
|
627
|
-
const storyPath = (0, import_path4.join)(outputDir, `${fileName}.stories.js`);
|
|
628
|
-
const storyContent = generateStoryContent(componentName);
|
|
629
|
-
(0, import_fs4.writeFileSync)(storyPath, storyContent);
|
|
630
|
-
files.push(storyPath);
|
|
631
|
-
}
|
|
632
|
-
nextSteps.push(`Import the component: import { ${componentName} } from '${path}/${fileName}.js'`);
|
|
633
|
-
nextSteps.push(`Use the component: ${componentName}({ /* props */ })`);
|
|
634
|
-
if (!skipTest) {
|
|
635
|
-
nextSteps.push("Run tests: npm test");
|
|
636
|
-
}
|
|
637
|
-
return { files, nextSteps };
|
|
638
|
-
}
|
|
639
|
-
function generateComponentContent(name, template) {
|
|
640
|
-
switch (template) {
|
|
641
|
-
case "functional":
|
|
642
|
-
return generateFunctionalComponent(name);
|
|
643
|
-
case "interactive":
|
|
644
|
-
return generateInteractiveComponent(name);
|
|
645
|
-
case "layout":
|
|
646
|
-
return generateLayoutComponent(name);
|
|
647
|
-
default:
|
|
648
|
-
return generateBasicComponent(name);
|
|
649
|
-
}
|
|
650
|
-
}
|
|
651
|
-
function generateBasicComponent(name) {
|
|
652
|
-
return `import { createComponent } from '@coherentjs/core';
|
|
653
|
-
|
|
654
|
-
/**
|
|
655
|
-
* ${name} component
|
|
656
|
-
*
|
|
657
|
-
* @param {Object} props - Component properties
|
|
658
|
-
* @param {string} props.className - CSS class name
|
|
659
|
-
* @param {Array|Object} props.children - Child elements
|
|
660
|
-
*/
|
|
661
|
-
export const ${name} = createComponent(({ className = '', children, ...props }) => ({
|
|
662
|
-
div: {
|
|
663
|
-
className: \`${name.toLowerCase()} \${className}\`.trim(),
|
|
664
|
-
children,
|
|
665
|
-
...props
|
|
666
|
-
}
|
|
667
|
-
}));
|
|
668
|
-
|
|
669
|
-
// Usage example:
|
|
670
|
-
// ${name}({
|
|
671
|
-
// className: 'custom-class',
|
|
672
|
-
// children: [
|
|
673
|
-
// { h2: { text: 'Hello from ${name}!' } }
|
|
674
|
-
// ]
|
|
675
|
-
// })
|
|
676
|
-
`;
|
|
677
|
-
}
|
|
678
|
-
function generateFunctionalComponent(name) {
|
|
679
|
-
return `import { createComponent } from '@coherentjs/core';
|
|
680
|
-
|
|
681
|
-
/**
|
|
682
|
-
* ${name} - Functional component with business logic
|
|
683
|
-
*
|
|
684
|
-
* @param {Object} props - Component properties
|
|
685
|
-
* @param {Array} props.items - Items to display
|
|
686
|
-
* @param {Function} props.onItemClick - Callback for item clicks
|
|
687
|
-
* @param {string} props.className - CSS class name
|
|
688
|
-
*/
|
|
689
|
-
export const ${name} = createComponent(({ items = [], onItemClick, className = '' }) => {
|
|
690
|
-
// Business logic
|
|
691
|
-
const processedItems = items.map((item, index) => ({
|
|
692
|
-
...item,
|
|
693
|
-
id: item.id || \`item-\${index}\`,
|
|
694
|
-
displayText: item.text || item.name || 'Untitled'
|
|
695
|
-
}));
|
|
696
|
-
|
|
697
|
-
return {
|
|
698
|
-
div: {
|
|
699
|
-
className: \`${name.toLowerCase()} \${className}\`.trim(),
|
|
700
|
-
children: [
|
|
701
|
-
{
|
|
702
|
-
h3: {
|
|
703
|
-
className: '${name.toLowerCase()}__title',
|
|
704
|
-
text: '${name}'
|
|
705
|
-
}
|
|
706
|
-
},
|
|
707
|
-
{
|
|
708
|
-
ul: {
|
|
709
|
-
className: '${name.toLowerCase()}__list',
|
|
710
|
-
children: processedItems.map(item => ({
|
|
711
|
-
li: {
|
|
712
|
-
key: item.id,
|
|
713
|
-
className: '${name.toLowerCase()}__item',
|
|
714
|
-
onclick: () => onItemClick && onItemClick(item),
|
|
715
|
-
children: [
|
|
716
|
-
{
|
|
717
|
-
span: {
|
|
718
|
-
className: '${name.toLowerCase()}__item-text',
|
|
719
|
-
text: item.displayText
|
|
720
|
-
}
|
|
721
|
-
}
|
|
722
|
-
]
|
|
723
|
-
}
|
|
724
|
-
}))
|
|
725
|
-
}
|
|
726
|
-
}
|
|
727
|
-
]
|
|
728
|
-
}
|
|
729
|
-
};
|
|
730
|
-
});
|
|
731
|
-
|
|
732
|
-
// Usage example:
|
|
733
|
-
// ${name}({
|
|
734
|
-
// items: [
|
|
735
|
-
// { id: '1', text: 'First item' },
|
|
736
|
-
// { id: '2', text: 'Second item' }
|
|
737
|
-
// ],
|
|
738
|
-
// onItemClick: (item) => console.log('Clicked:', item),
|
|
739
|
-
// className: 'custom-list'
|
|
740
|
-
// })
|
|
741
|
-
`;
|
|
742
|
-
}
|
|
743
|
-
function generateInteractiveComponent(name) {
|
|
744
|
-
return `import { createComponent } from '@coherentjs/core';
|
|
745
|
-
|
|
746
|
-
/**
|
|
747
|
-
* ${name} - Interactive component with state management
|
|
748
|
-
*
|
|
749
|
-
* @param {Object} props - Component properties
|
|
750
|
-
* @param {*} props.initialValue - Initial value
|
|
751
|
-
* @param {Function} props.onChange - Change callback
|
|
752
|
-
* @param {string} props.className - CSS class name
|
|
753
|
-
*/
|
|
754
|
-
export const ${name} = createComponent(({
|
|
755
|
-
initialValue = '',
|
|
756
|
-
onChange,
|
|
757
|
-
className = '',
|
|
758
|
-
...props
|
|
759
|
-
}) => {
|
|
760
|
-
// Component state (handled by Coherent.js hydration)
|
|
761
|
-
const state = {
|
|
762
|
-
value: initialValue,
|
|
763
|
-
isActive: false
|
|
764
|
-
};
|
|
765
|
-
|
|
766
|
-
const handleChange = (newValue) => {
|
|
767
|
-
state.value = newValue;
|
|
768
|
-
if (onChange) {
|
|
769
|
-
onChange(newValue);
|
|
770
|
-
}
|
|
771
|
-
};
|
|
772
|
-
|
|
773
|
-
const handleToggle = () => {
|
|
774
|
-
state.isActive = !state.isActive;
|
|
775
|
-
};
|
|
776
|
-
|
|
777
|
-
return {
|
|
778
|
-
div: {
|
|
779
|
-
className: \`${name.toLowerCase()} \${state.isActive ? 'active' : ''} \${className}\`.trim(),
|
|
780
|
-
'data-component': '${name}',
|
|
781
|
-
children: [
|
|
782
|
-
{
|
|
783
|
-
input: {
|
|
784
|
-
type: 'text',
|
|
785
|
-
value: state.value,
|
|
786
|
-
className: '${name.toLowerCase()}__input',
|
|
787
|
-
oninput: (event) => handleChange(event.target.value),
|
|
788
|
-
placeholder: 'Enter text...',
|
|
789
|
-
...props
|
|
790
|
-
}
|
|
791
|
-
},
|
|
792
|
-
{
|
|
793
|
-
button: {
|
|
794
|
-
type: 'button',
|
|
795
|
-
className: \`${name.toLowerCase()}__toggle \${state.isActive ? 'active' : ''}\`,
|
|
796
|
-
onclick: handleToggle,
|
|
797
|
-
text: state.isActive ? 'Deactivate' : 'Activate'
|
|
798
|
-
}
|
|
799
|
-
},
|
|
800
|
-
{
|
|
801
|
-
div: {
|
|
802
|
-
className: '${name.toLowerCase()}__display',
|
|
803
|
-
children: [
|
|
804
|
-
{
|
|
805
|
-
p: {
|
|
806
|
-
text: \`Value: \${state.value}\`
|
|
807
|
-
}
|
|
808
|
-
},
|
|
809
|
-
{
|
|
810
|
-
p: {
|
|
811
|
-
text: \`Status: \${state.isActive ? 'Active' : 'Inactive'}\`
|
|
812
|
-
}
|
|
813
|
-
}
|
|
814
|
-
]
|
|
815
|
-
}
|
|
816
|
-
}
|
|
817
|
-
]
|
|
818
|
-
}
|
|
819
|
-
};
|
|
820
|
-
});
|
|
821
|
-
|
|
822
|
-
// Usage example:
|
|
823
|
-
// ${name}({
|
|
824
|
-
// initialValue: 'Hello World',
|
|
825
|
-
// onChange: (value) => console.log('Changed:', value),
|
|
826
|
-
// className: 'my-interactive-component'
|
|
827
|
-
// })
|
|
828
|
-
`;
|
|
829
|
-
}
|
|
830
|
-
function generateLayoutComponent(name) {
|
|
831
|
-
return `import { createComponent } from '@coherentjs/core';
|
|
832
|
-
|
|
833
|
-
/**
|
|
834
|
-
* ${name} - Layout component for page structure
|
|
835
|
-
*
|
|
836
|
-
* @param {Object} props - Component properties
|
|
837
|
-
* @param {string} props.title - Page title
|
|
838
|
-
* @param {Array|Object} props.children - Child content
|
|
839
|
-
* @param {Object} props.header - Header content
|
|
840
|
-
* @param {Object} props.footer - Footer content
|
|
841
|
-
* @param {string} props.className - CSS class name
|
|
842
|
-
*/
|
|
843
|
-
export const ${name} = createComponent(({
|
|
844
|
-
title = 'Page Title',
|
|
845
|
-
children = [],
|
|
846
|
-
header = null,
|
|
847
|
-
footer = null,
|
|
848
|
-
className = ''
|
|
849
|
-
}) => ({
|
|
850
|
-
div: {
|
|
851
|
-
className: \`${name.toLowerCase()} \${className}\`.trim(),
|
|
852
|
-
children: [
|
|
853
|
-
// Header section
|
|
854
|
-
header ? {
|
|
855
|
-
header: {
|
|
856
|
-
className: '${name.toLowerCase()}__header',
|
|
857
|
-
children: Array.isArray(header) ? header : [header]
|
|
858
|
-
}
|
|
859
|
-
} : {
|
|
860
|
-
header: {
|
|
861
|
-
className: '${name.toLowerCase()}__header',
|
|
862
|
-
children: [
|
|
863
|
-
{
|
|
864
|
-
h1: {
|
|
865
|
-
className: '${name.toLowerCase()}__title',
|
|
866
|
-
text: title
|
|
867
|
-
}
|
|
868
|
-
}
|
|
869
|
-
]
|
|
870
|
-
}
|
|
871
|
-
},
|
|
872
|
-
|
|
873
|
-
// Main content
|
|
874
|
-
{
|
|
875
|
-
main: {
|
|
876
|
-
className: '${name.toLowerCase()}__content',
|
|
877
|
-
children: Array.isArray(children) ? children : [children]
|
|
878
|
-
}
|
|
879
|
-
},
|
|
880
|
-
|
|
881
|
-
// Footer section
|
|
882
|
-
footer ? {
|
|
883
|
-
footer: {
|
|
884
|
-
className: '${name.toLowerCase()}__footer',
|
|
885
|
-
children: Array.isArray(footer) ? footer : [footer]
|
|
886
|
-
}
|
|
887
|
-
} : {
|
|
888
|
-
footer: {
|
|
889
|
-
className: '${name.toLowerCase()}__footer',
|
|
890
|
-
children: [
|
|
891
|
-
{
|
|
892
|
-
p: {
|
|
893
|
-
text: \`\xA9 \${new Date().getFullYear()} ${name}\`
|
|
894
|
-
}
|
|
895
|
-
}
|
|
896
|
-
]
|
|
897
|
-
}
|
|
898
|
-
}
|
|
899
|
-
]
|
|
900
|
-
}
|
|
901
|
-
}));
|
|
902
|
-
|
|
903
|
-
// Usage example:
|
|
904
|
-
// ${name}({
|
|
905
|
-
// title: 'Welcome Page',
|
|
906
|
-
// header: { nav: { text: 'Navigation' } },
|
|
907
|
-
// children: [
|
|
908
|
-
// { h2: { text: 'Main Content' } },
|
|
909
|
-
// { p: { text: 'This is the main page content.' } }
|
|
910
|
-
// ],
|
|
911
|
-
// footer: { p: { text: 'Custom footer' } },
|
|
912
|
-
// className: 'page-layout'
|
|
913
|
-
// })
|
|
914
|
-
`;
|
|
915
|
-
}
|
|
916
|
-
function generateTestContent(name) {
|
|
917
|
-
return `import { test } from 'node:test';
|
|
918
|
-
import assert from 'node:assert';
|
|
919
|
-
import { renderToString } from '@coherentjs/core';
|
|
920
|
-
import { ${name} } from './${name}.js';
|
|
921
|
-
|
|
922
|
-
test('${name} renders correctly', () => {
|
|
923
|
-
const component = ${name}({});
|
|
924
|
-
const html = renderToString(component);
|
|
925
|
-
|
|
926
|
-
assert(typeof html === 'string');
|
|
927
|
-
assert(html.length > 0);
|
|
928
|
-
assert(html.includes('${name.toLowerCase()}'));
|
|
929
|
-
});
|
|
930
|
-
|
|
931
|
-
test('${name} accepts className prop', () => {
|
|
932
|
-
const component = ${name}({ className: 'test-class' });
|
|
933
|
-
const html = renderToString(component);
|
|
934
|
-
|
|
935
|
-
assert(html.includes('test-class'));
|
|
936
|
-
});
|
|
937
|
-
|
|
938
|
-
test('${name} renders children correctly', () => {
|
|
939
|
-
const children = [
|
|
940
|
-
{ p: { text: 'Test child content' } }
|
|
941
|
-
];
|
|
942
|
-
|
|
943
|
-
const component = ${name}({ children });
|
|
944
|
-
const html = renderToString(component);
|
|
945
|
-
|
|
946
|
-
assert(html.includes('Test child content'));
|
|
947
|
-
});
|
|
948
|
-
`;
|
|
949
|
-
}
|
|
950
|
-
function generateStoryContent(name) {
|
|
951
|
-
return `import { ${name} } from './${name}.js';
|
|
952
|
-
|
|
953
|
-
export default {
|
|
954
|
-
title: 'Components/${name}',
|
|
955
|
-
component: ${name},
|
|
956
|
-
argTypes: {
|
|
957
|
-
className: { control: 'text' },
|
|
958
|
-
children: { control: 'object' }
|
|
959
|
-
}
|
|
960
|
-
};
|
|
961
|
-
|
|
962
|
-
// Default story
|
|
963
|
-
export const Default = {
|
|
964
|
-
args: {}
|
|
965
|
-
};
|
|
966
|
-
|
|
967
|
-
// With custom class
|
|
968
|
-
export const WithCustomClass = {
|
|
969
|
-
args: {
|
|
970
|
-
className: 'custom-style'
|
|
971
|
-
}
|
|
972
|
-
};
|
|
973
|
-
|
|
974
|
-
// With children
|
|
975
|
-
export const WithChildren = {
|
|
976
|
-
args: {
|
|
977
|
-
children: [
|
|
978
|
-
{ h3: { text: 'Story Title' } },
|
|
979
|
-
{ p: { text: 'This is a story example with children.' } }
|
|
980
|
-
]
|
|
981
|
-
}
|
|
982
|
-
};
|
|
983
|
-
`;
|
|
984
|
-
}
|
|
985
|
-
function toPascalCase(str) {
|
|
986
|
-
return str.replace(/[-_\s]+(.)?/g, (_, c) => c ? c.toUpperCase() : "").replace(/^(.)/, (_, c) => c.toUpperCase());
|
|
987
|
-
}
|
|
988
|
-
|
|
989
|
-
// src/generators/page-generator.js
|
|
990
|
-
var import_fs5 = require("fs");
|
|
991
|
-
var import_path5 = require("path");
|
|
992
|
-
async function generatePage(name, options = {}) {
|
|
993
|
-
const { path = "src/pages", template = "basic", skipTest = false } = options;
|
|
994
|
-
const pageName = toPascalCase2(name);
|
|
995
|
-
const fileName = pageName;
|
|
996
|
-
const outputDir = (0, import_path5.join)(process.cwd(), path);
|
|
997
|
-
if (!(0, import_fs5.existsSync)(outputDir)) {
|
|
998
|
-
(0, import_fs5.mkdirSync)(outputDir, { recursive: true });
|
|
999
|
-
}
|
|
1000
|
-
const files = [];
|
|
1001
|
-
const nextSteps = [];
|
|
1002
|
-
const pagePath = (0, import_path5.join)(outputDir, `${fileName}.js`);
|
|
1003
|
-
const pageContent = generatePageContent(pageName, template);
|
|
1004
|
-
(0, import_fs5.writeFileSync)(pagePath, pageContent);
|
|
1005
|
-
files.push(pagePath);
|
|
1006
|
-
if (!skipTest) {
|
|
1007
|
-
const testPath = (0, import_path5.join)(outputDir, `${fileName}.test.js`);
|
|
1008
|
-
const testContent = generateTestContent2(pageName);
|
|
1009
|
-
(0, import_fs5.writeFileSync)(testPath, testContent);
|
|
1010
|
-
files.push(testPath);
|
|
1011
|
-
}
|
|
1012
|
-
nextSteps.push(`Import the page: import { ${pageName} } from '${path}/${fileName}.js'`);
|
|
1013
|
-
nextSteps.push(`Add route to your router for /${name.toLowerCase()}`);
|
|
1014
|
-
nextSteps.push(`Visit http://localhost:3000/${name.toLowerCase()} to see the page`);
|
|
1015
|
-
if (!skipTest) {
|
|
1016
|
-
nextSteps.push("Run tests: npm test");
|
|
1017
|
-
}
|
|
1018
|
-
return { files, nextSteps };
|
|
1019
|
-
}
|
|
1020
|
-
function generatePageContent(name, template) {
|
|
1021
|
-
switch (template) {
|
|
1022
|
-
case "dashboard":
|
|
1023
|
-
return generateDashboardPage(name);
|
|
1024
|
-
case "form":
|
|
1025
|
-
return generateFormPage(name);
|
|
1026
|
-
case "list":
|
|
1027
|
-
console.warn("List page generation not yet implemented, falling back to basic");
|
|
1028
|
-
return generateBasicPage(name);
|
|
1029
|
-
case "detail":
|
|
1030
|
-
console.warn("Detail page generation not yet implemented, falling back to basic");
|
|
1031
|
-
return generateBasicPage(name);
|
|
1032
|
-
default:
|
|
1033
|
-
return generateBasicPage(name);
|
|
1034
|
-
}
|
|
1035
|
-
}
|
|
1036
|
-
function generateBasicPage(name) {
|
|
1037
|
-
const routeName = name.toLowerCase();
|
|
1038
|
-
return `import { createComponent } from '@coherentjs/core';
|
|
1039
|
-
|
|
1040
|
-
/**
|
|
1041
|
-
* ${name} Page Component
|
|
1042
|
-
* Route: /${routeName}
|
|
1043
|
-
*
|
|
1044
|
-
* @param {Object} props - Page properties
|
|
1045
|
-
* @param {Object} props.params - Route parameters
|
|
1046
|
-
* @param {Object} props.query - Query parameters
|
|
1047
|
-
* @param {Object} props.request - Request object (SSR only)
|
|
1048
|
-
*/
|
|
1049
|
-
export const ${name} = createComponent(({ params = {}, query = {}, request, ...props }) => {
|
|
1050
|
-
// Page metadata
|
|
1051
|
-
const pageTitle = '${name}';
|
|
1052
|
-
const pageDescription = '${name} page description';
|
|
1053
|
-
|
|
1054
|
-
return {
|
|
1055
|
-
html: {
|
|
1056
|
-
children: [
|
|
1057
|
-
{
|
|
1058
|
-
head: {
|
|
1059
|
-
children: [
|
|
1060
|
-
{ title: { text: pageTitle } },
|
|
1061
|
-
{
|
|
1062
|
-
meta: {
|
|
1063
|
-
name: 'description',
|
|
1064
|
-
content: pageDescription
|
|
1065
|
-
}
|
|
1066
|
-
},
|
|
1067
|
-
{
|
|
1068
|
-
meta: {
|
|
1069
|
-
name: 'viewport',
|
|
1070
|
-
content: 'width=device-width, initial-scale=1.0'
|
|
1071
|
-
}
|
|
1072
|
-
}
|
|
1073
|
-
]
|
|
1074
|
-
}
|
|
1075
|
-
},
|
|
1076
|
-
{
|
|
1077
|
-
body: {
|
|
1078
|
-
children: [
|
|
1079
|
-
{
|
|
1080
|
-
div: {
|
|
1081
|
-
className: 'page ${routeName}-page',
|
|
1082
|
-
children: [
|
|
1083
|
-
// Header
|
|
1084
|
-
{
|
|
1085
|
-
header: {
|
|
1086
|
-
className: 'page-header',
|
|
1087
|
-
children: [
|
|
1088
|
-
{
|
|
1089
|
-
h1: {
|
|
1090
|
-
className: 'page-title',
|
|
1091
|
-
text: pageTitle
|
|
1092
|
-
}
|
|
1093
|
-
},
|
|
1094
|
-
{
|
|
1095
|
-
p: {
|
|
1096
|
-
className: 'page-description',
|
|
1097
|
-
text: pageDescription
|
|
1098
|
-
}
|
|
1099
|
-
}
|
|
1100
|
-
]
|
|
1101
|
-
}
|
|
1102
|
-
},
|
|
1103
|
-
|
|
1104
|
-
// Main content
|
|
1105
|
-
{
|
|
1106
|
-
main: {
|
|
1107
|
-
className: 'page-content',
|
|
1108
|
-
children: [
|
|
1109
|
-
{
|
|
1110
|
-
section: {
|
|
1111
|
-
className: 'welcome-section',
|
|
1112
|
-
children: [
|
|
1113
|
-
{
|
|
1114
|
-
h2: {
|
|
1115
|
-
text: 'Welcome to ${name}!'
|
|
1116
|
-
}
|
|
1117
|
-
},
|
|
1118
|
-
{
|
|
1119
|
-
p: {
|
|
1120
|
-
text: 'This is a generated page component. You can customize it by editing the ${name}.js file.'
|
|
1121
|
-
}
|
|
1122
|
-
},
|
|
1123
|
-
{
|
|
1124
|
-
div: {
|
|
1125
|
-
className: 'page-actions',
|
|
1126
|
-
children: [
|
|
1127
|
-
{
|
|
1128
|
-
button: {
|
|
1129
|
-
className: 'btn btn-primary',
|
|
1130
|
-
onclick: () => console.log('Button clicked!'),
|
|
1131
|
-
text: 'Get Started'
|
|
1132
|
-
}
|
|
1133
|
-
},
|
|
1134
|
-
{
|
|
1135
|
-
a: {
|
|
1136
|
-
href: '/',
|
|
1137
|
-
className: 'btn btn-secondary',
|
|
1138
|
-
text: 'Back to Home'
|
|
1139
|
-
}
|
|
1140
|
-
}
|
|
1141
|
-
]
|
|
1142
|
-
}
|
|
1143
|
-
}
|
|
1144
|
-
]
|
|
1145
|
-
}
|
|
1146
|
-
}
|
|
1147
|
-
]
|
|
1148
|
-
}
|
|
1149
|
-
},
|
|
1150
|
-
|
|
1151
|
-
// Footer
|
|
1152
|
-
{
|
|
1153
|
-
footer: {
|
|
1154
|
-
className: 'page-footer',
|
|
1155
|
-
children: [
|
|
1156
|
-
{
|
|
1157
|
-
p: {
|
|
1158
|
-
text: \`\xA9 \${new Date().getFullYear()} ${name} Page\`
|
|
1159
|
-
}
|
|
1160
|
-
}
|
|
1161
|
-
]
|
|
1162
|
-
}
|
|
1163
|
-
}
|
|
1164
|
-
]
|
|
1165
|
-
}
|
|
1166
|
-
}
|
|
1167
|
-
]
|
|
1168
|
-
}
|
|
1169
|
-
}
|
|
1170
|
-
]
|
|
1171
|
-
}
|
|
1172
|
-
};
|
|
1173
|
-
});
|
|
1174
|
-
|
|
1175
|
-
// Page configuration (optional)
|
|
1176
|
-
${name}.route = '/${routeName}';
|
|
1177
|
-
${name}.title = '${name}';
|
|
1178
|
-
${name}.description = '${name} page description';
|
|
1179
|
-
|
|
1180
|
-
// Usage in router:
|
|
1181
|
-
// app.get('/${routeName}', (req, res) => {
|
|
1182
|
-
// const html = renderToString(${name}({
|
|
1183
|
-
// params: req.params,
|
|
1184
|
-
// query: req.query,
|
|
1185
|
-
// request: req
|
|
1186
|
-
// }));
|
|
1187
|
-
// res.send(html);
|
|
1188
|
-
// });
|
|
1189
|
-
`;
|
|
1190
|
-
}
|
|
1191
|
-
function generateDashboardPage(name) {
|
|
1192
|
-
return `import { createComponent } from '@coherentjs/core';
|
|
1193
|
-
|
|
1194
|
-
/**
|
|
1195
|
-
* ${name} Dashboard Page
|
|
1196
|
-
* Route: /${name.toLowerCase()}
|
|
1197
|
-
*/
|
|
1198
|
-
export const ${name} = createComponent(({ stats = {}, user = null }) => {
|
|
1199
|
-
const defaultStats = {
|
|
1200
|
-
totalUsers: 0,
|
|
1201
|
-
totalOrders: 0,
|
|
1202
|
-
revenue: 0,
|
|
1203
|
-
...stats
|
|
1204
|
-
};
|
|
1205
|
-
|
|
1206
|
-
return {
|
|
1207
|
-
html: {
|
|
1208
|
-
children: [
|
|
1209
|
-
{
|
|
1210
|
-
head: {
|
|
1211
|
-
children: [
|
|
1212
|
-
{ title: { text: '${name} Dashboard' } },
|
|
1213
|
-
{
|
|
1214
|
-
meta: {
|
|
1215
|
-
name: 'viewport',
|
|
1216
|
-
content: 'width=device-width, initial-scale=1.0'
|
|
1217
|
-
}
|
|
1218
|
-
}
|
|
1219
|
-
]
|
|
1220
|
-
}
|
|
1221
|
-
},
|
|
1222
|
-
{
|
|
1223
|
-
body: {
|
|
1224
|
-
children: [
|
|
1225
|
-
{
|
|
1226
|
-
div: {
|
|
1227
|
-
className: 'dashboard',
|
|
1228
|
-
children: [
|
|
1229
|
-
// Header
|
|
1230
|
-
{
|
|
1231
|
-
header: {
|
|
1232
|
-
className: 'dashboard-header',
|
|
1233
|
-
children: [
|
|
1234
|
-
{
|
|
1235
|
-
h1: {
|
|
1236
|
-
text: '${name} Dashboard'
|
|
1237
|
-
}
|
|
1238
|
-
},
|
|
1239
|
-
user ? {
|
|
1240
|
-
p: {
|
|
1241
|
-
text: \`Welcome back, \${user.name || 'User'}!\`
|
|
1242
|
-
}
|
|
1243
|
-
} : null
|
|
1244
|
-
].filter(Boolean)
|
|
1245
|
-
}
|
|
1246
|
-
},
|
|
1247
|
-
|
|
1248
|
-
// Stats grid
|
|
1249
|
-
{
|
|
1250
|
-
div: {
|
|
1251
|
-
className: 'stats-grid',
|
|
1252
|
-
children: [
|
|
1253
|
-
{
|
|
1254
|
-
div: {
|
|
1255
|
-
className: 'stat-card',
|
|
1256
|
-
children: [
|
|
1257
|
-
{
|
|
1258
|
-
h3: {
|
|
1259
|
-
text: 'Total Users'
|
|
1260
|
-
}
|
|
1261
|
-
},
|
|
1262
|
-
{
|
|
1263
|
-
p: {
|
|
1264
|
-
className: 'stat-number',
|
|
1265
|
-
text: defaultStats.totalUsers.toLocaleString()
|
|
1266
|
-
}
|
|
1267
|
-
}
|
|
1268
|
-
]
|
|
1269
|
-
}
|
|
1270
|
-
},
|
|
1271
|
-
{
|
|
1272
|
-
div: {
|
|
1273
|
-
className: 'stat-card',
|
|
1274
|
-
children: [
|
|
1275
|
-
{
|
|
1276
|
-
h3: {
|
|
1277
|
-
text: 'Total Orders'
|
|
1278
|
-
}
|
|
1279
|
-
},
|
|
1280
|
-
{
|
|
1281
|
-
p: {
|
|
1282
|
-
className: 'stat-number',
|
|
1283
|
-
text: defaultStats.totalOrders.toLocaleString()
|
|
1284
|
-
}
|
|
1285
|
-
}
|
|
1286
|
-
]
|
|
1287
|
-
}
|
|
1288
|
-
},
|
|
1289
|
-
{
|
|
1290
|
-
div: {
|
|
1291
|
-
className: 'stat-card',
|
|
1292
|
-
children: [
|
|
1293
|
-
{
|
|
1294
|
-
h3: {
|
|
1295
|
-
text: 'Revenue'
|
|
1296
|
-
}
|
|
1297
|
-
},
|
|
1298
|
-
{
|
|
1299
|
-
p: {
|
|
1300
|
-
className: 'stat-number',
|
|
1301
|
-
text: \`$\${defaultStats.revenue.toLocaleString()}\`
|
|
1302
|
-
}
|
|
1303
|
-
}
|
|
1304
|
-
]
|
|
1305
|
-
}
|
|
1306
|
-
}
|
|
1307
|
-
]
|
|
1308
|
-
}
|
|
1309
|
-
},
|
|
1310
|
-
|
|
1311
|
-
// Main content area
|
|
1312
|
-
{
|
|
1313
|
-
main: {
|
|
1314
|
-
className: 'dashboard-content',
|
|
1315
|
-
children: [
|
|
1316
|
-
{
|
|
1317
|
-
section: {
|
|
1318
|
-
className: 'recent-activity',
|
|
1319
|
-
children: [
|
|
1320
|
-
{
|
|
1321
|
-
h2: {
|
|
1322
|
-
text: 'Recent Activity'
|
|
1323
|
-
}
|
|
1324
|
-
},
|
|
1325
|
-
{
|
|
1326
|
-
p: {
|
|
1327
|
-
text: 'No recent activity to display.'
|
|
1328
|
-
}
|
|
1329
|
-
}
|
|
1330
|
-
]
|
|
1331
|
-
}
|
|
1332
|
-
}
|
|
1333
|
-
]
|
|
1334
|
-
}
|
|
1335
|
-
}
|
|
1336
|
-
]
|
|
1337
|
-
}
|
|
1338
|
-
}
|
|
1339
|
-
]
|
|
1340
|
-
}
|
|
1341
|
-
}
|
|
1342
|
-
]
|
|
1343
|
-
}
|
|
1344
|
-
};
|
|
1345
|
-
});
|
|
1346
|
-
`;
|
|
1347
|
-
}
|
|
1348
|
-
function generateFormPage(name) {
|
|
1349
|
-
return `import { createComponent } from '@coherentjs/core';
|
|
1350
|
-
|
|
1351
|
-
/**
|
|
1352
|
-
* ${name} Form Page
|
|
1353
|
-
* Route: /${name.toLowerCase()}
|
|
1354
|
-
*/
|
|
1355
|
-
export const ${name} = createComponent(({ initialData = {}, errors = {} }) => {
|
|
1356
|
-
const handleSubmit = (formData) => {
|
|
1357
|
-
console.log('Form submitted:', formData);
|
|
1358
|
-
// Handle form submission logic here
|
|
1359
|
-
};
|
|
1360
|
-
|
|
1361
|
-
return {
|
|
1362
|
-
html: {
|
|
1363
|
-
children: [
|
|
1364
|
-
{
|
|
1365
|
-
head: {
|
|
1366
|
-
children: [
|
|
1367
|
-
{ title: { text: '${name} Form' } },
|
|
1368
|
-
{
|
|
1369
|
-
meta: {
|
|
1370
|
-
name: 'viewport',
|
|
1371
|
-
content: 'width=device-width, initial-scale=1.0'
|
|
1372
|
-
}
|
|
1373
|
-
}
|
|
1374
|
-
]
|
|
1375
|
-
}
|
|
1376
|
-
},
|
|
1377
|
-
{
|
|
1378
|
-
body: {
|
|
1379
|
-
children: [
|
|
1380
|
-
{
|
|
1381
|
-
div: {
|
|
1382
|
-
className: 'form-page',
|
|
1383
|
-
children: [
|
|
1384
|
-
{
|
|
1385
|
-
header: {
|
|
1386
|
-
className: 'form-header',
|
|
1387
|
-
children: [
|
|
1388
|
-
{
|
|
1389
|
-
h1: {
|
|
1390
|
-
text: '${name} Form'
|
|
1391
|
-
}
|
|
1392
|
-
},
|
|
1393
|
-
{
|
|
1394
|
-
p: {
|
|
1395
|
-
text: 'Please fill out the form below.'
|
|
1396
|
-
}
|
|
1397
|
-
}
|
|
1398
|
-
]
|
|
1399
|
-
}
|
|
1400
|
-
},
|
|
1401
|
-
{
|
|
1402
|
-
main: {
|
|
1403
|
-
className: 'form-content',
|
|
1404
|
-
children: [
|
|
1405
|
-
{
|
|
1406
|
-
form: {
|
|
1407
|
-
className: 'main-form',
|
|
1408
|
-
onsubmit: (event) => {
|
|
1409
|
-
event.preventDefault();
|
|
1410
|
-
const formData = new FormData(event.target);
|
|
1411
|
-
handleSubmit(Object.fromEntries(formData));
|
|
1412
|
-
},
|
|
1413
|
-
children: [
|
|
1414
|
-
{
|
|
1415
|
-
div: {
|
|
1416
|
-
className: 'form-group',
|
|
1417
|
-
children: [
|
|
1418
|
-
{
|
|
1419
|
-
label: {
|
|
1420
|
-
htmlFor: 'name',
|
|
1421
|
-
text: 'Name'
|
|
1422
|
-
}
|
|
1423
|
-
},
|
|
1424
|
-
{
|
|
1425
|
-
input: {
|
|
1426
|
-
type: 'text',
|
|
1427
|
-
id: 'name',
|
|
1428
|
-
name: 'name',
|
|
1429
|
-
value: initialData.name || '',
|
|
1430
|
-
className: errors.name ? '_error' : '',
|
|
1431
|
-
required: true
|
|
1432
|
-
}
|
|
1433
|
-
},
|
|
1434
|
-
errors.name ? {
|
|
1435
|
-
span: {
|
|
1436
|
-
className: '_error-message',
|
|
1437
|
-
text: errors.name
|
|
1438
|
-
}
|
|
1439
|
-
} : null
|
|
1440
|
-
].filter(Boolean)
|
|
1441
|
-
}
|
|
1442
|
-
},
|
|
1443
|
-
{
|
|
1444
|
-
div: {
|
|
1445
|
-
className: 'form-group',
|
|
1446
|
-
children: [
|
|
1447
|
-
{
|
|
1448
|
-
label: {
|
|
1449
|
-
htmlFor: 'email',
|
|
1450
|
-
text: 'Email'
|
|
1451
|
-
}
|
|
1452
|
-
},
|
|
1453
|
-
{
|
|
1454
|
-
input: {
|
|
1455
|
-
type: 'email',
|
|
1456
|
-
id: 'email',
|
|
1457
|
-
name: 'email',
|
|
1458
|
-
value: initialData.email || '',
|
|
1459
|
-
className: errors.email ? '_error' : '',
|
|
1460
|
-
required: true
|
|
1461
|
-
}
|
|
1462
|
-
},
|
|
1463
|
-
errors.email ? {
|
|
1464
|
-
span: {
|
|
1465
|
-
className: '_error-message',
|
|
1466
|
-
text: errors.email
|
|
1467
|
-
}
|
|
1468
|
-
} : null
|
|
1469
|
-
].filter(Boolean)
|
|
1470
|
-
}
|
|
1471
|
-
},
|
|
1472
|
-
{
|
|
1473
|
-
div: {
|
|
1474
|
-
className: 'form-actions',
|
|
1475
|
-
children: [
|
|
1476
|
-
{
|
|
1477
|
-
button: {
|
|
1478
|
-
type: 'submit',
|
|
1479
|
-
className: 'btn btn-primary',
|
|
1480
|
-
text: 'Submit'
|
|
1481
|
-
}
|
|
1482
|
-
},
|
|
1483
|
-
{
|
|
1484
|
-
button: {
|
|
1485
|
-
type: 'reset',
|
|
1486
|
-
className: 'btn btn-secondary',
|
|
1487
|
-
text: 'Reset'
|
|
1488
|
-
}
|
|
1489
|
-
}
|
|
1490
|
-
]
|
|
1491
|
-
}
|
|
1492
|
-
}
|
|
1493
|
-
]
|
|
1494
|
-
}
|
|
1495
|
-
}
|
|
1496
|
-
]
|
|
1497
|
-
}
|
|
1498
|
-
}
|
|
1499
|
-
]
|
|
1500
|
-
}
|
|
1501
|
-
}
|
|
1502
|
-
]
|
|
1503
|
-
}
|
|
1504
|
-
}
|
|
1505
|
-
]
|
|
1506
|
-
}
|
|
1507
|
-
};
|
|
1508
|
-
});
|
|
1509
|
-
`;
|
|
1510
|
-
}
|
|
1511
|
-
function generateTestContent2(name) {
|
|
1512
|
-
return `import { test } from 'node:test';
|
|
1513
|
-
import assert from 'node:assert';
|
|
1514
|
-
import { renderToString } from '@coherentjs/core';
|
|
1515
|
-
import { ${name} } from './${name}.js';
|
|
1516
|
-
|
|
1517
|
-
test('${name} page renders correctly', () => {
|
|
1518
|
-
const page = ${name}({});
|
|
1519
|
-
const html = renderToString(page);
|
|
1520
|
-
|
|
1521
|
-
assert(typeof html === 'string');
|
|
1522
|
-
assert(html.length > 0);
|
|
1523
|
-
assert(html.includes('<html>'));
|
|
1524
|
-
assert(html.includes('${name}'));
|
|
1525
|
-
});
|
|
1526
|
-
|
|
1527
|
-
test('${name} page includes proper head elements', () => {
|
|
1528
|
-
const page = ${name}({});
|
|
1529
|
-
const html = renderToString(page);
|
|
1530
|
-
|
|
1531
|
-
assert(html.includes('<title>'));
|
|
1532
|
-
assert(html.includes('<meta'));
|
|
1533
|
-
assert(html.includes('viewport'));
|
|
1534
|
-
});
|
|
1535
|
-
|
|
1536
|
-
test('${name} page renders with custom props', () => {
|
|
1537
|
-
const props = {
|
|
1538
|
-
params: { id: '123' },
|
|
1539
|
-
query: { search: 'test' }
|
|
1540
|
-
};
|
|
1541
|
-
|
|
1542
|
-
const page = ${name}(props);
|
|
1543
|
-
const html = renderToString(page);
|
|
1544
|
-
|
|
1545
|
-
assert(html.includes('${name}'));
|
|
1546
|
-
});
|
|
1547
|
-
`;
|
|
1548
|
-
}
|
|
1549
|
-
function toPascalCase2(str) {
|
|
1550
|
-
return str.replace(/[-_\s]+(.)?/g, (_, c) => c ? c.toUpperCase() : "").replace(/^(.)/, (_, c) => c.toUpperCase());
|
|
1551
|
-
}
|
|
1552
|
-
|
|
1553
|
-
// src/generators/api-generator.js
|
|
1554
|
-
var import_fs6 = require("fs");
|
|
1555
|
-
var import_path6 = require("path");
|
|
1556
|
-
async function generateAPI(name, options = {}) {
|
|
1557
|
-
const { path = "src/api", template = "rest", skipTest = false } = options;
|
|
1558
|
-
const apiName = toKebabCase(name);
|
|
1559
|
-
const fileName = apiName;
|
|
1560
|
-
const outputDir = (0, import_path6.join)(process.cwd(), path);
|
|
1561
|
-
if (!(0, import_fs6.existsSync)(outputDir)) {
|
|
1562
|
-
(0, import_fs6.mkdirSync)(outputDir, { recursive: true });
|
|
1563
|
-
}
|
|
1564
|
-
const files = [];
|
|
1565
|
-
const nextSteps = [];
|
|
1566
|
-
const apiPath = (0, import_path6.join)(outputDir, `${fileName}.js`);
|
|
1567
|
-
const apiContent = generateAPIContent(apiName, name, template);
|
|
1568
|
-
(0, import_fs6.writeFileSync)(apiPath, apiContent);
|
|
1569
|
-
files.push(apiPath);
|
|
1570
|
-
if (!skipTest) {
|
|
1571
|
-
const testPath = (0, import_path6.join)(outputDir, `${fileName}.test.js`);
|
|
1572
|
-
const testContent = generateTestContent3(apiName, name);
|
|
1573
|
-
(0, import_fs6.writeFileSync)(testPath, testContent);
|
|
1574
|
-
files.push(testPath);
|
|
1575
|
-
}
|
|
1576
|
-
nextSteps.push(`Import the API: import ${toPascalCase3(name)}API from '${path}/${fileName}.js'`);
|
|
1577
|
-
nextSteps.push(`Mount the API: app.use('/api/${apiName}', ${toPascalCase3(name)}API)`);
|
|
1578
|
-
nextSteps.push(`Test the API: curl http://localhost:3000/api/${apiName}`);
|
|
1579
|
-
if (!skipTest) {
|
|
1580
|
-
nextSteps.push("Run tests: npm test");
|
|
1581
|
-
}
|
|
1582
|
-
return { files, nextSteps };
|
|
1583
|
-
}
|
|
1584
|
-
function generateAPIContent(apiName, originalName, template) {
|
|
1585
|
-
switch (template) {
|
|
1586
|
-
case "graphql":
|
|
1587
|
-
console.warn("GraphQL API generation not yet implemented, falling back to REST");
|
|
1588
|
-
return generateRESTAPI(apiName, originalName);
|
|
1589
|
-
case "rpc":
|
|
1590
|
-
return generateRPCAPI(apiName, originalName);
|
|
1591
|
-
case "crud":
|
|
1592
|
-
return generateCRUDAPI(apiName, originalName);
|
|
1593
|
-
default:
|
|
1594
|
-
return generateRESTAPI(apiName, originalName);
|
|
1595
|
-
}
|
|
1596
|
-
}
|
|
1597
|
-
function generateRESTAPI(apiName, originalName) {
|
|
1598
|
-
const className = toPascalCase3(originalName);
|
|
1599
|
-
const camelCaseApiName = toCamelCase(apiName);
|
|
1600
|
-
return `import { createApiRouter, withValidation } from '@coherentjs/api';
|
|
1601
|
-
|
|
1602
|
-
/**
|
|
1603
|
-
* ${className} API Routes
|
|
1604
|
-
* REST API for ${apiName} resources
|
|
1605
|
-
*
|
|
1606
|
-
* Base URL: /api/${apiName}
|
|
1607
|
-
*/
|
|
1608
|
-
|
|
1609
|
-
// Create API router
|
|
1610
|
-
const ${camelCaseApiName}API = createApiRouter({
|
|
1611
|
-
prefix: '/${apiName}',
|
|
1612
|
-
version: 'v1'
|
|
1613
|
-
});
|
|
1614
|
-
|
|
1615
|
-
// Sample data (replace with database)
|
|
1616
|
-
const sampleData = [
|
|
1617
|
-
{ id: '1', name: 'Sample ${className} 1', createdAt: new Date().toISOString() },
|
|
1618
|
-
{ id: '2', name: 'Sample ${className} 2', createdAt: new Date().toISOString() }
|
|
1619
|
-
];
|
|
1620
|
-
|
|
1621
|
-
// Validation schemas
|
|
1622
|
-
const ${apiName}Schema = {
|
|
1623
|
-
type: 'object',
|
|
1624
|
-
properties: {
|
|
1625
|
-
name: {
|
|
1626
|
-
type: 'string',
|
|
1627
|
-
minLength: 1,
|
|
1628
|
-
maxLength: 100
|
|
1629
|
-
},
|
|
1630
|
-
description: {
|
|
1631
|
-
type: 'string',
|
|
1632
|
-
maxLength: 500
|
|
1633
|
-
}
|
|
1634
|
-
},
|
|
1635
|
-
required: ['name'],
|
|
1636
|
-
additionalProperties: false
|
|
1637
|
-
};
|
|
1638
|
-
|
|
1639
|
-
const ${apiName}UpdateSchema = {
|
|
1640
|
-
type: 'object',
|
|
1641
|
-
properties: {
|
|
1642
|
-
name: {
|
|
1643
|
-
type: 'string',
|
|
1644
|
-
minLength: 1,
|
|
1645
|
-
maxLength: 100
|
|
1646
|
-
},
|
|
1647
|
-
description: {
|
|
1648
|
-
type: 'string',
|
|
1649
|
-
maxLength: 500
|
|
1650
|
-
}
|
|
1651
|
-
},
|
|
1652
|
-
additionalProperties: false,
|
|
1653
|
-
minProperties: 1
|
|
1654
|
-
};
|
|
1655
|
-
|
|
1656
|
-
// Routes
|
|
1657
|
-
|
|
1658
|
-
/**
|
|
1659
|
-
* GET /${apiName}
|
|
1660
|
-
* Get all ${apiName} items
|
|
1661
|
-
*/
|
|
1662
|
-
${camelCaseApiName}API.get('/', (req, res) => {
|
|
1663
|
-
const { page = 1, limit = 10, search } = req.query;
|
|
1664
|
-
|
|
1665
|
-
let data = [...sampleData];
|
|
1666
|
-
|
|
1667
|
-
// Apply search filter
|
|
1668
|
-
if (search) {
|
|
1669
|
-
data = data.filter(item =>
|
|
1670
|
-
item.name.toLowerCase().includes(search.toLowerCase())
|
|
1671
|
-
);
|
|
1672
|
-
}
|
|
1673
|
-
|
|
1674
|
-
// Apply pagination
|
|
1675
|
-
const startIndex = (page - 1) * limit;
|
|
1676
|
-
const endIndex = startIndex + parseInt(limit);
|
|
1677
|
-
const paginatedData = data.slice(startIndex, endIndex);
|
|
1678
|
-
|
|
1679
|
-
return {
|
|
1680
|
-
data: paginatedData,
|
|
1681
|
-
pagination: {
|
|
1682
|
-
page: parseInt(page),
|
|
1683
|
-
limit: parseInt(limit),
|
|
1684
|
-
total: data.length,
|
|
1685
|
-
totalPages: Math.ceil(data.length / limit)
|
|
1686
|
-
}
|
|
1687
|
-
};
|
|
1688
|
-
});
|
|
1689
|
-
|
|
1690
|
-
/**
|
|
1691
|
-
* GET /${apiName}/:id
|
|
1692
|
-
* Get a specific ${apiName} item
|
|
1693
|
-
*/
|
|
1694
|
-
${camelCaseApiName}API.get('/:id', (req, res) => {
|
|
1695
|
-
const { id } = req.params;
|
|
1696
|
-
const item = sampleData.find(item => item.id === id);
|
|
1697
|
-
|
|
1698
|
-
if (!item) {
|
|
1699
|
-
return res.status(404).json({
|
|
1700
|
-
_error: '${className} not found',
|
|
1701
|
-
code: 'NOT_FOUND'
|
|
1702
|
-
});
|
|
1703
|
-
}
|
|
1704
|
-
|
|
1705
|
-
return { data: item };
|
|
1706
|
-
});
|
|
1707
|
-
|
|
1708
|
-
/**
|
|
1709
|
-
* POST /${apiName}
|
|
1710
|
-
* Create a new ${apiName} item
|
|
1711
|
-
*/
|
|
1712
|
-
${camelCaseApiName}API.post('/',
|
|
1713
|
-
withValidation(${apiName}Schema),
|
|
1714
|
-
(req, res) => {
|
|
1715
|
-
const { name, description } = req.body;
|
|
1716
|
-
|
|
1717
|
-
const newItem = {
|
|
1718
|
-
id: String(Date.now()),
|
|
1719
|
-
name,
|
|
1720
|
-
description,
|
|
1721
|
-
createdAt: new Date().toISOString(),
|
|
1722
|
-
updatedAt: new Date().toISOString()
|
|
1723
|
-
};
|
|
1724
|
-
|
|
1725
|
-
sampleData.push(newItem);
|
|
1726
|
-
|
|
1727
|
-
return res.status(201).json({
|
|
1728
|
-
data: newItem,
|
|
1729
|
-
message: '${className} created successfully'
|
|
1730
|
-
});
|
|
1731
|
-
}
|
|
1732
|
-
);
|
|
1733
|
-
|
|
1734
|
-
/**
|
|
1735
|
-
* PUT /${apiName}/:id
|
|
1736
|
-
* Update a ${apiName} item
|
|
1737
|
-
*/
|
|
1738
|
-
${camelCaseApiName}API.put('/:id',
|
|
1739
|
-
withValidation(${apiName}UpdateSchema),
|
|
1740
|
-
(req, res) => {
|
|
1741
|
-
const { id } = req.params;
|
|
1742
|
-
const itemIndex = sampleData.findIndex(item => item.id === id);
|
|
1743
|
-
|
|
1744
|
-
if (itemIndex === -1) {
|
|
1745
|
-
return res.status(404).json({
|
|
1746
|
-
_error: '${className} not found',
|
|
1747
|
-
code: 'NOT_FOUND'
|
|
1748
|
-
});
|
|
1749
|
-
}
|
|
1750
|
-
|
|
1751
|
-
const updatedItem = {
|
|
1752
|
-
...sampleData[itemIndex],
|
|
1753
|
-
...req.body,
|
|
1754
|
-
updatedAt: new Date().toISOString()
|
|
1755
|
-
};
|
|
1756
|
-
|
|
1757
|
-
sampleData[itemIndex] = updatedItem;
|
|
1758
|
-
|
|
1759
|
-
return {
|
|
1760
|
-
data: updatedItem,
|
|
1761
|
-
message: '${className} updated successfully'
|
|
1762
|
-
};
|
|
1763
|
-
}
|
|
1764
|
-
);
|
|
1765
|
-
|
|
1766
|
-
/**
|
|
1767
|
-
* DELETE /${apiName}/:id
|
|
1768
|
-
* Delete a ${apiName} item
|
|
1769
|
-
*/
|
|
1770
|
-
${camelCaseApiName}API.delete('/:id', (req, res) => {
|
|
1771
|
-
const { id } = req.params;
|
|
1772
|
-
const itemIndex = sampleData.findIndex(item => item.id === id);
|
|
1773
|
-
|
|
1774
|
-
if (itemIndex === -1) {
|
|
1775
|
-
return res.status(404).json({
|
|
1776
|
-
_error: '${className} not found',
|
|
1777
|
-
code: 'NOT_FOUND'
|
|
1778
|
-
});
|
|
1779
|
-
}
|
|
1780
|
-
|
|
1781
|
-
const deletedItem = sampleData.splice(itemIndex, 1)[0];
|
|
1782
|
-
|
|
1783
|
-
return {
|
|
1784
|
-
data: deletedItem,
|
|
1785
|
-
message: '${className} deleted successfully'
|
|
1786
|
-
};
|
|
1787
|
-
});
|
|
1788
|
-
|
|
1789
|
-
// Health check endpoint
|
|
1790
|
-
${camelCaseApiName}API.get('/health', (req, res) => {
|
|
1791
|
-
return {
|
|
1792
|
-
status: 'ok',
|
|
1793
|
-
timestamp: new Date().toISOString(),
|
|
1794
|
-
service: '${className} API'
|
|
1795
|
-
};
|
|
1796
|
-
});
|
|
1797
|
-
|
|
1798
|
-
export default ${camelCaseApiName}API;
|
|
1799
|
-
|
|
1800
|
-
// Usage example:
|
|
1801
|
-
// import express from 'express';
|
|
1802
|
-
// import ${camelCaseApiName}API from './api/${apiName}.js';
|
|
1803
|
-
//
|
|
1804
|
-
// const app = express();
|
|
1805
|
-
// app.use(express.json());
|
|
1806
|
-
// app.use('/api', ${camelCaseApiName}API.toExpress());
|
|
1807
|
-
//
|
|
1808
|
-
// app.listen(3000, () => {
|
|
1809
|
-
// console.log('Server running on http://localhost:3000');
|
|
1810
|
-
// });
|
|
1811
|
-
`;
|
|
1812
|
-
}
|
|
1813
|
-
function generateCRUDAPI(apiName, originalName) {
|
|
1814
|
-
return generateRESTAPI(apiName, originalName);
|
|
1815
|
-
}
|
|
1816
|
-
function generateRPCAPI(apiName, originalName) {
|
|
1817
|
-
const className = toPascalCase3(originalName);
|
|
1818
|
-
const camelCaseApiName = toCamelCase(apiName);
|
|
1819
|
-
return `import { createApiRouter, withValidation } from '@coherentjs/api';
|
|
1820
|
-
|
|
1821
|
-
/**
|
|
1822
|
-
* ${className} RPC API
|
|
1823
|
-
* Remote Procedure Call API for ${apiName}
|
|
1824
|
-
*
|
|
1825
|
-
* Base URL: /rpc/${apiName}
|
|
1826
|
-
*/
|
|
1827
|
-
|
|
1828
|
-
// Create RPC router
|
|
1829
|
-
const ${camelCaseApiName}RPC = createApiRouter({
|
|
1830
|
-
prefix: '/rpc/${apiName}'
|
|
1831
|
-
});
|
|
1832
|
-
|
|
1833
|
-
// Sample data
|
|
1834
|
-
const sampleData = new Map();
|
|
1835
|
-
sampleData.set('1', { id: '1', name: 'Sample ${className} 1', createdAt: new Date() });
|
|
1836
|
-
sampleData.set('2', { id: '2', name: 'Sample ${className} 2', createdAt: new Date() });
|
|
1837
|
-
|
|
1838
|
-
// RPC Methods
|
|
1839
|
-
|
|
1840
|
-
/**
|
|
1841
|
-
* RPC Method: ${apiName}.list
|
|
1842
|
-
* List all ${apiName} items
|
|
1843
|
-
*/
|
|
1844
|
-
${camelCaseApiName}RPC.post('/list', (req, res) => {
|
|
1845
|
-
const { params = {} } = req.body;
|
|
1846
|
-
const { limit = 10, offset = 0 } = params;
|
|
1847
|
-
|
|
1848
|
-
const items = Array.from(sampleData.values())
|
|
1849
|
-
.slice(offset, offset + limit);
|
|
1850
|
-
|
|
1851
|
-
return {
|
|
1852
|
-
jsonrpc: '2.0',
|
|
1853
|
-
result: {
|
|
1854
|
-
items,
|
|
1855
|
-
total: sampleData.size
|
|
1856
|
-
},
|
|
1857
|
-
id: req.body.id
|
|
1858
|
-
};
|
|
1859
|
-
});
|
|
1860
|
-
|
|
1861
|
-
/**
|
|
1862
|
-
* RPC Method: ${apiName}.get
|
|
1863
|
-
* Get a specific ${apiName} item
|
|
1864
|
-
*/
|
|
1865
|
-
${camelCaseApiName}RPC.post('/get',
|
|
1866
|
-
withValidation({
|
|
1867
|
-
type: 'object',
|
|
1868
|
-
properties: {
|
|
1869
|
-
params: {
|
|
1870
|
-
type: 'object',
|
|
1871
|
-
properties: {
|
|
1872
|
-
id: { type: 'string' }
|
|
1873
|
-
},
|
|
1874
|
-
required: ['id']
|
|
1875
|
-
}
|
|
1876
|
-
},
|
|
1877
|
-
required: ['params']
|
|
1878
|
-
}),
|
|
1879
|
-
(req, res) => {
|
|
1880
|
-
const { params } = req.body;
|
|
1881
|
-
const item = sampleData.get(params.id);
|
|
1882
|
-
|
|
1883
|
-
if (!item) {
|
|
1884
|
-
return {
|
|
1885
|
-
jsonrpc: '2.0',
|
|
1886
|
-
_error: {
|
|
1887
|
-
code: -32602,
|
|
1888
|
-
message: '${className} not found'
|
|
1889
|
-
},
|
|
1890
|
-
id: req.body.id
|
|
1891
|
-
};
|
|
1892
|
-
}
|
|
1893
|
-
|
|
1894
|
-
return {
|
|
1895
|
-
jsonrpc: '2.0',
|
|
1896
|
-
result: item,
|
|
1897
|
-
id: req.body.id
|
|
1898
|
-
};
|
|
1899
|
-
}
|
|
1900
|
-
);
|
|
1901
|
-
|
|
1902
|
-
/**
|
|
1903
|
-
* RPC Method: ${apiName}.create
|
|
1904
|
-
* Create a new ${apiName} item
|
|
1905
|
-
*/
|
|
1906
|
-
${camelCaseApiName}RPC.post('/create',
|
|
1907
|
-
withValidation({
|
|
1908
|
-
type: 'object',
|
|
1909
|
-
properties: {
|
|
1910
|
-
params: {
|
|
1911
|
-
type: 'object',
|
|
1912
|
-
properties: {
|
|
1913
|
-
name: { type: 'string', minLength: 1 },
|
|
1914
|
-
description: { type: 'string' }
|
|
1915
|
-
},
|
|
1916
|
-
required: ['name']
|
|
1917
|
-
}
|
|
1918
|
-
},
|
|
1919
|
-
required: ['params']
|
|
1920
|
-
}),
|
|
1921
|
-
(req, res) => {
|
|
1922
|
-
const { params } = req.body;
|
|
1923
|
-
const id = String(Date.now());
|
|
1924
|
-
|
|
1925
|
-
const newItem = {
|
|
1926
|
-
id,
|
|
1927
|
-
...params,
|
|
1928
|
-
createdAt: new Date(),
|
|
1929
|
-
updatedAt: new Date()
|
|
1930
|
-
};
|
|
1931
|
-
|
|
1932
|
-
sampleData.set(id, newItem);
|
|
1933
|
-
|
|
1934
|
-
return {
|
|
1935
|
-
jsonrpc: '2.0',
|
|
1936
|
-
result: newItem,
|
|
1937
|
-
id: req.body.id
|
|
1938
|
-
};
|
|
1939
|
-
}
|
|
1940
|
-
);
|
|
1941
|
-
|
|
1942
|
-
/**
|
|
1943
|
-
* RPC Method: ${apiName}.update
|
|
1944
|
-
* Update a ${apiName} item
|
|
1945
|
-
*/
|
|
1946
|
-
${camelCaseApiName}RPC.post('/update',
|
|
1947
|
-
withValidation({
|
|
1948
|
-
type: 'object',
|
|
1949
|
-
properties: {
|
|
1950
|
-
params: {
|
|
1951
|
-
type: 'object',
|
|
1952
|
-
properties: {
|
|
1953
|
-
id: { type: 'string' },
|
|
1954
|
-
name: { type: 'string' },
|
|
1955
|
-
description: { type: 'string' }
|
|
1956
|
-
},
|
|
1957
|
-
required: ['id']
|
|
1958
|
-
}
|
|
1959
|
-
},
|
|
1960
|
-
required: ['params']
|
|
1961
|
-
}),
|
|
1962
|
-
(req, res) => {
|
|
1963
|
-
const { params } = req.body;
|
|
1964
|
-
const existing = sampleData.get(params.id);
|
|
1965
|
-
|
|
1966
|
-
if (!existing) {
|
|
1967
|
-
return {
|
|
1968
|
-
jsonrpc: '2.0',
|
|
1969
|
-
_error: {
|
|
1970
|
-
code: -32602,
|
|
1971
|
-
message: '${className} not found'
|
|
1972
|
-
},
|
|
1973
|
-
id: req.body.id
|
|
1974
|
-
};
|
|
1975
|
-
}
|
|
1976
|
-
|
|
1977
|
-
const updated = {
|
|
1978
|
-
...existing,
|
|
1979
|
-
...params,
|
|
1980
|
-
updatedAt: new Date()
|
|
1981
|
-
};
|
|
1982
|
-
|
|
1983
|
-
sampleData.set(params.id, updated);
|
|
1984
|
-
|
|
1985
|
-
return {
|
|
1986
|
-
jsonrpc: '2.0',
|
|
1987
|
-
result: updated,
|
|
1988
|
-
id: req.body.id
|
|
1989
|
-
};
|
|
1990
|
-
}
|
|
1991
|
-
);
|
|
1992
|
-
|
|
1993
|
-
/**
|
|
1994
|
-
* RPC Method: ${apiName}.delete
|
|
1995
|
-
* Delete a ${apiName} item
|
|
1996
|
-
*/
|
|
1997
|
-
${camelCaseApiName}RPC.post('/delete',
|
|
1998
|
-
withValidation({
|
|
1999
|
-
type: 'object',
|
|
2000
|
-
properties: {
|
|
2001
|
-
params: {
|
|
2002
|
-
type: 'object',
|
|
2003
|
-
properties: {
|
|
2004
|
-
id: { type: 'string' }
|
|
2005
|
-
},
|
|
2006
|
-
required: ['id']
|
|
2007
|
-
}
|
|
2008
|
-
},
|
|
2009
|
-
required: ['params']
|
|
2010
|
-
}),
|
|
2011
|
-
(req, res) => {
|
|
2012
|
-
const { params } = req.body;
|
|
2013
|
-
const item = sampleData.get(params.id);
|
|
2014
|
-
|
|
2015
|
-
if (!item) {
|
|
2016
|
-
return {
|
|
2017
|
-
jsonrpc: '2.0',
|
|
2018
|
-
_error: {
|
|
2019
|
-
code: -32602,
|
|
2020
|
-
message: '${className} not found'
|
|
2021
|
-
},
|
|
2022
|
-
id: req.body.id
|
|
2023
|
-
};
|
|
2024
|
-
}
|
|
2025
|
-
|
|
2026
|
-
sampleData.delete(params.id);
|
|
2027
|
-
|
|
2028
|
-
return {
|
|
2029
|
-
jsonrpc: '2.0',
|
|
2030
|
-
result: { success: true, deleted: item },
|
|
2031
|
-
id: req.body.id
|
|
2032
|
-
};
|
|
2033
|
-
}
|
|
2034
|
-
);
|
|
2035
|
-
|
|
2036
|
-
export default ${camelCaseApiName}RPC;
|
|
2037
|
-
`;
|
|
2038
|
-
}
|
|
2039
|
-
function generateTestContent3(apiName, originalName) {
|
|
2040
|
-
const className = toPascalCase3(originalName);
|
|
2041
|
-
return `import { test } from 'node:test';
|
|
2042
|
-
import assert from 'node:assert';
|
|
2043
|
-
import ${apiName}API from './${apiName}.js';
|
|
2044
|
-
|
|
2045
|
-
test('${className} API should be defined', () => {
|
|
2046
|
-
assert(typeof ${apiName}API === 'object');
|
|
2047
|
-
assert(typeof ${apiName}API.get === 'function');
|
|
2048
|
-
assert(typeof ${apiName}API.post === 'function');
|
|
2049
|
-
});
|
|
2050
|
-
|
|
2051
|
-
test('${className} API should handle GET requests', async () => {
|
|
2052
|
-
const mockReq = {
|
|
2053
|
-
query: {}
|
|
2054
|
-
};
|
|
2055
|
-
const mockRes = {
|
|
2056
|
-
status: (code) => mockRes,
|
|
2057
|
-
json: (data) => data
|
|
2058
|
-
};
|
|
2059
|
-
|
|
2060
|
-
// This is a basic test structure
|
|
2061
|
-
// In a real test, you'd use a testing framework like supertest
|
|
2062
|
-
assert(true); // Placeholder
|
|
2063
|
-
});
|
|
2064
|
-
|
|
2065
|
-
// Add more specific tests for your API endpoints
|
|
2066
|
-
// Example:
|
|
2067
|
-
// test('POST /${apiName} should create new item', async () => {
|
|
2068
|
-
// // Test implementation
|
|
2069
|
-
// });
|
|
2070
|
-
//
|
|
2071
|
-
// test('GET /${apiName}/:id should return specific item', async () => {
|
|
2072
|
-
// // Test implementation
|
|
2073
|
-
// });
|
|
2074
|
-
`;
|
|
2075
|
-
}
|
|
2076
|
-
function toKebabCase(str) {
|
|
2077
|
-
return str.replace(/([a-z])([A-Z])/g, "$1-$2").replace(/[\s_]+/g, "-").toLowerCase();
|
|
2078
|
-
}
|
|
2079
|
-
function toCamelCase(str) {
|
|
2080
|
-
return str.replace(/[-_\s]+(.)?/g, (_, c) => c ? c.toUpperCase() : "").replace(/^(.)/, (_, c) => c.toLowerCase());
|
|
2081
|
-
}
|
|
2082
|
-
function toPascalCase3(str) {
|
|
2083
|
-
return str.replace(/[-_\s]+(.)?/g, (_, c) => c ? c.toUpperCase() : "").replace(/^(.)/, (_, c) => c.toUpperCase());
|
|
2084
|
-
}
|
|
2085
|
-
|
|
2086
|
-
// src/commands/generate.js
|
|
2087
|
-
var generateCommand = new import_commander2.Command("generate").alias("g").description("Generate components, pages, and APIs").argument("[type]", "type to generate (component, page, api)").argument("[name]", "name of the item to generate").option("-p, --path <path>", "custom output path").option("-t, --template <template>", "template to use").option("--skip-test", "skip generating test file").option("--skip-story", "skip generating story file").action(async (type, name, options) => {
|
|
2088
|
-
let generationType = type;
|
|
2089
|
-
let itemName = name;
|
|
2090
|
-
if (!generationType) {
|
|
2091
|
-
const response = await (0, import_prompts2.default)({
|
|
2092
|
-
type: "select",
|
|
2093
|
-
name: "type",
|
|
2094
|
-
message: "What would you like to generate?",
|
|
2095
|
-
choices: [
|
|
2096
|
-
{ title: "\u{1F9E9} Component", value: "component", description: "Reusable UI component" },
|
|
2097
|
-
{ title: "\u{1F4C4} Page", value: "page", description: "Full page with routing" },
|
|
2098
|
-
{ title: "\u{1F50C} API Route", value: "api", description: "API endpoint with validation" },
|
|
2099
|
-
{ title: "\u{1F4CA} Database Model", value: "model", description: "Database model with migrations" },
|
|
2100
|
-
{ title: "\u{1F504} Middleware", value: "middleware", description: "Express/Fastify middleware" }
|
|
2101
|
-
]
|
|
2102
|
-
});
|
|
2103
|
-
if (!response.type) {
|
|
2104
|
-
console.log(import_picocolors2.default.yellow("\u{1F44B} Generation cancelled"));
|
|
2105
|
-
process.exit(0);
|
|
2106
|
-
}
|
|
2107
|
-
generationType = response.type;
|
|
2108
|
-
}
|
|
2109
|
-
if (!itemName) {
|
|
2110
|
-
const response = await (0, import_prompts2.default)({
|
|
2111
|
-
type: "text",
|
|
2112
|
-
name: "name",
|
|
2113
|
-
message: `What is the ${generationType} name?`,
|
|
2114
|
-
validate: validateComponentName
|
|
2115
|
-
});
|
|
2116
|
-
if (!response.name) {
|
|
2117
|
-
console.log(import_picocolors2.default.yellow("\u{1F44B} Generation cancelled"));
|
|
2118
|
-
process.exit(0);
|
|
2119
|
-
}
|
|
2120
|
-
itemName = response.name;
|
|
2121
|
-
}
|
|
2122
|
-
const nameValidation = validateComponentName(itemName);
|
|
2123
|
-
if (nameValidation !== true) {
|
|
2124
|
-
console.error(import_picocolors2.default.red("\u274C Invalid name:"), nameValidation);
|
|
2125
|
-
process.exit(1);
|
|
2126
|
-
}
|
|
2127
|
-
console.log();
|
|
2128
|
-
console.log(import_picocolors2.default.cyan(`\u{1F680} Generating ${generationType}...`));
|
|
2129
|
-
console.log(import_picocolors2.default.gray("\u{1F4DD} Name:"), import_picocolors2.default.bold(itemName));
|
|
2130
|
-
if (options.path) {
|
|
2131
|
-
console.log(import_picocolors2.default.gray("\u{1F4CD} Path:"), options.path);
|
|
2132
|
-
}
|
|
2133
|
-
console.log();
|
|
2134
|
-
const spinner = (0, import_ora2.default)(`Generating ${generationType}...`).start();
|
|
2135
|
-
try {
|
|
2136
|
-
let result;
|
|
2137
|
-
switch (generationType) {
|
|
2138
|
-
case "component":
|
|
2139
|
-
case "comp":
|
|
2140
|
-
case "c":
|
|
2141
|
-
result = await generateComponent(itemName, options);
|
|
2142
|
-
break;
|
|
2143
|
-
case "page":
|
|
2144
|
-
case "p":
|
|
2145
|
-
result = await generatePage(itemName, options);
|
|
2146
|
-
break;
|
|
2147
|
-
case "api":
|
|
2148
|
-
case "route":
|
|
2149
|
-
case "r":
|
|
2150
|
-
result = await generateAPI(itemName, options);
|
|
2151
|
-
break;
|
|
2152
|
-
case "model":
|
|
2153
|
-
case "m":
|
|
2154
|
-
result = await generateModel(itemName, options);
|
|
2155
|
-
break;
|
|
2156
|
-
case "middleware":
|
|
2157
|
-
case "mw":
|
|
2158
|
-
result = await generateMiddleware(itemName, options);
|
|
2159
|
-
break;
|
|
2160
|
-
default:
|
|
2161
|
-
throw new Error(`Unknown generation type: ${generationType}`);
|
|
2162
|
-
}
|
|
2163
|
-
spinner.succeed(`${generationType} generated successfully!`);
|
|
2164
|
-
console.log();
|
|
2165
|
-
console.log(import_picocolors2.default.green(`\u2705 ${generationType} generated successfully!`));
|
|
2166
|
-
console.log();
|
|
2167
|
-
if (result?.files && result.files.length > 0) {
|
|
2168
|
-
console.log(import_picocolors2.default.cyan("\u{1F4C1} Generated files:"));
|
|
2169
|
-
result.files.forEach((file) => {
|
|
2170
|
-
console.log(import_picocolors2.default.gray(" \u2728"), file);
|
|
2171
|
-
});
|
|
2172
|
-
console.log();
|
|
2173
|
-
}
|
|
2174
|
-
if (result?.nextSteps && result.nextSteps.length > 0) {
|
|
2175
|
-
console.log(import_picocolors2.default.cyan("Next steps:"));
|
|
2176
|
-
result.nextSteps.forEach((step) => {
|
|
2177
|
-
console.log(import_picocolors2.default.gray(" \u2022"), step);
|
|
2178
|
-
});
|
|
2179
|
-
console.log();
|
|
2180
|
-
}
|
|
2181
|
-
} catch (_error) {
|
|
2182
|
-
spinner.fail(`Failed to generate ${generationType}`);
|
|
2183
|
-
console.error(import_picocolors2.default.red("\u274C Error:"), _error.message);
|
|
2184
|
-
process.exit(1);
|
|
2185
|
-
}
|
|
2186
|
-
});
|
|
2187
|
-
async function generateModel() {
|
|
2188
|
-
throw new Error("Model generation not implemented yet");
|
|
2189
|
-
}
|
|
2190
|
-
async function generateMiddleware() {
|
|
2191
|
-
throw new Error("Middleware generation not implemented yet");
|
|
2192
|
-
}
|
|
2193
|
-
|
|
2194
|
-
// src/commands/build.js
|
|
2195
|
-
var import_commander3 = require("commander");
|
|
2196
|
-
var import_ora3 = __toESM(require("ora"), 1);
|
|
2197
|
-
var import_picocolors3 = __toESM(require("picocolors"), 1);
|
|
2198
|
-
var import_child_process2 = require("child_process");
|
|
2199
|
-
var import_fs7 = require("fs");
|
|
2200
|
-
var import_path7 = require("path");
|
|
2201
|
-
var buildCommand = new import_commander3.Command("build").description("Build the project for production").option("-w, --watch", "watch for changes").option("--analyze", "analyze bundle size").option("--no-minify", "disable minification").option("--no-optimize", "disable optimizations").action(async (options) => {
|
|
2202
|
-
console.log(import_picocolors3.default.cyan("\u{1F3D7}\uFE0F Building Coherent.js project..."));
|
|
2203
|
-
console.log();
|
|
2204
|
-
const packageJsonPath = (0, import_path7.join)(process.cwd(), "package.json");
|
|
2205
|
-
if (!(0, import_fs7.existsSync)(packageJsonPath)) {
|
|
2206
|
-
console.error(import_picocolors3.default.red("\u274C No package.json found. Are you in a project directory?"));
|
|
2207
|
-
process.exit(1);
|
|
2208
|
-
}
|
|
2209
|
-
let packageJson;
|
|
2210
|
-
try {
|
|
2211
|
-
packageJson = JSON.parse((0, import_fs7.readFileSync)(packageJsonPath, "utf-8"));
|
|
2212
|
-
} catch {
|
|
2213
|
-
console.error(import_picocolors3.default.red("\u274C Failed to read package.json"));
|
|
2214
|
-
process.exit(1);
|
|
2215
|
-
}
|
|
2216
|
-
const hasCoherentDeps = packageJson.dependencies && (packageJson.dependencies["@coherentjs/core"] || packageJson.dependencies["coherentjs"]);
|
|
2217
|
-
if (!hasCoherentDeps) {
|
|
2218
|
-
console.error(import_picocolors3.default.red("\u274C This doesn't appear to be a Coherent.js project"));
|
|
2219
|
-
console.error(import_picocolors3.default.gray(" Missing @coherentjs/core dependency"));
|
|
2220
|
-
process.exit(1);
|
|
2221
|
-
}
|
|
2222
|
-
const spinner = (0, import_ora3.default)("Building project...").start();
|
|
2223
|
-
try {
|
|
2224
|
-
if (packageJson.scripts && packageJson.scripts.build) {
|
|
2225
|
-
spinner.text = "Running build script...";
|
|
2226
|
-
(0, import_child_process2.execSync)("npm run build", {
|
|
2227
|
-
stdio: options.watch ? "inherit" : "pipe",
|
|
2228
|
-
cwd: process.cwd()
|
|
2229
|
-
});
|
|
2230
|
-
} else {
|
|
2231
|
-
spinner.text = "Building with default configuration...";
|
|
2232
|
-
if ((0, import_fs7.existsSync)("vite.config.js") || (0, import_fs7.existsSync)("vite.config.ts")) {
|
|
2233
|
-
(0, import_child_process2.execSync)("npx vite build", {
|
|
2234
|
-
stdio: options.watch ? "inherit" : "pipe",
|
|
2235
|
-
cwd: process.cwd()
|
|
2236
|
-
});
|
|
2237
|
-
} else if ((0, import_fs7.existsSync)("webpack.config.js")) {
|
|
2238
|
-
(0, import_child_process2.execSync)("npx webpack --mode production", {
|
|
2239
|
-
stdio: options.watch ? "inherit" : "pipe",
|
|
2240
|
-
cwd: process.cwd()
|
|
2241
|
-
});
|
|
2242
|
-
} else if ((0, import_fs7.existsSync)("rollup.config.js")) {
|
|
2243
|
-
(0, import_child_process2.execSync)("npx rollup -c", {
|
|
2244
|
-
stdio: options.watch ? "inherit" : "pipe",
|
|
2245
|
-
cwd: process.cwd()
|
|
2246
|
-
});
|
|
2247
|
-
} else {
|
|
2248
|
-
spinner.text = "Building with esbuild (fallback)...";
|
|
2249
|
-
(0, import_child_process2.execSync)(`npx esbuild src/index.js --bundle --minify --outfile=dist/index.js --platform=node --format=esm`, {
|
|
2250
|
-
stdio: options.watch ? "inherit" : "pipe",
|
|
2251
|
-
cwd: process.cwd()
|
|
2252
|
-
});
|
|
2253
|
-
}
|
|
2254
|
-
}
|
|
2255
|
-
if (options.analyze) {
|
|
2256
|
-
spinner.text = "Analyzing bundle...";
|
|
2257
|
-
try {
|
|
2258
|
-
(0, import_child_process2.execSync)("npx webpack-bundle-analyzer dist/stats.json", {
|
|
2259
|
-
stdio: "inherit",
|
|
2260
|
-
cwd: process.cwd()
|
|
2261
|
-
});
|
|
2262
|
-
} catch {
|
|
2263
|
-
console.log(import_picocolors3.default.yellow("\u26A0\uFE0F Bundle analyzer not available"));
|
|
2264
|
-
console.log(import_picocolors3.default.gray(" Install webpack-bundle-analyzer for detailed analysis"));
|
|
2265
|
-
}
|
|
2266
|
-
}
|
|
2267
|
-
spinner.succeed("Build completed successfully!");
|
|
2268
|
-
console.log();
|
|
2269
|
-
console.log(import_picocolors3.default.green("\u2705 Build completed!"));
|
|
2270
|
-
if ((0, import_fs7.existsSync)("dist")) {
|
|
2271
|
-
try {
|
|
2272
|
-
const distSize = (0, import_child_process2.execSync)("du -sh dist", { encoding: "utf-8" }).trim().split(" ")[0];
|
|
2273
|
-
console.log(import_picocolors3.default.gray("\u{1F4E6} Output size:"), distSize);
|
|
2274
|
-
} catch {
|
|
2275
|
-
}
|
|
2276
|
-
}
|
|
2277
|
-
console.log();
|
|
2278
|
-
console.log(import_picocolors3.default.cyan("Next steps:"));
|
|
2279
|
-
console.log(import_picocolors3.default.gray(" Deploy your dist/ directory to your hosting provider"));
|
|
2280
|
-
console.log(import_picocolors3.default.gray(" Or run: npm run start (if available)"));
|
|
2281
|
-
console.log();
|
|
2282
|
-
} catch (error) {
|
|
2283
|
-
spinner.fail("Build failed");
|
|
2284
|
-
console.error(import_picocolors3.default.red("\u274C Build error:"));
|
|
2285
|
-
console.error(error.message);
|
|
2286
|
-
if (error.message.includes("command not found")) {
|
|
2287
|
-
console.log();
|
|
2288
|
-
console.log(import_picocolors3.default.yellow("\u{1F4A1} Try installing dependencies:"));
|
|
2289
|
-
console.log(import_picocolors3.default.gray(" npm install"));
|
|
2290
|
-
}
|
|
2291
|
-
process.exit(1);
|
|
2292
|
-
}
|
|
2293
|
-
});
|
|
2294
|
-
|
|
2295
|
-
// src/commands/dev.js
|
|
2296
|
-
var import_commander4 = require("commander");
|
|
2297
|
-
var import_ora4 = __toESM(require("ora"), 1);
|
|
2298
|
-
var import_picocolors4 = __toESM(require("picocolors"), 1);
|
|
2299
|
-
var import_child_process3 = require("child_process");
|
|
2300
|
-
var import_fs8 = require("fs");
|
|
2301
|
-
var import_path8 = require("path");
|
|
2302
|
-
var devCommand = new import_commander4.Command("dev").description("Start development server with hot reload").option("-p, --port <port>", "port number", "3000").option("-h, --host <host>", "host address", "localhost").option("--open", "open browser automatically").option("--no-hmr", "disable hot module replacement").action(async (options) => {
|
|
2303
|
-
console.log(import_picocolors4.default.cyan("\u{1F680} Starting Coherent.js development server..."));
|
|
2304
|
-
console.log();
|
|
2305
|
-
const packageJsonPath = (0, import_path8.join)(process.cwd(), "package.json");
|
|
2306
|
-
if (!(0, import_fs8.existsSync)(packageJsonPath)) {
|
|
2307
|
-
console.error(import_picocolors4.default.red("\u274C No package.json found. Are you in a project directory?"));
|
|
2308
|
-
process.exit(1);
|
|
2309
|
-
}
|
|
2310
|
-
let packageJson;
|
|
2311
|
-
try {
|
|
2312
|
-
packageJson = JSON.parse((0, import_fs8.readFileSync)(packageJsonPath, "utf-8"));
|
|
2313
|
-
} catch {
|
|
2314
|
-
console.error(import_picocolors4.default.red("\u274C Failed to read package.json"));
|
|
2315
|
-
process.exit(1);
|
|
2316
|
-
}
|
|
2317
|
-
const spinner = (0, import_ora4.default)("Starting development server...").start();
|
|
2318
|
-
try {
|
|
2319
|
-
let devProcess;
|
|
2320
|
-
if (packageJson.scripts && packageJson.scripts.dev) {
|
|
2321
|
-
spinner.text = "Running dev script...";
|
|
2322
|
-
devProcess = (0, import_child_process3.spawn)("npm", ["run", "dev"], {
|
|
2323
|
-
stdio: "inherit",
|
|
2324
|
-
cwd: process.cwd(),
|
|
2325
|
-
env: {
|
|
2326
|
-
...process.env,
|
|
2327
|
-
PORT: options.port,
|
|
2328
|
-
HOST: options.host
|
|
2329
|
-
}
|
|
2330
|
-
});
|
|
2331
|
-
} else {
|
|
2332
|
-
spinner.text = "Starting default dev server...";
|
|
2333
|
-
if ((0, import_fs8.existsSync)("vite.config.js") || (0, import_fs8.existsSync)("vite.config.ts")) {
|
|
2334
|
-
devProcess = (0, import_child_process3.spawn)("npx", ["vite", "--port", options.port, "--host", options.host], {
|
|
2335
|
-
stdio: "inherit",
|
|
2336
|
-
cwd: process.cwd()
|
|
2337
|
-
});
|
|
2338
|
-
} else if ((0, import_fs8.existsSync)("webpack.config.js")) {
|
|
2339
|
-
devProcess = (0, import_child_process3.spawn)("npx", ["webpack", "serve", "--port", options.port, "--host", options.host], {
|
|
2340
|
-
stdio: "inherit",
|
|
2341
|
-
cwd: process.cwd()
|
|
2342
|
-
});
|
|
2343
|
-
} else if (packageJson.type === "module" || (0, import_fs8.existsSync)("src/index.js")) {
|
|
2344
|
-
devProcess = (0, import_child_process3.spawn)("npx", ["nodemon", "src/index.js"], {
|
|
2345
|
-
stdio: "inherit",
|
|
2346
|
-
cwd: process.cwd(),
|
|
2347
|
-
env: {
|
|
2348
|
-
...process.env,
|
|
2349
|
-
PORT: options.port,
|
|
2350
|
-
HOST: options.host
|
|
2351
|
-
}
|
|
2352
|
-
});
|
|
2353
|
-
} else {
|
|
2354
|
-
throw new Error("No development server configuration found");
|
|
2355
|
-
}
|
|
2356
|
-
}
|
|
2357
|
-
spinner.stop();
|
|
2358
|
-
console.log(import_picocolors4.default.green("\u2705 Development server started!"));
|
|
2359
|
-
console.log();
|
|
2360
|
-
console.log(import_picocolors4.default.cyan("\u{1F310} Local:"), `http://${options.host}:${options.port}`);
|
|
2361
|
-
if (options.host !== "localhost") {
|
|
2362
|
-
console.log(import_picocolors4.default.cyan("\u{1F517} Network:"), `http://${options.host}:${options.port}`);
|
|
2363
|
-
}
|
|
2364
|
-
console.log();
|
|
2365
|
-
console.log(import_picocolors4.default.gray("Press Ctrl+C to stop the server"));
|
|
2366
|
-
console.log();
|
|
2367
|
-
if (options.open) {
|
|
2368
|
-
const { default: open } = await import("open");
|
|
2369
|
-
await open(`http://${options.host}:${options.port}`);
|
|
2370
|
-
}
|
|
2371
|
-
const cleanup = () => {
|
|
2372
|
-
console.log();
|
|
2373
|
-
console.log(import_picocolors4.default.yellow("\u{1F44B} Stopping development server..."));
|
|
2374
|
-
if (devProcess) {
|
|
2375
|
-
devProcess.kill();
|
|
2376
|
-
}
|
|
2377
|
-
process.exit(0);
|
|
2378
|
-
};
|
|
2379
|
-
process.on("SIGINT", cleanup);
|
|
2380
|
-
process.on("SIGTERM", cleanup);
|
|
2381
|
-
devProcess.on("exit", (code) => {
|
|
2382
|
-
if (code !== 0) {
|
|
2383
|
-
console.error(import_picocolors4.default.red(`\u274C Development server exited with code ${code}`));
|
|
2384
|
-
process.exit(code);
|
|
2385
|
-
}
|
|
2386
|
-
});
|
|
2387
|
-
devProcess.on("_error", (_error) => {
|
|
2388
|
-
console.error(import_picocolors4.default.red("\u274C Failed to start development server:"), _error.message);
|
|
2389
|
-
process.exit(1);
|
|
2390
|
-
});
|
|
2391
|
-
} catch (error) {
|
|
2392
|
-
spinner.fail("Failed to start development server");
|
|
2393
|
-
console.error(import_picocolors4.default.red("\u274C Error:"), error.message);
|
|
2394
|
-
console.log();
|
|
2395
|
-
console.log(import_picocolors4.default.yellow("\u{1F4A1} Suggestions:"));
|
|
2396
|
-
console.log(import_picocolors4.default.gray(" \u2022 Make sure you have a dev script in package.json"));
|
|
2397
|
-
console.log(import_picocolors4.default.gray(" \u2022 Install development dependencies: npm install"));
|
|
2398
|
-
console.log(import_picocolors4.default.gray(" \u2022 Check if port", options.port, "is available"));
|
|
2399
|
-
process.exit(1);
|
|
2400
|
-
}
|
|
2401
|
-
});
|
|
2402
|
-
|
|
2403
|
-
// src/index.js
|
|
2404
|
-
var import_meta2 = {};
|
|
2405
|
-
var __filename2 = (0, import_url2.fileURLToPath)(import_meta2.url);
|
|
2406
|
-
var __dirname2 = (0, import_path9.dirname)(__filename2);
|
|
2407
|
-
var version = "1.0.1";
|
|
2408
|
-
try {
|
|
2409
|
-
const packagePath = (0, import_path9.join)(__dirname2, "..", "package.json");
|
|
2410
|
-
const packageJson = JSON.parse((0, import_fs9.readFileSync)(packagePath, "utf-8"));
|
|
2411
|
-
version = packageJson.version;
|
|
2412
|
-
} catch {
|
|
2413
|
-
}
|
|
2414
|
-
async function createCLI() {
|
|
2415
|
-
const program = new import_commander5.Command();
|
|
2416
|
-
program.name("coherent").description(import_picocolors5.default.cyan("\u{1F680} Coherent.js CLI - Build modern web applications with pure JavaScript objects")).version(version, "-v, --version", "display version number");
|
|
2417
|
-
const banner = `
|
|
2418
|
-
${import_picocolors5.default.cyan(" \u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557")}
|
|
2419
|
-
${import_picocolors5.default.cyan(" \u2551")} ${import_picocolors5.default.bold("\u{1F680} Coherent.js CLI")} ${import_picocolors5.default.cyan("\u2551")}
|
|
2420
|
-
${import_picocolors5.default.cyan(" \u2551")} ${import_picocolors5.default.gray("Pure objects, pure performance")} ${import_picocolors5.default.cyan("\u2551")}
|
|
2421
|
-
${import_picocolors5.default.cyan(" \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D")}
|
|
2422
|
-
`;
|
|
2423
|
-
program.addCommand(createCommand).addCommand(generateCommand).addCommand(buildCommand).addCommand(devCommand);
|
|
2424
|
-
program.configureHelp({
|
|
2425
|
-
beforeAll: () => banner,
|
|
2426
|
-
afterAll: () => `
|
|
2427
|
-
${import_picocolors5.default.gray("Examples:")}
|
|
2428
|
-
${import_picocolors5.default.green("coherent create my-app")} Create a new project
|
|
2429
|
-
${import_picocolors5.default.green("coherent generate component Button")} Generate a component
|
|
2430
|
-
${import_picocolors5.default.green("coherent generate page Home")} Generate a page
|
|
2431
|
-
${import_picocolors5.default.green("coherent build")} Build for production
|
|
2432
|
-
${import_picocolors5.default.green("coherent dev")} Start development server
|
|
2433
|
-
|
|
2434
|
-
${import_picocolors5.default.gray("Learn more:")} ${import_picocolors5.default.blue("https://github.com/Tomdrouv1/coherent.js")}
|
|
2435
|
-
`
|
|
2436
|
-
});
|
|
2437
|
-
if (process.argv.length === 2) {
|
|
2438
|
-
console.log(banner);
|
|
2439
|
-
program.help();
|
|
2440
|
-
return;
|
|
2441
|
-
}
|
|
2442
|
-
try {
|
|
2443
|
-
await program.parseAsync(process.argv);
|
|
2444
|
-
} catch (error) {
|
|
2445
|
-
console.error(import_picocolors5.default.red("\u274C Error:"), error.message);
|
|
2446
|
-
process.exit(1);
|
|
2447
|
-
}
|
|
2448
|
-
}
|
|
2449
|
-
// Annotate the CommonJS export names for ESM import in node:
|
|
2450
|
-
0 && (module.exports = {
|
|
2451
|
-
buildCommand,
|
|
2452
|
-
createCLI,
|
|
2453
|
-
createCommand,
|
|
2454
|
-
devCommand,
|
|
2455
|
-
generateCommand
|
|
2456
|
-
});
|