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 CHANGED
@@ -9,7 +9,7 @@ Parameters
9
9
 
10
10
  Licensor: Boject Ltd
11
11
 
12
- Licensed Work: boject-cms 0.0.1-rc.1
12
+ Licensed Work: boject-cms 0.0.1-rc.2
13
13
  The Licensed Work is © 2026 Boject Ltd.
14
14
 
15
15
  Additional Use Grant: You may make production use of the Licensed Work,
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.1";
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
- - '4000:3000'
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
- starter
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({ starter, adminEmail }) {
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
- ${starterLine}Once the container is healthy, log in at http://localhost:4000/login with:
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({ sessionPassword, adminPassword, starter })
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:4000/login with:
425
+ Once the container is healthy, log in at http://localhost:${hostPort}/login with:
350
426
  Email: ${adminEmail}
351
427
  Password: ${adminPassword}
352
428
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-boject-cms",
3
- "version": "0.0.1-rc.1",
3
+ "version": "0.0.1-rc.2",
4
4
  "license": "BUSL-1.1",
5
5
  "private": false,
6
6
  "type": "module",