create-apollo-monorepo 0.8.0 → 0.9.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/index.mjs +189 -1
- package/package.json +1 -1
package/index.mjs
CHANGED
|
@@ -367,8 +367,31 @@ function writeRootPackageJson(targetDir, dirName) {
|
|
|
367
367
|
"pnpm predev:setup && pnpm --filter ./apps/backend exec next dev",
|
|
368
368
|
"dev:cron": "pnpm --filter ./apps/backend exec bun scripts/dev-cron.ts",
|
|
369
369
|
// Build pipeline: cms-plugins → apollo-cms's own plugins → backend → frontend.
|
|
370
|
+
// `prebuild` runs first (npm/pnpm convention) and fails fast if the
|
|
371
|
+
// backend's required env vars are missing.
|
|
372
|
+
prebuild: "node scripts/check-env.mjs",
|
|
370
373
|
build:
|
|
371
374
|
"pnpm cms-plugins:build && pnpm --filter ./apps/backend exec bun run plugins:build && pnpm --filter ./apps/backend build && pnpm --filter ./apps/frontend build",
|
|
375
|
+
"build:backend":
|
|
376
|
+
"pnpm cms-plugins:build && pnpm --filter ./apps/backend exec bun run plugins:build && pnpm --filter ./apps/backend build",
|
|
377
|
+
"build:frontend": "pnpm --filter ./apps/frontend build",
|
|
378
|
+
// Production start. Runs FE :3001 + BE :3000 in parallel via
|
|
379
|
+
// \`next start\`. Run \`pnpm backend:upgrade\` BEFORE this — start does
|
|
380
|
+
// NOT run migrations on boot (zero-downtime restart safety).
|
|
381
|
+
start:
|
|
382
|
+
"concurrently -k -n FE,BE -c blue,magenta \"pnpm --filter ./apps/frontend start\" \"pnpm --filter ./apps/backend exec next start\"",
|
|
383
|
+
// Single-origin production: FE + BE + Node reverse proxy on :3030.
|
|
384
|
+
"start:rp":
|
|
385
|
+
"concurrently -k -n FE,BE,PX -c blue,magenta,cyan \"pnpm --filter ./apps/frontend start\" \"pnpm --filter ./apps/backend exec next start\" \"pnpm start:proxy\"",
|
|
386
|
+
"start:proxy": "NODE_ENV=production node apps/proxy/server.mjs",
|
|
387
|
+
"start:frontend": "pnpm --filter ./apps/frontend start",
|
|
388
|
+
"start:backend": "pnpm --filter ./apps/backend exec next start",
|
|
389
|
+
// Self-hosted scheduler fallback. Vercel deploys use Vercel Cron via
|
|
390
|
+
// apps/backend/vercel.json — skip this on Vercel. For Docker / VPS
|
|
391
|
+
// you can either run \`pnpm start:cron\` alongside \`pnpm start\` (PM2
|
|
392
|
+
// handles this via ecosystem.config.cjs), or use system cron / k8s
|
|
393
|
+
// CronJob to hit /api/cron with CRON_SECRET.
|
|
394
|
+
"start:cron": "pnpm --filter ./apps/backend exec bun scripts/dev-cron.ts",
|
|
372
395
|
"cms-plugins:build":
|
|
373
396
|
"pnpm --filter './apps/cms-plugins/*' --parallel --if-present build",
|
|
374
397
|
"cms-plugins:dev":
|
|
@@ -1244,6 +1267,122 @@ console.log(\`Run: pnpm install && pnpm cms-plugins:build && pnpm dev:backend\`)
|
|
|
1244
1267
|
writeFileSync(resolve(scriptsDir, "new-cms-plugin.mjs"), script);
|
|
1245
1268
|
}
|
|
1246
1269
|
|
|
1270
|
+
// Pre-build env check. Fails the build before \`next build\` runs if backend
|
|
1271
|
+
// required vars are missing, so users don't sit through a 30s build only to
|
|
1272
|
+
// hit a runtime error on first request.
|
|
1273
|
+
function writeCheckEnvScript(targetDir) {
|
|
1274
|
+
const scriptsDir = resolve(targetDir, "scripts");
|
|
1275
|
+
mkdirSync(scriptsDir, { recursive: true });
|
|
1276
|
+
const script = `#!/usr/bin/env node
|
|
1277
|
+
// Pre-build sanity check. Reads apps/backend/.env.local and verifies that
|
|
1278
|
+
// required vars are present and non-empty. Skipped when SKIP_ENV_CHECK=1.
|
|
1279
|
+
//
|
|
1280
|
+
// In CI/Vercel where envs come from the platform (not files), set
|
|
1281
|
+
// SKIP_ENV_CHECK=1 and rely on the platform's own validation.
|
|
1282
|
+
|
|
1283
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
1284
|
+
import { dirname, resolve } from "node:path";
|
|
1285
|
+
import { fileURLToPath } from "node:url";
|
|
1286
|
+
|
|
1287
|
+
if (process.env.SKIP_ENV_CHECK === "1") process.exit(0);
|
|
1288
|
+
|
|
1289
|
+
const REQUIRED = ["DATABASE_URL", "APOLLO_SECRET", "NEXT_PUBLIC_DEFAULT_LOCALE"];
|
|
1290
|
+
|
|
1291
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
1292
|
+
const envPath = resolve(__dirname, "../apps/backend/.env.local");
|
|
1293
|
+
|
|
1294
|
+
if (!existsSync(envPath)) {
|
|
1295
|
+
// Allow build to proceed if env vars are coming from process.env directly.
|
|
1296
|
+
const fromProcess = REQUIRED.every((k) => process.env[k]);
|
|
1297
|
+
if (fromProcess) process.exit(0);
|
|
1298
|
+
console.error(
|
|
1299
|
+
\`✗ Missing apps/backend/.env.local and required vars not in process.env.\\n Required: \${REQUIRED.join(", ")}\\n Hint: re-run the installer or copy apps/backend/.env.local.example\`,
|
|
1300
|
+
);
|
|
1301
|
+
process.exit(1);
|
|
1302
|
+
}
|
|
1303
|
+
|
|
1304
|
+
const env = Object.fromEntries(
|
|
1305
|
+
readFileSync(envPath, "utf-8")
|
|
1306
|
+
.split("\\n")
|
|
1307
|
+
.filter((line) => line && !line.startsWith("#"))
|
|
1308
|
+
.map((line) => {
|
|
1309
|
+
const i = line.indexOf("=");
|
|
1310
|
+
return i === -1 ? [line, ""] : [line.slice(0, i).trim(), line.slice(i + 1).trim()];
|
|
1311
|
+
}),
|
|
1312
|
+
);
|
|
1313
|
+
|
|
1314
|
+
const missing = REQUIRED.filter((k) => !env[k] && !process.env[k]);
|
|
1315
|
+
if (missing.length) {
|
|
1316
|
+
console.error(\`✗ Missing required env vars: \${missing.join(", ")}\\n Edit apps/backend/.env.local or set them in process.env.\`);
|
|
1317
|
+
process.exit(1);
|
|
1318
|
+
}
|
|
1319
|
+
console.log("✓ env check passed");
|
|
1320
|
+
`;
|
|
1321
|
+
writeFileSync(resolve(scriptsDir, "check-env.mjs"), script);
|
|
1322
|
+
}
|
|
1323
|
+
|
|
1324
|
+
// PM2 ecosystem config — production process supervision for self-hosted deploys.
|
|
1325
|
+
// Includes FE, BE, optional proxy, and optional cron in fork mode (no clustering
|
|
1326
|
+
// since Next.js handles that internally and the proxy is single-threaded by design).
|
|
1327
|
+
function writePm2Config(targetDir, dirName, adminPrefix) {
|
|
1328
|
+
const singleOrigin = !!adminPrefix;
|
|
1329
|
+
const config = `// PM2 ecosystem config for ${dirName}.
|
|
1330
|
+
// Usage:
|
|
1331
|
+
// pnpm install
|
|
1332
|
+
// pnpm backend:upgrade
|
|
1333
|
+
// pnpm build
|
|
1334
|
+
// pm2 start ecosystem.config.cjs # FE + BE
|
|
1335
|
+
// pm2 start ecosystem.config.cjs --only proxy # add reverse proxy
|
|
1336
|
+
// pm2 start ecosystem.config.cjs --only cron # self-hosted cron fallback
|
|
1337
|
+
// pm2 save && pm2 startup # persist across reboots
|
|
1338
|
+
|
|
1339
|
+
module.exports = {
|
|
1340
|
+
apps: [
|
|
1341
|
+
{
|
|
1342
|
+
name: "frontend",
|
|
1343
|
+
cwd: "./apps/frontend",
|
|
1344
|
+
script: "node_modules/next/dist/bin/next",
|
|
1345
|
+
args: "start",
|
|
1346
|
+
env: { NODE_ENV: "production", PORT: 3001 },
|
|
1347
|
+
max_memory_restart: "1G",
|
|
1348
|
+
autorestart: true,
|
|
1349
|
+
},
|
|
1350
|
+
{
|
|
1351
|
+
name: "backend",
|
|
1352
|
+
cwd: "./apps/backend",
|
|
1353
|
+
script: "node_modules/next/dist/bin/next",
|
|
1354
|
+
args: "start",
|
|
1355
|
+
env: { NODE_ENV: "production", PORT: 3000 },
|
|
1356
|
+
max_memory_restart: "1G",
|
|
1357
|
+
autorestart: true,
|
|
1358
|
+
},
|
|
1359
|
+
{
|
|
1360
|
+
name: "proxy",
|
|
1361
|
+
script: "./apps/proxy/server.mjs",
|
|
1362
|
+
env: {
|
|
1363
|
+
NODE_ENV: "production",
|
|
1364
|
+
PORT: 3030,
|
|
1365
|
+
BACKEND: "http://127.0.0.1:3000",
|
|
1366
|
+
FRONTEND: "http://127.0.0.1:3001",
|
|
1367
|
+
${singleOrigin ? `ADMIN_PREFIX: "${adminPrefix}",` : ""}
|
|
1368
|
+
},
|
|
1369
|
+
max_memory_restart: "256M",
|
|
1370
|
+
autorestart: true,
|
|
1371
|
+
},
|
|
1372
|
+
{
|
|
1373
|
+
name: "cron",
|
|
1374
|
+
cwd: "./apps/backend",
|
|
1375
|
+
script: "scripts/dev-cron.ts",
|
|
1376
|
+
interpreter: "bun",
|
|
1377
|
+
env: { NODE_ENV: "production" },
|
|
1378
|
+
autorestart: true,
|
|
1379
|
+
},
|
|
1380
|
+
],
|
|
1381
|
+
};
|
|
1382
|
+
`;
|
|
1383
|
+
writeFileSync(resolve(targetDir, "ecosystem.config.cjs"), config);
|
|
1384
|
+
}
|
|
1385
|
+
|
|
1247
1386
|
function writeReadme(targetDir, dirName, frontendName, adminPrefix) {
|
|
1248
1387
|
const singleOrigin = !!adminPrefix;
|
|
1249
1388
|
|
|
@@ -1391,6 +1530,53 @@ Do **not** edit files inside \`apps/backend\`. Open issues / PRs in the
|
|
|
1391
1530
|
Shared dev env lives in the root \`.env.local\`. The backend reads its own
|
|
1392
1531
|
\`apps/backend/.env.local\` (already populated by the installer).
|
|
1393
1532
|
|
|
1533
|
+
## Deploy (self-hosted)
|
|
1534
|
+
|
|
1535
|
+
Standard sequence on a VPS / Docker host / bare metal:
|
|
1536
|
+
|
|
1537
|
+
\`\`\`bash
|
|
1538
|
+
pnpm install
|
|
1539
|
+
pnpm backend:upgrade # db push + replay data migrations + seed (run ONCE per release)
|
|
1540
|
+
pnpm build # builds plugins → backend → frontend
|
|
1541
|
+
pnpm start # FE :3001 + BE :3000 — or:
|
|
1542
|
+
pnpm start:rp # adds Node reverse proxy on :3030 (single origin)
|
|
1543
|
+
\`\`\`
|
|
1544
|
+
|
|
1545
|
+
\`pnpm start\` does **not** run migrations on boot — that lets you restart
|
|
1546
|
+
processes without re-running \`drizzle-kit push\` every time. Always run
|
|
1547
|
+
\`pnpm backend:upgrade\` once per release before \`pnpm start\`.
|
|
1548
|
+
|
|
1549
|
+
| Script | What it does |
|
|
1550
|
+
| ------------------- | ------------------------------------------------------------------- |
|
|
1551
|
+
| \`pnpm build\` | Full pipeline: cms-plugins → backend plugins → backend → frontend |
|
|
1552
|
+
| \`pnpm start\` | FE + BE (parallel \`next start\`) |
|
|
1553
|
+
| \`pnpm start:rp\` | FE + BE + reverse proxy on :3030 |
|
|
1554
|
+
| \`pnpm start:proxy\` | Reverse proxy alone (already running FE/BE separately) |
|
|
1555
|
+
| \`pnpm start:cron\` | Self-hosted cron fallback — only if not using Vercel Cron / k8s |
|
|
1556
|
+
|
|
1557
|
+
### PM2 (recommended for VPS)
|
|
1558
|
+
|
|
1559
|
+
The installer scaffolds \`ecosystem.config.cjs\`. To supervise everything:
|
|
1560
|
+
|
|
1561
|
+
\`\`\`bash
|
|
1562
|
+
pm2 start ecosystem.config.cjs # FE + BE + proxy + cron (all four)
|
|
1563
|
+
pm2 start ecosystem.config.cjs --only frontend,backend # just the two apps
|
|
1564
|
+
pm2 save && pm2 startup # persist across reboots
|
|
1565
|
+
pm2 logs # tail all processes
|
|
1566
|
+
pm2 reload all # zero-downtime restart
|
|
1567
|
+
\`\`\`
|
|
1568
|
+
|
|
1569
|
+
The proxy and cron processes can be omitted with \`--only\` if you front the
|
|
1570
|
+
apps with nginx/Caddy or use platform cron (system cron, k8s CronJob).
|
|
1571
|
+
|
|
1572
|
+
### Docker / k8s
|
|
1573
|
+
|
|
1574
|
+
For containerized deploys:
|
|
1575
|
+
- Bake the build into the image (\`pnpm install && pnpm build\`)
|
|
1576
|
+
- Set entrypoint to \`pnpm start\` or \`pnpm start:rp\`
|
|
1577
|
+
- Run \`pnpm backend:upgrade\` as a separate init container / Job before app pods start
|
|
1578
|
+
- Use platform-native cron (k8s CronJob hitting \`/api/cron\` with \`CRON_SECRET\`) instead of \`pnpm start:cron\`
|
|
1579
|
+
|
|
1394
1580
|
## Deploy on Vercel
|
|
1395
1581
|
|
|
1396
1582
|
Two Vercel projects, one repo. Each project picks up its own Root Directory.
|
|
@@ -1507,10 +1693,12 @@ async function main() {
|
|
|
1507
1693
|
writeReadme(targetDir, dirName, frontendName, adminPrefix);
|
|
1508
1694
|
if (adminPrefix) writeNginxSample(targetDir, adminPrefix);
|
|
1509
1695
|
writeProxyApp(targetDir, dirName, adminPrefix);
|
|
1696
|
+
writeCheckEnvScript(targetDir);
|
|
1697
|
+
writePm2Config(targetDir, dirName, adminPrefix);
|
|
1510
1698
|
success(
|
|
1511
1699
|
`package.json, pnpm-workspace.yaml, .gitignore, .env.local, README.md${
|
|
1512
1700
|
adminPrefix ? ", nginx.conf.sample" : ""
|
|
1513
|
-
}, apps/proxy`,
|
|
1701
|
+
}, apps/proxy, ecosystem.config.cjs, scripts/check-env.mjs`,
|
|
1514
1702
|
);
|
|
1515
1703
|
|
|
1516
1704
|
// ── Step 4: git init ──
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-apollo-monorepo",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"description": "Scaffold a monorepo with a frontend app and Apollo CMS as a git submodule backend (single-origin via Next.js rewrites + assetPrefix)",
|
|
5
5
|
"bin": {
|
|
6
6
|
"create-apollo-monorepo": "index.mjs"
|