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 +53 -0
- package/bin/nodepress.js +36 -0
- package/package.json +32 -0
- package/src/new.js +167 -0
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
|
package/bin/nodepress.js
ADDED
|
@@ -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
|
+
};
|