create-nxpress-app 1.0.1

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.
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,299 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __importDefault = (this && this.__importDefault) || function (mod) {
4
+ return (mod && mod.__esModule) ? mod : { "default": mod };
5
+ };
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ const prompts_1 = require("@clack/prompts");
8
+ const chalk_1 = __importDefault(require("chalk"));
9
+ const commander_1 = require("commander");
10
+ const fs_extra_1 = __importDefault(require("fs-extra"));
11
+ const path_1 = __importDefault(require("path"));
12
+ const child_process_1 = require("child_process");
13
+ const program = new commander_1.Command();
14
+ program
15
+ .name('create-nxpress-app')
16
+ .description('Scaffold a new Nxpress project')
17
+ .argument('[project-directory]', 'Directory for the project')
18
+ .option('-e, --engine <engine>', 'Template engine (handlebars, ejs, html)')
19
+ .option('--tailwind', 'Include Tailwind CSS support', true)
20
+ .option('--app-dir <dir>', 'Directory for application routes', 'app')
21
+ .option('--components-dir <dir>', 'Directory for components', 'components')
22
+ .option('--public-dir <dir>', 'Directory for static assets', 'public')
23
+ .option('--skip-install', 'Skip installing dependencies', false)
24
+ .action(async (projectDirArg, options) => {
25
+ console.log();
26
+ (0, prompts_1.intro)(chalk_1.default.bgCyan.black(' create-nxpress-app '));
27
+ let projectDir = projectDirArg;
28
+ if (!projectDir) {
29
+ const res = await (0, prompts_1.text)({
30
+ message: 'Where would you like to create your project?',
31
+ placeholder: 'my-nxpress-app',
32
+ defaultValue: 'my-nxpress-app',
33
+ });
34
+ if ((0, prompts_1.isCancel)(res)) {
35
+ (0, prompts_1.cancel)('Operation cancelled.');
36
+ process.exit(0);
37
+ }
38
+ projectDir = res;
39
+ }
40
+ const targetPath = path_1.default.resolve(process.cwd(), projectDir);
41
+ if (fs_extra_1.default.existsSync(targetPath) && fs_extra_1.default.readdirSync(targetPath).length > 0) {
42
+ const overwrite = await (0, prompts_1.confirm)({
43
+ message: `Directory ${chalk_1.default.cyan(projectDir)} is not empty. Continue anyway?`,
44
+ initialValue: false,
45
+ });
46
+ if ((0, prompts_1.isCancel)(overwrite) || !overwrite) {
47
+ (0, prompts_1.cancel)('Operation cancelled.');
48
+ process.exit(0);
49
+ }
50
+ }
51
+ let engine = options.engine;
52
+ if (!engine || !['handlebars', 'ejs', 'html'].includes(engine)) {
53
+ const selectedEngine = await (0, prompts_1.select)({
54
+ message: 'Which template engine do you want to use?',
55
+ options: [
56
+ { value: 'handlebars', label: 'Handlebars (Recommended)', hint: 'Clean syntax & partials' },
57
+ { value: 'ejs', label: 'EJS', hint: 'Embedded JavaScript templates' },
58
+ { value: 'html', label: 'HTML', hint: 'Plain HTML templates' },
59
+ ],
60
+ });
61
+ if ((0, prompts_1.isCancel)(selectedEngine)) {
62
+ (0, prompts_1.cancel)('Operation cancelled.');
63
+ process.exit(0);
64
+ }
65
+ engine = selectedEngine;
66
+ }
67
+ let appDirName = options.appDir;
68
+ let componentsDirName = options.componentsDir;
69
+ let publicDirName = options.publicDir;
70
+ if (process.stdin.isTTY && !options.appDir && !options.componentsDir && !options.publicDir) {
71
+ const customizeDirs = await (0, prompts_1.confirm)({
72
+ message: 'Do you want to customize directory names?',
73
+ initialValue: false,
74
+ });
75
+ if ((0, prompts_1.isCancel)(customizeDirs)) {
76
+ (0, prompts_1.cancel)('Operation cancelled.');
77
+ process.exit(0);
78
+ }
79
+ if (customizeDirs) {
80
+ const appRes = await (0, prompts_1.text)({
81
+ message: 'Routes directory name:',
82
+ placeholder: 'app',
83
+ defaultValue: 'app',
84
+ });
85
+ if ((0, prompts_1.isCancel)(appRes)) {
86
+ (0, prompts_1.cancel)('Operation cancelled.');
87
+ process.exit(0);
88
+ }
89
+ appDirName = appRes;
90
+ const compRes = await (0, prompts_1.text)({
91
+ message: 'Components directory name:',
92
+ placeholder: 'components',
93
+ defaultValue: 'components',
94
+ });
95
+ if ((0, prompts_1.isCancel)(compRes)) {
96
+ (0, prompts_1.cancel)('Operation cancelled.');
97
+ process.exit(0);
98
+ }
99
+ componentsDirName = compRes;
100
+ const pubRes = await (0, prompts_1.text)({
101
+ message: 'Public directory name:',
102
+ placeholder: 'public',
103
+ defaultValue: 'public',
104
+ });
105
+ if ((0, prompts_1.isCancel)(pubRes)) {
106
+ (0, prompts_1.cancel)('Operation cancelled.');
107
+ process.exit(0);
108
+ }
109
+ publicDirName = pubRes;
110
+ }
111
+ }
112
+ let shouldInstall = !options.skipInstall;
113
+ if (!options.skipInstall && process.stdin.isTTY) {
114
+ const res = await (0, prompts_1.confirm)({
115
+ message: 'Install dependencies using pnpm?',
116
+ initialValue: true,
117
+ });
118
+ if ((0, prompts_1.isCancel)(res)) {
119
+ (0, prompts_1.cancel)('Operation cancelled.');
120
+ process.exit(0);
121
+ }
122
+ shouldInstall = res;
123
+ }
124
+ const s = (0, prompts_1.spinner)();
125
+ s.start('Scaffolding project...');
126
+ fs_extra_1.default.ensureDirSync(targetPath);
127
+ // Create app structure with chosen directories
128
+ const appDir = path_1.default.join(targetPath, appDirName);
129
+ const componentsDir = path_1.default.join(targetPath, componentsDirName);
130
+ const publicDir = path_1.default.join(targetPath, publicDirName);
131
+ fs_extra_1.default.ensureDirSync(appDir);
132
+ fs_extra_1.default.ensureDirSync(componentsDir);
133
+ fs_extra_1.default.ensureDirSync(publicDir);
134
+ const ext = engine === 'handlebars' ? 'hbs' : engine;
135
+ // Root layout
136
+ const layoutContent = engine === 'handlebars'
137
+ ? `<!DOCTYPE html>
138
+ <html lang="en">
139
+ <head>
140
+ <meta charset="UTF-8">
141
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
142
+ <title>{{title}}</title>
143
+ <link rel="stylesheet" href="/app.css">
144
+ </head>
145
+ <body class="bg-slate-900 text-white min-h-screen">
146
+ {{{body}}}
147
+ </body>
148
+ </html>`
149
+ : engine === 'ejs'
150
+ ? `<!DOCTYPE html>
151
+ <html lang="en">
152
+ <head>
153
+ <meta charset="UTF-8">
154
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
155
+ <title><%= title %></title>
156
+ <link rel="stylesheet" href="/app.css">
157
+ </head>
158
+ <body class="bg-slate-900 text-white min-h-screen">
159
+ <%- body %>
160
+ </body>
161
+ </html>`
162
+ : `<!DOCTYPE html>
163
+ <html lang="en">
164
+ <head>
165
+ <meta charset="UTF-8">
166
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
167
+ <title>Nxpress App</title>
168
+ <link rel="stylesheet" href="/app.css">
169
+ </head>
170
+ <body class="bg-slate-900 text-white min-h-screen">
171
+ <!-- Content -->
172
+ </body>
173
+ </html>`;
174
+ fs_extra_1.default.writeFileSync(path_1.default.join(appDir, `layout.${ext}`), layoutContent);
175
+ // Index page
176
+ const indexPageContent = engine === 'handlebars'
177
+ ? `<div class="max-w-4xl mx-auto px-4 py-16 text-center">
178
+ <h1 class="text-5xl font-bold mb-4 bg-gradient-to-r from-blue-400 to-indigo-500 bg-clip-text text-transparent">
179
+ Welcome to {{title}}
180
+ </h1>
181
+ <p class="text-xl text-slate-400 mb-8">{{description}}</p>
182
+ <div class="inline-block bg-slate-800 border border-slate-700 rounded-lg p-6 text-left">
183
+ <p class="text-sm font-mono text-cyan-400">Edit <span class="text-amber-300">${appDirName}/index.${ext}</span> to get started.</p>
184
+ </div>
185
+ </div>`
186
+ : engine === 'ejs'
187
+ ? `<div class="max-w-4xl mx-auto px-4 py-16 text-center">
188
+ <h1 class="text-5xl font-bold mb-4 bg-gradient-to-r from-blue-400 to-indigo-500 bg-clip-text text-transparent">
189
+ Welcome to <%= title %>
190
+ </h1>
191
+ <p class="text-xl text-slate-400 mb-8"><%= description %></p>
192
+ <div class="inline-block bg-slate-800 border border-slate-700 rounded-lg p-6 text-left">
193
+ <p class="text-sm font-mono text-cyan-400">Edit <span class="text-amber-300">${appDirName}/index.${ext}</span> to get started.</p>
194
+ </div>
195
+ </div>`
196
+ : `<div class="max-w-4xl mx-auto px-4 py-16 text-center">
197
+ <h1 class="text-5xl font-bold mb-4 bg-gradient-to-r from-blue-400 to-indigo-500 bg-clip-text text-transparent">
198
+ Welcome to Nxpress
199
+ </h1>
200
+ <p class="text-xl text-slate-400 mb-8">Express.js with Next.js developer experience</p>
201
+ </div>`;
202
+ fs_extra_1.default.writeFileSync(path_1.default.join(appDir, `index.${ext}`), indexPageContent);
203
+ // Data loader for index page
204
+ const indexTsContent = `import { Request, Response } from 'express';
205
+
206
+ export async function props(req: Request, res: Response) {
207
+ return {
208
+ title: 'Nxpress App',
209
+ description: 'Express.js with Next.js developer experience',
210
+ };
211
+ }
212
+ `;
213
+ fs_extra_1.default.writeFileSync(path_1.default.join(appDir, 'index.ts'), indexTsContent);
214
+ // App CSS for Tailwind v4
215
+ fs_extra_1.default.writeFileSync(path_1.default.join(targetPath, 'app.css'), `@import "tailwindcss";\n`);
216
+ // Server file
217
+ const serverTsContent = `import { nxpress } from '@nxpress/core';
218
+
219
+ const app = nxpress({
220
+ engine: '${engine === 'handlebars' ? 'hbs' : engine}',
221
+ });
222
+
223
+ const PORT = process.env.PORT || 3000;
224
+ app.listen(PORT, () => {
225
+ console.log(\`Nxpress server running on http://localhost:\${PORT}\`);
226
+ });
227
+ `;
228
+ fs_extra_1.default.writeFileSync(path_1.default.join(targetPath, 'server.ts'), serverTsContent);
229
+ const pkgVersion = '1.0.1';
230
+ // nxpress.config.json
231
+ const nxConfig = {
232
+ $schema: `https://unpkg.com/@nxpress/core@${pkgVersion}/schema.json`,
233
+ port: 3000,
234
+ engine: engine === 'handlebars' ? 'hbs' : engine,
235
+ appDir: appDirName,
236
+ componentsDir: componentsDirName,
237
+ publicDir: publicDirName,
238
+ };
239
+ fs_extra_1.default.writeFileSync(path_1.default.join(targetPath, 'nxpress.config.json'), JSON.stringify(nxConfig, null, 2));
240
+ // package.json for target project
241
+ const projectPkgJson = {
242
+ name: path_1.default.basename(targetPath),
243
+ version: '0.1.0',
244
+ private: true,
245
+ scripts: {
246
+ dev: 'nxpress dev',
247
+ build: 'tsc',
248
+ start: 'nxpress start',
249
+ },
250
+ dependencies: {
251
+ '@nxpress/core': `^${pkgVersion}`,
252
+ ...(engine === 'handlebars' ? { hbs: '^4.2.0' } : {}),
253
+ ...(engine === 'ejs' ? { ejs: '^3.1.10' } : {}),
254
+ },
255
+ devDependencies: {
256
+ typescript: '^5.3.3',
257
+ '@types/node': '^20.11.24',
258
+ '@types/express': '^4.17.21',
259
+ },
260
+ };
261
+ fs_extra_1.default.writeFileSync(path_1.default.join(targetPath, 'package.json'), JSON.stringify(projectPkgJson, null, 2));
262
+ // tsconfig.json for target project
263
+ const projectTsConfig = {
264
+ compilerOptions: {
265
+ target: 'ES2022',
266
+ module: 'CommonJS',
267
+ moduleResolution: 'node',
268
+ strict: true,
269
+ esModuleInterop: true,
270
+ skipLibCheck: true,
271
+ },
272
+ include: ['**/*'],
273
+ };
274
+ fs_extra_1.default.writeFileSync(path_1.default.join(targetPath, 'tsconfig.json'), JSON.stringify(projectTsConfig, null, 2));
275
+ // .gitignore
276
+ const gitignoreContent = `node_modules
277
+ dist
278
+ .env
279
+ *.log
280
+ `;
281
+ fs_extra_1.default.writeFileSync(path_1.default.join(targetPath, '.gitignore'), gitignoreContent);
282
+ s.stop('Project structure created successfully.');
283
+ if (shouldInstall) {
284
+ const instSpinner = (0, prompts_1.spinner)();
285
+ instSpinner.start('Installing dependencies with pnpm...');
286
+ try {
287
+ (0, child_process_1.execSync)('pnpm install', { cwd: targetPath, stdio: 'ignore' });
288
+ instSpinner.stop('Dependencies installed successfully.');
289
+ }
290
+ catch (err) {
291
+ instSpinner.stop('Failed to install dependencies automatically.');
292
+ }
293
+ }
294
+ (0, prompts_1.outro)(`Project created in ${chalk_1.default.cyan(projectDir)}!\n\n` +
295
+ `Next steps:\n` +
296
+ ` ${chalk_1.default.cyan(`cd ${projectDir}`)}\n` +
297
+ ` ${chalk_1.default.cyan('pnpm dev')}`);
298
+ });
299
+ program.parse(process.argv);
package/package.json ADDED
@@ -0,0 +1,37 @@
1
+ {
2
+ "name": "create-nxpress-app",
3
+ "version": "1.0.1",
4
+ "description": "CLI tool to create Nxpress applications",
5
+ "main": "dist/index.js",
6
+ "bin": {
7
+ "create-nxpress-app": "./dist/index.js"
8
+ },
9
+ "files": [
10
+ "dist",
11
+ "templates"
12
+ ],
13
+ "keywords": [
14
+ "nxpress",
15
+ "cli",
16
+ "create-app",
17
+ "scaffold",
18
+ "express"
19
+ ],
20
+ "license": "MIT",
21
+ "dependencies": {
22
+ "@clack/prompts": "^0.8.2",
23
+ "chalk": "^4.1.2",
24
+ "commander": "^12.0.0",
25
+ "fs-extra": "^11.2.0"
26
+ },
27
+ "devDependencies": {
28
+ "@types/fs-extra": "^11.0.4",
29
+ "@types/node": "^20.11.24",
30
+ "typescript": "^5.3.3"
31
+ },
32
+ "scripts": {
33
+ "build": "tsc",
34
+ "dev": "tsc --watch",
35
+ "start": "node dist/index.js"
36
+ }
37
+ }