doccupine 0.0.93 → 0.0.95

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.
@@ -3,6 +3,7 @@ export const proxyTemplate = (analyticsConfig = null) => {
3
3
  const posthogImport = hasPostHog
4
4
  ? `import { getPostHogServerClient } from "@/lib/posthog";\n`
5
5
  : "";
6
+ const gateImport = `import { GATE_COOKIE_NAME, isGateUnlocked } from "@/lib/siteGate";\n`;
6
7
  const posthogPageviewFn = hasPostHog
7
8
  ? `
8
9
  const SKIP_PAGEVIEW_PATTERN = /^\\/(api|ingest|_next)\\//;
@@ -55,27 +56,30 @@ function captureServerPageview(req: NextRequest, event: NextFetchEvent) {
55
56
  ? ` captureServerPageview(req, event);\n`
56
57
  : "";
57
58
  const fnSignature = hasPostHog
58
- ? `export function proxy(req: NextRequest, event: NextFetchEvent)`
59
- : `export function proxy(req: NextRequest)`;
59
+ ? `export async function proxy(req: NextRequest, event: NextFetchEvent)`
60
+ : `export async function proxy(req: NextRequest)`;
60
61
  const eventImport = hasPostHog ? ", NextFetchEvent" : "";
61
- // Matcher scope:
62
- // - With PostHog server-side tracking: run on every path so pageviews
63
- // fire (the capture function uses event.waitUntil and never mutates
64
- // the response, so doc pages remain edge-cacheable).
65
- // - Otherwise: only run on /api/* — the only routes that need middleware
66
- // (e.g. MCP key auth). Doc pages bypass middleware entirely.
62
+ // Matcher scope: run on every path. The middleware handles MCP key auth,
63
+ // the SITE_PASSWORD gate for the content APIs, PostHog pageviews, and the
64
+ // X-Robots-Tag backstop while password protection is active — all of which
65
+ // need to see page and API requests alike. When SITE_PASSWORD is unset and
66
+ // PostHog is off the middleware just returns NextResponse.next() without
67
+ // mutating the response, so doc pages remain edge-cacheable.
67
68
  //
68
69
  // Theme detection happens client-side via the theme-init blocking script
69
- // in the root layout (sets a "dark" class on <html>). Middleware no longer
70
- // sets Vary, Accept-CH, or a theme cookie, because doing so would mark
71
- // every response as dynamic and disable caching at Vercel/Cloudflare.
72
- const matcher = hasPostHog ? `["/:path*"]` : `["/api/:path*"]`;
70
+ // in the root layout (sets a "dark" class on <html>). Middleware never sets
71
+ // Vary, Accept-CH, or a theme cookie, because doing so would mark every
72
+ // response as dynamic and disable caching at Vercel/Cloudflare.
73
+ const matcher = `["/:path*"]`;
73
74
  return `import { NextResponse } from "next/server";
74
75
  import type { NextRequest${eventImport} } from "next/server";
75
- ${posthogImport}${posthogPageviewFn}
76
+ ${gateImport}${posthogImport}${posthogPageviewFn}
76
77
  ${fnSignature} {
77
- ${posthogCall} // API key auth for /api/mcp when DOCS_API_KEY is configured
78
- if (req.nextUrl.pathname.startsWith("/api/mcp")) {
78
+ ${posthogCall} const pathname = req.nextUrl.pathname;
79
+ const sitePassword = process.env.SITE_PASSWORD;
80
+
81
+ // API key auth for /api/mcp when DOCS_API_KEY is configured
82
+ if (pathname.startsWith("/api/mcp")) {
79
83
  const apiKey = process.env.DOCS_API_KEY;
80
84
  if (apiKey) {
81
85
  const authHeader = req.headers.get("authorization");
@@ -89,7 +93,50 @@ ${posthogCall} // API key auth for /api/mcp when DOCS_API_KEY is configured
89
93
  }
90
94
  }
91
95
 
92
- return NextResponse.next();
96
+ // SITE_PASSWORD gate. Enforced here in the middleware (not the layout) so it
97
+ // works even though the doc pages render statically: pages can't read the
98
+ // request cookie, but the middleware always can. Skip Next internals so the
99
+ // gate screen's own assets keep loading.
100
+ if (sitePassword && !pathname.startsWith("/_next")) {
101
+ const unlocked = await isGateUnlocked(
102
+ req.cookies.get(GATE_COOKIE_NAME)?.value,
103
+ sitePassword,
104
+ );
105
+
106
+ // Content APIs (RAG chat + search) return 401 without a valid cookie so the
107
+ // docs can't be scraped around the login screen. /api/mcp keeps its own key
108
+ // auth above; /api/gate stays open so the gate can unlock.
109
+ if (
110
+ !unlocked &&
111
+ (pathname.startsWith("/api/rag") || pathname.startsWith("/api/search"))
112
+ ) {
113
+ return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
114
+ }
115
+
116
+ // Locked visitors see the gate screen. Rewrite (not redirect) so the URL is
117
+ // preserved — after unlocking, a reload lands them back on the page they
118
+ // asked for. API routes are never rewritten to HTML.
119
+ if (!unlocked && !pathname.startsWith("/api") && pathname !== "/gate") {
120
+ const res = NextResponse.rewrite(new URL("/gate", req.url));
121
+ res.headers.set("X-Robots-Tag", "noindex, nofollow");
122
+ return res;
123
+ }
124
+
125
+ // Unlocked visitors never need the gate screen.
126
+ if (unlocked && pathname === "/gate") {
127
+ return NextResponse.redirect(new URL("/", req.url));
128
+ }
129
+ }
130
+
131
+ const res = NextResponse.next();
132
+
133
+ // While password protection is active, keep the whole site out of search
134
+ // indexes as a header-level backstop to robots.txt's disallow rule.
135
+ if (sitePassword) {
136
+ res.headers.set("X-Robots-Tag", "noindex, nofollow");
137
+ }
138
+
139
+ return res;
93
140
  }
94
141
 
95
142
  export const config = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "doccupine",
3
- "version": "0.0.93",
3
+ "version": "0.0.95",
4
4
  "description": "Free and open-source documentation platform. Write MDX, get a production-ready site with AI chat, built-in components, and an MCP server - in one command.",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -37,18 +37,18 @@
37
37
  "chalk": "^5.6.2",
38
38
  "chokidar": "^5.0.0",
39
39
  "commander": "^15.0.0",
40
- "fs-extra": "^11.3.5",
40
+ "fs-extra": "^11.3.6",
41
41
  "gray-matter": "^4.0.3",
42
- "next": "^16.2.9",
42
+ "next": "^16.2.10",
43
43
  "prompts": "^2.4.2",
44
44
  "react": "^19.2.7",
45
45
  "react-dom": "^19.2.7"
46
46
  },
47
47
  "devDependencies": {
48
48
  "@types/fs-extra": "^11.0.4",
49
- "@types/node": "^25.9.3",
49
+ "@types/node": "^26.1.0",
50
50
  "@types/prompts": "^2.4.9",
51
- "prettier": "^3.8.4",
51
+ "prettier": "^3.9.4",
52
52
  "typescript": "^6.0.3",
53
53
  "vitest": "^4.1.9"
54
54
  },