create-apollo-monorepo 0.9.3 → 0.9.5

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.
Files changed (2) hide show
  1. package/index.mjs +76 -114
  2. package/package.json +1 -1
package/index.mjs CHANGED
@@ -62,7 +62,9 @@ ${COLORS.bold}Flags:${COLORS.reset}
62
62
  --backend-url <url> Backend git URL (default: ${BACKEND_REPO_URL})
63
63
  --backend-branch <name> Backend branch to track (default: ${BACKEND_BRANCH})
64
64
  -d, --db <url> DATABASE_URL for backend
65
- -u, --url <url> NEXT_PUBLIC_SITE_URL (default: http://localhost:3001 single-origin / 3000 separate)
65
+ -u, --url <url> NEXT_PUBLIC_SITE_URL (optional left blank so the CMS
66
+ respects the incoming request origin. Set this only when
67
+ you need a fixed origin for background jobs / cron / emails.)
66
68
  -l, --locale <code> NEXT_PUBLIC_DEFAULT_LOCALE (default: en)
67
69
  --admin-prefix <path> Backend prefix in single-origin mode — sets Next.js
68
70
  assetPrefix on the backend AND the path the frontend
@@ -124,6 +126,26 @@ function commandExists(cmd) {
124
126
 
125
127
  // ─── Arg Parsing ─────────────────────────────────────────────────────────────
126
128
 
129
+ // Flag dispatch table. Each entry: aliases → either { key } (takes a value)
130
+ // or { key, value } (boolean flag). `--asset-prefix` is a legacy alias.
131
+ const FLAG_TABLE = {
132
+ "-h": { key: "help", value: true },
133
+ "--help": { key: "help", value: true },
134
+ "--skip-install": { key: "skipInstall", value: true },
135
+ "--skip-submodule": { key: "skipSubmodule", value: true },
136
+ "--frontend-name": { key: "frontendName" },
137
+ "--backend-url": { key: "backendUrl" },
138
+ "--backend-branch": { key: "backendBranch" },
139
+ "-d": { key: "db" },
140
+ "--db": { key: "db" },
141
+ "-u": { key: "url" },
142
+ "--url": { key: "url" },
143
+ "-l": { key: "locale" },
144
+ "--locale": { key: "locale" },
145
+ "--admin-prefix": { key: "adminPrefix" },
146
+ "--asset-prefix": { key: "adminPrefix" },
147
+ };
148
+
127
149
  function parseArgs(argv) {
128
150
  const args = argv.slice(2);
129
151
  const flags = {
@@ -140,55 +162,16 @@ function parseArgs(argv) {
140
162
  help: false,
141
163
  };
142
164
 
143
- let i = 0;
144
- while (i < args.length) {
165
+ for (let i = 0; i < args.length; i++) {
145
166
  const arg = args[i];
146
- switch (arg) {
147
- case "-h":
148
- case "--help":
149
- flags.help = true;
150
- break;
151
- case "--frontend-name":
152
- flags.frontendName = args[++i];
153
- break;
154
- case "--backend-url":
155
- flags.backendUrl = args[++i];
156
- break;
157
- case "--backend-branch":
158
- flags.backendBranch = args[++i];
159
- break;
160
- case "-d":
161
- case "--db":
162
- flags.db = args[++i];
163
- break;
164
- case "-u":
165
- case "--url":
166
- flags.url = args[++i];
167
- break;
168
- case "-l":
169
- case "--locale":
170
- flags.locale = args[++i];
171
- break;
172
- case "--admin-prefix":
173
- case "--asset-prefix": // legacy alias
174
- flags.adminPrefix = args[++i];
175
- break;
176
- case "--skip-install":
177
- flags.skipInstall = true;
178
- break;
179
- case "--skip-submodule":
180
- flags.skipSubmodule = true;
181
- break;
182
- default:
183
- if (arg.startsWith("-")) {
184
- fatal(`Unknown flag: ${arg}\nRun with --help for usage.`);
185
- }
186
- if (flags.directory) {
187
- fatal(`Unexpected argument: ${arg}\nOnly one directory name is allowed.`);
188
- }
189
- flags.directory = arg;
167
+ const spec = FLAG_TABLE[arg];
168
+ if (spec) {
169
+ flags[spec.key] = "value" in spec ? spec.value : args[++i];
170
+ continue;
190
171
  }
191
- i++;
172
+ if (arg.startsWith("-")) fatal(`Unknown flag: ${arg}\nRun with --help for usage.`);
173
+ if (flags.directory) fatal(`Unexpected argument: ${arg}\nOnly one directory name is allowed.`);
174
+ flags.directory = arg;
192
175
  }
193
176
 
194
177
  return flags;
@@ -243,29 +226,22 @@ function preflight(flags) {
243
226
  }
244
227
  success(`Node.js ${process.versions.node}`);
245
228
 
246
- if (!commandExists("git")) {
247
- fatal("git is required but not found.\n Install: https://git-scm.com/");
248
- }
249
- success("git found");
250
-
251
- if (!commandExists("pnpm")) {
252
- warn("pnpm not found — install with: npm i -g pnpm (required for workspaces)");
253
- } else {
254
- success("pnpm found");
255
- }
256
-
257
229
  // bun is required at runtime by apps/backend (apollo-cms): db:push, db:seed,
258
230
  // dev:cron, plugins:build, the upgrade pipeline, and pre-commit hooks all
259
231
  // shell out to `bun`. The scaffold itself works without bun, but `pnpm dev`,
260
232
  // `pnpm backend:setup`, and `pnpm backend:upgrade` will fail without it.
261
- if (!commandExists("bun")) {
262
- warn(
263
- "bun not found — required by apps/backend for db:push, db:seed, dev:cron,\n" +
233
+ const TOOLS = [
234
+ { cmd: "git", required: true, missing: "git is required but not found.\n Install: https://git-scm.com/" },
235
+ { cmd: "pnpm", required: false, missing: "pnpm not found — install with: npm i -g pnpm (required for workspaces)" },
236
+ { cmd: "bun", required: false, missing:
237
+ "bun not found — required by apps/backend for db:push, db:seed, dev:cron,\n" +
264
238
  " plugins:build, and the upgrade pipeline.\n" +
265
- " Install: curl -fsSL https://bun.sh/install | bash (or: brew install bun)",
266
- );
267
- } else {
268
- success("bun found");
239
+ " Install: curl -fsSL https://bun.sh/install | bash (or: brew install bun)" },
240
+ ];
241
+ for (const t of TOOLS) {
242
+ if (commandExists(t.cmd)) success(`${t.cmd} found`);
243
+ else if (t.required) fatal(t.missing);
244
+ else warn(t.missing);
269
245
  }
270
246
 
271
247
  const targetDir = resolve(flags.directory);
@@ -288,24 +264,21 @@ function createPrompt() {
288
264
 
289
265
  async function gatherEnv(flags, { singleOrigin }) {
290
266
  let dbUrl = flags.db;
291
- // In single-origin mode the frontend is the public entry default :3001.
292
- // In separate-origins mode the backend is the public CMS URL default :3000.
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}`;
301
- let siteUrl = flags.url ?? defaultSiteUrl;
267
+ // NEXT_PUBLIC_SITE_URL is intentionally NOT prompted. Apollo CMS resolves
268
+ // the public origin in this priority order: NEXT_PUBLIC_SITE_URL >
269
+ // incoming request origin. Leaving it blank lets the runtime respect the
270
+ // actual request origin (works for localhost, preview URLs, prod domains,
271
+ // and reverse proxies without any reconfiguration). Override with --url
272
+ // only when you need a fixed origin for background jobs (cron / triggered
273
+ // emails) that run outside a request scope.
274
+ let siteUrl = flags.url ?? "";
302
275
  let locale = flags.locale ?? "en";
303
276
 
304
277
  if (flags.db && !isValidDbUrl(flags.db)) fatal("--db must start with postgresql:// or postgres://");
305
278
  if (flags.url && !isValidUrl(flags.url)) fatal("--url must start with http:// or https://");
306
279
  if (flags.locale && !isValidLocale(flags.locale)) fatal("--locale must be a 2-5 character code (e.g., en, th)");
307
280
 
308
- const needsPrompt = !dbUrl || !flags.url || !flags.locale;
281
+ const needsPrompt = !dbUrl || !flags.locale;
309
282
  if (!needsPrompt) return { dbUrl, siteUrl, locale };
310
283
 
311
284
  const { ask, close } = createPrompt();
@@ -321,16 +294,6 @@ async function gatherEnv(flags, { singleOrigin }) {
321
294
  }
322
295
  }
323
296
 
324
- if (!flags.url) {
325
- const ans = await ask(
326
- `\n ${COLORS.bold}NEXT_PUBLIC_SITE_URL${COLORS.reset} ${COLORS.dim}[${siteUrl}]${COLORS.reset}\n > `,
327
- );
328
- if (ans) {
329
- if (isValidUrl(ans)) siteUrl = ans;
330
- else warn(`Invalid URL, using default: ${siteUrl}`);
331
- }
332
- }
333
-
334
297
  if (!flags.locale) {
335
298
  const ans = await ask(
336
299
  `\n ${COLORS.bold}Default locale${COLORS.reset} ${COLORS.dim}[${locale}]${COLORS.reset}\n > `,
@@ -966,50 +929,49 @@ function writeRootEnv(targetDir, { dbUrl, siteUrl, locale, authSecret, cronSecre
966
929
  `DATABASE_URL=${dbUrl}`,
967
930
  `APOLLO_SECRET=${authSecret}`,
968
931
  `CRON_SECRET=${cronSecret}`,
969
- "# Public origin. In dev, leave blank to auto-derive from FRONTEND_PORT",
970
- "# (single-origin) or BACKEND_PORT (separate-origins) via scripts/with-env.mjs.",
971
- "# In prod set to the real domain (e.g. https://cms.example.com).",
932
+ "# Public origin. Leave blank so the CMS respects the incoming request",
933
+ "# origin (works for localhost, preview URLs, and prod domains automatically).",
934
+ "# Set this ONLY when background jobs (cron / triggered emails) need a fixed",
935
+ "# origin outside a request scope — e.g. NEXT_PUBLIC_SITE_URL=https://cms.example.com",
972
936
  `NEXT_PUBLIC_SITE_URL=${siteUrl}`,
973
937
  `NEXT_PUBLIC_DEFAULT_LOCALE=${locale}`,
974
938
  "",
975
939
  ];
976
940
 
941
+ lines.push("# ── Backend (apps/backend) ───────────────────────────────────────");
977
942
  if (adminPrefix) {
978
943
  lines.push(
979
- "# ── Backend (apps/backend) ───────────────────────────────────────",
980
944
  "# Single-origin admin/asset prefix. The frontend rewrites this path",
981
945
  "# to the backend, so backend chunks (served at <prefix>/_next/static)",
982
946
  "# and admin pages share one rewrite.",
983
947
  `APOLLO_ASSET_PREFIX=${adminPrefix}`,
984
- "# Project-specific plugins — keeps the apollo-cms submodule clean.",
985
- `APOLLO_EXTRA_PLUGINS_DIR=../cms-plugins`,
986
- "# PM2 namespace — groups this project's processes so you can",
987
- "# `pm2 stop <namespace>` / `pm2 restart <namespace>` independently of",
988
- "# other projects on the same host. Consumed by ecosystem.config.cjs.",
989
- `PM2_NAMESPACE=${pm2Namespace}`,
990
- "",
991
- "# ── Frontend (apps/frontend) ─────────────────────────────────────",
948
+ );
949
+ }
950
+ lines.push(
951
+ "# Project-specific plugins keeps the apollo-cms submodule clean.",
952
+ `APOLLO_EXTRA_PLUGINS_DIR=../cms-plugins`,
953
+ "# PM2 namespace — groups this project's processes so you can",
954
+ "# `pm2 stop <namespace>` / `pm2 restart <namespace>` independently of",
955
+ "# other projects on the same host. Consumed by ecosystem.config.cjs.",
956
+ `PM2_NAMESPACE=${pm2Namespace}`,
957
+ "",
958
+ "# ── Frontend (apps/frontend) ─────────────────────────────────────",
959
+ );
960
+ if (adminPrefix) {
961
+ lines.push(
992
962
  "# Where the frontend's rewrites point internally. In dev, leave blank",
993
963
  "# to auto-derive http://127.0.0.1:${BACKEND_PORT} via scripts/with-env.mjs.",
994
964
  "# In prod set to a private hostname (e.g. http://backend.internal:3000).",
995
965
  `BACKEND_INTERNAL_URL=${backendInternalUrl}`,
996
- "",
997
966
  );
998
967
  } else {
999
968
  lines.push(
1000
- "# ── Backend (apps/backend) ───────────────────────────────────────",
1001
- "# Project-specific plugins keeps the apollo-cms submodule clean.",
1002
- `APOLLO_EXTRA_PLUGINS_DIR=../cms-plugins`,
1003
- "# PM2 namespace — groups this project's processes so you can",
1004
- "# `pm2 stop <namespace>` / `pm2 restart <namespace>` independently of",
1005
- "# other projects on the same host. Consumed by ecosystem.config.cjs.",
1006
- `PM2_NAMESPACE=${pm2Namespace}`,
1007
- "",
1008
- "# ── Frontend (apps/frontend) ─────────────────────────────────────",
1009
- `NEXT_PUBLIC_BACKEND_URL=${siteUrl}`,
1010
- "",
969
+ "# Where the frontend reaches the backend in separate-origins mode.",
970
+ "# Defaults to the backend's dev port; override in prod to the real CMS host.",
971
+ `NEXT_PUBLIC_BACKEND_URL=${siteUrl || `http://localhost:${DEFAULT_BACKEND_PORT}`}`,
1011
972
  );
1012
973
  }
974
+ lines.push("");
1013
975
 
1014
976
  writeFileSync(resolve(targetDir, ".env.local"), lines.join("\n"));
1015
977
  }
@@ -1143,7 +1105,7 @@ export default config;
1143
1105
  ]
1144
1106
  : [
1145
1107
  `# Public URL of the backend (used by your client code)`,
1146
- `NEXT_PUBLIC_BACKEND_URL=${siteUrl}`,
1108
+ `NEXT_PUBLIC_BACKEND_URL=${siteUrl || `http://localhost:${DEFAULT_BACKEND_PORT}`}`,
1147
1109
  "",
1148
1110
  ];
1149
1111
  writeFileSync(resolve(dir, ".env.local.example"), envLocalLines.join("\n"));
@@ -2117,7 +2079,7 @@ async function main() {
2117
2079
  // BACKEND_INTERNAL_URL for the rewrites destination.
2118
2080
  const frontendFallback = adminPrefix
2119
2081
  ? [`BACKEND_INTERNAL_URL=${backendInternalUrl}`]
2120
- : [`NEXT_PUBLIC_BACKEND_URL=${siteUrl}`];
2082
+ : [`NEXT_PUBLIC_BACKEND_URL=${siteUrl || `http://localhost:${DEFAULT_BACKEND_PORT}`}`];
2121
2083
  linkRootEnvLocal(targetDir, FRONTEND_PATH, frontendFallback);
2122
2084
 
2123
2085
  // ── Step 7: Install ──
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-apollo-monorepo",
3
- "version": "0.9.3",
3
+ "version": "0.9.5",
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"