create-apollo-monorepo 0.9.1 → 0.9.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/README.md +7 -7
- package/index.mjs +196 -39
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -6,15 +6,15 @@ mounted as a **git submodule** backend (read-only — pull updates only).
|
|
|
6
6
|
## Usage
|
|
7
7
|
|
|
8
8
|
```bash
|
|
9
|
-
npx create-apollo-monorepo
|
|
9
|
+
npx create-apollo-monorepo my-site
|
|
10
10
|
```
|
|
11
11
|
|
|
12
12
|
With flags:
|
|
13
13
|
|
|
14
14
|
```bash
|
|
15
|
-
npx create-apollo-monorepo
|
|
16
|
-
--frontend-name "@
|
|
17
|
-
--db "postgresql://user:pass@localhost:5432/
|
|
15
|
+
npx create-apollo-monorepo my-site \
|
|
16
|
+
--frontend-name "@my-site/frontend" \
|
|
17
|
+
--db "postgresql://user:pass@localhost:5432/my-site" \
|
|
18
18
|
--url "http://localhost:3000" \
|
|
19
19
|
--locale th
|
|
20
20
|
```
|
|
@@ -22,9 +22,9 @@ npx create-apollo-monorepo thamc-new \
|
|
|
22
22
|
## Result
|
|
23
23
|
|
|
24
24
|
```
|
|
25
|
-
|
|
25
|
+
my-site/
|
|
26
26
|
├── apps/
|
|
27
|
-
│ ├── frontend/ ← @
|
|
27
|
+
│ ├── frontend/ ← @my-site/frontend (Next.js skeleton)
|
|
28
28
|
│ └── backend/ ← git submodule → apollo-cms
|
|
29
29
|
├── package.json ← root workspace
|
|
30
30
|
├── pnpm-workspace.yaml
|
|
@@ -89,7 +89,7 @@ subdomains (e.g. `cms.example.com` + `example.com`).
|
|
|
89
89
|
## After install
|
|
90
90
|
|
|
91
91
|
```bash
|
|
92
|
-
cd
|
|
92
|
+
cd my-site
|
|
93
93
|
pnpm backend:setup # push schema + seed apollo-cms
|
|
94
94
|
pnpm dev # frontend :3001 + backend :3000 in parallel
|
|
95
95
|
```
|
package/index.mjs
CHANGED
|
@@ -29,6 +29,13 @@ const FRONTEND_PATH = "apps/frontend";
|
|
|
29
29
|
const PROXY_PATH = "apps/proxy";
|
|
30
30
|
const MIN_NODE_MAJOR = 20;
|
|
31
31
|
|
|
32
|
+
// Default dev ports. Kept in sync with scripts/with-env.mjs and the proxy
|
|
33
|
+
// template. .env.local is the runtime source of truth — these are the
|
|
34
|
+
// installer-side fallbacks for computing initial URL defaults.
|
|
35
|
+
const DEFAULT_PROXY_PORT = 3030;
|
|
36
|
+
const DEFAULT_FRONTEND_PORT = 3001;
|
|
37
|
+
const DEFAULT_BACKEND_PORT = 3002;
|
|
38
|
+
|
|
32
39
|
const COLORS = {
|
|
33
40
|
reset: "\x1b[0m",
|
|
34
41
|
bold: "\x1b[1m",
|
|
@@ -46,9 +53,9 @@ ${COLORS.bold}Usage:${COLORS.reset}
|
|
|
46
53
|
npx create-apollo-monorepo <directory> [flags]
|
|
47
54
|
|
|
48
55
|
${COLORS.bold}Examples:${COLORS.reset}
|
|
49
|
-
npx create-apollo-monorepo
|
|
50
|
-
npx create-apollo-monorepo
|
|
51
|
-
npx create-apollo-monorepo
|
|
56
|
+
npx create-apollo-monorepo my-site
|
|
57
|
+
npx create-apollo-monorepo my-site --frontend-name "@my-site/frontend"
|
|
58
|
+
npx create-apollo-monorepo my-site --backend-branch develop --skip-install
|
|
52
59
|
|
|
53
60
|
${COLORS.bold}Flags:${COLORS.reset}
|
|
54
61
|
--frontend-name <name> Frontend package name (default: "@<dir>/frontend")
|
|
@@ -283,7 +290,14 @@ async function gatherEnv(flags, { singleOrigin }) {
|
|
|
283
290
|
let dbUrl = flags.db;
|
|
284
291
|
// In single-origin mode the frontend is the public entry → default :3001.
|
|
285
292
|
// In separate-origins mode the backend is the public CMS URL → default :3000.
|
|
286
|
-
|
|
293
|
+
// Public origin = frontend in single-origin mode (frontend rewrites /admin/*,
|
|
294
|
+
// /api/*, /uploads/* to the backend), backend in separate-origins mode.
|
|
295
|
+
// For `pnpm dev:rp` switch this to http://localhost:${PROXY_PORT} in .env.local.
|
|
296
|
+
// Derived from the PORT constants so changing a default updates both the
|
|
297
|
+
// .env.local seed and the prompt's suggested value.
|
|
298
|
+
const defaultSiteUrl = singleOrigin
|
|
299
|
+
? `http://localhost:${DEFAULT_FRONTEND_PORT}`
|
|
300
|
+
: `http://localhost:${DEFAULT_BACKEND_PORT}`;
|
|
287
301
|
let siteUrl = flags.url ?? defaultSiteUrl;
|
|
288
302
|
let locale = flags.locale ?? "en";
|
|
289
303
|
|
|
@@ -355,16 +369,17 @@ function writeRootPackageJson(targetDir, dirName) {
|
|
|
355
369
|
// rebuild and the backend's plugin loader picks up the fresh
|
|
356
370
|
// dist/server.mjs on next request.
|
|
357
371
|
dev:
|
|
358
|
-
"pnpm predev:setup && concurrently -k -n FE,BE,PL -c blue,magenta,yellow \"pnpm
|
|
372
|
+
"pnpm predev:setup && concurrently -k -n FE,BE,PL -c blue,magenta,yellow \"pnpm dev:frontend\" \"pnpm dev:backend\" \"pnpm cms-plugins:dev\"",
|
|
359
373
|
// Optional single-origin dev: same as `pnpm dev` but adds a Node.js
|
|
360
|
-
// reverse proxy on :3030 that fronts FE :3001
|
|
361
|
-
//
|
|
374
|
+
// reverse proxy on :3030 (PROXY_PORT) that fronts FE :3001 (FRONTEND_PORT)
|
|
375
|
+
// + BE :3002 (BACKEND_PORT). Ports come from .env.local; override there.
|
|
362
376
|
"dev:rp":
|
|
363
|
-
"pnpm predev:setup && concurrently -k -n FE,BE,PL,PX -c blue,magenta,yellow,cyan \"pnpm
|
|
364
|
-
"dev:proxy": "node apps/proxy/server.mjs",
|
|
365
|
-
"dev:frontend":
|
|
377
|
+
"pnpm predev:setup && concurrently -k -n FE,BE,PL,PX -c blue,magenta,yellow,cyan \"pnpm dev:frontend\" \"pnpm dev:backend\" \"pnpm cms-plugins:dev\" \"pnpm dev:proxy\"",
|
|
378
|
+
"dev:proxy": "node scripts/with-env.mjs node apps/proxy/server.mjs",
|
|
379
|
+
"dev:frontend":
|
|
380
|
+
"node scripts/with-env.mjs --port=FRONTEND_PORT pnpm --filter ./apps/frontend exec next dev",
|
|
366
381
|
"dev:backend":
|
|
367
|
-
"pnpm predev:setup && pnpm --filter ./apps/backend exec next dev",
|
|
382
|
+
"pnpm predev:setup && node scripts/with-env.mjs --port=BACKEND_PORT pnpm --filter ./apps/backend exec next dev",
|
|
368
383
|
"dev:cron": "pnpm --filter ./apps/backend exec bun scripts/dev-cron.ts",
|
|
369
384
|
// Build pipeline: cms-plugins → apollo-cms's own plugins → backend → frontend.
|
|
370
385
|
// `prebuild` runs first (npm/pnpm convention) and fails fast if the
|
|
@@ -375,17 +390,19 @@ function writeRootPackageJson(targetDir, dirName) {
|
|
|
375
390
|
"build:backend":
|
|
376
391
|
"pnpm cms-plugins:build && pnpm --filter ./apps/backend exec bun run plugins:build && pnpm --filter ./apps/backend build",
|
|
377
392
|
"build:frontend": "pnpm --filter ./apps/frontend build",
|
|
378
|
-
// Production start. Runs FE :
|
|
379
|
-
//
|
|
380
|
-
// NOT run migrations on boot (zero-downtime restart safety).
|
|
393
|
+
// Production start. Runs FE :FRONTEND_PORT + BE :BACKEND_PORT (defaults
|
|
394
|
+
// 3001 + 3002 from .env.local). Run \`pnpm backend:upgrade\` BEFORE this —
|
|
395
|
+
// start does NOT run migrations on boot (zero-downtime restart safety).
|
|
381
396
|
start:
|
|
382
|
-
"concurrently -k -n FE,BE -c blue,magenta \"pnpm
|
|
383
|
-
// Single-origin production: FE + BE + Node reverse proxy on :
|
|
397
|
+
"concurrently -k -n FE,BE -c blue,magenta \"pnpm start:frontend\" \"pnpm start:backend\"",
|
|
398
|
+
// Single-origin production: FE + BE + Node reverse proxy on :PROXY_PORT.
|
|
384
399
|
"start:rp":
|
|
385
|
-
"concurrently -k -n FE,BE,PX -c blue,magenta,cyan \"pnpm
|
|
386
|
-
"start:proxy": "NODE_ENV=production node apps/proxy/server.mjs",
|
|
387
|
-
"start:frontend":
|
|
388
|
-
|
|
400
|
+
"concurrently -k -n FE,BE,PX -c blue,magenta,cyan \"pnpm start:frontend\" \"pnpm start:backend\" \"pnpm start:proxy\"",
|
|
401
|
+
"start:proxy": "NODE_ENV=production node scripts/with-env.mjs node apps/proxy/server.mjs",
|
|
402
|
+
"start:frontend":
|
|
403
|
+
"node scripts/with-env.mjs --port=FRONTEND_PORT pnpm --filter ./apps/frontend exec next start",
|
|
404
|
+
"start:backend":
|
|
405
|
+
"node scripts/with-env.mjs --port=BACKEND_PORT pnpm --filter ./apps/backend exec next start",
|
|
389
406
|
// Self-hosted scheduler fallback. Vercel deploys use Vercel Cron via
|
|
390
407
|
// apps/backend/vercel.json — skip this on Vercel. For Docker / VPS
|
|
391
408
|
// you can either run \`pnpm start:cron\` alongside \`pnpm start\` (PM2
|
|
@@ -545,10 +562,43 @@ function writeProxyApp(targetDir, dirName, adminPrefix) {
|
|
|
545
562
|
|
|
546
563
|
import http from "node:http";
|
|
547
564
|
import net from "node:net";
|
|
565
|
+
import { readFileSync, existsSync } from "node:fs";
|
|
566
|
+
import { resolve, dirname } from "node:path";
|
|
567
|
+
import { fileURLToPath } from "node:url";
|
|
548
568
|
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
569
|
+
// Load root .env.local so the proxy honors PROXY_PORT/FRONTEND_PORT/BACKEND_PORT
|
|
570
|
+
// alongside the frontend & backend. Process env still wins (12-factor friendly).
|
|
571
|
+
(function loadRootEnv() {
|
|
572
|
+
try {
|
|
573
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
574
|
+
const envPath = resolve(here, "../../.env.local");
|
|
575
|
+
if (!existsSync(envPath)) return;
|
|
576
|
+
for (const raw of readFileSync(envPath, "utf8").split(/\\r?\\n/)) {
|
|
577
|
+
const line = raw.trim();
|
|
578
|
+
if (!line || line.startsWith("#")) continue;
|
|
579
|
+
const eq = line.indexOf("=");
|
|
580
|
+
if (eq < 0) continue;
|
|
581
|
+
const key = line.slice(0, eq).trim();
|
|
582
|
+
if (!/^[A-Z_][A-Z0-9_]*$/i.test(key)) continue;
|
|
583
|
+
if (process.env[key] !== undefined) continue;
|
|
584
|
+
let val = line.slice(eq + 1).trim();
|
|
585
|
+
if ((val.startsWith('"') && val.endsWith('"')) || (val.startsWith("'") && val.endsWith("'"))) {
|
|
586
|
+
val = val.slice(1, -1);
|
|
587
|
+
}
|
|
588
|
+
process.env[key] = val;
|
|
589
|
+
}
|
|
590
|
+
} catch {
|
|
591
|
+
// Best-effort. The proxy still runs on its hardcoded fallbacks.
|
|
592
|
+
}
|
|
593
|
+
})();
|
|
594
|
+
|
|
595
|
+
// Backward-compatible: PROXY_PORT/FRONTEND_PORT/BACKEND_PORT are the new names;
|
|
596
|
+
// PORT/FRONTEND/BACKEND are still honored if explicitly set in process.env.
|
|
597
|
+
const PORT = Number(process.env.PROXY_PORT ?? process.env.PORT ?? 3030);
|
|
598
|
+
const BACKEND_HOST = process.env.BACKEND ?? \`http://127.0.0.1:\${process.env.BACKEND_PORT ?? 3002}\`;
|
|
599
|
+
const FRONTEND_HOST = process.env.FRONTEND ?? \`http://127.0.0.1:\${process.env.FRONTEND_PORT ?? 3001}\`;
|
|
600
|
+
const BACKEND = parseTarget(BACKEND_HOST);
|
|
601
|
+
const FRONTEND = parseTarget(FRONTEND_HOST);
|
|
552
602
|
|
|
553
603
|
const ADMIN_PREFIX = process.env.ADMIN_PREFIX ?? "${prefix}";
|
|
554
604
|
// Pipe-separated list of /api/<segment> paths that route to the backend.
|
|
@@ -830,6 +880,10 @@ The Node proxy is intended for dev and small self-hosted deploys.
|
|
|
830
880
|
}
|
|
831
881
|
|
|
832
882
|
function writeRootGitignore(targetDir) {
|
|
883
|
+
// NOTE: .env.local is intentionally NOT ignored — it holds shared dev port
|
|
884
|
+
// defaults (PROXY_PORT/FRONTEND_PORT/BACKEND_PORT) and other non-secret
|
|
885
|
+
// workspace knobs that should travel with the repo. Keep real secrets in
|
|
886
|
+
// .env (ignored) or apps/backend/.env.local (ignored by the submodule).
|
|
833
887
|
const ignore = [
|
|
834
888
|
"node_modules",
|
|
835
889
|
".pnpm-store",
|
|
@@ -838,8 +892,6 @@ function writeRootGitignore(targetDir) {
|
|
|
838
892
|
"dist",
|
|
839
893
|
"build",
|
|
840
894
|
".env",
|
|
841
|
-
".env.local",
|
|
842
|
-
".env*.local",
|
|
843
895
|
"*.log",
|
|
844
896
|
".DS_Store",
|
|
845
897
|
"",
|
|
@@ -851,12 +903,12 @@ function writeRootEnv(targetDir, { dbUrl, siteUrl, locale, authSecret, cronSecre
|
|
|
851
903
|
const header = adminPrefix
|
|
852
904
|
? [
|
|
853
905
|
"# Single-origin monorepo dev env",
|
|
854
|
-
|
|
855
|
-
|
|
906
|
+
`# Public origin = frontend (apps/frontend on :${DEFAULT_FRONTEND_PORT}). Frontend rewrites /admin/*,`,
|
|
907
|
+
`# /api/*, /uploads/*, and the asset prefix to the backend (apps/backend on :${DEFAULT_BACKEND_PORT}).`,
|
|
856
908
|
]
|
|
857
909
|
: [
|
|
858
910
|
"# Separate-origins monorepo dev env",
|
|
859
|
-
|
|
911
|
+
`# Backend runs at http://localhost:${DEFAULT_BACKEND_PORT}, frontend at http://localhost:${DEFAULT_FRONTEND_PORT}.`,
|
|
860
912
|
"# To switch to single-origin: set APOLLO_ASSET_PREFIX (default: /admin) and",
|
|
861
913
|
"# BACKEND_INTERNAL_URL, then add rewrites() to apps/frontend/next.config.ts.",
|
|
862
914
|
];
|
|
@@ -864,10 +916,25 @@ function writeRootEnv(targetDir, { dbUrl, siteUrl, locale, authSecret, cronSecre
|
|
|
864
916
|
const lines = [
|
|
865
917
|
...header,
|
|
866
918
|
"",
|
|
919
|
+
"# ── Dev server ports ─────────────────────────────────────────────",
|
|
920
|
+
"# Single source of truth for local ports. Consumed by:",
|
|
921
|
+
"# • apps/proxy/server.mjs (PROXY_PORT, FRONTEND_PORT, BACKEND_PORT)",
|
|
922
|
+
"# • scripts/with-env.mjs (maps FRONTEND_PORT/BACKEND_PORT → PORT",
|
|
923
|
+
"# when launching apps/frontend & apps/backend,",
|
|
924
|
+
"# and derives NEXT_PUBLIC_SITE_URL /",
|
|
925
|
+
"# BACKEND_INTERNAL_URL when those are unset)",
|
|
926
|
+
"# Override any of these; the proxy/frontend/backend will follow.",
|
|
927
|
+
`PROXY_PORT=${DEFAULT_PROXY_PORT}`,
|
|
928
|
+
`FRONTEND_PORT=${DEFAULT_FRONTEND_PORT}`,
|
|
929
|
+
`BACKEND_PORT=${DEFAULT_BACKEND_PORT}`,
|
|
930
|
+
"",
|
|
867
931
|
"# ── Shared (consumed by both apps) ───────────────────────────────",
|
|
868
932
|
`DATABASE_URL=${dbUrl}`,
|
|
869
933
|
`APOLLO_SECRET=${authSecret}`,
|
|
870
934
|
`CRON_SECRET=${cronSecret}`,
|
|
935
|
+
"# Public origin. In dev, leave blank to auto-derive from FRONTEND_PORT",
|
|
936
|
+
"# (single-origin) or BACKEND_PORT (separate-origins) via scripts/with-env.mjs.",
|
|
937
|
+
"# In prod set to the real domain (e.g. https://cms.example.com).",
|
|
871
938
|
`NEXT_PUBLIC_SITE_URL=${siteUrl}`,
|
|
872
939
|
`NEXT_PUBLIC_DEFAULT_LOCALE=${locale}`,
|
|
873
940
|
"",
|
|
@@ -884,6 +951,9 @@ function writeRootEnv(targetDir, { dbUrl, siteUrl, locale, authSecret, cronSecre
|
|
|
884
951
|
`APOLLO_EXTRA_PLUGINS_DIR=../cms-plugins`,
|
|
885
952
|
"",
|
|
886
953
|
"# ── Frontend (apps/frontend) ─────────────────────────────────────",
|
|
954
|
+
"# Where the frontend's rewrites point internally. In dev, leave blank",
|
|
955
|
+
"# to auto-derive http://127.0.0.1:${BACKEND_PORT} via scripts/with-env.mjs.",
|
|
956
|
+
"# In prod set to a private hostname (e.g. http://backend.internal:3000).",
|
|
887
957
|
`BACKEND_INTERNAL_URL=${backendInternalUrl}`,
|
|
888
958
|
"",
|
|
889
959
|
);
|
|
@@ -913,9 +983,12 @@ function writeFrontendApp(targetDir, frontendName, siteUrl, adminPrefix, backend
|
|
|
913
983
|
version: "0.0.0",
|
|
914
984
|
private: true,
|
|
915
985
|
scripts: {
|
|
916
|
-
|
|
986
|
+
// No -p flag: Next.js reads PORT from env. The repo-root scripts use
|
|
987
|
+
// scripts/with-env.mjs --port=FRONTEND_PORT to inject the right value
|
|
988
|
+
// from .env.local (default 3001).
|
|
989
|
+
dev: "next dev",
|
|
917
990
|
build: "next build",
|
|
918
|
-
start: "next start
|
|
991
|
+
start: "next start",
|
|
919
992
|
lint: "next lint",
|
|
920
993
|
typecheck: "tsc --noEmit",
|
|
921
994
|
},
|
|
@@ -1045,7 +1118,8 @@ export default config;
|
|
|
1045
1118
|
|
|
1046
1119
|
writeFileSync(
|
|
1047
1120
|
resolve(dir, ".gitignore"),
|
|
1048
|
-
|
|
1121
|
+
// .env.local is intentionally committed — it only contains dev port hints.
|
|
1122
|
+
["node_modules", ".next", "dist", ""].join("\n"),
|
|
1049
1123
|
);
|
|
1050
1124
|
|
|
1051
1125
|
// Vercel project config for the frontend. Skip cron + region pinning is
|
|
@@ -1273,6 +1347,88 @@ console.log(\`Run: pnpm install && pnpm cms-plugins:build && pnpm dev:backend\`)
|
|
|
1273
1347
|
writeFileSync(resolve(scriptsDir, "new-cms-plugin.mjs"), script);
|
|
1274
1348
|
}
|
|
1275
1349
|
|
|
1350
|
+
// Small launcher that loads root .env.local, applies port defaults
|
|
1351
|
+
// (PROXY_PORT=3030 / FRONTEND_PORT=3001 / BACKEND_PORT=3002), optionally maps
|
|
1352
|
+
// one of them onto PORT (Next.js reads PORT), and execs the rest of argv.
|
|
1353
|
+
//
|
|
1354
|
+
// Usage:
|
|
1355
|
+
// node scripts/with-env.mjs [--port=VAR_NAME] <command> [args...]
|
|
1356
|
+
function writeWithEnvScript(targetDir) {
|
|
1357
|
+
const scriptsDir = resolve(targetDir, "scripts");
|
|
1358
|
+
mkdirSync(scriptsDir, { recursive: true });
|
|
1359
|
+
const script = `#!/usr/bin/env node
|
|
1360
|
+
// Loads <repo-root>/.env.local so frontend / backend / proxy all share one
|
|
1361
|
+
// source of truth for ports. Process env still wins.
|
|
1362
|
+
//
|
|
1363
|
+
// Usage:
|
|
1364
|
+
// node scripts/with-env.mjs [--port=VAR_NAME] <command> [args...]
|
|
1365
|
+
// --port=FRONTEND_PORT copies process.env.FRONTEND_PORT onto PORT before exec,
|
|
1366
|
+
// so \`next dev\` and \`next start\` pick up the right port without --port flags.
|
|
1367
|
+
|
|
1368
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
1369
|
+
import { spawn } from "node:child_process";
|
|
1370
|
+
import { dirname, resolve } from "node:path";
|
|
1371
|
+
import { fileURLToPath } from "node:url";
|
|
1372
|
+
|
|
1373
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
1374
|
+
const envPath = resolve(here, "../.env.local");
|
|
1375
|
+
|
|
1376
|
+
if (existsSync(envPath)) {
|
|
1377
|
+
for (const raw of readFileSync(envPath, "utf8").split(/\\r?\\n/)) {
|
|
1378
|
+
const line = raw.trim();
|
|
1379
|
+
if (!line || line.startsWith("#")) continue;
|
|
1380
|
+
const eq = line.indexOf("=");
|
|
1381
|
+
if (eq < 0) continue;
|
|
1382
|
+
const k = line.slice(0, eq).trim();
|
|
1383
|
+
if (!/^[A-Z_][A-Z0-9_]*$/i.test(k)) continue;
|
|
1384
|
+
if (process.env[k] !== undefined) continue;
|
|
1385
|
+
let v = line.slice(eq + 1).trim();
|
|
1386
|
+
if ((v.startsWith('"') && v.endsWith('"')) || (v.startsWith("'") && v.endsWith("'"))) {
|
|
1387
|
+
v = v.slice(1, -1);
|
|
1388
|
+
}
|
|
1389
|
+
process.env[k] = v;
|
|
1390
|
+
}
|
|
1391
|
+
}
|
|
1392
|
+
|
|
1393
|
+
// Fallbacks — keep in sync with apps/proxy/server.mjs and PM2 config.
|
|
1394
|
+
process.env.PROXY_PORT ||= "3030";
|
|
1395
|
+
process.env.FRONTEND_PORT ||= "3001";
|
|
1396
|
+
process.env.BACKEND_PORT ||= "3002";
|
|
1397
|
+
|
|
1398
|
+
// Derive URL vars from PORTs when unset. Dev-friendly: edit a port and
|
|
1399
|
+
// everything follows. Prod-friendly: set these explicitly to real hostnames
|
|
1400
|
+
// (e.g. https://cms.example.com, http://backend.internal:3000) and the
|
|
1401
|
+
// derivation is bypassed.
|
|
1402
|
+
//
|
|
1403
|
+
// NEXT_PUBLIC_SITE_URL is the public origin — defaults to the frontend port
|
|
1404
|
+
// (frontend rewrites /admin, /api, /uploads to the backend). For \`pnpm dev:rp\`
|
|
1405
|
+
// set it to http://localhost:\${PROXY_PORT} so Better Auth / OAuth callbacks
|
|
1406
|
+
// land on the unified origin.
|
|
1407
|
+
process.env.NEXT_PUBLIC_SITE_URL ||= \`http://localhost:\${process.env.FRONTEND_PORT}\`;
|
|
1408
|
+
process.env.BACKEND_INTERNAL_URL ||= \`http://127.0.0.1:\${process.env.BACKEND_PORT}\`;
|
|
1409
|
+
|
|
1410
|
+
const args = process.argv.slice(2);
|
|
1411
|
+
if (args[0]?.startsWith("--port=")) {
|
|
1412
|
+
const name = args.shift().slice(7);
|
|
1413
|
+
if (process.env[name]) process.env.PORT = process.env[name];
|
|
1414
|
+
}
|
|
1415
|
+
|
|
1416
|
+
if (!args.length) {
|
|
1417
|
+
console.error("Usage: node scripts/with-env.mjs [--port=VAR] <command> [args...]");
|
|
1418
|
+
process.exit(2);
|
|
1419
|
+
}
|
|
1420
|
+
|
|
1421
|
+
const [cmd, ...rest] = args;
|
|
1422
|
+
const child = spawn(cmd, rest, {
|
|
1423
|
+
stdio: "inherit",
|
|
1424
|
+
env: process.env,
|
|
1425
|
+
shell: process.platform === "win32",
|
|
1426
|
+
});
|
|
1427
|
+
child.on("exit", (code, sig) => process.exit(sig ? 1 : code ?? 0));
|
|
1428
|
+
`;
|
|
1429
|
+
writeFileSync(resolve(scriptsDir, "with-env.mjs"), script);
|
|
1430
|
+
}
|
|
1431
|
+
|
|
1276
1432
|
// Pre-build env check. Fails the build before \`next build\` runs if backend
|
|
1277
1433
|
// required vars are missing, so users don't sit through a 30s build only to
|
|
1278
1434
|
// hit a runtime error on first request.
|
|
@@ -1349,7 +1505,7 @@ module.exports = {
|
|
|
1349
1505
|
cwd: "./apps/frontend",
|
|
1350
1506
|
script: "node_modules/next/dist/bin/next",
|
|
1351
1507
|
args: "start",
|
|
1352
|
-
env: { NODE_ENV: "production", PORT: 3001 },
|
|
1508
|
+
env: { NODE_ENV: "production", PORT: Number(process.env.FRONTEND_PORT) || 3001 },
|
|
1353
1509
|
max_memory_restart: "1G",
|
|
1354
1510
|
autorestart: true,
|
|
1355
1511
|
},
|
|
@@ -1358,7 +1514,7 @@ module.exports = {
|
|
|
1358
1514
|
cwd: "./apps/backend",
|
|
1359
1515
|
script: "node_modules/next/dist/bin/next",
|
|
1360
1516
|
args: "start",
|
|
1361
|
-
env: { NODE_ENV: "production", PORT:
|
|
1517
|
+
env: { NODE_ENV: "production", PORT: Number(process.env.BACKEND_PORT) || 3002 },
|
|
1362
1518
|
max_memory_restart: "1G",
|
|
1363
1519
|
autorestart: true,
|
|
1364
1520
|
},
|
|
@@ -1367,9 +1523,9 @@ module.exports = {
|
|
|
1367
1523
|
script: "./apps/proxy/server.mjs",
|
|
1368
1524
|
env: {
|
|
1369
1525
|
NODE_ENV: "production",
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1526
|
+
PROXY_PORT: Number(process.env.PROXY_PORT) || 3030,
|
|
1527
|
+
FRONTEND_PORT: Number(process.env.FRONTEND_PORT) || 3001,
|
|
1528
|
+
BACKEND_PORT: Number(process.env.BACKEND_PORT) || 3002,
|
|
1373
1529
|
${singleOrigin ? `ADMIN_PREFIX: "${adminPrefix}",` : ""}
|
|
1374
1530
|
},
|
|
1375
1531
|
max_memory_restart: "256M",
|
|
@@ -1758,7 +1914,7 @@ async function main() {
|
|
|
1758
1914
|
|
|
1759
1915
|
if (!flags.directory) {
|
|
1760
1916
|
log(HELP_TEXT);
|
|
1761
|
-
fatal("Please provide a directory name.\n Example: npx create-apollo-monorepo
|
|
1917
|
+
fatal("Please provide a directory name.\n Example: npx create-apollo-monorepo my-site");
|
|
1762
1918
|
}
|
|
1763
1919
|
|
|
1764
1920
|
log(`\n${COLORS.bold}${COLORS.cyan} Apollo CMS Monorepo Installer${COLORS.reset}\n`);
|
|
@@ -1777,7 +1933,7 @@ async function main() {
|
|
|
1777
1933
|
// Required by apollo-cms's /api/cron route and scripts/dev-cron.ts in dev.
|
|
1778
1934
|
// Without it the dev cron loop hits 403 "CRON_SECRET not configured".
|
|
1779
1935
|
const cronSecret = randomBytes(24).toString("hex");
|
|
1780
|
-
const backendInternalUrl =
|
|
1936
|
+
const backendInternalUrl = `http://localhost:${DEFAULT_BACKEND_PORT}`;
|
|
1781
1937
|
success(`Frontend pkg name: ${frontendName}`);
|
|
1782
1938
|
success(`Admin prefix: ${adminPrefix || "(disabled — separate origins)"}`);
|
|
1783
1939
|
|
|
@@ -1794,11 +1950,12 @@ async function main() {
|
|
|
1794
1950
|
if (adminPrefix) writeNginxSample(targetDir, adminPrefix);
|
|
1795
1951
|
writeProxyApp(targetDir, dirName, adminPrefix);
|
|
1796
1952
|
writeCheckEnvScript(targetDir);
|
|
1953
|
+
writeWithEnvScript(targetDir);
|
|
1797
1954
|
writePm2Config(targetDir, dirName, adminPrefix);
|
|
1798
1955
|
success(
|
|
1799
1956
|
`package.json, pnpm-workspace.yaml, .gitignore, .env.local, README.md, CLAUDE.md${
|
|
1800
1957
|
adminPrefix ? ", nginx.conf.sample" : ""
|
|
1801
|
-
}, apps/proxy, ecosystem.config.cjs, scripts/check-env.mjs`,
|
|
1958
|
+
}, apps/proxy, ecosystem.config.cjs, scripts/check-env.mjs, scripts/with-env.mjs`,
|
|
1802
1959
|
);
|
|
1803
1960
|
|
|
1804
1961
|
// ── Step 4: git init ──
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-apollo-monorepo",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.2",
|
|
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"
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
],
|
|
22
22
|
"repository": {
|
|
23
23
|
"type": "git",
|
|
24
|
-
"url": "https://github.com/5Lab-Group-Co-Ltd/apollo-cms.git",
|
|
24
|
+
"url": "git+https://github.com/5Lab-Group-Co-Ltd/apollo-cms.git",
|
|
25
25
|
"directory": "installer-monorepo"
|
|
26
26
|
},
|
|
27
27
|
"license": "MIT"
|