create-apollo-monorepo 0.6.2 → 0.8.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.
Files changed (2) hide show
  1. package/index.mjs +358 -10
  2. package/package.json +1 -1
package/index.mjs CHANGED
@@ -26,6 +26,7 @@ const BACKEND_REPO_URL = "https://github.com/5Lab-Group-Co-Ltd/apollo-cms.git";
26
26
  const BACKEND_BRANCH = "main";
27
27
  const BACKEND_PATH = "apps/backend";
28
28
  const FRONTEND_PATH = "apps/frontend";
29
+ const PROXY_PATH = "apps/proxy";
29
30
  const MIN_NODE_MAJOR = 20;
30
31
 
31
32
  const COLORS = {
@@ -348,8 +349,19 @@ function writeRootPackageJson(targetDir, dirName) {
348
349
  // (production uses Vercel Cron via apps/backend/vercel.json).
349
350
  "predev:setup":
350
351
  "pnpm cms-plugins:build && pnpm --filter ./apps/backend exec bun run plugins:build",
352
+ // `pnpm dev` runs FE + BE Next.js dev servers (which watch their own
353
+ // src/) plus a parallel cms-plugins watcher, so editing
354
+ // apps/cms-plugins/<slug>/index.ts triggers an esbuild incremental
355
+ // rebuild and the backend's plugin loader picks up the fresh
356
+ // dist/server.mjs on next request.
351
357
  dev:
352
- "pnpm predev:setup && concurrently -k -n FE,BE -c blue,magenta \"pnpm --filter ./apps/frontend dev\" \"pnpm --filter ./apps/backend exec next dev\"",
358
+ "pnpm predev:setup && concurrently -k -n FE,BE,PL -c blue,magenta,yellow \"pnpm --filter ./apps/frontend dev\" \"pnpm --filter ./apps/backend exec next dev\" \"pnpm cms-plugins:dev\"",
359
+ // Optional single-origin dev: same as `pnpm dev` but adds a Node.js
360
+ // reverse proxy on :3030 that fronts FE :3001 + BE :3000. Use it when
361
+ // you want one URL, shared cookies, and no CORS — without nginx.
362
+ "dev:rp":
363
+ "pnpm predev:setup && concurrently -k -n FE,BE,PL,PX -c blue,magenta,yellow,cyan \"pnpm --filter ./apps/frontend dev\" \"pnpm --filter ./apps/backend exec next dev\" \"pnpm cms-plugins:dev\" \"pnpm dev:proxy\"",
364
+ "dev:proxy": "node apps/proxy/server.mjs",
353
365
  "dev:frontend": "pnpm --filter ./apps/frontend dev",
354
366
  "dev:backend":
355
367
  "pnpm predev:setup && pnpm --filter ./apps/backend exec next dev",
@@ -359,6 +371,8 @@ function writeRootPackageJson(targetDir, dirName) {
359
371
  "pnpm cms-plugins:build && pnpm --filter ./apps/backend exec bun run plugins:build && pnpm --filter ./apps/backend build && pnpm --filter ./apps/frontend build",
360
372
  "cms-plugins:build":
361
373
  "pnpm --filter './apps/cms-plugins/*' --parallel --if-present build",
374
+ "cms-plugins:dev":
375
+ "pnpm --filter './apps/cms-plugins/*' --parallel --if-present dev",
362
376
  "cms-plugin:new": "node scripts/new-cms-plugin.mjs",
363
377
  lint: "pnpm -r lint",
364
378
  typecheck: "pnpm -r typecheck",
@@ -480,6 +494,312 @@ server {
480
494
  writeFileSync(resolve(targetDir, "nginx.conf.sample"), conf);
481
495
  }
482
496
 
497
+ // Optional Node.js reverse proxy. Mirrors nginx.conf.sample routing rules
498
+ // but runs in-process — opt in via `pnpm dev:rp`. Zero deps, ~80 LOC.
499
+ function writeProxyApp(targetDir, dirName, adminPrefix) {
500
+ const dir = resolve(targetDir, PROXY_PATH);
501
+ mkdirSync(dir, { recursive: true });
502
+
503
+ const prefix = adminPrefix || "/admin";
504
+ const server = `// ${dirName} — single-origin reverse proxy (zero deps).
505
+ // Mirrors ../../nginx.conf.sample. Run via \`pnpm dev:rp\` from the repo root.
506
+ //
507
+ // Routes (in order):
508
+ // ${prefix}, ${prefix}/*, /uploads/*, /monitoring → backend
509
+ // /api/editing-presence/* → backend (SSE)
510
+ // /api/{auth,v1,cron,email,health,mcp,admin,...}/* → backend
511
+ // /__proxy/health → 200 OK (proxy itself)
512
+ // everything else → frontend
513
+ //
514
+ // Production note: this proxy doesn't terminate TLS. For HTTPS, sit behind a
515
+ // load balancer (Caddy, Cloudflare, ALB) or use \`nginx.conf.sample\` instead.
516
+
517
+ import http from "node:http";
518
+ import net from "node:net";
519
+
520
+ const PORT = Number(process.env.PORT ?? 3030);
521
+ const BACKEND = parseTarget(process.env.BACKEND ?? "http://127.0.0.1:3000");
522
+ const FRONTEND = parseTarget(process.env.FRONTEND ?? "http://127.0.0.1:3001");
523
+
524
+ const ADMIN_PREFIX = process.env.ADMIN_PREFIX ?? "${prefix}";
525
+ // Pipe-separated list of /api/<segment> paths that route to the backend.
526
+ const BACKEND_API_SEGMENTS = (
527
+ process.env.BACKEND_API_PATHS ??
528
+ "auth|v1|cron|email|health|mcp|admin|editing-presence"
529
+ ).trim();
530
+ const BACKEND_API = new RegExp(\`^/api/(\${BACKEND_API_SEGMENTS})(/|$)\`);
531
+
532
+ // 50MB matches nginx.conf.sample's client_max_body_size and Next.js's
533
+ // Server Actions body limit. Override via MAX_BODY_BYTES (0 = unlimited).
534
+ const MAX_BODY_BYTES = Number(process.env.MAX_BODY_BYTES ?? 50 * 1024 * 1024);
535
+ const UPSTREAM_TIMEOUT_MS = Number(process.env.UPSTREAM_TIMEOUT_MS ?? 60_000);
536
+ // When false (default), strip incoming X-Forwarded-* before adding our own —
537
+ // prevents header spoofing when the proxy faces the public internet directly.
538
+ // Set TRUST_PROXY=1 only when behind another trusted reverse proxy (CDN, LB).
539
+ const TRUST_PROXY = process.env.TRUST_PROXY === "1";
540
+
541
+ // Hop-by-hop headers (RFC 7230 §6.1) — must not be forwarded.
542
+ const HOP_BY_HOP = new Set([
543
+ "connection",
544
+ "keep-alive",
545
+ "proxy-authenticate",
546
+ "proxy-authorization",
547
+ "te",
548
+ "trailer",
549
+ "transfer-encoding",
550
+ "upgrade",
551
+ ]);
552
+
553
+ // Reuse TCP connections to upstreams — eliminates ~1ms per request.
554
+ const backendAgent = new http.Agent({ keepAlive: true });
555
+ const frontendAgent = new http.Agent({ keepAlive: true });
556
+
557
+ function pickUpstream(url) {
558
+ if (url === ADMIN_PREFIX || url.startsWith(\`\${ADMIN_PREFIX}/\`))
559
+ return { ...BACKEND, agent: backendAgent };
560
+ if (url.startsWith("/uploads/")) return { ...BACKEND, agent: backendAgent };
561
+ if (url === "/monitoring") return { ...BACKEND, agent: backendAgent };
562
+ if (BACKEND_API.test(url)) return { ...BACKEND, agent: backendAgent };
563
+ return { ...FRONTEND, agent: frontendAgent };
564
+ }
565
+
566
+ function sanitizeHeaders(req, clientIp) {
567
+ const out = {};
568
+ for (const [k, v] of Object.entries(req.headers)) {
569
+ const lower = k.toLowerCase();
570
+ if (HOP_BY_HOP.has(lower)) continue;
571
+ // Strip client-supplied X-Forwarded-* unless TRUST_PROXY is set.
572
+ if (!TRUST_PROXY && lower.startsWith("x-forwarded-")) continue;
573
+ out[k] = v;
574
+ }
575
+ // Always set X-Forwarded-* with our own values (append client IP).
576
+ const existingFor = TRUST_PROXY ? req.headers["x-forwarded-for"] : undefined;
577
+ out["x-forwarded-for"] = existingFor ? \`\${existingFor}, \${clientIp}\` : clientIp;
578
+ out["x-forwarded-proto"] = TRUST_PROXY
579
+ ? req.headers["x-forwarded-proto"] ?? "http"
580
+ : "http";
581
+ out["x-forwarded-host"] = TRUST_PROXY
582
+ ? req.headers["x-forwarded-host"] ?? req.headers.host ?? ""
583
+ : req.headers.host ?? "";
584
+ return out;
585
+ }
586
+
587
+ function logError(scope, err, extra) {
588
+ const ts = new Date().toISOString();
589
+ const detail = extra ? \` \${JSON.stringify(extra)}\` : "";
590
+ console.error(\`[\${ts}] proxy:\${scope} \${err.code ?? ""} \${err.message}\${detail}\`);
591
+ }
592
+
593
+ const server = http.createServer((req, res) => {
594
+ // Health check (proxy itself, never forwarded).
595
+ if (req.url === "/__proxy/health") {
596
+ res.writeHead(200, { "content-type": "application/json" });
597
+ res.end(JSON.stringify({ status: "ok", uptime: process.uptime() }));
598
+ return;
599
+ }
600
+
601
+ // Enforce body size limit (DoS protection).
602
+ if (MAX_BODY_BYTES > 0) {
603
+ const declared = Number(req.headers["content-length"] ?? 0);
604
+ if (declared > MAX_BODY_BYTES) {
605
+ res.writeHead(413, { "content-type": "text/plain" });
606
+ res.end("Payload Too Large");
607
+ return;
608
+ }
609
+ let received = 0;
610
+ req.on("data", (chunk) => {
611
+ received += chunk.length;
612
+ if (received > MAX_BODY_BYTES) {
613
+ req.destroy(new Error("Body exceeded MAX_BODY_BYTES"));
614
+ }
615
+ });
616
+ }
617
+
618
+ const upstream = pickUpstream(req.url);
619
+ const proxyReq = http.request(
620
+ {
621
+ host: upstream.host,
622
+ port: upstream.port,
623
+ method: req.method,
624
+ path: req.url,
625
+ headers: sanitizeHeaders(req, req.socket.remoteAddress ?? ""),
626
+ agent: upstream.agent,
627
+ timeout: UPSTREAM_TIMEOUT_MS,
628
+ },
629
+ (proxyRes) => {
630
+ // Strip hop-by-hop headers from upstream response too.
631
+ const safe = {};
632
+ for (const [k, v] of Object.entries(proxyRes.headers)) {
633
+ if (!HOP_BY_HOP.has(k.toLowerCase())) safe[k] = v;
634
+ }
635
+ res.writeHead(proxyRes.statusCode ?? 502, safe);
636
+ proxyRes.pipe(res);
637
+ },
638
+ );
639
+ proxyReq.on("timeout", () => {
640
+ logError("upstream-timeout", new Error("upstream timeout"), { url: req.url });
641
+ proxyReq.destroy(new Error("Upstream timeout"));
642
+ });
643
+ proxyReq.on("error", (err) => {
644
+ logError("upstream", err, { url: req.url });
645
+ if (!res.headersSent) {
646
+ res.writeHead(502, { "content-type": "text/plain" });
647
+ res.end("Bad Gateway");
648
+ }
649
+ if (!req.destroyed) req.destroy();
650
+ });
651
+ req.on("error", (err) => {
652
+ logError("client", err, { url: req.url });
653
+ if (!proxyReq.destroyed) proxyReq.destroy();
654
+ });
655
+ req.pipe(proxyReq);
656
+ });
657
+
658
+ // WebSocket / HTTP upgrade passthrough — required for Next.js HMR.
659
+ server.on("upgrade", (req, clientSocket, head) => {
660
+ const upstream = pickUpstream(req.url);
661
+ const upstreamSocket = net.connect(upstream.port, upstream.host, () => {
662
+ const headers = sanitizeHeaders(req, req.socket.remoteAddress ?? "");
663
+ // Re-add Connection: Upgrade & Upgrade headers stripped by sanitizeHeaders.
664
+ headers["connection"] = "Upgrade";
665
+ if (req.headers.upgrade) headers["upgrade"] = req.headers.upgrade;
666
+ const headerLines = [];
667
+ for (const [k, v] of Object.entries(headers)) {
668
+ const values = Array.isArray(v) ? v : [v];
669
+ for (const single of values) {
670
+ const str = String(single);
671
+ // Defense-in-depth: reject CRLF in header values (header injection).
672
+ if (/[\\r\\n]/.test(str)) continue;
673
+ headerLines.push(\`\${k}: \${str}\`);
674
+ }
675
+ }
676
+ upstreamSocket.write(
677
+ \`\${req.method} \${req.url} HTTP/\${req.httpVersion}\\r\\n\` +
678
+ headerLines.join("\\r\\n") +
679
+ "\\r\\n\\r\\n",
680
+ );
681
+ if (head?.length) upstreamSocket.write(head);
682
+ upstreamSocket.pipe(clientSocket);
683
+ clientSocket.pipe(upstreamSocket);
684
+ });
685
+ upstreamSocket.on("error", (err) => {
686
+ logError("ws-upstream", err, { url: req.url });
687
+ clientSocket.destroy();
688
+ });
689
+ clientSocket.on("error", (err) => {
690
+ logError("ws-client", err, { url: req.url });
691
+ upstreamSocket.destroy();
692
+ });
693
+ });
694
+
695
+ server.listen(PORT, () => {
696
+ console.log(\`apollo-proxy → http://localhost:\${PORT}\`);
697
+ console.log(\` \${ADMIN_PREFIX}/*, /uploads/*, /api/* → \${BACKEND.host}:\${BACKEND.port}\`);
698
+ console.log(\` /* → \${FRONTEND.host}:\${FRONTEND.port}\`);
699
+ console.log(\` TRUST_PROXY=\${TRUST_PROXY} MAX_BODY_BYTES=\${MAX_BODY_BYTES} UPSTREAM_TIMEOUT_MS=\${UPSTREAM_TIMEOUT_MS}\`);
700
+ });
701
+
702
+ // Graceful shutdown — let in-flight requests finish, then exit.
703
+ let shuttingDown = false;
704
+ function shutdown(signal) {
705
+ if (shuttingDown) return;
706
+ shuttingDown = true;
707
+ console.log(\`\\napollo-proxy: \${signal} received, draining...\`);
708
+ server.close(() => {
709
+ backendAgent.destroy();
710
+ frontendAgent.destroy();
711
+ process.exit(0);
712
+ });
713
+ // Hard exit after 10s if connections refuse to drain.
714
+ setTimeout(() => process.exit(1), 10_000).unref();
715
+ }
716
+ process.on("SIGTERM", () => shutdown("SIGTERM"));
717
+ process.on("SIGINT", () => shutdown("SIGINT"));
718
+
719
+ function parseTarget(input) {
720
+ const url = new URL(input.startsWith(":") ? \`http://127.0.0.1\${input}\` : input);
721
+ return { host: url.hostname, port: Number(url.port || 80) };
722
+ }
723
+ `;
724
+ writeFileSync(resolve(dir, "server.mjs"), server);
725
+
726
+ const pkg = {
727
+ name: `@${dirName}/proxy`,
728
+ private: true,
729
+ version: "0.0.0",
730
+ description: "Optional Node.js reverse proxy fronting frontend + backend on a single origin",
731
+ type: "module",
732
+ main: "server.mjs",
733
+ scripts: {
734
+ start: "node server.mjs",
735
+ },
736
+ engines: { node: ">=20" },
737
+ };
738
+ writeFileSync(resolve(dir, "package.json"), JSON.stringify(pkg, null, 2) + "\n");
739
+
740
+ const readme = `# @${dirName}/proxy
741
+
742
+ Optional single-origin reverse proxy. Routes \`${prefix}/*\`, \`/api/*\`, and
743
+ \`/uploads/*\` to the backend; everything else to the frontend. Zero deps.
744
+
745
+ ## When to use
746
+
747
+ - You want **one URL** for FE + BE locally (shared cookies, no CORS).
748
+ - You don't want to install nginx for local dev.
749
+
750
+ ## Usage
751
+
752
+ From the repo root:
753
+
754
+ \`\`\`bash
755
+ pnpm dev:rp # runs FE :3001 + BE :3000 + plugins watcher + proxy :3030
756
+ \`\`\`
757
+
758
+ Then open http://localhost:3030.
759
+
760
+ For best results, set \`NEXT_PUBLIC_SITE_URL=http://localhost:3030\` in your
761
+ \`.env.local\` so Better Auth, OAuth callbacks, and email links all use the
762
+ single-origin URL.
763
+
764
+ ## Configuration
765
+
766
+ | Env var | Default | Notes |
767
+ | --------------------- | -------------------------------- | -------------------------------------------------------------- |
768
+ | \`PORT\` | \`3030\` | Public port |
769
+ | \`BACKEND\` | \`http://127.0.0.1:3000\` | apps/backend dev server |
770
+ | \`FRONTEND\` | \`http://127.0.0.1:3001\` | apps/frontend dev server |
771
+ | \`ADMIN_PREFIX\` | \`${prefix}\` | Backend admin path prefix |
772
+ | \`BACKEND_API_PATHS\` | \`auth\\|v1\\|cron\\|email\\|health\\|mcp\\|admin\\|editing-presence\` | Pipe-separated /api/* segments routed to backend |
773
+ | \`MAX_BODY_BYTES\` | \`52428800\` (50MB) | Reject larger bodies. \`0\` = unlimited |
774
+ | \`UPSTREAM_TIMEOUT_MS\` | \`60000\` | Request timeout to upstream |
775
+ | \`TRUST_PROXY\` | \`0\` | Set \`1\` only if behind another trusted proxy (CDN/LB) |
776
+
777
+ ## Health check
778
+
779
+ \`GET /__proxy/health\` returns \`{"status":"ok","uptime":<seconds>}\`. Use it for
780
+ liveness probes; the path is reserved by the proxy and never forwarded.
781
+
782
+ ## Better Auth interaction
783
+
784
+ Apollo CMS's Better Auth derives the request origin from \`x-forwarded-host\` /
785
+ \`x-forwarded-proto\`. This proxy sets both correctly, so \`trustedOrigins\` keeps
786
+ working through the proxy. The recent fix in \`src/lib/auth/server.ts\` ensures
787
+ the actual request origin (e.g. \`localhost:3030\`) is added to trusted origins
788
+ even when \`NEXT_PUBLIC_SITE_URL\` points elsewhere.
789
+
790
+ ## Production
791
+
792
+ This proxy doesn't terminate TLS. For HTTPS, either:
793
+
794
+ - Sit it behind a TLS-terminating load balancer (Caddy, Cloudflare, ALB), **or**
795
+ - Use \`nginx.conf.sample\` at the repo root instead — nginx handles TLS, gzip,
796
+ and large-scale traffic better.
797
+
798
+ The Node proxy is intended for dev and small self-hosted deploys.
799
+ `;
800
+ writeFileSync(resolve(dir, "README.md"), readme);
801
+ }
802
+
483
803
  function writeRootGitignore(targetDir) {
484
804
  const ignore = [
485
805
  "node_modules",
@@ -741,6 +1061,11 @@ function writeExampleCmsPlugin(targetDir, dirName) {
741
1061
  },
742
1062
  scripts: {
743
1063
  build: "node ./build.mjs",
1064
+ // `dev` is the watcher entry — `pnpm dev` from the monorepo root fans
1065
+ // out to it via `cms-plugins:dev`, so editing index.ts triggers an
1066
+ // incremental rebuild and the backend's plugin loader picks up the new
1067
+ // dist/server.mjs on the next request.
1068
+ dev: "node ./build.mjs --watch",
744
1069
  },
745
1070
  devDependencies: {
746
1071
  esbuild: "^0.24.0",
@@ -749,9 +1074,12 @@ function writeExampleCmsPlugin(targetDir, dirName) {
749
1074
  writeFileSync(resolve(pluginDir, "package.json"), JSON.stringify(pkg, null, 2) + "\n");
750
1075
 
751
1076
  // Tiny zero-config build: bundle index.ts → dist/server.mjs as ESM Node.
752
- const buildMjs = `import { build } from "esbuild";
1077
+ // Pass --watch to keep esbuild's context alive and rebuild on file changes.
1078
+ const buildMjs = `import { context, build } from "esbuild";
1079
+
1080
+ const watch = process.argv.includes("--watch");
753
1081
 
754
- await build({
1082
+ const options = {
755
1083
  entryPoints: ["./index.ts"],
756
1084
  outfile: "./dist/server.mjs",
757
1085
  format: "esm",
@@ -762,7 +1090,15 @@ await build({
762
1090
  // backend at runtime via the loader's dynamic import().
763
1091
  external: ["@/*", "next", "react", "react-dom"],
764
1092
  logLevel: "info",
765
- });
1093
+ };
1094
+
1095
+ if (watch) {
1096
+ const ctx = await context(options);
1097
+ await ctx.watch();
1098
+ console.log("[cms-plugin] watching index.ts → dist/server.mjs");
1099
+ } else {
1100
+ await build(options);
1101
+ }
766
1102
  `;
767
1103
  writeFileSync(resolve(pluginDir, "build.mjs"), buildMjs);
768
1104
 
@@ -928,12 +1264,17 @@ paths to the backend so /_next/* doesn't collide:
928
1264
  The frontend MUST NOT define routes at \`/admin\`, \`/api/auth\`, \`/api/v1\`, etc.
929
1265
  If you need your own API, namespace it under \`/api/internal/*\` or similar.
930
1266
 
931
- ### Reverse proxy (nginx)
1267
+ ### Reverse proxy
932
1268
 
933
- A reference \`nginx.conf.sample\` ships at the repo root with the same routing
934
- baked in (frontend on :3001, backend on :3000). Use it when fronting both apps
935
- behind a single TLS-terminating proxy outside Verceldrop in your domain and
936
- SSL certs.
1269
+ Two options ship out of the box:
1270
+
1271
+ - **\`apps/proxy\`** (Node.js, zero deps) opt in with \`pnpm dev:rp\`. Runs FE,
1272
+ BE, plugins watcher, and a reverse proxy on **:3030** so everything sits on
1273
+ one origin (shared cookies, no CORS). Best for local dev and small self-hosted
1274
+ deploys. Configure via \`PORT\`, \`BACKEND\`, \`FRONTEND\`, \`ADMIN_PREFIX\`.
1275
+ - **\`nginx.conf.sample\`** (production) — same routing baked in (frontend on
1276
+ :3001, backend on :3000). Use it when fronting both apps behind a single
1277
+ TLS-terminating proxy outside Vercel — drop in your domain and SSL certs.
937
1278
 
938
1279
  To **disable** single-origin and run the backend on its own subdomain,
939
1280
  delete \`APOLLO_ASSET_PREFIX\` from \`apps/backend/.env.local\` and remove the
@@ -984,8 +1325,14 @@ ${dirName}/
984
1325
  pnpm install
985
1326
  pnpm backend:setup # push schema + seed apollo-cms
986
1327
  pnpm dev # runs frontend (3001) + backend (3000) in parallel
1328
+ # or:
1329
+ pnpm dev:rp # same + reverse proxy on :3030 (single-origin, shared cookies)
987
1330
  \`\`\`
988
1331
 
1332
+ When using \`pnpm dev:rp\`, set \`NEXT_PUBLIC_SITE_URL=http://localhost:3030\`
1333
+ in your \`.env.local\` so Better Auth, OAuth callbacks, and email links use
1334
+ the unified origin.
1335
+
989
1336
  ${originSection}
990
1337
  ## Custom plugins
991
1338
 
@@ -1159,10 +1506,11 @@ async function main() {
1159
1506
  writeRootEnv(targetDir, { dbUrl, siteUrl, locale, authSecret, cronSecret, adminPrefix, backendInternalUrl });
1160
1507
  writeReadme(targetDir, dirName, frontendName, adminPrefix);
1161
1508
  if (adminPrefix) writeNginxSample(targetDir, adminPrefix);
1509
+ writeProxyApp(targetDir, dirName, adminPrefix);
1162
1510
  success(
1163
1511
  `package.json, pnpm-workspace.yaml, .gitignore, .env.local, README.md${
1164
1512
  adminPrefix ? ", nginx.conf.sample" : ""
1165
- }`,
1513
+ }, apps/proxy`,
1166
1514
  );
1167
1515
 
1168
1516
  // ── Step 4: git init ──
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-apollo-monorepo",
3
- "version": "0.6.2",
3
+ "version": "0.8.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"