create-nodepress-app 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,53 @@
1
+ # NodePress CLI
2
+
3
+ Scaffold a self-hosted [NodePress](https://gitlab.com/leo9karthik/nodepress) headless CMS project in one command.
4
+
5
+ ```bash
6
+ npx nodepress new my-cms
7
+ ```
8
+
9
+ ## What it does
10
+
11
+ - Clones the NodePress repository into a new folder
12
+ - Generates cryptographically random `JWT_SECRET` and database password
13
+ - Writes `backend/.env`, `frontend/.env.local`, and `.env` (Docker) with all values filled in
14
+ - Runs `npm install` in both `backend/` and `frontend/`
15
+
16
+ ## Requirements
17
+
18
+ - Node.js 18+
19
+ - Git
20
+ - PostgreSQL 14+ **or** Docker
21
+
22
+ ## Usage
23
+
24
+ ```bash
25
+ npx nodepress new <project-name>
26
+ npx nodepress --version
27
+ npx nodepress --help
28
+ ```
29
+
30
+ ## Quick start
31
+
32
+ ```bash
33
+ npx nodepress new my-cms
34
+ cd my-cms
35
+
36
+ # Option A — Docker (recommended)
37
+ docker-compose -f docker-compose.prod.yml up -d
38
+
39
+ # Option B — Local development
40
+ cd backend && npx prisma migrate dev && npm run start:dev
41
+ # In a second terminal:
42
+ cd frontend && npm run dev
43
+ ```
44
+
45
+ - Admin panel: `http://localhost:5173`
46
+ - API: `http://localhost:3000/api`
47
+ - API docs: `http://localhost:3000/api/docs`
48
+
49
+ On first load the admin panel redirects to `/setup` where you create the first admin account.
50
+
51
+ ## License
52
+
53
+ MIT
@@ -0,0 +1,36 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const [,, command, ...args] = process.argv;
5
+
6
+ const HELP = `
7
+ NodePress CLI
8
+
9
+ Usage:
10
+ npx nodepress new <project-name> Scaffold a new NodePress project
11
+ npx nodepress --version Show version
12
+ npx nodepress --help Show this help
13
+
14
+ Examples:
15
+ npx nodepress new my-website
16
+ npx nodepress new company-cms
17
+ `;
18
+
19
+ switch (command) {
20
+ case 'new':
21
+ require('../src/new')(args[0]);
22
+ break;
23
+ case '--version':
24
+ case '-v':
25
+ console.log(require('../package.json').version);
26
+ break;
27
+ case '--help':
28
+ case '-h':
29
+ case undefined:
30
+ console.log(HELP);
31
+ break;
32
+ default:
33
+ console.error(`\n Unknown command: "${command}"`);
34
+ console.log(HELP);
35
+ process.exit(1);
36
+ }
package/package.json ADDED
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "create-nodepress-app",
3
+ "version": "1.0.0",
4
+ "description": "Scaffold a self-hosted NodePress headless CMS in one command",
5
+ "keywords": [
6
+ "cms",
7
+ "headless",
8
+ "nestjs",
9
+ "nextjs",
10
+ "nodepress",
11
+ "self-hosted"
12
+ ],
13
+ "license": "MIT",
14
+ "author": "leo9karthik",
15
+ "homepage": "https://nodepress.buildwithkode.com",
16
+ "repository": {
17
+ "type": "git",
18
+ "url": "git+https://github.com/buildwithkode/nodepress.git"
19
+ },
20
+ "bin": {
21
+ "create-nodepress-app": "bin/nodepress.js"
22
+ },
23
+ "files": [
24
+ "bin/",
25
+ "src/",
26
+ "README.md"
27
+ ],
28
+ "engines": {
29
+ "node": ">=18.0.0"
30
+ },
31
+ "dependencies": {}
32
+ }
package/src/new.js ADDED
@@ -0,0 +1,167 @@
1
+ 'use strict';
2
+
3
+ const { execSync, spawnSync } = require('child_process');
4
+ const { randomBytes } = require('crypto');
5
+ const fs = require('fs');
6
+ const path = require('path');
7
+
8
+ const REPO_URL = 'https://github.com/buildwithkode/nodepress.git';
9
+
10
+ // ANSI colours (degrade gracefully if not supported)
11
+ const c = {
12
+ reset: '\x1b[0m',
13
+ bold: '\x1b[1m',
14
+ green: '\x1b[32m',
15
+ blue: '\x1b[34m',
16
+ yellow: '\x1b[33m',
17
+ cyan: '\x1b[36m',
18
+ dim: '\x1b[2m',
19
+ };
20
+
21
+ function log(msg) { process.stdout.write(msg + '\n'); }
22
+ function ok(msg) { log(` ${c.green}✔${c.reset} ${msg}`); }
23
+ function info(msg) { log(` ${c.blue}→${c.reset} ${msg}`); }
24
+ function warn(msg) { log(` ${c.yellow}⚠${c.reset} ${msg}`); }
25
+ function header(msg) { log(`\n${c.bold}${msg}${c.reset}`); }
26
+
27
+ function secret(bytes = 32) {
28
+ return randomBytes(bytes).toString('hex');
29
+ }
30
+
31
+ function writeEnvFile(filePath, vars) {
32
+ const content = Object.entries(vars)
33
+ .map(([k, v]) => (v === '' ? `# ${k}=` : `${k}="${v}"`))
34
+ .join('\n');
35
+ fs.writeFileSync(filePath, content + '\n', 'utf8');
36
+ }
37
+
38
+ function run(cmd, cwd, stdio = 'pipe') {
39
+ return spawnSync(cmd, { shell: true, cwd, stdio, encoding: 'utf8' });
40
+ }
41
+
42
+ module.exports = async function createProject(name) {
43
+ // ── Validate name ──────────────────────────────────────────────────────────
44
+ if (!name) {
45
+ log(`\n ${c.bold}Usage:${c.reset} npx nodepress new <project-name>\n`);
46
+ process.exit(1);
47
+ }
48
+
49
+ const safeName = name.replace(/[^a-zA-Z0-9_-]/g, '-').toLowerCase();
50
+ const projectDir = path.resolve(process.cwd(), safeName);
51
+
52
+ if (fs.existsSync(projectDir)) {
53
+ warn(`Directory "${safeName}" already exists. Choose a different name.`);
54
+ process.exit(1);
55
+ }
56
+
57
+ header(`\n NodePress — Creating "${safeName}"`);
58
+ log('');
59
+
60
+ // ── Check dependencies ─────────────────────────────────────────────────────
61
+ const hasGit = run('git --version').status === 0;
62
+ const hasDocker = run('docker --version').status === 0;
63
+ const hasNode = run('node --version').status === 0;
64
+
65
+ if (!hasNode) { warn('Node.js 18+ is required.'); process.exit(1); }
66
+ if (!hasGit) { warn('Git is required. Install from https://git-scm.com'); process.exit(1); }
67
+
68
+ // ── Clone repository ───────────────────────────────────────────────────────
69
+ info(`Cloning NodePress into ./${safeName} …`);
70
+ const cloneResult = run(`git clone --depth 1 ${REPO_URL} "${safeName}"`, process.cwd(), 'inherit');
71
+ if (cloneResult.status !== 0) {
72
+ warn('Clone failed. Check your internet connection or the repository URL.');
73
+ process.exit(1);
74
+ }
75
+ ok('Repository cloned');
76
+
77
+ // Remove git history (this is a fresh project, not a fork)
78
+ // Use fs.rmSync for cross-platform support (rm -rf fails on Windows)
79
+ fs.rmSync(path.join(projectDir, '.git'), { recursive: true, force: true });
80
+ run(`git init`, projectDir);
81
+ ok('Fresh git repository initialized');
82
+
83
+ // ── Generate secrets ───────────────────────────────────────────────────────
84
+ const dbPassword = secret(24);
85
+ const jwtSecret = secret(48);
86
+
87
+ // ── Write backend .env ─────────────────────────────────────────────────────
88
+ const dbUrl = `postgresql://postgres:${dbPassword}@localhost:5432/nodepress`;
89
+ const backendEnv = {
90
+ // Database — DIRECT_URL bypasses PgBouncer for migrations (same as DATABASE_URL in local dev)
91
+ DATABASE_URL: dbUrl,
92
+ DIRECT_URL: dbUrl,
93
+ PORT: '3000',
94
+ JWT_SECRET: jwtSecret,
95
+ CORS_ORIGIN: 'http://localhost:5173',
96
+ APP_URL: 'http://localhost:3000',
97
+ SITE_URL: 'http://localhost:5173',
98
+ // Email — configure to enable password reset emails
99
+ SMTP_HOST: '',
100
+ SMTP_PORT: '587',
101
+ SMTP_SECURE: 'false',
102
+ SMTP_USER: '',
103
+ SMTP_PASS: '',
104
+ SMTP_FROM: '',
105
+ };
106
+ writeEnvFile(path.join(projectDir, 'backend', '.env'), backendEnv);
107
+ ok('backend/.env generated with random secrets');
108
+
109
+ // ── Write frontend .env.local ──────────────────────────────────────────────
110
+ const frontendEnv = {
111
+ BACKEND_URL: 'http://localhost:3000',
112
+ };
113
+ writeEnvFile(path.join(projectDir, 'frontend', '.env.local'), frontendEnv);
114
+ ok('frontend/.env.local generated');
115
+
116
+ // ── Write root .env (for Docker Compose) ──────────────────────────────────
117
+ const rootEnv = {
118
+ DB_PASSWORD: dbPassword,
119
+ JWT_SECRET: jwtSecret,
120
+ CORS_ORIGIN: 'http://localhost',
121
+ APP_URL: 'http://localhost:3000',
122
+ SITE_URL: 'http://localhost',
123
+ };
124
+ writeEnvFile(path.join(projectDir, '.env'), rootEnv);
125
+ ok('.env generated for Docker Compose');
126
+
127
+ // ── Install dependencies ───────────────────────────────────────────────────
128
+ info('Installing backend dependencies …');
129
+ run('npm install', path.join(projectDir, 'backend'), 'inherit');
130
+ ok('Backend dependencies installed');
131
+
132
+ info('Installing frontend dependencies …');
133
+ run('npm install', path.join(projectDir, 'frontend'), 'inherit');
134
+ ok('Frontend dependencies installed');
135
+
136
+ // ── Done ───────────────────────────────────────────────────────────────────
137
+ log('');
138
+ log(` ${c.green}${c.bold}✓ NodePress "${safeName}" is ready!${c.reset}`);
139
+ log('');
140
+ log(` ${c.bold}Next steps:${c.reset}`);
141
+ log('');
142
+
143
+ if (hasDocker) {
144
+ log(` ${c.cyan}Option A — Docker (recommended for production):${c.reset}`);
145
+ log(` cd ${safeName}`);
146
+ log(` docker-compose -f docker-compose.prod.yml up -d`);
147
+ log('');
148
+ log(` ${c.cyan}Option B — Local development:${c.reset}`);
149
+ } else {
150
+ log(` ${c.cyan}Start local development:${c.reset}`);
151
+ warn('Docker not found — you\'ll need PostgreSQL running locally (port 5432)');
152
+ }
153
+
154
+ log(` cd ${safeName}/backend`);
155
+ log(` npx prisma migrate dev ${c.dim}# create DB tables${c.reset}`);
156
+ log(` npm run start:dev ${c.dim}# backend on :3000${c.reset}`);
157
+ log('');
158
+ log(` cd ${safeName}/frontend`);
159
+ log(` npm run dev ${c.dim}# admin panel on :5173${c.reset}`);
160
+ log('');
161
+ log(` ${c.cyan}Admin panel:${c.reset} http://localhost:5173`);
162
+ log(` ${c.cyan}API docs:${c.reset} http://localhost:3000/api/docs`);
163
+ log(` ${c.cyan}Health check:${c.reset} http://localhost:3000/api/health`);
164
+ log('');
165
+ log(` ${c.dim}Docs: https://nodepress.buildwithkode.com${c.reset}`);
166
+ log('');
167
+ };