create-boject-cms 0.0.1-rc.1 → 0.0.1-rc.2
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/LICENSE +1 -1
- package/README.md +6 -0
- package/dist/index.js +90 -14
- package/package.json +1 -1
package/LICENSE
CHANGED
package/README.md
CHANGED
|
@@ -27,9 +27,14 @@ Non-interactive use:
|
|
|
27
27
|
```bash
|
|
28
28
|
pnpm create boject-cms my-site --starter base
|
|
29
29
|
pnpm create boject-cms my-site --starter base --image ghcr.io/bojectify/boject-cms:1.4.2
|
|
30
|
+
pnpm create boject-cms my-site --starter base --port 4100 # host port (default 4000)
|
|
30
31
|
pnpm create boject-cms my-site --force # scaffold into a non-empty directory
|
|
31
32
|
```
|
|
32
33
|
|
|
34
|
+
The CMS is published on host port **4000** by default. Pass `--port <n>` at
|
|
35
|
+
scaffold time (or edit `BOJECT_HOST_PORT` in the generated `.env` afterwards) to
|
|
36
|
+
run several projects on one machine, or to avoid a clash with another service.
|
|
37
|
+
|
|
33
38
|
## What gets scaffolded
|
|
34
39
|
|
|
35
40
|
```
|
|
@@ -73,6 +78,7 @@ Once the container is healthy, log in at http://localhost:4000/login. The admin
|
|
|
73
78
|
| `BOJECT_ADMIN_EMAIL` | Initial admin (`admin@local`). |
|
|
74
79
|
| `BOJECT_ADMIN_PASSWORD` | 16-byte random initial admin password. |
|
|
75
80
|
| `STORAGE_DRIVER` | `local` (default), `s3`, or `r2`. |
|
|
81
|
+
| `BOJECT_HOST_PORT` | Host port the CMS is published on (default 4000; Compose maps it to container port 3000). |
|
|
76
82
|
| `BOJECT_SCHEMA_DIR` | `/app/content-types` — the entrypoint applies any `*.boject.json` here on every boot. |
|
|
77
83
|
| `BOJECT_INITIAL_STARTER` | First-boot starter path (`/starters/<starter>.boject.json`). |
|
|
78
84
|
| `# BOJECT_SCHEMA_READONLY=true` | Commented. Set on production / staging to disable schema editing in the UI. |
|
package/dist/index.js
CHANGED
|
@@ -5,6 +5,21 @@ import { fileURLToPath } from "url";
|
|
|
5
5
|
import { dirname, join as join2, resolve } from "path";
|
|
6
6
|
import { parseArgs } from "util";
|
|
7
7
|
|
|
8
|
+
// src/hostPort.ts
|
|
9
|
+
var DEFAULT_HOST_PORT = 4e3;
|
|
10
|
+
function resolveHostPort(flag) {
|
|
11
|
+
if (flag === void 0) {
|
|
12
|
+
return DEFAULT_HOST_PORT;
|
|
13
|
+
}
|
|
14
|
+
const port = Number(flag);
|
|
15
|
+
if (!Number.isInteger(port) || port < 1 || port > 65535) {
|
|
16
|
+
throw new Error(
|
|
17
|
+
`Invalid --port "${flag}": expected an integer between 1 and 65535.`
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
return port;
|
|
21
|
+
}
|
|
22
|
+
|
|
8
23
|
// src/prompts.ts
|
|
9
24
|
import { isCancel, select } from "@clack/prompts";
|
|
10
25
|
var CHOICES = ["base", "sport", "rugby", "none"];
|
|
@@ -46,7 +61,7 @@ async function resolveStarter({
|
|
|
46
61
|
}
|
|
47
62
|
|
|
48
63
|
// src/version.ts
|
|
49
|
-
var IMAGE_TAG = "ghcr.io/bojectify/boject-cms:0.0.1-rc.
|
|
64
|
+
var IMAGE_TAG = "ghcr.io/bojectify/boject-cms:0.0.1-rc.2";
|
|
50
65
|
|
|
51
66
|
// src/writeProject.ts
|
|
52
67
|
import { copyFile, mkdir, readdir, writeFile } from "fs/promises";
|
|
@@ -64,11 +79,15 @@ function renderDockerCompose({
|
|
|
64
79
|
image: ${imageTag}
|
|
65
80
|
restart: unless-stopped
|
|
66
81
|
ports:
|
|
67
|
-
|
|
82
|
+
# Host port comes from BOJECT_HOST_PORT in .env (default 4000). Change it
|
|
83
|
+
# there to run several projects side by side or dodge a port clash.
|
|
84
|
+
- '\${BOJECT_HOST_PORT:-4000}:3000'
|
|
68
85
|
env_file:
|
|
69
86
|
- .env
|
|
70
87
|
depends_on:
|
|
71
88
|
- db
|
|
89
|
+
- meilisearch
|
|
90
|
+
- redis
|
|
72
91
|
volumes:
|
|
73
92
|
- storage:/app/storage
|
|
74
93
|
- ./content-types:/app/content-types:ro
|
|
@@ -82,9 +101,31 @@ ${starterMount} db:
|
|
|
82
101
|
volumes:
|
|
83
102
|
- pgdata:/var/lib/postgresql/data
|
|
84
103
|
|
|
104
|
+
meilisearch:
|
|
105
|
+
image: getmeili/meilisearch:v1.45.2
|
|
106
|
+
restart: unless-stopped
|
|
107
|
+
environment:
|
|
108
|
+
# Production mode: all routes require the master key. The CMS reads the
|
|
109
|
+
# same key from .env (MEILI_MASTER_KEY). Compose interpolates it here.
|
|
110
|
+
MEILI_ENV: production
|
|
111
|
+
MEILI_MASTER_KEY: \${MEILI_MASTER_KEY}
|
|
112
|
+
MEILI_NO_ANALYTICS: 'true'
|
|
113
|
+
volumes:
|
|
114
|
+
# The search index must survive restarts, else search is empty until a
|
|
115
|
+
# manual \`pnpm search:reindex\`.
|
|
116
|
+
- meilidata:/meili_data
|
|
117
|
+
|
|
118
|
+
redis:
|
|
119
|
+
image: redis:7.4-alpine
|
|
120
|
+
restart: unless-stopped
|
|
121
|
+
# Cache only \u2014 no persistence, no volume. Restarts cold by design
|
|
122
|
+
# (repopulates from Postgres).
|
|
123
|
+
command: redis-server --save "" --appendonly no
|
|
124
|
+
|
|
85
125
|
volumes:
|
|
86
126
|
pgdata:
|
|
87
127
|
storage:
|
|
128
|
+
meilidata:
|
|
88
129
|
`;
|
|
89
130
|
}
|
|
90
131
|
|
|
@@ -92,10 +133,16 @@ volumes:
|
|
|
92
133
|
function renderEnvFile({
|
|
93
134
|
sessionPassword,
|
|
94
135
|
adminPassword,
|
|
95
|
-
|
|
136
|
+
meiliMasterKey,
|
|
137
|
+
starter,
|
|
138
|
+
hostPort
|
|
96
139
|
}) {
|
|
97
140
|
const lines = [
|
|
98
141
|
"DATABASE_URL=postgresql://boject:boject@db:5432/boject",
|
|
142
|
+
"# Search (Meilisearch) + cache (Redis) sidecars \u2014 see docker-compose.yml.",
|
|
143
|
+
"MEILI_URL=http://meilisearch:7700",
|
|
144
|
+
`MEILI_MASTER_KEY=${meiliMasterKey}`,
|
|
145
|
+
"REDIS_URL=redis://redis:6379",
|
|
99
146
|
`NUXT_SESSION_PASSWORD=${sessionPassword}`,
|
|
100
147
|
"BOJECT_ADMIN_EMAIL=admin@local",
|
|
101
148
|
`BOJECT_ADMIN_PASSWORD=${adminPassword}`,
|
|
@@ -106,6 +153,10 @@ function renderEnvFile({
|
|
|
106
153
|
lines.push(`BOJECT_INITIAL_STARTER=/starters/${starter}.boject.json`);
|
|
107
154
|
}
|
|
108
155
|
lines.push(
|
|
156
|
+
"",
|
|
157
|
+
"# Host port the CMS is published on (mapped to the container port 3000).",
|
|
158
|
+
"# Change it to run several projects side by side, or to avoid a port clash.",
|
|
159
|
+
`BOJECT_HOST_PORT=${hostPort}`,
|
|
109
160
|
"",
|
|
110
161
|
'# Set to "true" on production / staging to disable schema editing in the UI.',
|
|
111
162
|
"# Schema changes should flow from git on locked environments.",
|
|
@@ -141,7 +192,11 @@ function renderPackageJson({ name }) {
|
|
|
141
192
|
}
|
|
142
193
|
|
|
143
194
|
// src/templates/readme.ts
|
|
144
|
-
function renderReadme({
|
|
195
|
+
function renderReadme({
|
|
196
|
+
starter,
|
|
197
|
+
adminEmail,
|
|
198
|
+
hostPort
|
|
199
|
+
}) {
|
|
145
200
|
const starterLine = starter === "none" ? "" : `The \`${starter}\` starter bundle will be imported on first boot.
|
|
146
201
|
|
|
147
202
|
`;
|
|
@@ -155,11 +210,17 @@ A new boject-cms project scaffolded by \`create-boject-cms\`.
|
|
|
155
210
|
docker compose up -d
|
|
156
211
|
\`\`\`
|
|
157
212
|
|
|
158
|
-
|
|
213
|
+
This starts four services: the CMS, PostgreSQL, Meilisearch (search), and Redis
|
|
214
|
+
(response cache).
|
|
215
|
+
|
|
216
|
+
${starterLine}Once the container is healthy, log in at http://localhost:${hostPort}/login with:
|
|
159
217
|
|
|
160
218
|
- Email: \`${adminEmail}\`
|
|
161
219
|
- Password: see \`BOJECT_ADMIN_PASSWORD\` in \`.env\`
|
|
162
220
|
|
|
221
|
+
The CMS is published on host port \`${hostPort}\` \u2014 change \`BOJECT_HOST_PORT\` in
|
|
222
|
+
\`.env\` to run several projects side by side or to avoid a port clash.
|
|
223
|
+
|
|
163
224
|
## Content types
|
|
164
225
|
|
|
165
226
|
Your content type schema lives in \`content-types/schema.boject.json\` and is
|
|
@@ -231,6 +292,9 @@ function generateSessionPassword() {
|
|
|
231
292
|
function generateAdminPassword() {
|
|
232
293
|
return randomBytes(16).toString("base64");
|
|
233
294
|
}
|
|
295
|
+
function generateMeiliMasterKey() {
|
|
296
|
+
return randomBytes(32).toString("base64");
|
|
297
|
+
}
|
|
234
298
|
|
|
235
299
|
// src/writeProject.ts
|
|
236
300
|
async function writeProject({
|
|
@@ -238,7 +302,8 @@ async function writeProject({
|
|
|
238
302
|
starter,
|
|
239
303
|
imageTag,
|
|
240
304
|
force,
|
|
241
|
-
startersSourceDir
|
|
305
|
+
startersSourceDir,
|
|
306
|
+
hostPort
|
|
242
307
|
}) {
|
|
243
308
|
await mkdir(targetDir, { recursive: true });
|
|
244
309
|
const existing = await readdir(targetDir);
|
|
@@ -249,6 +314,7 @@ async function writeProject({
|
|
|
249
314
|
}
|
|
250
315
|
const sessionPassword = generateSessionPassword();
|
|
251
316
|
const adminPassword = generateAdminPassword();
|
|
317
|
+
const meiliMasterKey = generateMeiliMasterKey();
|
|
252
318
|
const adminEmail = "admin@local";
|
|
253
319
|
const projectName = sanitiseProjectName(basename(targetDir));
|
|
254
320
|
await writeFile(
|
|
@@ -257,7 +323,13 @@ async function writeProject({
|
|
|
257
323
|
);
|
|
258
324
|
await writeFile(
|
|
259
325
|
join(targetDir, ".env"),
|
|
260
|
-
renderEnvFile({
|
|
326
|
+
renderEnvFile({
|
|
327
|
+
sessionPassword,
|
|
328
|
+
adminPassword,
|
|
329
|
+
meiliMasterKey,
|
|
330
|
+
starter,
|
|
331
|
+
hostPort
|
|
332
|
+
})
|
|
261
333
|
);
|
|
262
334
|
await writeFile(
|
|
263
335
|
join(targetDir, "package.json"),
|
|
@@ -266,7 +338,7 @@ async function writeProject({
|
|
|
266
338
|
await writeFile(join(targetDir, ".gitignore"), GITIGNORE);
|
|
267
339
|
await writeFile(
|
|
268
340
|
join(targetDir, "README.md"),
|
|
269
|
-
renderReadme({ starter, adminEmail })
|
|
341
|
+
renderReadme({ starter, adminEmail, hostPort })
|
|
270
342
|
);
|
|
271
343
|
if (starter !== "none") {
|
|
272
344
|
const startersTarget = join(targetDir, "starters");
|
|
@@ -301,12 +373,13 @@ function parseCli(argv) {
|
|
|
301
373
|
options: {
|
|
302
374
|
force: { type: "boolean", default: false },
|
|
303
375
|
starter: { type: "string" },
|
|
304
|
-
image: { type: "string" }
|
|
376
|
+
image: { type: "string" },
|
|
377
|
+
port: { type: "string" }
|
|
305
378
|
}
|
|
306
379
|
});
|
|
307
380
|
if (positionals.length !== 1) {
|
|
308
381
|
process.stderr.write(
|
|
309
|
-
"Usage: create-boject-cms <target-dir> [--force] [--starter <name>] [--image <tag>]\n"
|
|
382
|
+
"Usage: create-boject-cms <target-dir> [--force] [--starter <name>] [--image <tag>] [--port <n>]\n"
|
|
310
383
|
);
|
|
311
384
|
process.exit(1);
|
|
312
385
|
}
|
|
@@ -314,7 +387,8 @@ function parseCli(argv) {
|
|
|
314
387
|
targetDir: resolve(positionals[0]),
|
|
315
388
|
force: values.force === true,
|
|
316
389
|
starter: values.starter,
|
|
317
|
-
imageTag: values.image ?? IMAGE_TAG
|
|
390
|
+
imageTag: values.image ?? IMAGE_TAG,
|
|
391
|
+
hostPort: resolveHostPort(values.port)
|
|
318
392
|
};
|
|
319
393
|
}
|
|
320
394
|
function resolveStartersSourceDir() {
|
|
@@ -326,7 +400,8 @@ async function main() {
|
|
|
326
400
|
targetDir,
|
|
327
401
|
force,
|
|
328
402
|
starter: starterFlag,
|
|
329
|
-
imageTag
|
|
403
|
+
imageTag,
|
|
404
|
+
hostPort
|
|
330
405
|
} = parseCli(process.argv.slice(2));
|
|
331
406
|
const starter = await resolveStarter({
|
|
332
407
|
flag: starterFlag,
|
|
@@ -337,7 +412,8 @@ async function main() {
|
|
|
337
412
|
starter,
|
|
338
413
|
imageTag,
|
|
339
414
|
force,
|
|
340
|
-
startersSourceDir: resolveStartersSourceDir()
|
|
415
|
+
startersSourceDir: resolveStartersSourceDir(),
|
|
416
|
+
hostPort
|
|
341
417
|
});
|
|
342
418
|
process.stdout.write(`
|
|
343
419
|
Scaffolded boject-cms project at ${targetDir}
|
|
@@ -346,7 +422,7 @@ Next steps:
|
|
|
346
422
|
cd ${targetDir}
|
|
347
423
|
docker compose up -d
|
|
348
424
|
|
|
349
|
-
Once the container is healthy, log in at http://localhost
|
|
425
|
+
Once the container is healthy, log in at http://localhost:${hostPort}/login with:
|
|
350
426
|
Email: ${adminEmail}
|
|
351
427
|
Password: ${adminPassword}
|
|
352
428
|
|