@djangocfg/nextjs 2.1.569 → 2.1.571

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/dist/index.mjs CHANGED
@@ -24,6 +24,14 @@ async function parseAndCheckSize(r, url, label) {
24
24
  }
25
25
  return JSON.parse(text);
26
26
  }
27
+ var isBuilding = () => process.env.NEXT_PHASE === "phase-production-build";
28
+ function degrade(fallback, reason, err) {
29
+ if (!isBuilding()) {
30
+ throw new Error(`[sitemap] ${reason}; keeping the last good copy`, { cause: err });
31
+ }
32
+ console.warn(`[sitemap] ${reason}; falling back to empty during build`, err ?? "");
33
+ return fallback;
34
+ }
27
35
  var EMPTY_INDEX = {
28
36
  sources: [],
29
37
  generated_at: (/* @__PURE__ */ new Date(0)).toISOString(),
@@ -43,14 +51,10 @@ async function fetchSitemapIndex(apiUrl, revalidate) {
43
51
  const r = await fetch(url, {
44
52
  next: { revalidate }
45
53
  });
46
- if (!r.ok) {
47
- console.warn(`[sitemap] index fetch returned ${r.status}; falling back to empty`);
48
- return EMPTY_INDEX;
49
- }
54
+ if (!r.ok) return degrade(EMPTY_INDEX, `index fetch returned ${r.status}`);
50
55
  return await parseAndCheckSize(r, url, "index");
51
56
  } catch (err) {
52
- console.warn("[sitemap] index fetch failed:", err);
53
- return EMPTY_INDEX;
57
+ return degrade(EMPTY_INDEX, "index fetch failed", err);
54
58
  }
55
59
  }
56
60
  async function fetchSitemapFeed(apiUrl, source, cursor, revalidate) {
@@ -61,14 +65,10 @@ async function fetchSitemapFeed(apiUrl, source, cursor, revalidate) {
61
65
  const r = await fetch(url, {
62
66
  next: { revalidate }
63
67
  });
64
- if (!r.ok) {
65
- console.warn(`[sitemap] feed ${source} returned ${r.status}; falling back to empty`);
66
- return emptyFeed(source, cursor);
67
- }
68
+ if (!r.ok) return degrade(emptyFeed(source, cursor), `feed ${source} returned ${r.status}`);
68
69
  return await parseAndCheckSize(r, url, `feed ${source}`);
69
70
  } catch (err) {
70
- console.warn(`[sitemap] feed ${source} fetch failed:`, err);
71
- return emptyFeed(source, cursor);
71
+ return degrade(emptyFeed(source, cursor), `feed ${source} fetch failed`, err);
72
72
  }
73
73
  }
74
74
 
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/sitemap/constants.ts","../src/sitemap/fetch.ts","../src/sitemap/ids.ts","../src/sitemap/select.ts","../src/sitemap/sitemap.ts","../src/sitemap/index-route.ts","../src/sitemap/robots.ts","../src/health/route.ts","../src/navigation/utils.ts","../src/i18n/plugin.ts","../src/pwa/plugin.ts","../src/config/constants.ts","../src/config/csp.ts","../src/config/plugins/compression.ts","../src/config/packages/checker.ts","../src/config/utils/env.ts","../src/config/packages/definitions.ts","../src/config/plugins/devStartup.ts","../src/ai/constants.ts","../src/config/packages/installer.ts","../src/config/packages/updater.ts","../src/config/utils/version.ts","../src/config/utils/deepMerge.ts","../src/config/createNextConfig.ts","../src/ai/client.ts"],"sourcesContent":["/**\n * Shared sitemap constants.\n *\n * Pulled out so both the chunk factory (`createDjangoSitemap`) and the\n * index Route Handler (`createSitemapIndex`) agree on the id of the\n * frontend-only static-routes chunk.\n */\n\nexport const STATIC_ID = 'static';\nexport const DEFAULT_INDEX_REVALIDATE = 600;\nexport const DEFAULT_FEED_REVALIDATE = 3600;\n","/**\n * Sitemap fetch helpers — never throw.\n *\n * Failures are converted to empty payloads so a transient Django outage\n * doesn't break `next build`. The next ISR cycle (`revalidate`) recovers\n * automatically.\n */\n\nimport type { SitemapFeedPage, SitemapIndex } from './types';\n\n/**\n * Next.js refuses to write a fetch response larger than 2MB into its data\n * cache. It logs one line and serves the response uncached, so `revalidate`\n * silently becomes a no-op and every regeneration refetches the full payload\n * from Django. Nothing else surfaces this, so we measure the body ourselves.\n */\nconst NEXT_DATA_CACHE_LIMIT = 2 * 1024 * 1024;\n\n// One warning per URL: `sitemap()` runs once per chunk per build, and a\n// repeated line for the same feed reads as a new problem rather than the\n// same one.\nconst oversizeWarned = new Set<string>();\n\n/**\n * Read the body as text so its exact size is known, then parse.\n *\n * `Content-Length` is not a substitute: it is absent under chunked transfer\n * and reports the compressed size when the response is gzipped, while the\n * 2MB ceiling applies to the decoded bytes Next.js would store.\n */\nasync function parseAndCheckSize<T>(r: Response, url: string, label: string): Promise<T> {\n const text = await r.text();\n const bytes = new TextEncoder().encode(text).length;\n if (bytes > NEXT_DATA_CACHE_LIMIT && !oversizeWarned.has(url)) {\n oversizeWarned.add(url);\n console.warn(\n `[sitemap] ${label} is ${bytes} bytes, over Next.js's 2MB data-cache ceiling: ` +\n `revalidate is inert for this feed and every regeneration refetches it from Django. ` +\n `Lower the backend page_size if the extra load matters — note 50,000 URLs is the ` +\n `sitemap protocol's own maximum, so a large payload here is not itself a bug.`,\n );\n }\n return JSON.parse(text) as T;\n}\n\nconst EMPTY_INDEX: SitemapIndex = {\n sources: [],\n generated_at: new Date(0).toISOString(),\n ttl_seconds: 60,\n};\n\nconst emptyFeed = (source: string, cursor: string | null): SitemapFeedPage => ({\n source,\n chunk_id: `${source}-empty`,\n count: 0,\n has_more: false,\n next_cursor: cursor,\n entries: [],\n});\n\nexport async function fetchSitemapIndex(\n apiUrl: string,\n revalidate: number,\n): Promise<SitemapIndex> {\n const url = `${apiUrl}/cfg/sitemap/index/`;\n try {\n const r = await fetch(url, {\n next: { revalidate },\n });\n if (!r.ok) {\n console.warn(`[sitemap] index fetch returned ${r.status}; falling back to empty`);\n return EMPTY_INDEX;\n }\n return await parseAndCheckSize<SitemapIndex>(r, url, 'index');\n } catch (err) {\n console.warn('[sitemap] index fetch failed:', err);\n return EMPTY_INDEX;\n }\n}\n\nexport async function fetchSitemapFeed(\n apiUrl: string,\n source: string,\n cursor: string | null,\n revalidate: number,\n): Promise<SitemapFeedPage> {\n const params = new URLSearchParams({ source });\n if (cursor) params.set('cursor', cursor);\n const url = `${apiUrl}/cfg/sitemap/feed/?${params}`;\n try {\n const r = await fetch(url, {\n next: { revalidate },\n });\n if (!r.ok) {\n console.warn(`[sitemap] feed ${source} returned ${r.status}; falling back to empty`);\n return emptyFeed(source, cursor);\n }\n return await parseAndCheckSize<SitemapFeedPage>(r, url, `feed ${source}`);\n } catch (err) {\n console.warn(`[sitemap] feed ${source} fetch failed:`, err);\n return emptyFeed(source, cursor);\n }\n}\n","/**\n * Sitemap chunk id encoding.\n *\n * Each Next.js sitemap file is addressed by an opaque string id. We\n * round-trip a `(source, cursor)` pair through that id so the default\n * sitemap handler knows which Django chunk to fetch.\n *\n * Separator `--` is unambiguous: backend cursors use URL-safe base64 (`-_`).\n */\n\nexport function encodeChunkId(source: string, cursor: string | null): string {\n return `${source}--${cursor ?? ''}`;\n}\n\nexport function decodeChunkId(id: string): { source: string; cursor: string | null } {\n const sep = id.indexOf('--');\n if (sep === -1) return { source: id, cursor: null };\n const source = id.slice(0, sep);\n const cursorRaw = id.slice(sep + 2);\n return { source, cursor: cursorRaw === '' ? null : cursorRaw };\n}\n","/**\n * Which backend sources a frontend claims.\n *\n * `GET /cfg/sitemap/index/` answers for the whole Django project — every\n * source any installed app registered. One frontend is not always entitled\n * to all of them: a backend serving several brands registers a source per\n * brand (`properties-caribbean`, `properties-bali`), and each brand's app\n * must emit only its own, or it advertises the other's URLs under its own\n * host — every one of them a 404, and the sitemap that carries them is\n * discredited whole.\n *\n * The selector lives here, shared, for the same reason `STATIC_ID` does:\n * `createDjangoSitemap` enumerates the chunks and `createSitemapIndex`\n * enumerates them again for the index document. If the two applied\n * different filters, the index would advertise shards the chunk route\n * refuses to build — and that mismatch is invisible until a crawler reads\n * both. One function, called by both, cannot drift.\n *\n * Source NAMES are the contract. The module ships no notion of a brand or\n * a tenant, and it should not: the backend chose what its source names\n * mean, and only the app knows which of them are its own.\n */\n\nimport type { SitemapIndex, SitemapSourceInfo, SourceSelector } from './types';\n\n/**\n * A predicate from whatever the caller passed.\n *\n * Omitted means every source — the single-tenant default, and what every\n * caller before this option existed already relied on.\n */\nexport function sourceFilter(select: SourceSelector | undefined): (name: string) => boolean {\n if (select === undefined) return () => true;\n if (typeof select === 'function') return select;\n const allowed = new Set(select);\n return (name) => allowed.has(name);\n}\n\n/** The index's sources, narrowed to those the caller claims. */\nexport function selectSources(\n index: SitemapIndex,\n select: SourceSelector | undefined,\n): SitemapSourceInfo[] {\n const keep = sourceFilter(select);\n return index.sources.filter((source) => keep(source.name));\n}\n\n/**\n * Every chunk id the selected sources produce, in index order.\n *\n * The one place `(source, chunk)` becomes an id, so the chunk route and the\n * index document enumerate the same set by construction.\n */\nexport function selectedChunkIds(\n index: SitemapIndex,\n select: SourceSelector | undefined,\n encode: (source: string, cursor: string | null) => string,\n): string[] {\n const ids: string[] = [];\n for (const source of selectSources(index, select)) {\n for (const chunk of source.chunks) {\n ids.push(encode(source.name, chunk.cursor_to));\n }\n }\n return ids;\n}\n\n/**\n * A selector for one tenant of a multi-brand backend.\n *\n * Matches the `<source>-<tenant>` names that a backend registering per\n * tenant emits. It splits on the LAST hyphen and compares the tail exactly,\n * rather than testing a suffix: `endsWith('-bali')` is correct for\n * `properties-bali` but the same shape reads `pricing-plans-premium` as\n * belonging to a `premium` tenant, and a substring match is not a contract.\n *\n * ```ts\n * sources: tenantSources('bali') // properties-bali, communities-bali, …\n * ```\n *\n * Sources with no hyphen never match: an untenanted source belongs to the\n * project rather than to a brand, and claiming it for one is the error this\n * function exists to prevent.\n */\nexport function tenantSources(tenant: string): (name: string) => boolean {\n return (name) => {\n const sep = name.lastIndexOf('-');\n return sep !== -1 && name.slice(sep + 1) === tenant;\n };\n}\n","/**\n * createDjangoSitemap — factory for Next.js `app/sitemap.ts`.\n *\n * Backed by `django_cfg.modules.django_sitemap`: the backend exposes a\n * paginated JSON feed, this factory turns each chunk into a Next.js\n * sitemap file via `generateSitemaps()`.\n *\n * Usage:\n *\n * ```ts\n * // apps/<app>/app/sitemap.ts\n * import { createDjangoSitemap } from '@djangocfg/nextjs/sitemap';\n *\n * const { generateSitemaps, sitemap } = createDjangoSitemap({\n * host: process.env.NEXT_PUBLIC_SITE_URL!,\n * apiUrl: process.env.NEXT_PUBLIC_API_URL!,\n * staticRoutes: [\n * { path: '/', changeFrequency: 'daily', priority: 1.0 },\n * { path: '/catalog', changeFrequency: 'daily', priority: 0.9 },\n * ],\n * });\n *\n * export { generateSitemaps, sitemap as default };\n * export const revalidate = 3600;\n * ```\n *\n * Without `apiUrl`, the sitemap emits only the static routes — handy for\n * marketing sites that don't have a backend-driven URL space.\n */\n\nimport type { MetadataRoute } from 'next';\n\nimport {\n DEFAULT_FEED_REVALIDATE,\n DEFAULT_INDEX_REVALIDATE,\n STATIC_ID,\n} from './constants';\nimport { fetchSitemapFeed, fetchSitemapIndex } from './fetch';\nimport { decodeChunkId, encodeChunkId } from './ids';\nimport { selectedChunkIds, sourceFilter } from './select';\n\nimport type { CreateDjangoSitemapOptions, StaticRoute } from './types';\n\ninterface SitemapApi {\n generateSitemaps: () => Promise<Array<{ id: string }>>;\n sitemap: (props: { id: Promise<string> }) => Promise<MetadataRoute.Sitemap>;\n}\n\nexport function createDjangoSitemap(opts: CreateDjangoSitemapOptions): SitemapApi {\n const {\n host,\n apiUrl,\n staticRoutes = [],\n sources,\n indexRevalidate = DEFAULT_INDEX_REVALIDATE,\n feedRevalidate = DEFAULT_FEED_REVALIDATE,\n } = opts;\n\n return {\n async generateSitemaps() {\n const ids: Array<{ id: string }> = [{ id: STATIC_ID }];\n if (!apiUrl) return ids;\n const index = await fetchSitemapIndex(apiUrl, indexRevalidate);\n for (const id of selectedChunkIds(index, sources, encodeChunkId)) {\n ids.push({ id });\n }\n return ids;\n },\n\n async sitemap({ id: idPromise }) {\n const id = await idPromise;\n\n if (id === STATIC_ID) {\n return renderStatic(host, staticRoutes);\n }\n\n if (!apiUrl) return [];\n\n const { source, cursor } = decodeChunkId(id);\n // A chunk this app does not claim renders empty rather than fetching.\n // `generateSitemaps` never mints such an id, but the route stays\n // reachable by hand, and serving another tenant's URLs from a guessed\n // address would undo the filter above.\n if (!sourceFilter(sources)(source)) return [];\n\n const feed = await fetchSitemapFeed(apiUrl, source, cursor, feedRevalidate);\n return feed.entries.map((e) => ({\n url: `${host}${e.loc}`,\n lastModified: e.lastmod ? new Date(e.lastmod) : undefined,\n }));\n },\n };\n}\n\nfunction renderStatic(host: string, routes: StaticRoute[]): MetadataRoute.Sitemap {\n return routes.map((r) => ({\n url: `${host}${r.path}`,\n changeFrequency: r.changeFrequency,\n priority: r.priority,\n }));\n}\n","/**\n * createSitemapIndex — Route Handler factory for the sitemap-index XML.\n *\n * Next.js's `generateSitemaps()` API ships chunks at `/sitemap/<id>.xml`\n * but does NOT emit a single sitemap-index document. Without one,\n * robots.txt + crawlers point at a 404 and never discover the chunks.\n * This factory closes that gap: it returns a `GET` Route Handler that\n * mints a `<sitemapindex>` XML listing every chunk the matching\n * `createDjangoSitemap` would produce.\n *\n * Mount it at **`app/sitemap_index.xml/route.ts`** — NOT `sitemap.xml`.\n * `app/sitemap.ts` (where `createDjangoSitemap` lives) already owns\n * `/sitemap.xml` in Next.js's metadata routing, even when its output is\n * fan-out via `generateSitemaps()` and the singular URL serves 404.\n * Putting a route handler at `app/sitemap.xml/route.ts` throws at build\n * time: *Conflicting route and metadata at /sitemap.xml*.\n *\n * Usage — the full pattern with the matching chunk + robots factories:\n *\n * ```ts\n * // apps/<app>/app/sitemap.ts — chunks\n * import { createDjangoSitemap } from '@djangocfg/nextjs/sitemap';\n * const { generateSitemaps, sitemap } = createDjangoSitemap({ ... });\n * export { generateSitemaps, sitemap as default };\n *\n * // apps/<app>/app/sitemap_index.xml/route.ts — the index\n * import { createSitemapIndex } from '@djangocfg/nextjs/sitemap';\n * export const { GET } = createSitemapIndex({\n * host: process.env.NEXT_PUBLIC_SITE_URL!,\n * apiUrl: process.env.NEXT_PUBLIC_API_URL!,\n * });\n * export const revalidate = 600;\n *\n * // apps/<app>/app/robots.ts — point crawlers at the index URL above\n * import { createRobots } from '@djangocfg/nextjs/sitemap';\n * const host = process.env.NEXT_PUBLIC_SITE_URL!;\n * export default createRobots({\n * host,\n * sitemap: `${host}/sitemap_index.xml`,\n * });\n * ```\n *\n * Keep `host` / `apiUrl` in sync with the same app's `app/sitemap.ts`\n * — the index just enumerates the chunks the other file emits.\n */\n\nimport {\n DEFAULT_INDEX_REVALIDATE,\n STATIC_ID,\n} from './constants';\nimport { fetchSitemapIndex } from './fetch';\nimport { encodeChunkId } from './ids';\nimport { selectedChunkIds } from './select';\n\nimport type { CreateSitemapIndexOptions } from './types';\n\ninterface SitemapIndexRoute {\n GET: () => Promise<Response>;\n}\n\nexport function createSitemapIndex(opts: CreateSitemapIndexOptions): SitemapIndexRoute {\n const {\n host,\n apiUrl,\n sources,\n indexRevalidate = DEFAULT_INDEX_REVALIDATE,\n includeStatic = true,\n } = opts;\n\n return {\n async GET() {\n const ids: string[] = includeStatic ? [STATIC_ID] : [];\n\n if (apiUrl) {\n const index = await fetchSitemapIndex(apiUrl, indexRevalidate);\n ids.push(...selectedChunkIds(index, sources, encodeChunkId));\n }\n\n const lastmod = new Date().toISOString();\n const body = renderIndex(host, ids, lastmod);\n\n return new Response(body, {\n status: 200,\n headers: {\n 'content-type': 'application/xml; charset=utf-8',\n // Mirror the per-chunk revalidate so a cached CDN edge expires\n // at the same cadence as the index it points at.\n 'cache-control': `public, s-maxage=${indexRevalidate}, stale-while-revalidate=${indexRevalidate}`,\n },\n });\n },\n };\n}\n\nfunction renderIndex(host: string, ids: string[], lastmod: string): string {\n const lines: string[] = [];\n lines.push('<?xml version=\"1.0\" encoding=\"UTF-8\"?>');\n lines.push('<sitemapindex xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">');\n for (const id of ids) {\n lines.push(' <sitemap>');\n lines.push(` <loc>${escapeXml(`${host}/sitemap/${id}.xml`)}</loc>`);\n lines.push(` <lastmod>${lastmod}</lastmod>`);\n lines.push(' </sitemap>');\n }\n lines.push('</sitemapindex>');\n return lines.join('\\n');\n}\n\nfunction escapeXml(s: string): string {\n return s\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;')\n .replace(/'/g, '&apos;');\n}\n","/**\n * createRobots — factory for Next.js `app/robots.ts`.\n *\n * Emits a single `User-Agent: *` block plus a Sitemap pointer. Disallow\n * defaults cover the common cases (auth, account, API surface) — override\n * per app if needed.\n *\n * The `sitemap` default `${host}/sitemap.xml` only makes sense for the\n * single-file case. When the app uses `createDjangoSitemap`\n * (`generateSitemaps()` → chunked output) **plus** `createSitemapIndex`\n * — the canonical pairing — pass an explicit\n * `sitemap: \\`${host}/sitemap_index.xml\\`` to point crawlers at the\n * index. The default `/sitemap.xml` will serve a 404 in that setup\n * (Next.js reserves it for the metadata route but doesn't emit content\n * there).\n *\n * Usage:\n *\n * ```ts\n * // apps/<app>/app/robots.ts\n * import { createRobots } from '@djangocfg/nextjs/sitemap';\n *\n * const host = process.env.NEXT_PUBLIC_SITE_URL!;\n *\n * export default createRobots({\n * host,\n * disallow: ['/account/', '/auth', '/api/', '/apix/'],\n * sitemap: `${host}/sitemap_index.xml`, // matches createSitemapIndex\n * });\n * ```\n */\n\nimport type { MetadataRoute } from 'next';\n\nimport type { CreateRobotsOptions } from './types';\n\nconst DEFAULT_DISALLOW = ['/account/', '/auth', '/api/'];\n\nexport function createRobots(opts: CreateRobotsOptions): () => MetadataRoute.Robots {\n const { host, disallow = DEFAULT_DISALLOW, sitemap } = opts;\n return () => ({\n rules: [{ userAgent: '*', allow: '/', disallow }],\n sitemap: sitemap ?? `${host}/sitemap.xml`,\n host,\n });\n}\n","/**\n * Health Route Handler for Next.js App Router\n *\n * Usage:\n * ```tsx\n * // app/healthz/route.ts\n * import { createHealthHandler } from '@djangocfg/nextjs/health';\n *\n * export const GET = createHealthHandler({\n * version: process.env.NEXT_PUBLIC_APP_VERSION || '1.0.0',\n * checks: [\n * {\n * name: 'database',\n * check: async () => {\n * // Check database connection\n * return await checkDatabase();\n * },\n * },\n * ],\n * });\n * ```\n */\n\nimport { NextResponse } from 'next/server';\n\nimport type { HealthConfig, HealthResponse } from './types';\n\nexport function createHealthHandler(config: HealthConfig = {}) {\n const { version, checks = [], customData = {} } = config;\n\n return async function GET() {\n const startTime = Date.now();\n let status: 'ok' | 'error' = 'ok';\n const checkResults: Record<string, boolean> = {};\n\n // Run health checks\n if (checks.length > 0) {\n for (const check of checks) {\n try {\n const result = await check.check();\n checkResults[check.name] = result;\n if (!result) {\n status = 'error';\n }\n } catch {\n checkResults[check.name] = false;\n status = 'error';\n }\n }\n }\n\n const response: HealthResponse = {\n status,\n timestamp: new Date().toISOString(),\n uptime: process.uptime(),\n ...(version && { version }),\n ...(checks.length > 0 && {\n checks: checkResults,\n durationMs: Date.now() - startTime,\n }),\n ...customData,\n };\n\n const statusCode = status === 'ok' ? 200 : 503;\n\n return NextResponse.json(response, { status: statusCode });\n };\n}\n","/**\n * Navigation Utilities\n *\n * Common utilities for route definitions and navigation\n */\n\nimport type { RouteDefinition, RouteMetadata, MenuItem, NavGroup, NavGroupWithRoutes } from './types';\n\n// ─────────────────────────────────────────────────────────────────────────\n// Route Definition Helper\n// ─────────────────────────────────────────────────────────────────────────\n\n/**\n * Define a route with metadata\n *\n * IMPORTANT: Next.js automatically handles basePath for <Link> components when\n * basePath is set in next.config.ts. We should NOT add basePath manually here,\n * as it would cause double-prefixing in static builds.\n *\n * @param path - Route path (e.g., '/dashboard', '/admin/users')\n * @param metadata - Route metadata (label, icon, etc.)\n */\nexport function defineRoute(\n path: string,\n metadata: RouteMetadata,\n): RouteDefinition {\n // Always return path as-is. Next.js will handle basePath automatically\n // for <Link> components when basePath is set in next.config.ts\n return {\n path,\n metadata,\n };\n}\n\n// ─────────────────────────────────────────────────────────────────────────\n// Route Guards\n// ─────────────────────────────────────────────────────────────────────────\n\n/**\n * Get redirect path for unauthenticated users\n */\nexport function getUnauthenticatedRedirect(\n path: string,\n authPath: string = '/auth',\n): string | null {\n if (path.startsWith('/private') || path.startsWith('/admin')) {\n // Return path as-is. Next.js will handle basePath automatically for router.push()\n // when basePath is set in next.config.ts\n return authPath;\n }\n return null;\n}\n\n/**\n * Get redirect path to auth page\n */\nexport function redirectToAuth(\n authPath: string = '/auth',\n): string {\n // Return path as-is. Next.js will handle basePath automatically for router.push()\n // when basePath is set in next.config.ts\n return authPath;\n}\n\n// ─────────────────────────────────────────────────────────────────────────\n// Route Lookup\n// ─────────────────────────────────────────────────────────────────────────\n\nexport function findRoute(\n routes: RouteDefinition[],\n path: string\n): RouteDefinition | undefined {\n return routes.find((r) => r.path === path);\n}\n\nexport function findRouteByPattern(\n routes: RouteDefinition[],\n path: string\n): RouteDefinition | undefined {\n const exact = findRoute(routes, path);\n if (exact) return exact;\n\n const segments = path.split('/').filter(Boolean);\n for (let i = segments.length; i > 0; i--) {\n const parentPath = '/' + segments.slice(0, i).join('/');\n const parent = findRoute(routes, parentPath);\n if (parent) return parent;\n }\n\n return undefined;\n}\n\n// ─────────────────────────────────────────────────────────────────────────\n// Page Title\n// ─────────────────────────────────────────────────────────────────────────\n\nexport function getPageTitle(\n routes: RouteDefinition[],\n path: string,\n fallback = 'Dashboard'\n): string {\n const route = findRouteByPattern(routes, path);\n return route?.metadata.label || fallback;\n}\n\n// ─────────────────────────────────────────────────────────────────────────\n// Active Route\n// ─────────────────────────────────────────────────────────────────────────\n\n/**\n * Check if a route is active\n *\n * @param current - Current pathname\n * @param target - Target route path to check\n * @param allRoutes - Optional array of all routes to prevent parent paths from being active when child paths are active\n * @returns true if the route is active\n */\nexport function isActive(\n current: string,\n target: string,\n allRoutes?: RouteDefinition[]\n): boolean {\n const matches =\n current === target || (target !== '/' && current.startsWith(target + '/'));\n\n // If allRoutes is provided, check for more specific paths\n if (matches && allRoutes) {\n return !allRoutes.some(\n (otherRoute) =>\n otherRoute.path !== target &&\n otherRoute.path.startsWith(target + '/') &&\n (current === otherRoute.path ||\n current.startsWith(otherRoute.path + '/'))\n );\n }\n\n return matches;\n}\n\n// ─────────────────────────────────────────────────────────────────────────\n// Menu Generation Helper\n// ─────────────────────────────────────────────────────────────────────────\n\n/**\n * Filter and convert routes to menu items\n */\nexport function routesToMenuItems(\n routes: RouteDefinition[],\n groupName: string\n): MenuItem[] {\n return routes\n .filter(\n (r) =>\n r.metadata.group === groupName &&\n r.metadata.icon &&\n (r.metadata.show === undefined || r.metadata.show === true)\n )\n .sort((a, b) => (a.metadata.order || 0) - (b.metadata.order || 0))\n .map((r) => ({\n path: r.path,\n label: r.metadata.label,\n icon: r.metadata.icon!,\n ...(r.metadata.badge !== undefined && { badge: r.metadata.badge }),\n }));\n}\n\n// ─────────────────────────────────────────────────────────────────────────\n// Navigation Groups Helper\n// ─────────────────────────────────────────────────────────────────────────\n\n/**\n * Group routes by navigation groups\n *\n * @param routes - Array of route definitions\n * @param groups - Array of navigation group definitions\n * @returns Array of groups with their routes, filtered by dynamic flag\n *\n * @example\n * ```ts\n * const navGroups = [\n * { id: 'dashboard', label: 'Dashboard', order: 1 },\n * { id: 'extensions', label: 'Extensions', order: 2, dynamic: true },\n * ];\n *\n * const groupedRoutes = groupRoutesByNavGroups(allRoutes, navGroups);\n * // Returns groups with routes, hiding dynamic groups with no routes\n * ```\n */\nexport function groupRoutesByNavGroups(\n routes: RouteDefinition[],\n groups: NavGroup[]\n): NavGroupWithRoutes[] {\n return groups\n .map((group) => ({\n ...group,\n routes: routes\n .filter(\n (r) =>\n r.metadata.group === group.id &&\n (r.metadata.show === undefined || r.metadata.show === true)\n )\n .sort((a, b) => (a.metadata.order || 0) - (b.metadata.order || 0)),\n }))\n .filter((group) => {\n // Dynamic groups are only shown if they have routes\n if (group.dynamic) {\n return group.routes.length > 0;\n }\n // Non-dynamic groups are always shown\n return true;\n })\n .sort((a, b) => a.order - b.order);\n}\n\n","/**\n * i18n Plugin for Next.js Configuration\n *\n * Wraps next-intl plugin for use with createBaseNextConfig\n *\n * @example\n * ```ts\n * // next.config.ts\n * import { createBaseNextConfig } from '@djangocfg/nextjs/config';\n *\n * export default createBaseNextConfig({\n * i18n: {\n * locales: ['en', 'ru', 'ko'],\n * defaultLocale: 'en',\n * },\n * });\n * ```\n */\n\nimport createNextIntlPlugin from 'next-intl/plugin';\n\nimport type { NextConfig } from 'next';\nimport type { I18nPluginOptions } from './types';\n\nexport type { I18nPluginOptions } from './types';\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Plugin Factory\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Create next-intl plugin wrapper\n *\n * @param options - i18n configuration options\n * @returns Next.js config wrapper function\n */\nexport function createI18nPlugin(options?: I18nPluginOptions) {\n // Determine request config path\n const requestConfigPath = options?.requestConfig\n ?? './i18n/request.ts';\n\n // Create next-intl plugin\n const withNextIntl = createNextIntlPlugin(requestConfigPath);\n\n return withNextIntl;\n}\n\n/**\n * Apply i18n plugin to Next.js config\n *\n * @param config - Base Next.js config\n * @param options - i18n options\n * @returns Modified Next.js config\n */\nexport function withI18n(\n config: NextConfig,\n options?: I18nPluginOptions\n): NextConfig {\n const plugin = createI18nPlugin(options);\n return plugin(config);\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Export for direct use\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport { createNextIntlPlugin };\n","/**\n * PWA (Progressive Web App) Plugin — dependency-free.\n *\n * History: this used to wrap `@serwist/next`. Serwist is a **webpack** plugin,\n * and Next.js 16 defaults to Turbopack, which does not run webpack loaders and\n * chokes on serwist's `fileURLToPath(new URL(\".\", import.meta.url))` during\n * static tracing — failing the whole build even when PWA was never enabled\n * (the mere `require('@serwist/next')` reference was enough). Since no app in\n * this monorepo actually turned PWA on (`withPWA` is opt-in and unused), we\n * dropped serwist entirely.\n *\n * What remains:\n * - `createManifest` (in `./manifest`) — the web app manifest, which Next.js\n * supports natively via `app/manifest.ts`; needs no library at all.\n * - `withPWA` — a thin, Turbopack-safe pass-through kept for API stability.\n * - `getServiceWorkerTemplate` — a vanilla service worker you can drop into\n * `app/sw.ts`, no framework runtime required.\n *\n * If you want a full precache/offline story back, wire a real service worker\n * (the template below, or Workbox) yourself — but do it in an app, not here, so\n * one app's PWA choice can't break every consumer's build.\n */\n\nimport type { NextConfig } from 'next';\n\nexport interface PWAPluginOptions {\n /**\n * Path to service worker source file (relative to project root).\n * @default 'app/sw.ts'\n */\n swSrc?: string;\n\n /**\n * Destination for the compiled service worker.\n * @default 'public/sw.js'\n */\n swDest?: string;\n\n /**\n * Disable PWA completely.\n * @default true in development / static export, false otherwise\n */\n disable?: boolean;\n\n /** Cache pages on navigation. Consumed by a custom SW, if any. @default true */\n cacheOnNavigation?: boolean;\n\n /** Reload the app when the device comes back online. @default true */\n reloadOnOnline?: boolean;\n}\n\n/**\n * Turbopack-safe PWA pass-through.\n *\n * This no longer injects a build plugin — Next.js's native manifest support\n * plus a hand-written `app/sw.ts` (see {@link getServiceWorkerTemplate}) cover\n * the PWA surface without a webpack-only dependency. Kept as a named export so\n * existing `withPWA(config)` / `createBaseNextConfig({ pwa })` call sites keep\n * type-checking and behave predictably (they return the config unchanged).\n *\n * @example\n * ```ts\n * import { createBaseNextConfig } from '@djangocfg/nextjs/config';\n * export default createBaseNextConfig({ pwa: { swSrc: 'app/sw.ts' } });\n * ```\n */\nexport function withPWA(\n nextConfig: NextConfig,\n _options: PWAPluginOptions = {}\n): NextConfig {\n return nextConfig;\n}\n\n/**\n * Vanilla service-worker template — no library.\n *\n * Returns ready-to-use, framework-free service worker source. Copy it into\n * `app/sw.ts` (or generate it) if you want offline support. It uses only the\n * standard Service Worker + Cache APIs, so it builds under Turbopack with no\n * extra dependency.\n *\n * @example\n * ```ts\n * import { getServiceWorkerTemplate } from '@djangocfg/nextjs/config';\n * console.log(getServiceWorkerTemplate()); // → paste into app/sw.ts\n * ```\n */\nexport function getServiceWorkerTemplate(): string {\n return `/**\n * Service Worker (vanilla) — standard Cache + Fetch APIs, no library.\n */\n\n/// <reference lib=\"webworker\" />\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ndeclare const self: any;\n\nconst CACHE = 'app-cache-v1';\nconst OFFLINE_URL = '/_offline';\n\n// Precache the offline fallback on install.\nself.addEventListener('install', (event: any) => {\n event.waitUntil(\n caches.open(CACHE).then((cache: Cache) => cache.addAll([OFFLINE_URL]))\n );\n self.skipWaiting();\n});\n\n// Drop stale caches on activate and take control immediately.\nself.addEventListener('activate', (event: any) => {\n event.waitUntil(\n caches.keys().then((keys: string[]) =>\n Promise.all(keys.filter((k) => k !== CACHE).map((k) => caches.delete(k)))\n )\n );\n self.clients.claim();\n});\n\n// Network-first for navigations, with an offline fallback.\nself.addEventListener('fetch', (event: any) => {\n const { request } = event;\n if (request.mode !== 'navigate') return;\n event.respondWith(\n fetch(request).catch(() =>\n caches.match(OFFLINE_URL).then((r: Response | undefined) => r ?? Response.error())\n )\n );\n});\n`;\n}\n","/**\n * Shared Constants for DjangoCFG Next.js Configuration\n */\n\n// Package name for version checks\nexport const PACKAGE_NAME = '@djangocfg/nextjs';\n\n// Version cache TTL (1 hour)\nexport const VERSION_CACHE_TTL_MS = 60 * 60 * 1000;\n\n// ASCII Art Banner for Django CFG\nexport const DJANGO_CFG_BANNER = `\n██████╗ ██╗ █████╗ ███╗ ██╗ ██████╗ ██████╗ ██████╗███████╗ ██████╗\n██╔══██╗ ██║██╔══██╗████╗ ██║██╔════╝ ██╔═══██╗ ██╔════╝██╔════╝██╔════╝\n██║ ██║ ██║███████║██╔██╗ ██║██║ ███╗██║ ██║ ██║ █████╗ ██║ ███╗\n██║ ██║██ ██║██╔══██║██║╚██╗██║██║ ██║██║ ██║ ██║ ██╔══╝ ██║ ██║\n██████╔╝╚█████╔╝██║ ██║██║ ╚████║╚██████╔╝╚██████╔╝ ╚██████╗██║ ╚██████╔╝\n╚═════╝ ╚════╝ ╚═╝ ╚═╝╚═╝ ╚═══╝ ╚═════╝ ╚═════╝ ╚═════╝╚═╝ ╚═════╝\n`;\n\n// All @djangocfg packages that can be updated together\nexport const DJANGOCFG_PACKAGES = [\n '@djangocfg/ui-core',\n '@djangocfg/layouts',\n '@djangocfg/nextjs',\n '@djangocfg/api',\n '@djangocfg/eslint-config',\n '@djangocfg/typescript-config',\n] as const;\n\n// Default packages to transpile\n// Required for proper RSC (React Server Components) handling.\n//\n// Every widget is here because widgets ship SOURCE — `main` points at\n// `./src/index.ts`, there is no build step, and an untranspiled `.ts` in\n// node_modules is a syntax error at the app's first import, not a warning.\nexport const DEFAULT_TRANSPILE_PACKAGES = [\n '@djangocfg/i18n',\n '@djangocfg/ui-core',\n '@djangocfg/layouts',\n '@djangocfg/api',\n '@djangocfg/analytics',\n '@djangocfg/devtools',\n '@djangocfg/payments',\n '@djangocfg/widget-agent',\n '@djangocfg/widget-avatar',\n '@djangocfg/widget-charts',\n '@djangocfg/widget-chat',\n '@djangocfg/widget-code',\n '@djangocfg/widget-data',\n '@djangocfg/widget-diagram',\n '@djangocfg/widget-editor',\n '@djangocfg/widget-forms',\n '@djangocfg/widget-kit',\n '@djangocfg/widget-map',\n '@djangocfg/widget-media',\n '@djangocfg/widget-ogimage',\n '@djangocfg/widget-overlay',\n '@djangocfg/widget-player',\n '@djangocfg/widget-visual',\n] as const;\n\n// Default packages to optimize imports\nexport const DEFAULT_OPTIMIZE_PACKAGES = [\n '@djangocfg/ui-core',\n '@djangocfg/layouts',\n 'lucide-react',\n 'recharts',\n] as const;\n","/**\n * CSP Vendor Presets\n *\n * The problem this solves: every third-party script a DjangoCFG app loads needs\n * its origin named in the CSP, and until now each app had to hand-list those\n * origins (`csp: { scriptSrc: [...] }`) in its own `next.config.ts`. That\n * repeats the same Stripe/Cloudflare/Google origin strings across every app,\n * and — worse — nobody remembers which directive a given vendor needs, so the\n * usual outcome is a production-only console error and a scramble.\n *\n * Instead, apps declare WHAT THEY USE by name (`csp: { vendors: ['stripe'] }`)\n * and this registry owns WHICH ORIGINS that implies. When a vendor changes a\n * host, it is fixed once here and every app inherits the fix on its next build.\n *\n * Adding a vendor: add an entry below, keyed by a short stable name, listing\n * only the directives it genuinely needs. Keep the origins exact — never widen\n * a directive to a bare `https:` to make a vendor work.\n */\n\n/** The CSP directives a vendor preset may contribute origins to. */\nexport interface CspOrigins {\n /** `script-src` — the vendor's SDK/tag `<script>` hosts. */\n scriptSrc?: string[];\n /** `frame-src` — hosts the vendor mounts iframes from. */\n frameSrc?: string[];\n /** `connect-src` — hosts the vendor makes XHR/fetch/WebSocket calls to. */\n connectSrc?: string[];\n /** `img-src` — hosts the vendor loads images/pixels from. */\n imgSrc?: string[];\n /** `font-src` — hosts the vendor loads web fonts from. */\n fontSrc?: string[];\n}\n\n/**\n * Known third-party vendors and the CSP origins each one requires.\n *\n * Every entry is OFF by default — an app opts in via `csp.vendors`, so an app\n * that mounts nothing third-party keeps the tightest possible policy.\n */\nexport const CSP_VENDOR_PRESETS = {\n /**\n * Stripe.js / Checkout / Elements.\n *\n * `js.stripe.com` serves the SDK and also hosts the Elements iframes;\n * `checkout.stripe.com` is the hosted Checkout iframe; `api.stripe.com`\n * takes the tokenization/confirm XHR. Without all three the payment form\n * silently fails to render.\n */\n stripe: {\n scriptSrc: ['https://js.stripe.com'],\n frameSrc: ['https://js.stripe.com', 'https://checkout.stripe.com'],\n connectSrc: ['https://api.stripe.com'],\n },\n\n /**\n * Cloudflare Web Analytics (the RUM beacon).\n *\n * IMPORTANT: this script is NOT in the app's own HTML — when Web Analytics\n * is enabled on the zone, Cloudflare INJECTS `beacon.min.js` into the\n * response at the edge, after Next.js has already emitted the CSP header.\n * So any app served through a Cloudflare zone with that feature on will\n * violate its own CSP unless this preset is enabled, no matter what the\n * source tree contains. The beacon also POSTs its payload back to\n * `cloudflareinsights.com`, hence the `connect-src` entry.\n */\n cloudflare: {\n scriptSrc: ['https://static.cloudflareinsights.com'],\n connectSrc: ['https://cloudflareinsights.com', 'https://static.cloudflareinsights.com'],\n },\n\n /** Google Analytics 4 / gtag.js. */\n 'google-analytics': {\n scriptSrc: ['https://www.googletagmanager.com', 'https://www.google-analytics.com'],\n connectSrc: [\n 'https://www.google-analytics.com',\n 'https://analytics.google.com',\n 'https://region1.google-analytics.com',\n ],\n imgSrc: ['https://www.google-analytics.com', 'https://www.googletagmanager.com'],\n },\n\n /** Google Tag Manager (the container itself; tags it loads may need more). */\n 'google-tag-manager': {\n scriptSrc: ['https://www.googletagmanager.com'],\n connectSrc: ['https://www.googletagmanager.com'],\n imgSrc: ['https://www.googletagmanager.com'],\n frameSrc: ['https://www.googletagmanager.com'],\n },\n\n /** Google Fonts (stylesheet host + the font files themselves). */\n 'google-fonts': {\n fontSrc: ['https://fonts.gstatic.com'],\n },\n\n /** Sentry browser SDK — errors/traces are POSTed to the ingest host. */\n sentry: {\n connectSrc: ['https://*.ingest.sentry.io', 'https://*.sentry.io'],\n },\n\n /** YouTube embeds (privacy-enhanced host included). */\n youtube: {\n frameSrc: ['https://www.youtube.com', 'https://www.youtube-nocookie.com'],\n imgSrc: ['https://i.ytimg.com'],\n },\n\n /** Vimeo embeds. */\n vimeo: {\n frameSrc: ['https://player.vimeo.com'],\n imgSrc: ['https://i.vimeocdn.com'],\n },\n} as const satisfies Record<string, CspOrigins>;\n\n/** Name of a vendor preset in {@link CSP_VENDOR_PRESETS}. */\nexport type CspVendor = keyof typeof CSP_VENDOR_PRESETS;\n\n/**\n * Vendors applied to every app unless explicitly disabled.\n *\n * The rule for this list: an origin belongs here when the app CANNOT DECLARE IT\n * FROM `next.config.ts`, because whatever turns the script on lives somewhere\n * the config file cannot read. Leaving such a vendor opt-in does not produce a\n * build error or a dev warning — it produces a production-only console error\n * that nobody sees until a user reports a broken page.\n *\n * - `cloudflare` — the Web Analytics beacon is rewritten into the HTML AT THE\n * EDGE, after Next.js has already sent the CSP header. Nothing in the source\n * tree reveals it. (Observed 2026-07-22: all six DjangoCFG-served hosts were\n * violating their own CSP.)\n *\n * - `google-analytics` — gtag.js is injected AT RUNTIME by `@djangocfg/layouts`\n * (`BaseApp analytics={{ googleTrackingId }}` → the lazy `ga4()` transport).\n * That id is an ordinary TS value in the app's `settings.ts`, not an env var,\n * so `next.config.ts` — a different module, evaluated in a different process,\n * before any of that code runs — has no way to see whether GA is on. The app\n * author enables analytics in one file and silently breaks CSP in another.\n * (Observed 2026-07-23: cmdop `apps/web` blocked on\n * `googletagmanager.com/gtag/js?id=G-YJ0F48S83F`.)\n *\n * - `youtube` / `vimeo` — these existed for `VideoPlayer`\n * (`@djangocfg/ui-tools`), which mounted the iframes AT RUNTIME from a URL\n * inside a chat message. That player was retired with `ui-tools`;\n * `@djangocfg/widget-player` embeds no third-party frames, so **nothing in\n * this repo needs these presets today**. They are kept because the shape of\n * the problem outlives the component: an app author enables a chat, and\n * whether a user ever pastes a YouTube link is not knowable at build time,\n * so no `next.config.ts` can declare it honestly.\n *\n * Note what these have in common: the script/frame is turned on by the RUNTIME\n * (the edge, a package, or an end user), never by the app author editing the\n * config. Stripe is the counter-example and deliberately NOT here — a developer\n * decides to mount a payment form, knows they did, and can say `vendors:\n * ['stripe']`; putting it in the baseline would hand `frame-src js.stripe.com`\n * to marketing sites that never take a payment.\n *\n * Do NOT add ordinary third parties here — anything an app knowingly mounts and\n * can name in its own config belongs in `csp.vendors`, so unrelated apps keep\n * the tighter policy. The cost of an entry here is that every app permits these\n * hosts; that is only worth paying when the alternative is silent breakage.\n */\nexport const CSP_BASELINE_VENDORS: CspVendor[] = [\n 'cloudflare',\n 'google-analytics',\n 'youtube',\n 'vimeo',\n];\n\n/**\n * Merge the named vendor presets into a single set of extra origins.\n *\n * Unknown names are ignored rather than thrown on: a typo must not break the\n * production build of an app whose only mistake was a misspelled analytics\n * vendor. `warnUnknown` surfaces them on the build log instead.\n */\nexport function resolveCspVendors(\n vendors: readonly string[] | undefined,\n warnUnknown = true\n): CspOrigins {\n const merged: Required<CspOrigins> = {\n scriptSrc: [],\n frameSrc: [],\n connectSrc: [],\n imgSrc: [],\n fontSrc: [],\n };\n\n if (!vendors?.length) return merged;\n\n const unknown: string[] = [];\n\n for (const name of vendors) {\n const preset = (CSP_VENDOR_PRESETS as Record<string, CspOrigins>)[name];\n if (!preset) {\n unknown.push(name);\n continue;\n }\n for (const key of Object.keys(merged) as (keyof Required<CspOrigins>)[]) {\n const origins = preset[key];\n if (origins) merged[key].push(...origins);\n }\n }\n\n if (unknown.length && warnUnknown) {\n const known = Object.keys(CSP_VENDOR_PRESETS).join(', ');\n console.warn(\n `[djangocfg] Unknown CSP vendor(s): ${unknown.join(', ')}. Known vendors: ${known}`\n );\n }\n\n // De-duplicate so two presets sharing a host (e.g. GA + GTM both using\n // googletagmanager.com) don't emit it twice in the header.\n for (const key of Object.keys(merged) as (keyof Required<CspOrigins>)[]) {\n merged[key] = [...new Set(merged[key])];\n }\n\n return merged;\n}\n","/**\n * Compression Plugin Setup\n *\n * Adds Gzip and Brotli compression for static builds.\n */\n\nimport type { Configuration as WebpackConfig } from 'webpack';\nimport { createRequire } from 'module';\n\nimport { isPackageInstalled } from '../packages/checker';\n\n// Optional peer dependencies are loaded synchronously at config time, which\n// `import()` cannot do. `createRequire` is the ESM-safe equivalent of `require`.\nconst requirePeer = createRequire(import.meta.url);\n\nexport interface CompressionPluginOptions {\n /** Enable gzip compression */\n gzip?: boolean;\n /** Enable brotli compression */\n brotli?: boolean;\n /** Minimum file size to compress (default: 8192) */\n threshold?: number;\n /** Minimum compression ratio (default: 0.8) */\n minRatio?: number;\n /** Brotli compression level (default: 8) */\n brotliLevel?: number;\n}\n\nconst DEFAULT_OPTIONS: Required<CompressionPluginOptions> = {\n gzip: true,\n brotli: true,\n threshold: 8192,\n minRatio: 0.8,\n brotliLevel: 8,\n};\n\n/**\n * Add compression plugins to webpack config\n *\n * Returns true if plugins were added, false if compression-webpack-plugin is not installed\n */\nexport function addCompressionPlugins(\n config: WebpackConfig,\n options: CompressionPluginOptions = {}\n): boolean {\n // Check if compression-webpack-plugin is installed\n if (!isPackageInstalled('compression-webpack-plugin')) {\n return false;\n }\n\n const opts = { ...DEFAULT_OPTIONS, ...options };\n\n try {\n // Resolved at call time to avoid bundling it when it is not installed.\n const CompressionPlugin = requirePeer('compression-webpack-plugin');\n\n if (!config.plugins) {\n config.plugins = [];\n }\n\n // Gzip compression\n if (opts.gzip) {\n config.plugins.push(\n new CompressionPlugin({\n filename: '[path][base].gz',\n algorithm: 'gzip',\n test: /\\.(js|css|html|svg|json)$/,\n threshold: opts.threshold,\n minRatio: opts.minRatio,\n })\n );\n }\n\n // Brotli compression\n if (opts.brotli) {\n config.plugins.push(\n new CompressionPlugin({\n filename: '[path][base].br',\n algorithm: 'brotliCompress',\n test: /\\.(js|css|html|svg|json)$/,\n threshold: opts.threshold,\n minRatio: opts.minRatio,\n compressionOptions: {\n level: opts.brotliLevel,\n },\n })\n );\n }\n\n return true;\n } catch {\n // compression-webpack-plugin failed to load\n return false;\n }\n}\n\n/**\n * Check if compression is available\n */\nexport function isCompressionAvailable(): boolean {\n return isPackageInstalled('compression-webpack-plugin');\n}\n","/**\n * Package Checker\n *\n * Checks which optional packages are missing and need to be installed.\n */\n\nimport { createRequire } from 'module';\nimport { join } from 'path';\n\nimport { isDev, isStaticBuild } from '../utils/env';\nimport { getPackagesForContext, OPTIONAL_PACKAGES } from './definitions';\n\nimport type { PackageDefinition } from './definitions';\nexport interface MissingPackage extends PackageDefinition {\n /** Why is this package needed? */\n reason: string;\n}\n\n/**\n * Check if a package is installed in the consumer's project\n * Uses process.cwd() to check from the consumer's context, not the library's\n */\nexport function isPackageInstalled(packageName: string): boolean {\n try {\n // Create require from consumer's cwd to check their node_modules\n const consumerRequire = createRequire(join(process.cwd(), 'package.json'));\n consumerRequire.resolve(packageName);\n return true;\n } catch {\n // Fallback: try regular require (for packages hoisted to root)\n try {\n require.resolve(packageName);\n return true;\n } catch {\n return false;\n }\n }\n}\n\n/**\n * Get all missing packages for current context\n */\nexport function getMissingPackages(): MissingPackage[] {\n const neededPackages = getPackagesForContext({ isStaticBuild, isDev });\n const missing: MissingPackage[] = [];\n\n for (const pkg of neededPackages) {\n if (!isPackageInstalled(pkg.name)) {\n missing.push({\n ...pkg,\n reason: getReasonText(pkg),\n });\n }\n }\n\n return missing;\n}\n\n/**\n * Check specific packages\n */\nexport function checkPackages(packageNames: string[]): MissingPackage[] {\n const missing: MissingPackage[] = [];\n\n for (const name of packageNames) {\n if (!isPackageInstalled(name)) {\n const definition = OPTIONAL_PACKAGES.find(p => p.name === name);\n missing.push({\n name,\n description: definition?.description || 'Optional package',\n condition: definition?.condition || 'always',\n devDependency: definition?.devDependency ?? true,\n reason: definition ? getReasonText(definition) : 'Requested by configuration',\n });\n }\n }\n\n return missing;\n}\n\n/**\n * Get human-readable reason for package requirement\n */\nfunction getReasonText(pkg: PackageDefinition): string {\n switch (pkg.condition) {\n case 'static-build':\n return 'Required for static build (NEXT_PUBLIC_STATIC_BUILD=true)';\n case 'dev':\n return 'Required for development mode';\n case 'always':\n return 'Required for all builds';\n default:\n return 'Optional feature';\n }\n}\n","/**\n * Environment Variable Utilities\n */\n\nimport consola from 'consola';\n\nexport const isStaticBuild = process.env.NEXT_PUBLIC_STATIC_BUILD === 'true';\nexport const isDev = process.env.NODE_ENV === 'development';\nexport const isProduction = !isDev;\nexport const isCI = process.env.CI === 'true';\n\nexport function getBasePath(isDefaultCfgAdmin?: boolean): string {\n if (!isStaticBuild) return '';\n return isDefaultCfgAdmin ? '/cfg/admin' : '/cfg/nextjs-admin';\n}\n\n/**\n * Resolve the API origin that gets inlined into the client bundle.\n *\n * `NEXT_PUBLIC_*` is substituted at BUILD time, so a value supplied only at\n * container runtime (a compose `environment:` entry, a `docker run -e`) arrives\n * far too late — the bundle already shipped with whatever was present during\n * `next build`.\n *\n * An empty result means \"no API origin is configured\". Callers MUST treat that\n * as *do not send the request* rather than falling back to a relative path: the\n * browser resolves a relative URL against the CURRENT page, so on an\n * i18n-prefixed route `/cfg/accounts/profile` silently becomes\n * `https://site.com/<locale>/cfg/accounts/profile` — a 404 that reproduces only\n * in production. Use {@link hasApiUrl} to gate those call sites.\n *\n * The one case where empty legitimately means \"relative\": a static build served\n * from the same origin as Django.\n */\nexport function getApiUrl(): string {\n return isStaticBuild ? '' : (process.env.NEXT_PUBLIC_API_URL || '');\n}\n\n/**\n * Whether a usable API origin is configured for this build.\n *\n * Gate every network call that targets Django on this. An app with no backend\n * (a pure marketing site) then needs no configuration at all — its optional\n * transports simply stay dormant instead of firing requests at the page's own\n * origin.\n *\n * A static build is same-origin by design, so relative paths ARE the intent\n * there and this reports true despite the empty string.\n */\nexport function hasApiUrl(): boolean {\n return isStaticBuild || Boolean(process.env.NEXT_PUBLIC_API_URL);\n}\n\n/**\n * Warn once, at build time, when no API origin is configured.\n *\n * Not an error: an app with no backend is a legitimate configuration. But the\n * same empty value also appears when a build *should* have had an API URL and\n * lost it — a `.dockerignore` rule stripping `.env.production`, or a value\n * supplied only through a runtime `environment:` entry. Both produce a bundle\n * whose API-backed features are silently inert, so say so while the build log\n * is still being read.\n */\nexport function warnIfApiUrlMissing(): void {\n if (hasApiUrl() || isDev) return;\n\n consola.warn(\n 'NEXT_PUBLIC_API_URL is not set — API-backed features are disabled in this build.\\n' +\n 'Requests that need it are skipped rather than sent to a relative path.\\n' +\n 'If this app DOES have a backend, the value must be present at BUILD time:\\n' +\n ' • commit it to the app\\'s `.env.production`, and check no `.dockerignore`\\n' +\n ' rule (e.g. `**/.env*`) strips that file from the build context;\\n' +\n ' • or pass it as a Dockerfile ARG/ENV before the build step.\\n' +\n 'A compose `environment:` entry is too late — it only exists at runtime.',\n );\n}\n\nexport function getSiteUrl(): string {\n return isStaticBuild ? '' : (process.env.NEXT_PUBLIC_SITE_URL || '');\n}\n","/**\n * Package Definitions\n *\n * Defines optional packages that can be auto-installed when needed.\n */\n\nexport interface PackageDefinition {\n /** npm package name */\n name: string;\n /** Human-readable description */\n description: string;\n /** When is this package needed? */\n condition: 'static-build' | 'dev' | 'always';\n /** Is this a dev dependency? */\n devDependency: boolean;\n /** Feature flag that enables this package */\n featureFlag?: string;\n}\n\n/**\n * Optional packages that can be auto-installed\n */\nexport const OPTIONAL_PACKAGES: PackageDefinition[] = [\n {\n name: 'compression-webpack-plugin',\n description: 'Gzip and Brotli compression for static builds',\n condition: 'static-build',\n devDependency: true,\n },\n {\n name: '@next/bundle-analyzer',\n description: 'Bundle analyzer for analyzing build output',\n condition: 'dev',\n devDependency: true,\n featureFlag: 'ANALYZE',\n },\n];\n\n/**\n * Required peer dependencies (should already be installed)\n */\nexport const PEER_DEPENDENCIES = [\n 'next',\n 'react',\n 'react-dom',\n] as const;\n\n/**\n * Get packages needed for current build context\n */\nexport function getPackagesForContext(options: {\n isStaticBuild: boolean;\n isDev: boolean;\n}): PackageDefinition[] {\n return OPTIONAL_PACKAGES.filter(pkg => {\n switch (pkg.condition) {\n case 'static-build':\n return options.isStaticBuild;\n case 'dev':\n return options.isDev;\n case 'always':\n return true;\n default:\n return false;\n }\n });\n}\n","/**\n * Dev Startup Webpack Plugin\n *\n * Handles banner display, version checking, and package updates.\n */\n\nimport type { Compiler } from 'webpack';\nimport chalk from 'chalk';\nimport { existsSync } from 'fs';\nimport { join } from 'path';\n\nimport { AI_DOCS_HINT } from '../../ai/constants';\nimport { DJANGO_CFG_BANNER } from '../constants';\nimport { checkAndInstallPackages } from '../packages/installer';\nimport { checkAndUpdatePackages } from '../packages/updater';\nimport { getCurrentVersion } from '../utils/version';\n\n// Track if startup tasks were already run (persists across HMR)\nlet startupDone = false;\n\nexport interface DevStartupPluginOptions {\n /** Check for missing optional packages (default: true) */\n checkPackages?: boolean;\n /** Auto-install missing packages without prompting */\n autoInstall?: boolean;\n /** Check for @djangocfg/* package updates (default: true) */\n checkUpdates?: boolean;\n /** Auto-update packages without prompting */\n autoUpdate?: boolean;\n /** Force check workspace:* packages (for testing in monorepo) */\n forceCheckWorkspace?: boolean;\n}\n\n/**\n * Webpack plugin for dev startup tasks\n */\nexport class DevStartupPlugin {\n private options: DevStartupPluginOptions;\n\n constructor(options: DevStartupPluginOptions = {}) {\n this.options = options;\n }\n\n apply(compiler: Compiler): void {\n // Use tapPromise for proper async handling\n compiler.hooks.done.tapPromise('DevStartupPlugin', async () => {\n // Run startup tasks only once\n if (!startupDone) {\n startupDone = true;\n await this.runStartupTasks();\n }\n });\n }\n\n private async runStartupTasks(): Promise<void> {\n // 1. Print banner\n console.log('\\n' + chalk.yellowBright.bold(DJANGO_CFG_BANNER));\n\n // 2. Print current version\n const version = getCurrentVersion();\n if (version) {\n console.log(chalk.dim(` 📦 @djangocfg/nextjs v${version}`));\n }\n\n // 3. Check PWA setup\n this.checkPWASetup();\n\n // 4. Print AI docs hint\n console.log(chalk.magenta(` ${AI_DOCS_HINT}\\n`));\n\n // 5. Check for package updates\n if (this.options.checkUpdates !== false) {\n try {\n await checkAndUpdatePackages({\n autoUpdate: this.options.autoUpdate,\n forceCheckWorkspace: this.options.forceCheckWorkspace,\n force: true, // Force check ignoring cooldown\n });\n } catch (err) {\n console.log(chalk.red(' Update check failed:'), err);\n }\n }\n\n // 6. Check for missing optional packages\n if (this.options.checkPackages !== false) {\n await checkAndInstallPackages({\n autoInstall: this.options.autoInstall,\n });\n }\n }\n\n private checkPWASetup(): void {\n const cwd = process.cwd();\n const swPath = join(cwd, 'app', 'sw.ts');\n const manifestPath = join(cwd, 'app', 'manifest.ts');\n\n const hasSW = existsSync(swPath);\n const hasManifest = existsSync(manifestPath);\n\n if (hasSW || hasManifest) {\n console.log(chalk.cyan(' 📱 PWA Configuration:'));\n\n if (hasSW) {\n console.log(chalk.green(' ✓ Service Worker: app/sw.ts'));\n } else {\n console.log(chalk.yellow(' ⚠ Service Worker: not found'));\n }\n\n if (hasManifest) {\n console.log(chalk.green(' ✓ Manifest: app/manifest.ts'));\n } else {\n console.log(chalk.yellow(' ⚠ Manifest: not found'));\n }\n\n console.log(chalk.dim(' → Check: DevTools → Application → Service Workers'));\n console.log(chalk.dim(' → Test push: import from @djangocfg/nextjs/pwa'));\n }\n }\n}\n\n/**\n * Reset plugin state (useful for tests)\n */\nexport function resetDevStartupState(): void {\n startupDone = false;\n}\n","/**\n * AI Documentation Constants\n */\n\n// MCP Server URLs\nexport const MCP_BASE_URL = 'https://mcp.djangocfg.com';\nexport const MCP_SERVER_URL = `${MCP_BASE_URL}/mcp`;\nexport const MCP_API_URL = `${MCP_BASE_URL}/api/search`;\n\n// API Endpoints\nexport const API_SEARCH_ENDPOINT = '/api/search';\nexport const API_INFO_ENDPOINT = '/api/info';\nexport const DEFAULT_TIMEOUT = 10000; // ms\n\n// AI Hint for console output\nexport const AI_DOCS_HINT = `🤖 AI Docs: ${MCP_SERVER_URL} | pnpm ai-docs search \"query\"`;\n\n// Full AI Hint description\nexport const AI_HINT = `\nDjangoCFG Documentation is available via MCP server.\n\nTo get help with DjangoCFG configuration:\n1. Use the MCP server: ${MCP_SERVER_URL}\n2. Or search directly: ${MCP_API_URL}?q=YOUR_QUERY&limit=5\n\nExample queries:\n- \"How to configure PostgreSQL database?\"\n- \"What is DatabaseConfig?\"\n- \"How to setup Redis cache?\"\n- \"Email configuration with SMTP\"\n`;\n","/**\n * Package Installer\n *\n * Provides auto-installation functionality with user confirmation and progress indication.\n */\n\nimport chalk from 'chalk';\nimport { execSync, spawn } from 'child_process';\nimport Conf from 'conf';\nimport consola from 'consola';\nimport { existsSync } from 'fs';\nimport { join } from 'path';\nimport { createInterface } from 'readline';\n\nimport { isCI } from '../utils/env';\nimport { getMissingPackages } from './checker';\n\nimport type { MissingPackage } from './checker';\ntype InstallerCacheShape = {\n autoInstall?: boolean;\n skipPackages?: string[];\n lastPrompt?: number;\n};\n\ninterface CacheLike<T extends Record<string, unknown>> {\n get<K extends keyof T>(key: K): T[K] | undefined;\n set<K extends keyof T>(key: K, value: T[K]): void;\n clear(): void;\n}\n\nfunction createMemoryCache<T extends Record<string, unknown>>(): CacheLike<T> {\n const store = new Map<string, unknown>();\n return {\n get<K extends keyof T>(key: K): T[K] | undefined {\n return store.get(String(key)) as T[K] | undefined;\n },\n set<K extends keyof T>(key: K, value: T[K]): void {\n store.set(String(key), value);\n },\n clear(): void {\n store.clear();\n },\n };\n}\n\nfunction createInstallerCache(): CacheLike<InstallerCacheShape> {\n try {\n return new Conf<InstallerCacheShape>({\n projectName: 'djangocfg-nextjs-installer',\n projectVersion: '1.0.0',\n });\n } catch {\n // Fallback for corrupted conf file or invalid JSON in cache storage.\n return createMemoryCache<InstallerCacheShape>();\n }\n}\n\n// Installer preferences cache\nconst installerCache = createInstallerCache();\n\n// Don't prompt more than once per hour\nconst PROMPT_COOLDOWN_MS = 60 * 60 * 1000;\n\n// Spinner frames for progress\nconst SPINNER_FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];\n\nexport interface InstallOptions {\n /** Auto-install without prompting */\n autoInstall?: boolean;\n /** Skip specific packages */\n skipPackages?: string[];\n /** Force prompt even if recently prompted */\n force?: boolean;\n}\n\nexport interface InstallProgress {\n current: number;\n total: number;\n package: string;\n status: 'pending' | 'installing' | 'done' | 'error';\n}\n\n/**\n * Detect package manager\n */\nexport function detectPackageManager(): 'pnpm' | 'yarn' | 'npm' {\n try {\n // Check for lockfiles\n const cwd = process.cwd();\n\n if (existsSync(join(cwd, 'pnpm-lock.yaml'))) return 'pnpm';\n if (existsSync(join(cwd, 'yarn.lock'))) return 'yarn';\n if (existsSync(join(cwd, 'package-lock.json'))) return 'npm';\n\n // Check for global package manager\n try {\n execSync('pnpm --version', { stdio: 'ignore' });\n return 'pnpm';\n } catch {\n try {\n execSync('yarn --version', { stdio: 'ignore' });\n return 'yarn';\n } catch {\n return 'npm';\n }\n }\n } catch {\n return 'npm';\n }\n}\n\n/**\n * Build install command for a single package\n */\nexport function buildSingleInstallCommand(\n packageName: string,\n isDev: boolean,\n pm: 'pnpm' | 'yarn' | 'npm'\n): string {\n const devFlag = isDev ? '-D ' : '';\n switch (pm) {\n case 'pnpm':\n return `pnpm add ${devFlag}${packageName}`;\n case 'yarn':\n return `yarn add ${devFlag}${packageName}`;\n case 'npm':\n return `npm install ${devFlag}${packageName}`;\n }\n}\n\n/**\n * Build install command for multiple packages (for display)\n */\nexport function buildInstallCommand(packages: MissingPackage[], pm: 'pnpm' | 'yarn' | 'npm'): string {\n const devPackages = packages.filter(p => p.devDependency).map(p => p.name);\n const prodPackages = packages.filter(p => !p.devDependency).map(p => p.name);\n\n const commands: string[] = [];\n\n if (devPackages.length > 0) {\n switch (pm) {\n case 'pnpm':\n commands.push(`pnpm add -D ${devPackages.join(' ')}`);\n break;\n case 'yarn':\n commands.push(`yarn add -D ${devPackages.join(' ')}`);\n break;\n case 'npm':\n commands.push(`npm install -D ${devPackages.join(' ')}`);\n break;\n }\n }\n\n if (prodPackages.length > 0) {\n switch (pm) {\n case 'pnpm':\n commands.push(`pnpm add ${prodPackages.join(' ')}`);\n break;\n case 'yarn':\n commands.push(`yarn add ${prodPackages.join(' ')}`);\n break;\n case 'npm':\n commands.push(`npm install ${prodPackages.join(' ')}`);\n break;\n }\n }\n\n return commands.join(' && ');\n}\n\n/**\n * Ask user for confirmation via readline\n */\nasync function askConfirmation(question: string): Promise<boolean> {\n // Skip prompt in CI or non-TTY environments\n if (isCI || !process.stdin.isTTY) {\n return false;\n }\n\n return new Promise((resolve) => {\n const rl = createInterface({\n input: process.stdin,\n output: process.stdout,\n });\n\n rl.question(question, (answer) => {\n rl.close();\n const normalized = answer.toLowerCase().trim();\n resolve(normalized === '' || normalized === 'y' || normalized === 'yes');\n });\n });\n}\n\n/**\n * Create a simple spinner\n */\nfunction createSpinner(text: string) {\n let frameIndex = 0;\n let interval: NodeJS.Timeout | null = null;\n let currentText = text;\n\n const render = () => {\n const frame = SPINNER_FRAMES[frameIndex];\n process.stdout.write(`\\r${chalk.cyan(frame)} ${currentText}`);\n frameIndex = (frameIndex + 1) % SPINNER_FRAMES.length;\n };\n\n return {\n start() {\n if (process.stdout.isTTY) {\n interval = setInterval(render, 80);\n render();\n } else {\n console.log(` ${currentText}`);\n }\n return this;\n },\n text(newText: string) {\n currentText = newText;\n if (!process.stdout.isTTY) {\n console.log(` ${newText}`);\n }\n return this;\n },\n succeed(text?: string) {\n if (interval) clearInterval(interval);\n if (process.stdout.isTTY) {\n process.stdout.write(`\\r${chalk.green('✓')} ${text || currentText}\\n`);\n } else {\n console.log(` ${chalk.green('✓')} ${text || currentText}`);\n }\n return this;\n },\n fail(text?: string) {\n if (interval) clearInterval(interval);\n if (process.stdout.isTTY) {\n process.stdout.write(`\\r${chalk.red('✗')} ${text || currentText}\\n`);\n } else {\n console.log(` ${chalk.red('✗')} ${text || currentText}`);\n }\n return this;\n },\n stop() {\n if (interval) clearInterval(interval);\n if (process.stdout.isTTY) {\n process.stdout.write('\\r\\x1b[K'); // Clear line\n }\n return this;\n },\n };\n}\n\n/**\n * Install a single package with progress\n */\nasync function installSinglePackage(\n pkg: MissingPackage,\n pm: 'pnpm' | 'yarn' | 'npm',\n index: number,\n total: number\n): Promise<boolean> {\n const command = buildSingleInstallCommand(pkg.name, pkg.devDependency, pm);\n const progress = `[${index + 1}/${total}]`;\n const spinner = createSpinner(`${chalk.dim(progress)} Installing ${chalk.cyan(pkg.name)}...`);\n\n spinner.start();\n\n return new Promise((resolve) => {\n // `split` returns `string[]`, so `cmd` is `string | undefined` and no\n // `spawn` overload accepts that — which typed `proc` as `never` and made\n // every `proc.*` below an error. `buildSingleInstallCommand` never returns\n // an empty string; the fallback states that rather than asserting it.\n const [cmd = pm, ...args] = command.split(' ');\n const proc = spawn(cmd, args, {\n shell: true,\n cwd: process.cwd(),\n stdio: ['ignore', 'pipe', 'pipe'],\n });\n\n let stderr = '';\n\n proc.stderr?.on('data', (data) => {\n stderr += data.toString();\n });\n\n proc.on('close', (code) => {\n if (code === 0) {\n spinner.succeed(`${chalk.dim(progress)} ${chalk.cyan(pkg.name)} ${chalk.green('installed')}`);\n resolve(true);\n } else {\n spinner.fail(`${chalk.dim(progress)} ${chalk.cyan(pkg.name)} ${chalk.red('failed')}`);\n if (stderr) {\n console.log(chalk.dim(` ${stderr.split('\\n')[0]}`));\n }\n resolve(false);\n }\n });\n\n proc.on('error', () => {\n spinner.fail(`${chalk.dim(progress)} ${chalk.cyan(pkg.name)} ${chalk.red('failed')}`);\n resolve(false);\n });\n });\n}\n\n/**\n * Install packages with progress indication\n */\nexport async function installPackagesWithProgress(packages: MissingPackage[]): Promise<boolean> {\n if (packages.length === 0) return true;\n\n const pm = detectPackageManager();\n const total = packages.length;\n\n console.log('');\n console.log(chalk.bold(` Installing ${total} package${total > 1 ? 's' : ''}...`));\n console.log('');\n\n let successCount = 0;\n const failedPackages: string[] = [];\n\n for (const [i, pkg] of packages.entries()) {\n const success = await installSinglePackage(pkg, pm, i, total);\n if (success) {\n successCount++;\n } else {\n failedPackages.push(pkg.name);\n }\n }\n\n console.log('');\n\n if (failedPackages.length === 0) {\n consola.success(`All ${total} packages installed successfully!`);\n return true;\n } else if (successCount > 0) {\n consola.warn(`${successCount}/${total} packages installed. Failed: ${failedPackages.join(', ')}`);\n return false;\n } else {\n consola.error(`Failed to install packages: ${failedPackages.join(', ')}`);\n return false;\n }\n}\n\n/**\n * Install packages (simple version without per-package progress)\n */\nexport async function installPackages(packages: MissingPackage[]): Promise<boolean> {\n // Use progress version for multiple packages in TTY\n if (packages.length > 1 && process.stdout.isTTY) {\n return installPackagesWithProgress(packages);\n }\n\n // Simple installation for single package or non-TTY\n if (packages.length === 0) return true;\n\n const pm = detectPackageManager();\n const command = buildInstallCommand(packages, pm);\n\n consola.info(`Installing: ${chalk.cyan(packages.map(p => p.name).join(', '))}`);\n\n const spinner = createSpinner('Installing packages...');\n spinner.start();\n\n return new Promise((resolve) => {\n const proc = spawn(command, {\n shell: true,\n cwd: process.cwd(),\n stdio: ['ignore', 'pipe', 'pipe'],\n });\n\n proc.on('close', (code) => {\n if (code === 0) {\n spinner.succeed('Packages installed successfully!');\n resolve(true);\n } else {\n spinner.fail('Failed to install packages');\n resolve(false);\n }\n });\n\n proc.on('error', () => {\n spinner.fail('Installation failed');\n resolve(false);\n });\n });\n}\n\n/**\n * Check and prompt for missing packages\n *\n * Returns true if all packages are available (either already installed or just installed)\n */\nexport async function checkAndInstallPackages(options: InstallOptions = {}): Promise<boolean> {\n const missing = getMissingPackages();\n\n // Filter out skipped packages\n const skipList = options.skipPackages || installerCache.get('skipPackages') || [];\n const toInstall = missing.filter(p => !skipList.includes(p.name));\n\n if (toInstall.length === 0) {\n return true;\n }\n\n // Check cooldown (don't prompt too often)\n const lastPrompt = installerCache.get('lastPrompt') || 0;\n if (!options.force && (Date.now() - lastPrompt) < PROMPT_COOLDOWN_MS) {\n // Show info but don't prompt\n printMissingPackagesInfo(toInstall);\n return false;\n }\n\n // Auto-install if configured\n if (options.autoInstall || installerCache.get('autoInstall')) {\n return installPackages(toInstall);\n }\n\n // Show missing packages\n console.log('');\n consola.box('📦 Missing Optional Packages');\n console.log('');\n\n for (const pkg of toInstall) {\n console.log(` ${chalk.yellow('•')} ${chalk.bold(pkg.name)}`);\n console.log(` ${chalk.dim(pkg.description)}`);\n console.log(` ${chalk.dim(pkg.reason)}`);\n console.log('');\n }\n\n // Build install command for display\n const pm = detectPackageManager();\n const command = buildInstallCommand(toInstall, pm);\n\n console.log(` ${chalk.cyan('Command:')} ${command}`);\n console.log('');\n\n // Ask for confirmation\n installerCache.set('lastPrompt', Date.now());\n\n const shouldInstall = await askConfirmation(\n `${chalk.green('?')} Install these packages now? ${chalk.dim('[Y/n]')} `\n );\n\n if (shouldInstall) {\n const success = await installPackages(toInstall);\n\n // Ask if user wants to enable auto-install for future\n if (success) {\n const enableAuto = await askConfirmation(\n `${chalk.green('?')} Enable auto-install for future? ${chalk.dim('[y/N]')} `\n );\n if (enableAuto) {\n installerCache.set('autoInstall', true);\n consola.info('Auto-install enabled. Run with --no-auto-install to disable.');\n }\n }\n\n return success;\n }\n\n // User declined, ask if they want to skip these packages permanently\n const skipPermanently = await askConfirmation(\n `${chalk.green('?')} Skip these packages in future prompts? ${chalk.dim('[y/N]')} `\n );\n\n if (skipPermanently) {\n const currentSkip = installerCache.get('skipPackages') || [];\n installerCache.set('skipPackages', [...currentSkip, ...toInstall.map(p => p.name)]);\n consola.info('Packages added to skip list.');\n }\n\n return false;\n}\n\n/**\n * Print info about missing packages without prompting\n */\nfunction printMissingPackagesInfo(packages: MissingPackage[]): void {\n if (packages.length === 0) return;\n\n const pm = detectPackageManager();\n const command = buildInstallCommand(packages, pm);\n\n consola.warn(`Missing optional packages: ${packages.map(p => p.name).join(', ')}`);\n consola.info(`Install with: ${chalk.cyan(command)}`);\n}\n\n/**\n * Reset installer preferences\n */\nexport function resetInstallerPreferences(): void {\n installerCache.clear();\n consola.success('Installer preferences reset');\n}\n","/**\n * Package Updater\n *\n * Checks for outdated @djangocfg/* packages and offers to update them.\n */\n\nimport chalk from 'chalk';\nimport { spawn } from 'child_process';\nimport Conf from 'conf';\nimport consola from 'consola';\nimport { existsSync, readFileSync } from 'fs';\nimport { createRequire } from 'module';\nimport { join } from 'path';\nimport { createInterface } from 'readline';\nimport semver from 'semver';\n\nimport { DJANGOCFG_PACKAGES, PACKAGE_NAME } from '../constants';\nimport { isCI } from '../utils/env';\nimport { detectPackageManager } from './installer';\n\ntype UpdaterCacheShape = {\n autoUpdate?: boolean;\n lastCheck?: number;\n skippedVersions?: Record<string, string>;\n};\n\ninterface CacheLike<T extends Record<string, unknown>> {\n get<K extends keyof T>(key: K): T[K] | undefined;\n set<K extends keyof T>(key: K, value: T[K]): void;\n clear(): void;\n}\n\nfunction createMemoryCache<T extends Record<string, unknown>>(): CacheLike<T> {\n const store = new Map<string, unknown>();\n return {\n get<K extends keyof T>(key: K): T[K] | undefined {\n return store.get(String(key)) as T[K] | undefined;\n },\n set<K extends keyof T>(key: K, value: T[K]): void {\n store.set(String(key), value);\n },\n clear(): void {\n store.clear();\n },\n };\n}\n\nfunction createUpdaterCache(): CacheLike<UpdaterCacheShape> {\n try {\n return new Conf<UpdaterCacheShape>({\n projectName: 'djangocfg-nextjs-updater',\n projectVersion: '1.0.0',\n });\n } catch {\n // Fallback for corrupted conf file or invalid JSON in cache storage.\n return createMemoryCache<UpdaterCacheShape>();\n }\n}\n\n// Updater preferences cache\nconst updaterCache = createUpdaterCache();\n\n// Check for updates once per hour\nconst UPDATE_CHECK_COOLDOWN_MS = 60 * 60 * 1000;\n\n// Spinner frames\nconst SPINNER_FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];\n\nexport interface PackageVersion {\n name: string;\n current: string | null;\n latest: string | null;\n hasUpdate: boolean;\n}\n\nexport interface UpdateOptions {\n /** Auto-update without prompting */\n autoUpdate?: boolean;\n /** Force check even if recently checked (ignores cooldown) */\n force?: boolean;\n /** Force check even for workspace:* packages (for testing) */\n forceCheckWorkspace?: boolean;\n}\n\n/**\n * Check if package is a workspace dependency\n */\nfunction isWorkspacePackage(packageName: string): boolean {\n try {\n const pkgJsonPath = join(process.cwd(), 'package.json');\n const pkgJson = JSON.parse(readFileSync(pkgJsonPath, 'utf-8'));\n\n const deps = { ...pkgJson.dependencies, ...pkgJson.devDependencies };\n const version = deps[packageName];\n\n return version?.startsWith('workspace:') || false;\n } catch {\n return false;\n }\n}\n\n/**\n * Get installed version of a package from consumer's project\n * Uses multiple fallback strategies for monorepo compatibility\n */\nexport function getInstalledVersion(packageName: string): string | null {\n const cwd = process.cwd();\n\n // Strategy 1: Direct read from node_modules (works with symlinks)\n try {\n const directPath = join(cwd, 'node_modules', packageName, 'package.json');\n if (existsSync(directPath)) {\n const pkg = JSON.parse(readFileSync(directPath, 'utf-8'));\n return pkg.version || null;\n }\n } catch {\n // Continue to next strategy\n }\n\n // Strategy 2: Resolve from the consumer's project root. `createRequire` is the\n // ESM-safe way to do this — a bare `require()` here does not exist once tsup\n // emits an .mjs bundle, and its shim throws rather than falling through to the\n // next strategy.\n try {\n const consumerRequire = createRequire(join(cwd, 'package.json'));\n const pkgPath = consumerRequire.resolve(`${packageName}/package.json`);\n return JSON.parse(readFileSync(pkgPath, 'utf-8')).version || null;\n } catch {\n // Continue to next strategy\n }\n\n // Strategy 3: Resolve relative to THIS module, for the hoisted/global case.\n try {\n const localRequire = createRequire(import.meta.url);\n const pkgPath = localRequire.resolve(`${packageName}/package.json`);\n return JSON.parse(readFileSync(pkgPath, 'utf-8')).version || null;\n } catch {\n return null;\n }\n}\n\n/**\n * Check if we should skip update checking for this package\n * (e.g., workspace packages shouldn't be checked against npm)\n */\nexport function shouldCheckUpdates(packageName: string): boolean {\n return !isWorkspacePackage(packageName);\n}\n\n/**\n * Fetch latest version from npm registry\n */\nasync function fetchLatestVersion(packageName: string): Promise<string | null> {\n try {\n const https = await import('https');\n return new Promise((resolve) => {\n const req = https.get(\n `https://registry.npmjs.org/${packageName}/latest`,\n { timeout: 5000 },\n (res) => {\n let data = '';\n res.on('data', (chunk: string) => { data += chunk; });\n res.on('end', () => {\n try {\n const json = JSON.parse(data);\n resolve(json.version || null);\n } catch {\n resolve(null);\n }\n });\n }\n );\n req.on('error', () => resolve(null));\n req.on('timeout', () => { req.destroy(); resolve(null); });\n });\n } catch {\n return null;\n }\n}\n\n/**\n * Check all @djangocfg packages for updates\n * All packages are aligned to the version of @djangocfg/nextjs (master package)\n * Skips workspace:* packages unless forceCheckWorkspace is true\n */\nexport async function checkForUpdates(options: { forceCheckWorkspace?: boolean } = {}): Promise<PackageVersion[]> {\n const results: PackageVersion[] = [];\n\n // First, get the latest version of master package (@djangocfg/nextjs)\n // All other packages should align to this version\n const masterLatest = await fetchLatestVersion(PACKAGE_NAME);\n if (!masterLatest) {\n // Can't determine target version, skip update check\n return results;\n }\n\n // Check packages against master version\n const checks = DJANGOCFG_PACKAGES.map(async (name) => {\n // Skip workspace packages unless force mode\n const isWorkspace = !shouldCheckUpdates(name);\n if (!options.forceCheckWorkspace && isWorkspace) {\n return null;\n }\n\n const current = getInstalledVersion(name);\n if (!current) {\n return null;\n }\n\n // All packages should update to master package version\n const hasUpdate = !!(masterLatest && current && semver.gt(masterLatest, current));\n\n return { name, current, latest: masterLatest, hasUpdate };\n });\n\n const checkResults = await Promise.all(checks);\n\n for (const result of checkResults) {\n if (result) {\n results.push(result);\n }\n }\n\n return results;\n}\n\n/**\n * Get packages that need updating\n */\nexport async function getOutdatedPackages(options: { forceCheckWorkspace?: boolean } = {}): Promise<PackageVersion[]> {\n const all = await checkForUpdates(options);\n const skipped = updaterCache.get('skippedVersions') || {};\n\n return all.filter(pkg => {\n if (!pkg.hasUpdate) return false;\n // Skip if user chose to skip this specific version\n if (skipped[pkg.name] === pkg.latest) return false;\n return true;\n });\n}\n\n/**\n * Create a simple spinner\n */\nfunction createSpinner(text: string) {\n let frameIndex = 0;\n let interval: NodeJS.Timeout | null = null;\n let currentText = text;\n\n const render = () => {\n const frame = SPINNER_FRAMES[frameIndex];\n process.stdout.write(`\\r${chalk.cyan(frame)} ${currentText}`);\n frameIndex = (frameIndex + 1) % SPINNER_FRAMES.length;\n };\n\n return {\n start() {\n if (process.stdout.isTTY) {\n interval = setInterval(render, 80);\n render();\n } else {\n console.log(` ${currentText}`);\n }\n return this;\n },\n text(newText: string) {\n currentText = newText;\n return this;\n },\n succeed(text?: string) {\n if (interval) clearInterval(interval);\n if (process.stdout.isTTY) {\n process.stdout.write(`\\r${chalk.green('✓')} ${text || currentText}\\n`);\n } else {\n console.log(` ${chalk.green('✓')} ${text || currentText}`);\n }\n return this;\n },\n fail(text?: string) {\n if (interval) clearInterval(interval);\n if (process.stdout.isTTY) {\n process.stdout.write(`\\r${chalk.red('✗')} ${text || currentText}\\n`);\n } else {\n console.log(` ${chalk.red('✗')} ${text || currentText}`);\n }\n return this;\n },\n stop() {\n if (interval) clearInterval(interval);\n if (process.stdout.isTTY) {\n process.stdout.write('\\r\\x1b[K');\n }\n return this;\n },\n };\n}\n\n/**\n * Ask user for confirmation\n */\nasync function askConfirmation(question: string): Promise<boolean> {\n if (isCI || !process.stdin.isTTY) {\n return false;\n }\n\n return new Promise((resolve) => {\n const rl = createInterface({\n input: process.stdin,\n output: process.stdout,\n });\n\n rl.question(question, (answer) => {\n rl.close();\n const normalized = answer.toLowerCase().trim();\n resolve(normalized === '' || normalized === 'y' || normalized === 'yes');\n });\n });\n}\n\n/**\n * Update a single package with progress\n */\nasync function updateSinglePackage(\n pkg: PackageVersion,\n pm: 'pnpm' | 'yarn' | 'npm',\n index: number,\n total: number\n): Promise<boolean> {\n const progress = `[${index + 1}/${total}]`;\n const versionInfo = `${chalk.red(pkg.current)} → ${chalk.green(pkg.latest)}`;\n const spinner = createSpinner(\n `${chalk.dim(progress)} Updating ${chalk.cyan(pkg.name)} ${versionInfo}`\n );\n\n spinner.start();\n\n // Always install with @latest to keep package.json consistent\n const command = pm === 'pnpm'\n ? `pnpm add ${pkg.name}@latest`\n : pm === 'yarn'\n ? `yarn add ${pkg.name}@latest`\n : `npm install ${pkg.name}@latest`;\n\n return new Promise((resolve) => {\n const proc = spawn(command, {\n shell: true,\n cwd: process.cwd(),\n stdio: ['ignore', 'pipe', 'pipe'],\n });\n\n proc.on('close', (code) => {\n if (code === 0) {\n spinner.succeed(\n `${chalk.dim(progress)} ${chalk.cyan(pkg.name)} ${chalk.green(pkg.latest!)}`\n );\n resolve(true);\n } else {\n spinner.fail(\n `${chalk.dim(progress)} ${chalk.cyan(pkg.name)} ${chalk.red('failed')}`\n );\n resolve(false);\n }\n });\n\n proc.on('error', () => {\n spinner.fail(`${chalk.dim(progress)} ${chalk.cyan(pkg.name)} ${chalk.red('failed')}`);\n resolve(false);\n });\n });\n}\n\n/**\n * Update packages with progress\n */\nexport async function updatePackagesWithProgress(packages: PackageVersion[]): Promise<boolean> {\n if (packages.length === 0) return true;\n\n const pm = detectPackageManager();\n const total = packages.length;\n\n console.log('');\n console.log(chalk.bold(` Updating ${total} package${total > 1 ? 's' : ''}...`));\n console.log('');\n\n let successCount = 0;\n const failedPackages: string[] = [];\n\n for (const [i, pkg] of packages.entries()) {\n const success = await updateSinglePackage(pkg, pm, i, total);\n if (success) {\n successCount++;\n } else {\n failedPackages.push(pkg.name);\n }\n }\n\n console.log('');\n\n if (failedPackages.length === 0) {\n consola.success(`All ${total} packages updated successfully!`);\n return true;\n } else if (successCount > 0) {\n consola.warn(`${successCount}/${total} packages updated. Failed: ${failedPackages.join(', ')}`);\n return false;\n } else {\n consola.error(`Failed to update packages: ${failedPackages.join(', ')}`);\n return false;\n }\n}\n\n/**\n * Check and prompt for package updates\n */\nexport async function checkAndUpdatePackages(options: UpdateOptions = {}): Promise<boolean> {\n // Check cooldown\n const lastCheck = updaterCache.get('lastCheck') || 0;\n if (!options.force && (Date.now() - lastCheck) < UPDATE_CHECK_COOLDOWN_MS) {\n return true;\n }\n\n updaterCache.set('lastCheck', Date.now());\n\n // Check for updates\n const spinner = createSpinner('Checking for updates...');\n spinner.start();\n\n const outdated = await getOutdatedPackages({\n forceCheckWorkspace: options.forceCheckWorkspace,\n });\n\n spinner.stop();\n\n console.log(chalk.dim(` Found ${outdated.length} outdated package(s)`));\n\n if (outdated.length === 0) {\n console.log(chalk.green(' ✓ All packages are up to date'));\n return true;\n }\n\n // Auto-update if configured\n if (options.autoUpdate || updaterCache.get('autoUpdate')) {\n return updatePackagesWithProgress(outdated);\n }\n\n // Show outdated packages\n console.log('');\n consola.box('📦 Updates Available');\n console.log('');\n\n for (const pkg of outdated) {\n console.log(\n ` ${chalk.yellow('•')} ${chalk.bold(pkg.name)} ` +\n `${chalk.red(pkg.current)} → ${chalk.green(pkg.latest)}`\n );\n }\n console.log('');\n\n // Ask for confirmation\n const shouldUpdate = await askConfirmation(\n `${chalk.green('?')} Update these packages now? ${chalk.dim('[Y/n]')} `\n );\n\n if (shouldUpdate) {\n const success = await updatePackagesWithProgress(outdated);\n\n if (success) {\n const enableAuto = await askConfirmation(\n `${chalk.green('?')} Enable auto-update for future? ${chalk.dim('[y/N]')} `\n );\n if (enableAuto) {\n updaterCache.set('autoUpdate', true);\n consola.info('Auto-update enabled.');\n }\n }\n\n return success;\n }\n\n // User declined, ask if they want to skip these versions\n const skipVersions = await askConfirmation(\n `${chalk.green('?')} Skip these versions in future? ${chalk.dim('[y/N]')} `\n );\n\n if (skipVersions) {\n const skipped = updaterCache.get('skippedVersions') || {};\n for (const pkg of outdated) {\n if (pkg.latest) {\n skipped[pkg.name] = pkg.latest;\n }\n }\n updaterCache.set('skippedVersions', skipped);\n consola.info('Versions skipped. Will prompt again for newer versions.');\n }\n\n return false;\n}\n\n/**\n * Reset updater preferences\n */\nexport function resetUpdaterPreferences(): void {\n updaterCache.clear();\n consola.success('Updater preferences reset');\n}\n","/**\n * Version Checking Utilities\n */\n\nimport chalk from 'chalk';\nimport Conf from 'conf';\nimport consola from 'consola';\nimport { readFileSync } from 'fs';\nimport { createRequire } from 'module';\nimport semver from 'semver';\n\nimport { DJANGOCFG_PACKAGES, PACKAGE_NAME, VERSION_CACHE_TTL_MS } from '../constants';\n\ntype VersionCacheShape = {\n latestVersion?: string;\n lastCheck?: number;\n};\n\ninterface CacheLike<T extends Record<string, unknown>> {\n get<K extends keyof T>(key: K): T[K] | undefined;\n set<K extends keyof T>(key: K, value: T[K]): void;\n clear(): void;\n}\n\nfunction createMemoryCache<T extends Record<string, unknown>>(): CacheLike<T> {\n const store = new Map<string, unknown>();\n return {\n get<K extends keyof T>(key: K): T[K] | undefined {\n return store.get(String(key)) as T[K] | undefined;\n },\n set<K extends keyof T>(key: K, value: T[K]): void {\n store.set(String(key), value);\n },\n clear(): void {\n store.clear();\n },\n };\n}\n\nfunction createVersionCache(): CacheLike<VersionCacheShape> {\n try {\n return new Conf<VersionCacheShape>({\n projectName: 'djangocfg-nextjs',\n projectVersion: '1.0.0',\n });\n } catch {\n // Fallback for corrupted conf file or invalid JSON in cache storage.\n return createMemoryCache<VersionCacheShape>();\n }\n}\n\n// Version cache using conf (stores in ~/.config/djangocfg-nextjs/)\nconst versionCache = createVersionCache();\n\n/**\n * Get current package version from package.json\n */\nexport function getCurrentVersion(): string | null {\n try {\n // `createRequire(import.meta.url).resolve` rather than a bare `require`: the\n // published bundle is ESM, where `require` does not exist. Resolving is only\n // used to locate the file — the read itself goes through `fs`, so this does\n // not depend on JSON-module import support.\n const localRequire = createRequire(import.meta.url);\n const pkgPath = localRequire.resolve('../../../package.json');\n return JSON.parse(readFileSync(pkgPath, 'utf-8')).version || null;\n } catch {\n return null;\n }\n}\n\n/**\n * Fetch latest version from npm registry (with 1 hour caching via conf)\n */\nexport async function fetchLatestVersion(): Promise<string | null> {\n // Check cache first\n const lastCheck = versionCache.get('lastCheck') || 0;\n const cachedVersion = versionCache.get('latestVersion');\n\n if (cachedVersion && (Date.now() - lastCheck) < VERSION_CACHE_TTL_MS) {\n return cachedVersion;\n }\n\n // Fetch from npm registry\n try {\n const https = await import('https');\n return new Promise((resolve) => {\n const req = https.get(\n `https://registry.npmjs.org/${PACKAGE_NAME}/latest`,\n { timeout: 5000 },\n (res) => {\n let data = '';\n res.on('data', (chunk: string) => { data += chunk; });\n res.on('end', () => {\n try {\n const json = JSON.parse(data);\n const version = json.version || null;\n if (version) {\n versionCache.set('latestVersion', version);\n versionCache.set('lastCheck', Date.now());\n }\n resolve(version);\n } catch {\n resolve(cachedVersion || null);\n }\n });\n }\n );\n req.on('error', () => resolve(cachedVersion || null));\n req.on('timeout', () => { req.destroy(); resolve(cachedVersion || null); });\n });\n } catch {\n return cachedVersion || null;\n }\n}\n\n/**\n * Check if update is available\n */\nexport async function checkForUpdate(): Promise<{\n hasUpdate: boolean;\n currentVersion: string | null;\n latestVersion: string | null;\n}> {\n const currentVersion = getCurrentVersion();\n if (!currentVersion) {\n return { hasUpdate: false, currentVersion: null, latestVersion: null };\n }\n\n const latestVersion = await fetchLatestVersion();\n const hasUpdate = !!(latestVersion && semver.gt(latestVersion, currentVersion));\n\n return { hasUpdate, currentVersion, latestVersion };\n}\n\n/**\n * Get update command for all djangocfg packages\n */\nexport function getUpdateCommand(): string {\n return `pnpm add ${DJANGOCFG_PACKAGES.map(p => `${p}@latest`).join(' ')}`;\n}\n\n/**\n * Print version info and update notification\n */\nexport async function printVersionInfo(): Promise<void> {\n const { hasUpdate, currentVersion, latestVersion } = await checkForUpdate();\n\n if (!currentVersion) return;\n\n // Print current version\n consola.box(`📦 @djangocfg/nextjs v${currentVersion}`);\n\n // Show update notification if available\n if (hasUpdate && latestVersion) {\n consola.warn(`Update Available! ${chalk.red(currentVersion)} → ${chalk.green(latestVersion)}`);\n consola.info(`Run: ${chalk.cyan(getUpdateCommand())}`);\n console.log('');\n }\n}\n","/**\n * Deep Merge Utility\n *\n * Recursively merges objects, replacing arrays instead of merging them.\n */\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\n/**\n * `T extends object` rather than `Record<string, unknown>`: call sites pass\n * Next.js config interfaces, which have declared keys and no index signature —\n * they are not assignable to `Record<string, unknown>`.\n */\nexport function deepMerge<T extends object>(target: T, source: Partial<T>): T {\n const output: T = { ...target };\n\n for (const key of Object.keys(source) as Array<keyof T & string>) {\n const sourceValue: unknown = source[key];\n if (sourceValue === undefined) continue;\n\n const targetValue: unknown = output[key];\n\n // Objects: deep merge. Arrays and primitives: replace.\n // The recursion is instantiated at Record<string, unknown> — all the guards\n // prove — and the result cast back to the key's declared type.\n const merged =\n isPlainObject(sourceValue) && isPlainObject(targetValue)\n ? deepMerge(targetValue, sourceValue)\n : sourceValue;\n\n output[key] = merged as T[typeof key];\n }\n\n return output;\n}\n","/**\n * Base Next.js Configuration Factory\n *\n * Universal, reusable Next.js config for all DjangoCFG projects\n * Provides standard settings that can be extended per project\n *\n * @example\n * ```typescript\n * // In your project's next.config.ts\n * import { createBaseNextConfig } from '@djangocfg/nextjs/config';\n * import bundleAnalyzer from '@next/bundle-analyzer';\n *\n * const withBundleAnalyzer = bundleAnalyzer({\n * enabled: process.env.ANALYZE === 'true',\n * });\n *\n * export default withBundleAnalyzer(createBaseNextConfig({\n * // Your project-specific overrides\n * transpilePackages: ['my-custom-package'],\n * }));\n * ```\n */\n\nimport type { NextConfig } from 'next';\nimport type { Configuration as WebpackConfig } from 'webpack';\n\nimport { type I18nPluginOptions, withI18n } from '../i18n/plugin';\nimport { type PWAPluginOptions, withPWA } from '../pwa/plugin';\nimport { DEFAULT_OPTIMIZE_PACKAGES, DEFAULT_TRANSPILE_PACKAGES } from './constants';\nimport { CSP_BASELINE_VENDORS, type CspVendor, resolveCspVendors } from './csp';\nimport { addCompressionPlugins } from './plugins/compression';\nimport { DevStartupPlugin } from './plugins/devStartup';\nimport { deepMerge } from './utils/deepMerge';\nimport {\n getApiUrl,\n getBasePath,\n getSiteUrl,\n isDev,\n isStaticBuild,\n warnIfApiUrlMissing,\n} from './utils/env';\n\n// ─────────────────────────────────────────────────────────────────────────\n// Configuration Options\n// ─────────────────────────────────────────────────────────────────────────\n\n/**\n * Second argument Next.js passes to the `webpack` hook. Derived from Next's own\n * signature rather than re-declared, so it tracks upstream.\n */\ntype WebpackContext = Parameters<NonNullable<NextConfig['webpack']>>[1];\n\n/**\n * DjangoCFG-specific options. Every other Next.js option passes through via the\n * `NextConfig` half of `BaseNextConfigOptions`; the keys redeclared here are\n * omitted from it because this package narrows or re-purposes them.\n */\ninterface DjangoCfgNextOptions {\n /** Static build path (used when NEXT_PUBLIC_STATIC_BUILD=true) */\n isDefaultCfgAdmin?: boolean;\n /** Additional transpile packages (merged with defaults) */\n transpilePackages?: string[];\n /** Additional optimize package imports (merged with defaults) */\n optimizePackageImports?: string[];\n /** Check for @djangocfg/* package updates on startup (default: true) */\n checkUpdates?: boolean;\n /** Auto-update outdated packages without prompting (default: false) */\n autoUpdate?: boolean;\n /** Force check workspace:* packages for updates (for testing in monorepo) */\n forceCheckWorkspace?: boolean;\n /** Check for missing optional packages on startup (default: true) */\n checkPackages?: boolean;\n /** Auto-install missing packages without prompting (default: false) */\n autoInstall?: boolean;\n /**\n * Allow embedding this app in iframe from specified origins\n * Set to ['*'] to allow all origins, or specify domains like ['https://djangocfg.com']\n */\n allowIframeFrom?: string[];\n /**\n * Enable DPoP (RFC 9449) sender-constrained tokens on the generated API\n * client. When true, the client holds a non-extractable key and signs a\n * per-request `DPoP` proof so a stolen token is useless. Must be paired with\n * `jwt = JWTConfig(dpop_enabled=True)` on the Django backend. Default: false.\n *\n * Inlines `NEXT_PUBLIC_DPOP_ENABLED='true'` so the generated auth.ts activates.\n */\n dpop?: boolean;\n /**\n * Loosen the baseline CSP just enough for Stripe.js / Stripe Checkout &\n * Elements to work. When true, the required Stripe origins are added to\n * `script-src` (js.stripe.com — loads the SDK), `frame-src`\n * (js.stripe.com + checkout.stripe.com — Elements/Checkout mount iframes)\n * and `connect-src` (api.stripe.com — tokenization/confirm XHR). Without\n * this, the browser blocks Stripe.js and the payment form never renders.\n * Enable ONLY on apps that actually mount Stripe (e.g. the billing\n * dashboard), so every other app keeps the tighter default. Default: false.\n */\n stripe?: boolean;\n /**\n * Content-Security-Policy control.\n *\n * - `false` — do not emit the `Content-Security-Policy` header at all.\n * Use this on apps with no user auth/payment surface and unpredictable\n * third-party embeds (a pitch deck, a marketing microsite) where\n * naming every future embed's origin is not worth the maintenance —\n * `X-Content-Type-Options`/`Referrer-Policy`/`X-Frame-Options` below\n * still apply.\n * - object — keep the baseline CSP but additively loosen it. Prefer\n * `vendors` (declare WHAT you use by name; the package owns which origins\n * that implies) over hand-listing origins per directive. Every array is\n * additive; omit a key to leave that directive untouched.\n * - omitted — tightest default (`frame-src 'self'`, etc.), same as today.\n *\n * **Scripts nothing in this file can see are already handled.** Cloudflare\n * Web Analytics (injected at the edge) and Google Analytics (injected at\n * runtime by `BaseApp analytics={{ googleTrackingId }}`) are both switched on\n * outside `next.config.ts`, so their origins are in the baseline and you do\n * NOT need to list them — see `CSP_BASELINE_VENDORS`. Turn them off with\n * `baseline: false`.\n *\n * @example\n * ```ts\n * // Declare the third parties this app actually uses — origins come from\n * // the shared registry, so a vendor host change is fixed in one place.\n * // (Cloudflare + GA are already in the baseline; no need to list them.)\n * createBaseNextConfig({ csp: { vendors: ['stripe'] } })\n *\n * // Escape hatch for something not in the registry yet\n * createBaseNextConfig({ csp: { frameSrc: ['https://www.youtube-nocookie.com'] } })\n *\n * // No CSP at all — simplest option for a low-risk app with ad hoc embeds\n * createBaseNextConfig({ csp: false })\n * ```\n */\n csp?:\n | false\n | {\n /**\n * Named third-party vendors this app uses (`'stripe'`, `'cloudflare'`,\n * `'google-analytics'`, …). Each expands to the exact origins that\n * vendor needs across the relevant directives. See `CSP_VENDOR_PRESETS`\n * for the full list. Unknown names warn at build time, never throw.\n */\n vendors?: (CspVendor | (string & {}))[];\n /**\n * Drop the baseline vendors (see `CSP_BASELINE_VENDORS`: Cloudflare Web\n * Analytics + Google Analytics). They are on by default because each is\n * switched on somewhere `next.config.ts` cannot read — the Cloudflare\n * beacon is injected at the edge, and gtag.js is injected at runtime by\n * `BaseApp analytics={{ googleTrackingId }}` — so an opt-in flag would\n * only ever be discovered as a production console error.\n *\n * Set `false` for an app you know mounts neither, to get back the\n * tightest `script-src`.\n * @default true\n */\n baseline?: boolean;\n /** Extra `frame-src` origins (iframes this app embeds). */\n frameSrc?: string[];\n /** Extra `script-src` origins (third-party SDK scripts). */\n scriptSrc?: string[];\n /** Extra `connect-src` origins (XHR/fetch/WebSocket to a third party). */\n connectSrc?: string[];\n /** Extra `img-src` origins (hotlinked third-party images). */\n imgSrc?: string[];\n /** Extra `font-src` origins (third-party web fonts). */\n fontSrc?: string[];\n };\n /**\n * PWA configuration options\n * Set to false to disable PWA, or provide custom options\n * @default { enabled: true (in production), disable: true (in development) }\n */\n pwa?: PWAPluginOptions | false;\n /**\n * i18n configuration options for internationalization\n * Enables URL-based locale routing (e.g., /en/about, /ru/about)\n *\n * @example\n * ```ts\n * i18n: {\n * locales: ['en', 'ru', 'ko'],\n * defaultLocale: 'en',\n * }\n * ```\n */\n i18n?: I18nPluginOptions;\n /** Turbopack configuration (Next.js 16+ default bundler) */\n turbopack?: NextConfig['turbopack'];\n /** Custom webpack configuration function (called after base webpack logic) */\n webpack?: (\n config: WebpackConfig,\n options: WebpackContext\n ) => WebpackConfig | void;\n /** Custom experimental options (merged with defaults) */\n experimental?: NextConfig['experimental'];\n /** Custom env variables (merged with defaults) */\n env?: Record<string, string | undefined>;\n /** Additional redirects (merged with base URL normalization redirects) */\n redirects?: NextConfig['redirects'];\n /** Additional headers (merged with base CORS/security headers) */\n headers?: NextConfig['headers'];\n /** Additional rewrites */\n rewrites?: NextConfig['rewrites'];\n}\n\n/**\n * DjangoCFG options plus every remaining Next.js config option.\n */\nexport type BaseNextConfigOptions = DjangoCfgNextOptions &\n Omit<NextConfig, keyof DjangoCfgNextOptions>;\n\n// ─────────────────────────────────────────────────────────────────────────\n// Base Configuration Factory\n// ─────────────────────────────────────────────────────────────────────────\n\n/**\n * Create base Next.js configuration with standard DjangoCFG settings\n *\n * @param options - Custom configuration options to merge with base config\n * @returns Next.js configuration function\n */\nexport function createBaseNextConfig(\n options: BaseNextConfigOptions = {}\n): NextConfig {\n\n const basePath = getBasePath(options.isDefaultCfgAdmin);\n warnIfApiUrlMissing();\n const apiUrl = getApiUrl();\n const siteUrl = getSiteUrl();\n\n // Base configuration\n const baseConfig: NextConfig = {\n reactStrictMode: true,\n trailingSlash: true,\n\n // Dev indicator position — set explicitly so it's managed from one place\n // across every @djangocfg app. Apps can override via `devIndicators` in\n // their options (deep-merged over this). Kept bottom-left for now.\n devIndicators: { position: 'bottom-left' },\n\n // Static export configuration\n ...(isStaticBuild && {\n output: 'export' as const,\n distDir: 'out',\n basePath: basePath,\n // Fix for Next.js 15.5.4: prevent 500.html generation issue\n generateBuildId: async () => {\n return 'static-build';\n },\n }),\n\n // Standalone output for Docker (only in production, not dev)\n ...(!isStaticBuild && !isDev && {\n output: 'standalone' as const,\n }),\n\n // Environment variables\n env: {\n NEXT_PUBLIC_BASE_PATH: basePath,\n NEXT_PUBLIC_API_URL: apiUrl,\n NEXT_PUBLIC_SITE_URL: siteUrl,\n // DPoP toggle for the generated auth client (RFC 9449). Only inlined when\n // explicitly enabled, so non-DPoP apps stay on the plain Bearer path.\n ...(options.dpop ? { NEXT_PUBLIC_DPOP_ENABLED: 'true' } : {}),\n // Disable Next.js telemetry (Next.js 15+ uses env var instead of config option)\n NEXT_TELEMETRY_DISABLED: '1',\n ...options.env,\n },\n\n // Images configuration\n images: {\n unoptimized: true,\n },\n\n // CORS headers for static files and iframe embedding\n async headers() {\n const headers: { source: string; headers: { key: string; value: string }[] }[] = [\n {\n source: '/static/:path*',\n headers: [\n { key: 'Access-Control-Allow-Origin', value: '*' },\n { key: 'Access-Control-Allow-Methods', value: 'GET, OPTIONS' },\n { key: 'Access-Control-Allow-Headers', value: 'Origin, Content-Type, Accept' },\n ],\n },\n ];\n\n // ── Baseline security headers on every route ──────────────────────────\n // First line of defence against XSS/clickjacking/sniffing. The CSP here is\n // a PRAGMATIC baseline: it kills the high-value injection vectors\n // (plugins/objects, <base> hijacking, form-action exfiltration, iframe\n // embedding) while still allowing the inline/eval scripts a Next.js app\n // needs in dev. A fully strict nonce-based `script-src` is a follow-up\n // (needs middleware + per-request nonce wiring) — tracked in the\n // auth-hardening plan. `allowIframeFrom` (below) overrides frame-ancestors.\n const hasIframeAllowlist =\n !!options.allowIframeFrom && options.allowIframeFrom.length > 0;\n const frameAncestors = hasIframeAllowlist\n ? (options.allowIframeFrom!.includes('*')\n ? '*'\n : `'self' ${options.allowIframeFrom!.join(' ')}`)\n : \"'none'\";\n\n // `csp: false` opts an app out of the CSP header entirely (see the\n // option's doc comment) — the other baseline headers still apply.\n const cspDisabled = options.csp === false;\n const cspExtra = typeof options.csp === 'object' ? options.csp : undefined;\n\n // In dev the backend (Django) is plain http/ws on localhost,\n // so connect/img must allow http:/ws:. Production stays strict (https/wss\n // only) to forbid downgrade/cleartext exfiltration.\n //\n // Third-party origins come from the shared vendor registry (./csp), so\n // \"which hosts does Stripe need\" is answered in exactly one place. The\n // legacy `stripe: true` flag is just sugar for `vendors: ['stripe']` and\n // resolves through that same registry — no second copy of the origins.\n // Baseline vendors apply unless explicitly opted out. They are baseline\n // rather than per-app because each is enabled somewhere this file cannot\n // read — Cloudflare's beacon is injected by the edge after this header is\n // emitted, and gtag.js is injected at runtime by `BaseApp analytics={{\n // googleTrackingId }}` from the app's settings.ts. Leaving either opt-in\n // guarantees a silent production breakage in every app that forgets it\n // (which was all of them). See CSP_BASELINE_VENDORS for the full rule.\n const vendorNames = [\n ...(cspExtra?.baseline === false ? [] : CSP_BASELINE_VENDORS),\n ...(options.stripe ? ['stripe'] : []),\n ...(cspExtra?.vendors ?? []),\n ];\n const vendor = resolveCspVendors(vendorNames);\n\n // Vendor-derived origins and the per-directive escape hatch are equivalent\n // and additive; a host named by both is emitted once.\n const join = (...groups: (string[] | undefined)[]) => {\n const all = [...new Set(groups.flatMap((g) => g ?? []))];\n return all.length ? ' ' + all.join(' ') : '';\n };\n\n const extraScript = join(vendor.scriptSrc, cspExtra?.scriptSrc);\n const extraConnect = join(vendor.connectSrc, cspExtra?.connectSrc);\n const extraFrame = join(vendor.frameSrc, cspExtra?.frameSrc);\n const extraImg = join(vendor.imgSrc, cspExtra?.imgSrc);\n const extraFont = join(vendor.fontSrc, cspExtra?.fontSrc);\n\n const connectSrc = isDev\n ? `connect-src 'self' http: https: ws: wss:${extraConnect}`\n : `connect-src 'self' https: wss:${extraConnect}`;\n const imgSrc = isDev\n ? `img-src 'self' data: blob: http: https:${extraImg}`\n : `img-src 'self' data: blob: https:${extraImg}`;\n\n if (!cspDisabled) {\n const cspDirectives = [\n \"default-src 'self'\",\n // Next.js requires inline + eval for its runtime/HMR. Tighten to nonces later.\n `script-src 'self' 'unsafe-inline' 'unsafe-eval'${extraScript}`,\n \"style-src 'self' 'unsafe-inline'\",\n imgSrc,\n `font-src 'self' data:${extraFont}`,\n // API calls go cross-origin to Django.\n connectSrc,\n // Declared vendors (Stripe Elements/Checkout, video embeds…) and any\n // app-declared trusted origins mount iframes; add their hosts here.\n `frame-src 'self'${extraFrame}`,\n \"object-src 'none'\",\n \"base-uri 'self'\",\n \"form-action 'self'\",\n `frame-ancestors ${frameAncestors}`,\n ];\n\n headers.push({\n source: '/:path*',\n headers: [\n { key: 'X-Content-Type-Options', value: 'nosniff' },\n { key: 'Referrer-Policy', value: 'strict-origin-when-cross-origin' },\n { key: 'Content-Security-Policy', value: cspDirectives.join('; ') },\n // X-Frame-Options for older browsers. Omitted when a wildcard iframe\n // allowlist is set (browsers then rely on CSP frame-ancestors).\n ...(hasIframeAllowlist && options.allowIframeFrom!.includes('*')\n ? []\n : [{ key: 'X-Frame-Options', value: hasIframeAllowlist ? 'SAMEORIGIN' : 'DENY' }]),\n ],\n });\n } else {\n // Still ship the non-CSP baseline headers even with CSP disabled.\n headers.push({\n source: '/:path*',\n headers: [\n { key: 'X-Content-Type-Options', value: 'nosniff' },\n { key: 'Referrer-Policy', value: 'strict-origin-when-cross-origin' },\n ...(hasIframeAllowlist && options.allowIframeFrom!.includes('*')\n ? []\n : [{ key: 'X-Frame-Options', value: hasIframeAllowlist ? 'SAMEORIGIN' : 'DENY' }]),\n ],\n });\n }\n\n // Merge with user-provided headers\n const userHeaders = options.headers ? await options.headers() : [];\n return [...headers, ...userHeaders];\n },\n\n // Rewrites (user-provided only)\n ...(options.rewrites && {\n rewrites: options.rewrites,\n }),\n\n // Redirects for URL normalization\n // Fixes \"Failed to construct 'URL': Invalid URL\" error with double slashes\n async redirects() {\n const baseRedirects = [\n // Normalize double slashes in URL path (e.g., /foo//bar -> /foo/bar)\n {\n source: '/:path*(//)/:rest*',\n destination: '/:path*/:rest*',\n permanent: true,\n },\n // Handle root double slash (e.g., // -> /)\n {\n source: '//',\n destination: '/',\n permanent: true,\n },\n ];\n\n // Merge with user-provided redirects\n const userRedirects = options.redirects ? await options.redirects() : [];\n return [...baseRedirects, ...userRedirects];\n },\n\n // Transpile packages (merge with user-provided)\n transpilePackages: [\n ...DEFAULT_TRANSPILE_PACKAGES,\n ...(options.transpilePackages || []),\n ],\n\n // Turbopack configuration (Next.js 16+ default bundler)\n // Always set turbopack config to silence Next.js 16 warning about webpack config\n turbopack: options.turbopack || {},\n\n // Experimental features\n experimental: {\n // Optimize package imports (only in production)\n ...(!isDev && {\n optimizePackageImports: [\n ...DEFAULT_OPTIMIZE_PACKAGES,\n ...(options.optimizePackageImports || []),\n ],\n }),\n // Dev mode optimizations\n ...(isDev && {\n optimizeCss: false,\n }),\n // User experimental options applied last (can override base settings)\n ...options.experimental,\n },\n\n // Webpack configuration\n webpack: (config: WebpackConfig, webpackOptions: WebpackContext) => {\n const { isServer, dev } = webpackOptions;\n\n // Add dev startup plugin (client-side only in dev)\n if (dev && !isServer) {\n if (!config.plugins) {\n config.plugins = [];\n }\n config.plugins.push(\n new DevStartupPlugin({\n checkUpdates: options.checkUpdates,\n autoUpdate: options.autoUpdate,\n forceCheckWorkspace: options.forceCheckWorkspace,\n checkPackages: options.checkPackages,\n autoInstall: options.autoInstall,\n })\n );\n }\n\n // Dev mode optimizations\n if (dev) {\n config.optimization = {\n ...config.optimization,\n removeAvailableModules: false,\n removeEmptyChunks: false,\n splitChunks: false, // Disable code splitting in dev for faster compilation\n };\n }\n\n // Filesystem cache (dev and production)\n config.cache = {\n type: 'filesystem',\n buildDependencies: {},\n };\n\n // Compression plugins (only for static build in production, client-side)\n if (!isServer && isStaticBuild && !dev) {\n addCompressionPlugins(config);\n }\n\n // Call user's webpack function if provided\n if (options.webpack) {\n return options.webpack(config, webpackOptions);\n }\n\n return config;\n },\n };\n\n const { i18n: i18nOptions, pwa: pwaOptions } = options;\n\n // Keys already consumed above; they must not reach deepMerge, either because\n // Next.js does not understand them or because baseConfig already derived the\n // real value. `transpilePackages` is the load-bearing case: baseConfig merged\n // it with DEFAULT_TRANSPILE_PACKAGES, and leaving it here made deepMerge\n // REPLACE that merged array with the user's bare list (dropping the defaults,\n // so every @djangocfg source package stopped transpiling).\n const DJANGOCFG_ONLY_KEYS = [\n 'i18n',\n 'pwa',\n 'transpilePackages',\n 'optimizePackageImports',\n 'isDefaultCfgAdmin',\n 'checkUpdates',\n 'autoUpdate',\n 'forceCheckWorkspace',\n 'checkPackages',\n 'autoInstall',\n 'allowIframeFrom',\n 'dpop',\n 'stripe',\n 'csp',\n ] as const satisfies readonly (keyof BaseNextConfigOptions)[];\n\n const nextConfigOptions = Object.fromEntries(\n Object.entries(options).filter(\n ([key]) => !(DJANGOCFG_ONLY_KEYS as readonly string[]).includes(key)\n )\n ) as Partial<NextConfig>;\n\n // Deep merge only valid Next.js options with base config\n let finalConfig = deepMerge(baseConfig, nextConfigOptions);\n\n // Apply PWA wrapper only if explicitly configured (opt-in)\n // PWA requires sw.ts file in the app, so apps must explicitly enable it\n if (pwaOptions) {\n finalConfig = withPWA(finalConfig, pwaOptions);\n }\n\n // Apply i18n wrapper if configured\n // Enables next-intl for locale routing and translations\n if (i18nOptions) {\n finalConfig = withI18n(finalConfig, i18nOptions);\n }\n\n return finalConfig;\n}\n","/**\n * DjangoCFG Documentation Client\n *\n * HTTP client for accessing DjangoCFG documentation via MCP server API.\n */\n\nimport { API_INFO_ENDPOINT, API_SEARCH_ENDPOINT, DEFAULT_TIMEOUT, MCP_BASE_URL } from './constants';\n\nimport type { SearchResult, SearchOptions, McpConfig, ApiSearchResponse } from './types';\n\n/**\n * Client for DjangoCFG Documentation API.\n */\nexport class DjangoCfgDocsClient {\n private baseUrl: string;\n private timeout: number;\n\n constructor(baseUrl: string = MCP_BASE_URL, timeout: number = DEFAULT_TIMEOUT) {\n this.baseUrl = baseUrl.replace(/\\/$/, '');\n this.timeout = timeout;\n }\n\n private async makeRequest<T>(endpoint: string, params?: Record<string, string | number>): Promise<T> {\n let url = `${this.baseUrl}${endpoint}`;\n\n if (params) {\n const searchParams = new URLSearchParams();\n for (const [key, value] of Object.entries(params)) {\n searchParams.set(key, String(value));\n }\n url = `${url}?${searchParams.toString()}`;\n }\n\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), this.timeout);\n\n try {\n const response = await fetch(url, {\n method: 'GET',\n headers: {\n Accept: 'application/json',\n 'User-Agent': 'DjangoCFG-AI-Client/1.0',\n },\n signal: controller.signal,\n });\n\n if (!response.ok) {\n throw new Error(`HTTP Error ${response.status}: ${response.statusText}`);\n }\n\n return await response.json();\n } finally {\n clearTimeout(timeoutId);\n }\n }\n\n /**\n * Search documentation.\n */\n async search(query: string, options: SearchOptions = {}): Promise<SearchResult[]> {\n const { limit = 5, category } = options;\n\n const params: Record<string, string | number> = { q: query, limit };\n if (category) {\n params.category = category;\n }\n\n const data = await this.makeRequest<ApiSearchResponse>(API_SEARCH_ENDPOINT, params);\n\n const results = data.results || (Array.isArray(data) ? data : []);\n\n return results.map((item) => ({\n title: item.title || '',\n content: item.content || item.snippet || '',\n url: item.url || '',\n score: item.score || 0,\n category: item.category || '',\n }));\n }\n\n /**\n * Get detailed info about a specific topic.\n */\n async getInfo(topic: string): Promise<Record<string, unknown>> {\n return this.makeRequest(API_INFO_ENDPOINT, { topic });\n }\n\n /**\n * Get MCP server configuration for AI assistants.\n */\n getMcpConfig(): McpConfig {\n return {\n mcpServers: {\n 'djangocfg-docs': {\n url: `${this.baseUrl}/mcp`,\n },\n },\n };\n }\n}\n\n// Default client instance\nlet defaultClient: DjangoCfgDocsClient | null = null;\n\nfunction getClient(): DjangoCfgDocsClient {\n if (!defaultClient) {\n defaultClient = new DjangoCfgDocsClient();\n }\n return defaultClient;\n}\n\n/**\n * Search DjangoCFG documentation.\n *\n * @example\n * ```ts\n * const results = await search('database configuration');\n * results.forEach(r => console.log(r.title, r.url));\n * ```\n */\nexport async function search(query: string, options: SearchOptions = {}): Promise<SearchResult[]> {\n return getClient().search(query, options);\n}\n\n/**\n * Get documentation as formatted text.\n *\n * @example\n * ```ts\n * const docs = await getDocs('How to configure PostgreSQL?');\n * console.log(docs);\n * ```\n */\nexport async function getDocs(query: string, limit: number = 3): Promise<string> {\n const results = await search(query, { limit });\n\n if (results.length === 0) {\n return `No documentation found for: ${query}`;\n }\n\n const output: string[] = [];\n\n results.forEach((r, i) => {\n output.push(`## ${i + 1}. ${r.title}`);\n output.push(r.content);\n if (r.url) {\n output.push(`📖 Read more: ${r.url}`);\n }\n output.push('');\n });\n\n return output.join('\\n');\n}\n\n/**\n * Get detailed info about a topic.\n */\nexport async function getInfo(topic: string): Promise<Record<string, unknown>> {\n return getClient().getInfo(topic);\n}\n\n/**\n * Get MCP server configuration.\n */\nexport function getMcpConfig(): McpConfig {\n return getClient().getMcpConfig();\n}\n"],"mappings":"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","names":["createRequire","createRequire","chalk","existsSync","join","consola","join","join","isDev","text","consola","chalk","spawn","Conf","consola","existsSync","createRequire","join","createInterface","createMemoryCache","Conf","SPINNER_FRAMES","join","existsSync","createRequire","createSpinner","chalk","text","askConfirmation","createInterface","spawn","consola","chalk","Conf","consola","readFileSync","createRequire","semver","createMemoryCache","Conf","createRequire","readFileSync","fetchLatestVersion","semver","consola","chalk","chalk","join","existsSync","join"]}
1
+ {"version":3,"sources":["../src/sitemap/constants.ts","../src/sitemap/fetch.ts","../src/sitemap/ids.ts","../src/sitemap/select.ts","../src/sitemap/sitemap.ts","../src/sitemap/index-route.ts","../src/sitemap/robots.ts","../src/health/route.ts","../src/navigation/utils.ts","../src/i18n/plugin.ts","../src/pwa/plugin.ts","../src/config/constants.ts","../src/config/csp.ts","../src/config/plugins/compression.ts","../src/config/packages/checker.ts","../src/config/utils/env.ts","../src/config/packages/definitions.ts","../src/config/plugins/devStartup.ts","../src/ai/constants.ts","../src/config/packages/installer.ts","../src/config/packages/updater.ts","../src/config/utils/version.ts","../src/config/utils/deepMerge.ts","../src/config/createNextConfig.ts","../src/ai/client.ts"],"sourcesContent":["/**\n * Shared sitemap constants.\n *\n * Pulled out so both the chunk factory (`createDjangoSitemap`) and the\n * index Route Handler (`createSitemapIndex`) agree on the id of the\n * frontend-only static-routes chunk.\n */\n\nexport const STATIC_ID = 'static';\nexport const DEFAULT_INDEX_REVALIDATE = 600;\nexport const DEFAULT_FEED_REVALIDATE = 3600;\n","/**\n * Sitemap fetch helpers.\n *\n * During `next build` a failure becomes an empty payload, so a Django outage\n * does not fail the build. At runtime it throws: ISR then keeps serving the\n * last good copy. An empty payload there would be cached for the whole\n * revalidate window — a sitemap index with only the static chunk, which\n * crawlers and IndexNow diffs read as every listing removed.\n */\n\nimport type { SitemapFeedPage, SitemapIndex } from './types';\n\n/**\n * Next.js refuses to write a fetch response larger than 2MB into its data\n * cache. It logs one line and serves the response uncached, so `revalidate`\n * silently becomes a no-op and every regeneration refetches the full payload\n * from Django. Nothing else surfaces this, so we measure the body ourselves.\n */\nconst NEXT_DATA_CACHE_LIMIT = 2 * 1024 * 1024;\n\n// One warning per URL: `sitemap()` runs once per chunk per build, and a\n// repeated line for the same feed reads as a new problem rather than the\n// same one.\nconst oversizeWarned = new Set<string>();\n\n/**\n * Read the body as text so its exact size is known, then parse.\n *\n * `Content-Length` is not a substitute: it is absent under chunked transfer\n * and reports the compressed size when the response is gzipped, while the\n * 2MB ceiling applies to the decoded bytes Next.js would store.\n */\nasync function parseAndCheckSize<T>(r: Response, url: string, label: string): Promise<T> {\n const text = await r.text();\n const bytes = new TextEncoder().encode(text).length;\n if (bytes > NEXT_DATA_CACHE_LIMIT && !oversizeWarned.has(url)) {\n oversizeWarned.add(url);\n console.warn(\n `[sitemap] ${label} is ${bytes} bytes, over Next.js's 2MB data-cache ceiling: ` +\n `revalidate is inert for this feed and every regeneration refetches it from Django. ` +\n `Lower the backend page_size if the extra load matters — note 50,000 URLs is the ` +\n `sitemap protocol's own maximum, so a large payload here is not itself a bug.`,\n );\n }\n return JSON.parse(text) as T;\n}\n\n/** True while `next build` prerenders; false in the serving process. */\nconst isBuilding = (): boolean => process.env.NEXT_PHASE === 'phase-production-build';\n\nfunction degrade<T>(fallback: T, reason: string, err?: unknown): T {\n if (!isBuilding()) {\n throw new Error(`[sitemap] ${reason}; keeping the last good copy`, { cause: err });\n }\n console.warn(`[sitemap] ${reason}; falling back to empty during build`, err ?? '');\n return fallback;\n}\n\nconst EMPTY_INDEX: SitemapIndex = {\n sources: [],\n generated_at: new Date(0).toISOString(),\n ttl_seconds: 60,\n};\n\nconst emptyFeed = (source: string, cursor: string | null): SitemapFeedPage => ({\n source,\n chunk_id: `${source}-empty`,\n count: 0,\n has_more: false,\n next_cursor: cursor,\n entries: [],\n});\n\nexport async function fetchSitemapIndex(\n apiUrl: string,\n revalidate: number,\n): Promise<SitemapIndex> {\n const url = `${apiUrl}/cfg/sitemap/index/`;\n try {\n const r = await fetch(url, {\n next: { revalidate },\n });\n if (!r.ok) return degrade(EMPTY_INDEX, `index fetch returned ${r.status}`);\n return await parseAndCheckSize<SitemapIndex>(r, url, 'index');\n } catch (err) {\n return degrade(EMPTY_INDEX, 'index fetch failed', err);\n }\n}\n\nexport async function fetchSitemapFeed(\n apiUrl: string,\n source: string,\n cursor: string | null,\n revalidate: number,\n): Promise<SitemapFeedPage> {\n const params = new URLSearchParams({ source });\n if (cursor) params.set('cursor', cursor);\n const url = `${apiUrl}/cfg/sitemap/feed/?${params}`;\n try {\n const r = await fetch(url, {\n next: { revalidate },\n });\n if (!r.ok) return degrade(emptyFeed(source, cursor), `feed ${source} returned ${r.status}`);\n return await parseAndCheckSize<SitemapFeedPage>(r, url, `feed ${source}`);\n } catch (err) {\n return degrade(emptyFeed(source, cursor), `feed ${source} fetch failed`, err);\n }\n}\n","/**\n * Sitemap chunk id encoding.\n *\n * Each Next.js sitemap file is addressed by an opaque string id. We\n * round-trip a `(source, cursor)` pair through that id so the default\n * sitemap handler knows which Django chunk to fetch.\n *\n * Separator `--` is unambiguous: backend cursors use URL-safe base64 (`-_`).\n */\n\nexport function encodeChunkId(source: string, cursor: string | null): string {\n return `${source}--${cursor ?? ''}`;\n}\n\nexport function decodeChunkId(id: string): { source: string; cursor: string | null } {\n const sep = id.indexOf('--');\n if (sep === -1) return { source: id, cursor: null };\n const source = id.slice(0, sep);\n const cursorRaw = id.slice(sep + 2);\n return { source, cursor: cursorRaw === '' ? null : cursorRaw };\n}\n","/**\n * Which backend sources a frontend claims.\n *\n * `GET /cfg/sitemap/index/` answers for the whole Django project — every\n * source any installed app registered. One frontend is not always entitled\n * to all of them: a backend serving several brands registers a source per\n * brand (`properties-caribbean`, `properties-bali`), and each brand's app\n * must emit only its own, or it advertises the other's URLs under its own\n * host — every one of them a 404, and the sitemap that carries them is\n * discredited whole.\n *\n * The selector lives here, shared, for the same reason `STATIC_ID` does:\n * `createDjangoSitemap` enumerates the chunks and `createSitemapIndex`\n * enumerates them again for the index document. If the two applied\n * different filters, the index would advertise shards the chunk route\n * refuses to build — and that mismatch is invisible until a crawler reads\n * both. One function, called by both, cannot drift.\n *\n * Source NAMES are the contract. The module ships no notion of a brand or\n * a tenant, and it should not: the backend chose what its source names\n * mean, and only the app knows which of them are its own.\n */\n\nimport type { SitemapIndex, SitemapSourceInfo, SourceSelector } from './types';\n\n/**\n * A predicate from whatever the caller passed.\n *\n * Omitted means every source — the single-tenant default, and what every\n * caller before this option existed already relied on.\n */\nexport function sourceFilter(select: SourceSelector | undefined): (name: string) => boolean {\n if (select === undefined) return () => true;\n if (typeof select === 'function') return select;\n const allowed = new Set(select);\n return (name) => allowed.has(name);\n}\n\n/** The index's sources, narrowed to those the caller claims. */\nexport function selectSources(\n index: SitemapIndex,\n select: SourceSelector | undefined,\n): SitemapSourceInfo[] {\n const keep = sourceFilter(select);\n return index.sources.filter((source) => keep(source.name));\n}\n\n/**\n * Every chunk id the selected sources produce, in index order.\n *\n * The one place `(source, chunk)` becomes an id, so the chunk route and the\n * index document enumerate the same set by construction.\n */\nexport function selectedChunkIds(\n index: SitemapIndex,\n select: SourceSelector | undefined,\n encode: (source: string, cursor: string | null) => string,\n): string[] {\n const ids: string[] = [];\n for (const source of selectSources(index, select)) {\n for (const chunk of source.chunks) {\n ids.push(encode(source.name, chunk.cursor_to));\n }\n }\n return ids;\n}\n\n/**\n * A selector for one tenant of a multi-brand backend.\n *\n * Matches the `<source>-<tenant>` names that a backend registering per\n * tenant emits. It splits on the LAST hyphen and compares the tail exactly,\n * rather than testing a suffix: `endsWith('-bali')` is correct for\n * `properties-bali` but the same shape reads `pricing-plans-premium` as\n * belonging to a `premium` tenant, and a substring match is not a contract.\n *\n * ```ts\n * sources: tenantSources('bali') // properties-bali, communities-bali, …\n * ```\n *\n * Sources with no hyphen never match: an untenanted source belongs to the\n * project rather than to a brand, and claiming it for one is the error this\n * function exists to prevent.\n */\nexport function tenantSources(tenant: string): (name: string) => boolean {\n return (name) => {\n const sep = name.lastIndexOf('-');\n return sep !== -1 && name.slice(sep + 1) === tenant;\n };\n}\n","/**\n * createDjangoSitemap — factory for Next.js `app/sitemap.ts`.\n *\n * Backed by `django_cfg.modules.django_sitemap`: the backend exposes a\n * paginated JSON feed, this factory turns each chunk into a Next.js\n * sitemap file via `generateSitemaps()`.\n *\n * Usage:\n *\n * ```ts\n * // apps/<app>/app/sitemap.ts\n * import { createDjangoSitemap } from '@djangocfg/nextjs/sitemap';\n *\n * const { generateSitemaps, sitemap } = createDjangoSitemap({\n * host: process.env.NEXT_PUBLIC_SITE_URL!,\n * apiUrl: process.env.NEXT_PUBLIC_API_URL!,\n * staticRoutes: [\n * { path: '/', changeFrequency: 'daily', priority: 1.0 },\n * { path: '/catalog', changeFrequency: 'daily', priority: 0.9 },\n * ],\n * });\n *\n * export { generateSitemaps, sitemap as default };\n * export const revalidate = 3600;\n * ```\n *\n * Without `apiUrl`, the sitemap emits only the static routes — handy for\n * marketing sites that don't have a backend-driven URL space.\n */\n\nimport type { MetadataRoute } from 'next';\n\nimport {\n DEFAULT_FEED_REVALIDATE,\n DEFAULT_INDEX_REVALIDATE,\n STATIC_ID,\n} from './constants';\nimport { fetchSitemapFeed, fetchSitemapIndex } from './fetch';\nimport { decodeChunkId, encodeChunkId } from './ids';\nimport { selectedChunkIds, sourceFilter } from './select';\n\nimport type { CreateDjangoSitemapOptions, StaticRoute } from './types';\n\ninterface SitemapApi {\n generateSitemaps: () => Promise<Array<{ id: string }>>;\n sitemap: (props: { id: Promise<string> }) => Promise<MetadataRoute.Sitemap>;\n}\n\nexport function createDjangoSitemap(opts: CreateDjangoSitemapOptions): SitemapApi {\n const {\n host,\n apiUrl,\n staticRoutes = [],\n sources,\n indexRevalidate = DEFAULT_INDEX_REVALIDATE,\n feedRevalidate = DEFAULT_FEED_REVALIDATE,\n } = opts;\n\n return {\n async generateSitemaps() {\n const ids: Array<{ id: string }> = [{ id: STATIC_ID }];\n if (!apiUrl) return ids;\n const index = await fetchSitemapIndex(apiUrl, indexRevalidate);\n for (const id of selectedChunkIds(index, sources, encodeChunkId)) {\n ids.push({ id });\n }\n return ids;\n },\n\n async sitemap({ id: idPromise }) {\n const id = await idPromise;\n\n if (id === STATIC_ID) {\n return renderStatic(host, staticRoutes);\n }\n\n if (!apiUrl) return [];\n\n const { source, cursor } = decodeChunkId(id);\n // A chunk this app does not claim renders empty rather than fetching.\n // `generateSitemaps` never mints such an id, but the route stays\n // reachable by hand, and serving another tenant's URLs from a guessed\n // address would undo the filter above.\n if (!sourceFilter(sources)(source)) return [];\n\n const feed = await fetchSitemapFeed(apiUrl, source, cursor, feedRevalidate);\n return feed.entries.map((e) => ({\n url: `${host}${e.loc}`,\n lastModified: e.lastmod ? new Date(e.lastmod) : undefined,\n }));\n },\n };\n}\n\nfunction renderStatic(host: string, routes: StaticRoute[]): MetadataRoute.Sitemap {\n return routes.map((r) => ({\n url: `${host}${r.path}`,\n changeFrequency: r.changeFrequency,\n priority: r.priority,\n }));\n}\n","/**\n * createSitemapIndex — Route Handler factory for the sitemap-index XML.\n *\n * Next.js's `generateSitemaps()` API ships chunks at `/sitemap/<id>.xml`\n * but does NOT emit a single sitemap-index document. Without one,\n * robots.txt + crawlers point at a 404 and never discover the chunks.\n * This factory closes that gap: it returns a `GET` Route Handler that\n * mints a `<sitemapindex>` XML listing every chunk the matching\n * `createDjangoSitemap` would produce.\n *\n * Mount it at **`app/sitemap_index.xml/route.ts`** — NOT `sitemap.xml`.\n * `app/sitemap.ts` (where `createDjangoSitemap` lives) already owns\n * `/sitemap.xml` in Next.js's metadata routing, even when its output is\n * fan-out via `generateSitemaps()` and the singular URL serves 404.\n * Putting a route handler at `app/sitemap.xml/route.ts` throws at build\n * time: *Conflicting route and metadata at /sitemap.xml*.\n *\n * Usage — the full pattern with the matching chunk + robots factories:\n *\n * ```ts\n * // apps/<app>/app/sitemap.ts — chunks\n * import { createDjangoSitemap } from '@djangocfg/nextjs/sitemap';\n * const { generateSitemaps, sitemap } = createDjangoSitemap({ ... });\n * export { generateSitemaps, sitemap as default };\n *\n * // apps/<app>/app/sitemap_index.xml/route.ts — the index\n * import { createSitemapIndex } from '@djangocfg/nextjs/sitemap';\n * export const { GET } = createSitemapIndex({\n * host: process.env.NEXT_PUBLIC_SITE_URL!,\n * apiUrl: process.env.NEXT_PUBLIC_API_URL!,\n * });\n * export const revalidate = 600;\n *\n * // apps/<app>/app/robots.ts — point crawlers at the index URL above\n * import { createRobots } from '@djangocfg/nextjs/sitemap';\n * const host = process.env.NEXT_PUBLIC_SITE_URL!;\n * export default createRobots({\n * host,\n * sitemap: `${host}/sitemap_index.xml`,\n * });\n * ```\n *\n * Keep `host` / `apiUrl` in sync with the same app's `app/sitemap.ts`\n * — the index just enumerates the chunks the other file emits.\n */\n\nimport {\n DEFAULT_INDEX_REVALIDATE,\n STATIC_ID,\n} from './constants';\nimport { fetchSitemapIndex } from './fetch';\nimport { encodeChunkId } from './ids';\nimport { selectedChunkIds } from './select';\n\nimport type { CreateSitemapIndexOptions } from './types';\n\ninterface SitemapIndexRoute {\n GET: () => Promise<Response>;\n}\n\nexport function createSitemapIndex(opts: CreateSitemapIndexOptions): SitemapIndexRoute {\n const {\n host,\n apiUrl,\n sources,\n indexRevalidate = DEFAULT_INDEX_REVALIDATE,\n includeStatic = true,\n } = opts;\n\n return {\n async GET() {\n const ids: string[] = includeStatic ? [STATIC_ID] : [];\n\n if (apiUrl) {\n const index = await fetchSitemapIndex(apiUrl, indexRevalidate);\n ids.push(...selectedChunkIds(index, sources, encodeChunkId));\n }\n\n const lastmod = new Date().toISOString();\n const body = renderIndex(host, ids, lastmod);\n\n return new Response(body, {\n status: 200,\n headers: {\n 'content-type': 'application/xml; charset=utf-8',\n // Mirror the per-chunk revalidate so a cached CDN edge expires\n // at the same cadence as the index it points at.\n 'cache-control': `public, s-maxage=${indexRevalidate}, stale-while-revalidate=${indexRevalidate}`,\n },\n });\n },\n };\n}\n\nfunction renderIndex(host: string, ids: string[], lastmod: string): string {\n const lines: string[] = [];\n lines.push('<?xml version=\"1.0\" encoding=\"UTF-8\"?>');\n lines.push('<sitemapindex xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">');\n for (const id of ids) {\n lines.push(' <sitemap>');\n lines.push(` <loc>${escapeXml(`${host}/sitemap/${id}.xml`)}</loc>`);\n lines.push(` <lastmod>${lastmod}</lastmod>`);\n lines.push(' </sitemap>');\n }\n lines.push('</sitemapindex>');\n return lines.join('\\n');\n}\n\nfunction escapeXml(s: string): string {\n return s\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;')\n .replace(/'/g, '&apos;');\n}\n","/**\n * createRobots — factory for Next.js `app/robots.ts`.\n *\n * Emits a single `User-Agent: *` block plus a Sitemap pointer. Disallow\n * defaults cover the common cases (auth, account, API surface) — override\n * per app if needed.\n *\n * The `sitemap` default `${host}/sitemap.xml` only makes sense for the\n * single-file case. When the app uses `createDjangoSitemap`\n * (`generateSitemaps()` → chunked output) **plus** `createSitemapIndex`\n * — the canonical pairing — pass an explicit\n * `sitemap: \\`${host}/sitemap_index.xml\\`` to point crawlers at the\n * index. The default `/sitemap.xml` will serve a 404 in that setup\n * (Next.js reserves it for the metadata route but doesn't emit content\n * there).\n *\n * Usage:\n *\n * ```ts\n * // apps/<app>/app/robots.ts\n * import { createRobots } from '@djangocfg/nextjs/sitemap';\n *\n * const host = process.env.NEXT_PUBLIC_SITE_URL!;\n *\n * export default createRobots({\n * host,\n * disallow: ['/account/', '/auth', '/api/', '/apix/'],\n * sitemap: `${host}/sitemap_index.xml`, // matches createSitemapIndex\n * });\n * ```\n */\n\nimport type { MetadataRoute } from 'next';\n\nimport type { CreateRobotsOptions } from './types';\n\nconst DEFAULT_DISALLOW = ['/account/', '/auth', '/api/'];\n\nexport function createRobots(opts: CreateRobotsOptions): () => MetadataRoute.Robots {\n const { host, disallow = DEFAULT_DISALLOW, sitemap } = opts;\n return () => ({\n rules: [{ userAgent: '*', allow: '/', disallow }],\n sitemap: sitemap ?? `${host}/sitemap.xml`,\n host,\n });\n}\n","/**\n * Health Route Handler for Next.js App Router\n *\n * Usage:\n * ```tsx\n * // app/healthz/route.ts\n * import { createHealthHandler } from '@djangocfg/nextjs/health';\n *\n * export const GET = createHealthHandler({\n * version: process.env.NEXT_PUBLIC_APP_VERSION || '1.0.0',\n * checks: [\n * {\n * name: 'database',\n * check: async () => {\n * // Check database connection\n * return await checkDatabase();\n * },\n * },\n * ],\n * });\n * ```\n */\n\nimport { NextResponse } from 'next/server';\n\nimport type { HealthConfig, HealthResponse } from './types';\n\nexport function createHealthHandler(config: HealthConfig = {}) {\n const { version, checks = [], customData = {} } = config;\n\n return async function GET() {\n const startTime = Date.now();\n let status: 'ok' | 'error' = 'ok';\n const checkResults: Record<string, boolean> = {};\n\n // Run health checks\n if (checks.length > 0) {\n for (const check of checks) {\n try {\n const result = await check.check();\n checkResults[check.name] = result;\n if (!result) {\n status = 'error';\n }\n } catch {\n checkResults[check.name] = false;\n status = 'error';\n }\n }\n }\n\n const response: HealthResponse = {\n status,\n timestamp: new Date().toISOString(),\n uptime: process.uptime(),\n ...(version && { version }),\n ...(checks.length > 0 && {\n checks: checkResults,\n durationMs: Date.now() - startTime,\n }),\n ...customData,\n };\n\n const statusCode = status === 'ok' ? 200 : 503;\n\n return NextResponse.json(response, { status: statusCode });\n };\n}\n","/**\n * Navigation Utilities\n *\n * Common utilities for route definitions and navigation\n */\n\nimport type { RouteDefinition, RouteMetadata, MenuItem, NavGroup, NavGroupWithRoutes } from './types';\n\n// ─────────────────────────────────────────────────────────────────────────\n// Route Definition Helper\n// ─────────────────────────────────────────────────────────────────────────\n\n/**\n * Define a route with metadata\n *\n * IMPORTANT: Next.js automatically handles basePath for <Link> components when\n * basePath is set in next.config.ts. We should NOT add basePath manually here,\n * as it would cause double-prefixing in static builds.\n *\n * @param path - Route path (e.g., '/dashboard', '/admin/users')\n * @param metadata - Route metadata (label, icon, etc.)\n */\nexport function defineRoute(\n path: string,\n metadata: RouteMetadata,\n): RouteDefinition {\n // Always return path as-is. Next.js will handle basePath automatically\n // for <Link> components when basePath is set in next.config.ts\n return {\n path,\n metadata,\n };\n}\n\n// ─────────────────────────────────────────────────────────────────────────\n// Route Guards\n// ─────────────────────────────────────────────────────────────────────────\n\n/**\n * Get redirect path for unauthenticated users\n */\nexport function getUnauthenticatedRedirect(\n path: string,\n authPath: string = '/auth',\n): string | null {\n if (path.startsWith('/private') || path.startsWith('/admin')) {\n // Return path as-is. Next.js will handle basePath automatically for router.push()\n // when basePath is set in next.config.ts\n return authPath;\n }\n return null;\n}\n\n/**\n * Get redirect path to auth page\n */\nexport function redirectToAuth(\n authPath: string = '/auth',\n): string {\n // Return path as-is. Next.js will handle basePath automatically for router.push()\n // when basePath is set in next.config.ts\n return authPath;\n}\n\n// ─────────────────────────────────────────────────────────────────────────\n// Route Lookup\n// ─────────────────────────────────────────────────────────────────────────\n\nexport function findRoute(\n routes: RouteDefinition[],\n path: string\n): RouteDefinition | undefined {\n return routes.find((r) => r.path === path);\n}\n\nexport function findRouteByPattern(\n routes: RouteDefinition[],\n path: string\n): RouteDefinition | undefined {\n const exact = findRoute(routes, path);\n if (exact) return exact;\n\n const segments = path.split('/').filter(Boolean);\n for (let i = segments.length; i > 0; i--) {\n const parentPath = '/' + segments.slice(0, i).join('/');\n const parent = findRoute(routes, parentPath);\n if (parent) return parent;\n }\n\n return undefined;\n}\n\n// ─────────────────────────────────────────────────────────────────────────\n// Page Title\n// ─────────────────────────────────────────────────────────────────────────\n\nexport function getPageTitle(\n routes: RouteDefinition[],\n path: string,\n fallback = 'Dashboard'\n): string {\n const route = findRouteByPattern(routes, path);\n return route?.metadata.label || fallback;\n}\n\n// ─────────────────────────────────────────────────────────────────────────\n// Active Route\n// ─────────────────────────────────────────────────────────────────────────\n\n/**\n * Check if a route is active\n *\n * @param current - Current pathname\n * @param target - Target route path to check\n * @param allRoutes - Optional array of all routes to prevent parent paths from being active when child paths are active\n * @returns true if the route is active\n */\nexport function isActive(\n current: string,\n target: string,\n allRoutes?: RouteDefinition[]\n): boolean {\n const matches =\n current === target || (target !== '/' && current.startsWith(target + '/'));\n\n // If allRoutes is provided, check for more specific paths\n if (matches && allRoutes) {\n return !allRoutes.some(\n (otherRoute) =>\n otherRoute.path !== target &&\n otherRoute.path.startsWith(target + '/') &&\n (current === otherRoute.path ||\n current.startsWith(otherRoute.path + '/'))\n );\n }\n\n return matches;\n}\n\n// ─────────────────────────────────────────────────────────────────────────\n// Menu Generation Helper\n// ─────────────────────────────────────────────────────────────────────────\n\n/**\n * Filter and convert routes to menu items\n */\nexport function routesToMenuItems(\n routes: RouteDefinition[],\n groupName: string\n): MenuItem[] {\n return routes\n .filter(\n (r) =>\n r.metadata.group === groupName &&\n r.metadata.icon &&\n (r.metadata.show === undefined || r.metadata.show === true)\n )\n .sort((a, b) => (a.metadata.order || 0) - (b.metadata.order || 0))\n .map((r) => ({\n path: r.path,\n label: r.metadata.label,\n icon: r.metadata.icon!,\n ...(r.metadata.badge !== undefined && { badge: r.metadata.badge }),\n }));\n}\n\n// ─────────────────────────────────────────────────────────────────────────\n// Navigation Groups Helper\n// ─────────────────────────────────────────────────────────────────────────\n\n/**\n * Group routes by navigation groups\n *\n * @param routes - Array of route definitions\n * @param groups - Array of navigation group definitions\n * @returns Array of groups with their routes, filtered by dynamic flag\n *\n * @example\n * ```ts\n * const navGroups = [\n * { id: 'dashboard', label: 'Dashboard', order: 1 },\n * { id: 'extensions', label: 'Extensions', order: 2, dynamic: true },\n * ];\n *\n * const groupedRoutes = groupRoutesByNavGroups(allRoutes, navGroups);\n * // Returns groups with routes, hiding dynamic groups with no routes\n * ```\n */\nexport function groupRoutesByNavGroups(\n routes: RouteDefinition[],\n groups: NavGroup[]\n): NavGroupWithRoutes[] {\n return groups\n .map((group) => ({\n ...group,\n routes: routes\n .filter(\n (r) =>\n r.metadata.group === group.id &&\n (r.metadata.show === undefined || r.metadata.show === true)\n )\n .sort((a, b) => (a.metadata.order || 0) - (b.metadata.order || 0)),\n }))\n .filter((group) => {\n // Dynamic groups are only shown if they have routes\n if (group.dynamic) {\n return group.routes.length > 0;\n }\n // Non-dynamic groups are always shown\n return true;\n })\n .sort((a, b) => a.order - b.order);\n}\n\n","/**\n * i18n Plugin for Next.js Configuration\n *\n * Wraps next-intl plugin for use with createBaseNextConfig\n *\n * @example\n * ```ts\n * // next.config.ts\n * import { createBaseNextConfig } from '@djangocfg/nextjs/config';\n *\n * export default createBaseNextConfig({\n * i18n: {\n * locales: ['en', 'ru', 'ko'],\n * defaultLocale: 'en',\n * },\n * });\n * ```\n */\n\nimport createNextIntlPlugin from 'next-intl/plugin';\n\nimport type { NextConfig } from 'next';\nimport type { I18nPluginOptions } from './types';\n\nexport type { I18nPluginOptions } from './types';\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Plugin Factory\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Create next-intl plugin wrapper\n *\n * @param options - i18n configuration options\n * @returns Next.js config wrapper function\n */\nexport function createI18nPlugin(options?: I18nPluginOptions) {\n // Determine request config path\n const requestConfigPath = options?.requestConfig\n ?? './i18n/request.ts';\n\n // Create next-intl plugin\n const withNextIntl = createNextIntlPlugin(requestConfigPath);\n\n return withNextIntl;\n}\n\n/**\n * Apply i18n plugin to Next.js config\n *\n * @param config - Base Next.js config\n * @param options - i18n options\n * @returns Modified Next.js config\n */\nexport function withI18n(\n config: NextConfig,\n options?: I18nPluginOptions\n): NextConfig {\n const plugin = createI18nPlugin(options);\n return plugin(config);\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Export for direct use\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport { createNextIntlPlugin };\n","/**\n * PWA (Progressive Web App) Plugin — dependency-free.\n *\n * History: this used to wrap `@serwist/next`. Serwist is a **webpack** plugin,\n * and Next.js 16 defaults to Turbopack, which does not run webpack loaders and\n * chokes on serwist's `fileURLToPath(new URL(\".\", import.meta.url))` during\n * static tracing — failing the whole build even when PWA was never enabled\n * (the mere `require('@serwist/next')` reference was enough). Since no app in\n * this monorepo actually turned PWA on (`withPWA` is opt-in and unused), we\n * dropped serwist entirely.\n *\n * What remains:\n * - `createManifest` (in `./manifest`) — the web app manifest, which Next.js\n * supports natively via `app/manifest.ts`; needs no library at all.\n * - `withPWA` — a thin, Turbopack-safe pass-through kept for API stability.\n * - `getServiceWorkerTemplate` — a vanilla service worker you can drop into\n * `app/sw.ts`, no framework runtime required.\n *\n * If you want a full precache/offline story back, wire a real service worker\n * (the template below, or Workbox) yourself — but do it in an app, not here, so\n * one app's PWA choice can't break every consumer's build.\n */\n\nimport type { NextConfig } from 'next';\n\nexport interface PWAPluginOptions {\n /**\n * Path to service worker source file (relative to project root).\n * @default 'app/sw.ts'\n */\n swSrc?: string;\n\n /**\n * Destination for the compiled service worker.\n * @default 'public/sw.js'\n */\n swDest?: string;\n\n /**\n * Disable PWA completely.\n * @default true in development / static export, false otherwise\n */\n disable?: boolean;\n\n /** Cache pages on navigation. Consumed by a custom SW, if any. @default true */\n cacheOnNavigation?: boolean;\n\n /** Reload the app when the device comes back online. @default true */\n reloadOnOnline?: boolean;\n}\n\n/**\n * Turbopack-safe PWA pass-through.\n *\n * This no longer injects a build plugin — Next.js's native manifest support\n * plus a hand-written `app/sw.ts` (see {@link getServiceWorkerTemplate}) cover\n * the PWA surface without a webpack-only dependency. Kept as a named export so\n * existing `withPWA(config)` / `createBaseNextConfig({ pwa })` call sites keep\n * type-checking and behave predictably (they return the config unchanged).\n *\n * @example\n * ```ts\n * import { createBaseNextConfig } from '@djangocfg/nextjs/config';\n * export default createBaseNextConfig({ pwa: { swSrc: 'app/sw.ts' } });\n * ```\n */\nexport function withPWA(\n nextConfig: NextConfig,\n _options: PWAPluginOptions = {}\n): NextConfig {\n return nextConfig;\n}\n\n/**\n * Vanilla service-worker template — no library.\n *\n * Returns ready-to-use, framework-free service worker source. Copy it into\n * `app/sw.ts` (or generate it) if you want offline support. It uses only the\n * standard Service Worker + Cache APIs, so it builds under Turbopack with no\n * extra dependency.\n *\n * @example\n * ```ts\n * import { getServiceWorkerTemplate } from '@djangocfg/nextjs/config';\n * console.log(getServiceWorkerTemplate()); // → paste into app/sw.ts\n * ```\n */\nexport function getServiceWorkerTemplate(): string {\n return `/**\n * Service Worker (vanilla) — standard Cache + Fetch APIs, no library.\n */\n\n/// <reference lib=\"webworker\" />\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ndeclare const self: any;\n\nconst CACHE = 'app-cache-v1';\nconst OFFLINE_URL = '/_offline';\n\n// Precache the offline fallback on install.\nself.addEventListener('install', (event: any) => {\n event.waitUntil(\n caches.open(CACHE).then((cache: Cache) => cache.addAll([OFFLINE_URL]))\n );\n self.skipWaiting();\n});\n\n// Drop stale caches on activate and take control immediately.\nself.addEventListener('activate', (event: any) => {\n event.waitUntil(\n caches.keys().then((keys: string[]) =>\n Promise.all(keys.filter((k) => k !== CACHE).map((k) => caches.delete(k)))\n )\n );\n self.clients.claim();\n});\n\n// Network-first for navigations, with an offline fallback.\nself.addEventListener('fetch', (event: any) => {\n const { request } = event;\n if (request.mode !== 'navigate') return;\n event.respondWith(\n fetch(request).catch(() =>\n caches.match(OFFLINE_URL).then((r: Response | undefined) => r ?? Response.error())\n )\n );\n});\n`;\n}\n","/**\n * Shared Constants for DjangoCFG Next.js Configuration\n */\n\n// Package name for version checks\nexport const PACKAGE_NAME = '@djangocfg/nextjs';\n\n// Version cache TTL (1 hour)\nexport const VERSION_CACHE_TTL_MS = 60 * 60 * 1000;\n\n// ASCII Art Banner for Django CFG\nexport const DJANGO_CFG_BANNER = `\n██████╗ ██╗ █████╗ ███╗ ██╗ ██████╗ ██████╗ ██████╗███████╗ ██████╗\n██╔══██╗ ██║██╔══██╗████╗ ██║██╔════╝ ██╔═══██╗ ██╔════╝██╔════╝██╔════╝\n██║ ██║ ██║███████║██╔██╗ ██║██║ ███╗██║ ██║ ██║ █████╗ ██║ ███╗\n██║ ██║██ ██║██╔══██║██║╚██╗██║██║ ██║██║ ██║ ██║ ██╔══╝ ██║ ██║\n██████╔╝╚█████╔╝██║ ██║██║ ╚████║╚██████╔╝╚██████╔╝ ╚██████╗██║ ╚██████╔╝\n╚═════╝ ╚════╝ ╚═╝ ╚═╝╚═╝ ╚═══╝ ╚═════╝ ╚═════╝ ╚═════╝╚═╝ ╚═════╝\n`;\n\n// All @djangocfg packages that can be updated together\nexport const DJANGOCFG_PACKAGES = [\n '@djangocfg/ui-core',\n '@djangocfg/layouts',\n '@djangocfg/nextjs',\n '@djangocfg/api',\n '@djangocfg/eslint-config',\n '@djangocfg/typescript-config',\n] as const;\n\n// Default packages to transpile\n// Required for proper RSC (React Server Components) handling.\n//\n// Every widget is here because widgets ship SOURCE — `main` points at\n// `./src/index.ts`, there is no build step, and an untranspiled `.ts` in\n// node_modules is a syntax error at the app's first import, not a warning.\nexport const DEFAULT_TRANSPILE_PACKAGES = [\n '@djangocfg/i18n',\n '@djangocfg/ui-core',\n '@djangocfg/layouts',\n '@djangocfg/api',\n '@djangocfg/analytics',\n '@djangocfg/devtools',\n '@djangocfg/payments',\n '@djangocfg/widget-agent',\n '@djangocfg/widget-avatar',\n '@djangocfg/widget-charts',\n '@djangocfg/widget-chat',\n '@djangocfg/widget-code',\n '@djangocfg/widget-data',\n '@djangocfg/widget-diagram',\n '@djangocfg/widget-editor',\n '@djangocfg/widget-forms',\n '@djangocfg/widget-kit',\n '@djangocfg/widget-map',\n '@djangocfg/widget-media',\n '@djangocfg/widget-ogimage',\n '@djangocfg/widget-overlay',\n '@djangocfg/widget-player',\n '@djangocfg/widget-visual',\n] as const;\n\n// Default packages to optimize imports\nexport const DEFAULT_OPTIMIZE_PACKAGES = [\n '@djangocfg/ui-core',\n '@djangocfg/layouts',\n 'lucide-react',\n 'recharts',\n] as const;\n","/**\n * CSP Vendor Presets\n *\n * The problem this solves: every third-party script a DjangoCFG app loads needs\n * its origin named in the CSP, and until now each app had to hand-list those\n * origins (`csp: { scriptSrc: [...] }`) in its own `next.config.ts`. That\n * repeats the same Stripe/Cloudflare/Google origin strings across every app,\n * and — worse — nobody remembers which directive a given vendor needs, so the\n * usual outcome is a production-only console error and a scramble.\n *\n * Instead, apps declare WHAT THEY USE by name (`csp: { vendors: ['stripe'] }`)\n * and this registry owns WHICH ORIGINS that implies. When a vendor changes a\n * host, it is fixed once here and every app inherits the fix on its next build.\n *\n * Adding a vendor: add an entry below, keyed by a short stable name, listing\n * only the directives it genuinely needs. Keep the origins exact — never widen\n * a directive to a bare `https:` to make a vendor work.\n */\n\n/** The CSP directives a vendor preset may contribute origins to. */\nexport interface CspOrigins {\n /** `script-src` — the vendor's SDK/tag `<script>` hosts. */\n scriptSrc?: string[];\n /** `frame-src` — hosts the vendor mounts iframes from. */\n frameSrc?: string[];\n /** `connect-src` — hosts the vendor makes XHR/fetch/WebSocket calls to. */\n connectSrc?: string[];\n /** `img-src` — hosts the vendor loads images/pixels from. */\n imgSrc?: string[];\n /** `font-src` — hosts the vendor loads web fonts from. */\n fontSrc?: string[];\n}\n\n/**\n * Known third-party vendors and the CSP origins each one requires.\n *\n * Every entry is OFF by default — an app opts in via `csp.vendors`, so an app\n * that mounts nothing third-party keeps the tightest possible policy.\n */\nexport const CSP_VENDOR_PRESETS = {\n /**\n * Stripe.js / Checkout / Elements.\n *\n * `js.stripe.com` serves the SDK and also hosts the Elements iframes;\n * `checkout.stripe.com` is the hosted Checkout iframe; `api.stripe.com`\n * takes the tokenization/confirm XHR. Without all three the payment form\n * silently fails to render.\n */\n stripe: {\n scriptSrc: ['https://js.stripe.com'],\n frameSrc: ['https://js.stripe.com', 'https://checkout.stripe.com'],\n connectSrc: ['https://api.stripe.com'],\n },\n\n /**\n * Cloudflare Web Analytics (the RUM beacon).\n *\n * IMPORTANT: this script is NOT in the app's own HTML — when Web Analytics\n * is enabled on the zone, Cloudflare INJECTS `beacon.min.js` into the\n * response at the edge, after Next.js has already emitted the CSP header.\n * So any app served through a Cloudflare zone with that feature on will\n * violate its own CSP unless this preset is enabled, no matter what the\n * source tree contains. The beacon also POSTs its payload back to\n * `cloudflareinsights.com`, hence the `connect-src` entry.\n */\n cloudflare: {\n scriptSrc: ['https://static.cloudflareinsights.com'],\n connectSrc: ['https://cloudflareinsights.com', 'https://static.cloudflareinsights.com'],\n },\n\n /** Google Analytics 4 / gtag.js. */\n 'google-analytics': {\n scriptSrc: ['https://www.googletagmanager.com', 'https://www.google-analytics.com'],\n connectSrc: [\n 'https://www.google-analytics.com',\n 'https://analytics.google.com',\n 'https://region1.google-analytics.com',\n ],\n imgSrc: ['https://www.google-analytics.com', 'https://www.googletagmanager.com'],\n },\n\n /** Google Tag Manager (the container itself; tags it loads may need more). */\n 'google-tag-manager': {\n scriptSrc: ['https://www.googletagmanager.com'],\n connectSrc: ['https://www.googletagmanager.com'],\n imgSrc: ['https://www.googletagmanager.com'],\n frameSrc: ['https://www.googletagmanager.com'],\n },\n\n /** Google Fonts (stylesheet host + the font files themselves). */\n 'google-fonts': {\n fontSrc: ['https://fonts.gstatic.com'],\n },\n\n /** Sentry browser SDK — errors/traces are POSTed to the ingest host. */\n sentry: {\n connectSrc: ['https://*.ingest.sentry.io', 'https://*.sentry.io'],\n },\n\n /** YouTube embeds (privacy-enhanced host included). */\n youtube: {\n frameSrc: ['https://www.youtube.com', 'https://www.youtube-nocookie.com'],\n imgSrc: ['https://i.ytimg.com'],\n },\n\n /** Vimeo embeds. */\n vimeo: {\n frameSrc: ['https://player.vimeo.com'],\n imgSrc: ['https://i.vimeocdn.com'],\n },\n} as const satisfies Record<string, CspOrigins>;\n\n/** Name of a vendor preset in {@link CSP_VENDOR_PRESETS}. */\nexport type CspVendor = keyof typeof CSP_VENDOR_PRESETS;\n\n/**\n * Vendors applied to every app unless explicitly disabled.\n *\n * The rule for this list: an origin belongs here when the app CANNOT DECLARE IT\n * FROM `next.config.ts`, because whatever turns the script on lives somewhere\n * the config file cannot read. Leaving such a vendor opt-in does not produce a\n * build error or a dev warning — it produces a production-only console error\n * that nobody sees until a user reports a broken page.\n *\n * - `cloudflare` — the Web Analytics beacon is rewritten into the HTML AT THE\n * EDGE, after Next.js has already sent the CSP header. Nothing in the source\n * tree reveals it. (Observed 2026-07-22: all six DjangoCFG-served hosts were\n * violating their own CSP.)\n *\n * - `google-analytics` — gtag.js is injected AT RUNTIME by `@djangocfg/layouts`\n * (`BaseApp analytics={{ googleTrackingId }}` → the lazy `ga4()` transport).\n * That id is an ordinary TS value in the app's `settings.ts`, not an env var,\n * so `next.config.ts` — a different module, evaluated in a different process,\n * before any of that code runs — has no way to see whether GA is on. The app\n * author enables analytics in one file and silently breaks CSP in another.\n * (Observed 2026-07-23: cmdop `apps/web` blocked on\n * `googletagmanager.com/gtag/js?id=G-YJ0F48S83F`.)\n *\n * - `youtube` / `vimeo` — these existed for `VideoPlayer`\n * (`@djangocfg/ui-tools`), which mounted the iframes AT RUNTIME from a URL\n * inside a chat message. That player was retired with `ui-tools`;\n * `@djangocfg/widget-player` embeds no third-party frames, so **nothing in\n * this repo needs these presets today**. They are kept because the shape of\n * the problem outlives the component: an app author enables a chat, and\n * whether a user ever pastes a YouTube link is not knowable at build time,\n * so no `next.config.ts` can declare it honestly.\n *\n * Note what these have in common: the script/frame is turned on by the RUNTIME\n * (the edge, a package, or an end user), never by the app author editing the\n * config. Stripe is the counter-example and deliberately NOT here — a developer\n * decides to mount a payment form, knows they did, and can say `vendors:\n * ['stripe']`; putting it in the baseline would hand `frame-src js.stripe.com`\n * to marketing sites that never take a payment.\n *\n * Do NOT add ordinary third parties here — anything an app knowingly mounts and\n * can name in its own config belongs in `csp.vendors`, so unrelated apps keep\n * the tighter policy. The cost of an entry here is that every app permits these\n * hosts; that is only worth paying when the alternative is silent breakage.\n */\nexport const CSP_BASELINE_VENDORS: CspVendor[] = [\n 'cloudflare',\n 'google-analytics',\n 'youtube',\n 'vimeo',\n];\n\n/**\n * Merge the named vendor presets into a single set of extra origins.\n *\n * Unknown names are ignored rather than thrown on: a typo must not break the\n * production build of an app whose only mistake was a misspelled analytics\n * vendor. `warnUnknown` surfaces them on the build log instead.\n */\nexport function resolveCspVendors(\n vendors: readonly string[] | undefined,\n warnUnknown = true\n): CspOrigins {\n const merged: Required<CspOrigins> = {\n scriptSrc: [],\n frameSrc: [],\n connectSrc: [],\n imgSrc: [],\n fontSrc: [],\n };\n\n if (!vendors?.length) return merged;\n\n const unknown: string[] = [];\n\n for (const name of vendors) {\n const preset = (CSP_VENDOR_PRESETS as Record<string, CspOrigins>)[name];\n if (!preset) {\n unknown.push(name);\n continue;\n }\n for (const key of Object.keys(merged) as (keyof Required<CspOrigins>)[]) {\n const origins = preset[key];\n if (origins) merged[key].push(...origins);\n }\n }\n\n if (unknown.length && warnUnknown) {\n const known = Object.keys(CSP_VENDOR_PRESETS).join(', ');\n console.warn(\n `[djangocfg] Unknown CSP vendor(s): ${unknown.join(', ')}. Known vendors: ${known}`\n );\n }\n\n // De-duplicate so two presets sharing a host (e.g. GA + GTM both using\n // googletagmanager.com) don't emit it twice in the header.\n for (const key of Object.keys(merged) as (keyof Required<CspOrigins>)[]) {\n merged[key] = [...new Set(merged[key])];\n }\n\n return merged;\n}\n","/**\n * Compression Plugin Setup\n *\n * Adds Gzip and Brotli compression for static builds.\n */\n\nimport type { Configuration as WebpackConfig } from 'webpack';\nimport { createRequire } from 'module';\n\nimport { isPackageInstalled } from '../packages/checker';\n\n// Optional peer dependencies are loaded synchronously at config time, which\n// `import()` cannot do. `createRequire` is the ESM-safe equivalent of `require`.\nconst requirePeer = createRequire(import.meta.url);\n\nexport interface CompressionPluginOptions {\n /** Enable gzip compression */\n gzip?: boolean;\n /** Enable brotli compression */\n brotli?: boolean;\n /** Minimum file size to compress (default: 8192) */\n threshold?: number;\n /** Minimum compression ratio (default: 0.8) */\n minRatio?: number;\n /** Brotli compression level (default: 8) */\n brotliLevel?: number;\n}\n\nconst DEFAULT_OPTIONS: Required<CompressionPluginOptions> = {\n gzip: true,\n brotli: true,\n threshold: 8192,\n minRatio: 0.8,\n brotliLevel: 8,\n};\n\n/**\n * Add compression plugins to webpack config\n *\n * Returns true if plugins were added, false if compression-webpack-plugin is not installed\n */\nexport function addCompressionPlugins(\n config: WebpackConfig,\n options: CompressionPluginOptions = {}\n): boolean {\n // Check if compression-webpack-plugin is installed\n if (!isPackageInstalled('compression-webpack-plugin')) {\n return false;\n }\n\n const opts = { ...DEFAULT_OPTIONS, ...options };\n\n try {\n // Resolved at call time to avoid bundling it when it is not installed.\n const CompressionPlugin = requirePeer('compression-webpack-plugin');\n\n if (!config.plugins) {\n config.plugins = [];\n }\n\n // Gzip compression\n if (opts.gzip) {\n config.plugins.push(\n new CompressionPlugin({\n filename: '[path][base].gz',\n algorithm: 'gzip',\n test: /\\.(js|css|html|svg|json)$/,\n threshold: opts.threshold,\n minRatio: opts.minRatio,\n })\n );\n }\n\n // Brotli compression\n if (opts.brotli) {\n config.plugins.push(\n new CompressionPlugin({\n filename: '[path][base].br',\n algorithm: 'brotliCompress',\n test: /\\.(js|css|html|svg|json)$/,\n threshold: opts.threshold,\n minRatio: opts.minRatio,\n compressionOptions: {\n level: opts.brotliLevel,\n },\n })\n );\n }\n\n return true;\n } catch {\n // compression-webpack-plugin failed to load\n return false;\n }\n}\n\n/**\n * Check if compression is available\n */\nexport function isCompressionAvailable(): boolean {\n return isPackageInstalled('compression-webpack-plugin');\n}\n","/**\n * Package Checker\n *\n * Checks which optional packages are missing and need to be installed.\n */\n\nimport { createRequire } from 'module';\nimport { join } from 'path';\n\nimport { isDev, isStaticBuild } from '../utils/env';\nimport { getPackagesForContext, OPTIONAL_PACKAGES } from './definitions';\n\nimport type { PackageDefinition } from './definitions';\nexport interface MissingPackage extends PackageDefinition {\n /** Why is this package needed? */\n reason: string;\n}\n\n/**\n * Check if a package is installed in the consumer's project\n * Uses process.cwd() to check from the consumer's context, not the library's\n */\nexport function isPackageInstalled(packageName: string): boolean {\n try {\n // Create require from consumer's cwd to check their node_modules\n const consumerRequire = createRequire(join(process.cwd(), 'package.json'));\n consumerRequire.resolve(packageName);\n return true;\n } catch {\n // Fallback: try regular require (for packages hoisted to root)\n try {\n require.resolve(packageName);\n return true;\n } catch {\n return false;\n }\n }\n}\n\n/**\n * Get all missing packages for current context\n */\nexport function getMissingPackages(): MissingPackage[] {\n const neededPackages = getPackagesForContext({ isStaticBuild, isDev });\n const missing: MissingPackage[] = [];\n\n for (const pkg of neededPackages) {\n if (!isPackageInstalled(pkg.name)) {\n missing.push({\n ...pkg,\n reason: getReasonText(pkg),\n });\n }\n }\n\n return missing;\n}\n\n/**\n * Check specific packages\n */\nexport function checkPackages(packageNames: string[]): MissingPackage[] {\n const missing: MissingPackage[] = [];\n\n for (const name of packageNames) {\n if (!isPackageInstalled(name)) {\n const definition = OPTIONAL_PACKAGES.find(p => p.name === name);\n missing.push({\n name,\n description: definition?.description || 'Optional package',\n condition: definition?.condition || 'always',\n devDependency: definition?.devDependency ?? true,\n reason: definition ? getReasonText(definition) : 'Requested by configuration',\n });\n }\n }\n\n return missing;\n}\n\n/**\n * Get human-readable reason for package requirement\n */\nfunction getReasonText(pkg: PackageDefinition): string {\n switch (pkg.condition) {\n case 'static-build':\n return 'Required for static build (NEXT_PUBLIC_STATIC_BUILD=true)';\n case 'dev':\n return 'Required for development mode';\n case 'always':\n return 'Required for all builds';\n default:\n return 'Optional feature';\n }\n}\n","/**\n * Environment Variable Utilities\n */\n\nimport consola from 'consola';\n\nexport const isStaticBuild = process.env.NEXT_PUBLIC_STATIC_BUILD === 'true';\nexport const isDev = process.env.NODE_ENV === 'development';\nexport const isProduction = !isDev;\nexport const isCI = process.env.CI === 'true';\n\nexport function getBasePath(isDefaultCfgAdmin?: boolean): string {\n if (!isStaticBuild) return '';\n return isDefaultCfgAdmin ? '/cfg/admin' : '/cfg/nextjs-admin';\n}\n\n/**\n * Resolve the API origin that gets inlined into the client bundle.\n *\n * `NEXT_PUBLIC_*` is substituted at BUILD time, so a value supplied only at\n * container runtime (a compose `environment:` entry, a `docker run -e`) arrives\n * far too late — the bundle already shipped with whatever was present during\n * `next build`.\n *\n * An empty result means \"no API origin is configured\". Callers MUST treat that\n * as *do not send the request* rather than falling back to a relative path: the\n * browser resolves a relative URL against the CURRENT page, so on an\n * i18n-prefixed route `/cfg/accounts/profile` silently becomes\n * `https://site.com/<locale>/cfg/accounts/profile` — a 404 that reproduces only\n * in production. Use {@link hasApiUrl} to gate those call sites.\n *\n * The one case where empty legitimately means \"relative\": a static build served\n * from the same origin as Django.\n */\nexport function getApiUrl(): string {\n return isStaticBuild ? '' : (process.env.NEXT_PUBLIC_API_URL || '');\n}\n\n/**\n * Whether a usable API origin is configured for this build.\n *\n * Gate every network call that targets Django on this. An app with no backend\n * (a pure marketing site) then needs no configuration at all — its optional\n * transports simply stay dormant instead of firing requests at the page's own\n * origin.\n *\n * A static build is same-origin by design, so relative paths ARE the intent\n * there and this reports true despite the empty string.\n */\nexport function hasApiUrl(): boolean {\n return isStaticBuild || Boolean(process.env.NEXT_PUBLIC_API_URL);\n}\n\n/**\n * Warn once, at build time, when no API origin is configured.\n *\n * Not an error: an app with no backend is a legitimate configuration. But the\n * same empty value also appears when a build *should* have had an API URL and\n * lost it — a `.dockerignore` rule stripping `.env.production`, or a value\n * supplied only through a runtime `environment:` entry. Both produce a bundle\n * whose API-backed features are silently inert, so say so while the build log\n * is still being read.\n */\nexport function warnIfApiUrlMissing(): void {\n if (hasApiUrl() || isDev) return;\n\n consola.warn(\n 'NEXT_PUBLIC_API_URL is not set — API-backed features are disabled in this build.\\n' +\n 'Requests that need it are skipped rather than sent to a relative path.\\n' +\n 'If this app DOES have a backend, the value must be present at BUILD time:\\n' +\n ' • commit it to the app\\'s `.env.production`, and check no `.dockerignore`\\n' +\n ' rule (e.g. `**/.env*`) strips that file from the build context;\\n' +\n ' • or pass it as a Dockerfile ARG/ENV before the build step.\\n' +\n 'A compose `environment:` entry is too late — it only exists at runtime.',\n );\n}\n\nexport function getSiteUrl(): string {\n return isStaticBuild ? '' : (process.env.NEXT_PUBLIC_SITE_URL || '');\n}\n","/**\n * Package Definitions\n *\n * Defines optional packages that can be auto-installed when needed.\n */\n\nexport interface PackageDefinition {\n /** npm package name */\n name: string;\n /** Human-readable description */\n description: string;\n /** When is this package needed? */\n condition: 'static-build' | 'dev' | 'always';\n /** Is this a dev dependency? */\n devDependency: boolean;\n /** Feature flag that enables this package */\n featureFlag?: string;\n}\n\n/**\n * Optional packages that can be auto-installed\n */\nexport const OPTIONAL_PACKAGES: PackageDefinition[] = [\n {\n name: 'compression-webpack-plugin',\n description: 'Gzip and Brotli compression for static builds',\n condition: 'static-build',\n devDependency: true,\n },\n {\n name: '@next/bundle-analyzer',\n description: 'Bundle analyzer for analyzing build output',\n condition: 'dev',\n devDependency: true,\n featureFlag: 'ANALYZE',\n },\n];\n\n/**\n * Required peer dependencies (should already be installed)\n */\nexport const PEER_DEPENDENCIES = [\n 'next',\n 'react',\n 'react-dom',\n] as const;\n\n/**\n * Get packages needed for current build context\n */\nexport function getPackagesForContext(options: {\n isStaticBuild: boolean;\n isDev: boolean;\n}): PackageDefinition[] {\n return OPTIONAL_PACKAGES.filter(pkg => {\n switch (pkg.condition) {\n case 'static-build':\n return options.isStaticBuild;\n case 'dev':\n return options.isDev;\n case 'always':\n return true;\n default:\n return false;\n }\n });\n}\n","/**\n * Dev Startup Webpack Plugin\n *\n * Handles banner display, version checking, and package updates.\n */\n\nimport type { Compiler } from 'webpack';\nimport chalk from 'chalk';\nimport { existsSync } from 'fs';\nimport { join } from 'path';\n\nimport { AI_DOCS_HINT } from '../../ai/constants';\nimport { DJANGO_CFG_BANNER } from '../constants';\nimport { checkAndInstallPackages } from '../packages/installer';\nimport { checkAndUpdatePackages } from '../packages/updater';\nimport { getCurrentVersion } from '../utils/version';\n\n// Track if startup tasks were already run (persists across HMR)\nlet startupDone = false;\n\nexport interface DevStartupPluginOptions {\n /** Check for missing optional packages (default: true) */\n checkPackages?: boolean;\n /** Auto-install missing packages without prompting */\n autoInstall?: boolean;\n /** Check for @djangocfg/* package updates (default: true) */\n checkUpdates?: boolean;\n /** Auto-update packages without prompting */\n autoUpdate?: boolean;\n /** Force check workspace:* packages (for testing in monorepo) */\n forceCheckWorkspace?: boolean;\n}\n\n/**\n * Webpack plugin for dev startup tasks\n */\nexport class DevStartupPlugin {\n private options: DevStartupPluginOptions;\n\n constructor(options: DevStartupPluginOptions = {}) {\n this.options = options;\n }\n\n apply(compiler: Compiler): void {\n // Use tapPromise for proper async handling\n compiler.hooks.done.tapPromise('DevStartupPlugin', async () => {\n // Run startup tasks only once\n if (!startupDone) {\n startupDone = true;\n await this.runStartupTasks();\n }\n });\n }\n\n private async runStartupTasks(): Promise<void> {\n // 1. Print banner\n console.log('\\n' + chalk.yellowBright.bold(DJANGO_CFG_BANNER));\n\n // 2. Print current version\n const version = getCurrentVersion();\n if (version) {\n console.log(chalk.dim(` 📦 @djangocfg/nextjs v${version}`));\n }\n\n // 3. Check PWA setup\n this.checkPWASetup();\n\n // 4. Print AI docs hint\n console.log(chalk.magenta(` ${AI_DOCS_HINT}\\n`));\n\n // 5. Check for package updates\n if (this.options.checkUpdates !== false) {\n try {\n await checkAndUpdatePackages({\n autoUpdate: this.options.autoUpdate,\n forceCheckWorkspace: this.options.forceCheckWorkspace,\n force: true, // Force check ignoring cooldown\n });\n } catch (err) {\n console.log(chalk.red(' Update check failed:'), err);\n }\n }\n\n // 6. Check for missing optional packages\n if (this.options.checkPackages !== false) {\n await checkAndInstallPackages({\n autoInstall: this.options.autoInstall,\n });\n }\n }\n\n private checkPWASetup(): void {\n const cwd = process.cwd();\n const swPath = join(cwd, 'app', 'sw.ts');\n const manifestPath = join(cwd, 'app', 'manifest.ts');\n\n const hasSW = existsSync(swPath);\n const hasManifest = existsSync(manifestPath);\n\n if (hasSW || hasManifest) {\n console.log(chalk.cyan(' 📱 PWA Configuration:'));\n\n if (hasSW) {\n console.log(chalk.green(' ✓ Service Worker: app/sw.ts'));\n } else {\n console.log(chalk.yellow(' ⚠ Service Worker: not found'));\n }\n\n if (hasManifest) {\n console.log(chalk.green(' ✓ Manifest: app/manifest.ts'));\n } else {\n console.log(chalk.yellow(' ⚠ Manifest: not found'));\n }\n\n console.log(chalk.dim(' → Check: DevTools → Application → Service Workers'));\n console.log(chalk.dim(' → Test push: import from @djangocfg/nextjs/pwa'));\n }\n }\n}\n\n/**\n * Reset plugin state (useful for tests)\n */\nexport function resetDevStartupState(): void {\n startupDone = false;\n}\n","/**\n * AI Documentation Constants\n */\n\n// MCP Server URLs\nexport const MCP_BASE_URL = 'https://mcp.djangocfg.com';\nexport const MCP_SERVER_URL = `${MCP_BASE_URL}/mcp`;\nexport const MCP_API_URL = `${MCP_BASE_URL}/api/search`;\n\n// API Endpoints\nexport const API_SEARCH_ENDPOINT = '/api/search';\nexport const API_INFO_ENDPOINT = '/api/info';\nexport const DEFAULT_TIMEOUT = 10000; // ms\n\n// AI Hint for console output\nexport const AI_DOCS_HINT = `🤖 AI Docs: ${MCP_SERVER_URL} | pnpm ai-docs search \"query\"`;\n\n// Full AI Hint description\nexport const AI_HINT = `\nDjangoCFG Documentation is available via MCP server.\n\nTo get help with DjangoCFG configuration:\n1. Use the MCP server: ${MCP_SERVER_URL}\n2. Or search directly: ${MCP_API_URL}?q=YOUR_QUERY&limit=5\n\nExample queries:\n- \"How to configure PostgreSQL database?\"\n- \"What is DatabaseConfig?\"\n- \"How to setup Redis cache?\"\n- \"Email configuration with SMTP\"\n`;\n","/**\n * Package Installer\n *\n * Provides auto-installation functionality with user confirmation and progress indication.\n */\n\nimport chalk from 'chalk';\nimport { execSync, spawn } from 'child_process';\nimport Conf from 'conf';\nimport consola from 'consola';\nimport { existsSync } from 'fs';\nimport { join } from 'path';\nimport { createInterface } from 'readline';\n\nimport { isCI } from '../utils/env';\nimport { getMissingPackages } from './checker';\n\nimport type { MissingPackage } from './checker';\ntype InstallerCacheShape = {\n autoInstall?: boolean;\n skipPackages?: string[];\n lastPrompt?: number;\n};\n\ninterface CacheLike<T extends Record<string, unknown>> {\n get<K extends keyof T>(key: K): T[K] | undefined;\n set<K extends keyof T>(key: K, value: T[K]): void;\n clear(): void;\n}\n\nfunction createMemoryCache<T extends Record<string, unknown>>(): CacheLike<T> {\n const store = new Map<string, unknown>();\n return {\n get<K extends keyof T>(key: K): T[K] | undefined {\n return store.get(String(key)) as T[K] | undefined;\n },\n set<K extends keyof T>(key: K, value: T[K]): void {\n store.set(String(key), value);\n },\n clear(): void {\n store.clear();\n },\n };\n}\n\nfunction createInstallerCache(): CacheLike<InstallerCacheShape> {\n try {\n return new Conf<InstallerCacheShape>({\n projectName: 'djangocfg-nextjs-installer',\n projectVersion: '1.0.0',\n });\n } catch {\n // Fallback for corrupted conf file or invalid JSON in cache storage.\n return createMemoryCache<InstallerCacheShape>();\n }\n}\n\n// Installer preferences cache\nconst installerCache = createInstallerCache();\n\n// Don't prompt more than once per hour\nconst PROMPT_COOLDOWN_MS = 60 * 60 * 1000;\n\n// Spinner frames for progress\nconst SPINNER_FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];\n\nexport interface InstallOptions {\n /** Auto-install without prompting */\n autoInstall?: boolean;\n /** Skip specific packages */\n skipPackages?: string[];\n /** Force prompt even if recently prompted */\n force?: boolean;\n}\n\nexport interface InstallProgress {\n current: number;\n total: number;\n package: string;\n status: 'pending' | 'installing' | 'done' | 'error';\n}\n\n/**\n * Detect package manager\n */\nexport function detectPackageManager(): 'pnpm' | 'yarn' | 'npm' {\n try {\n // Check for lockfiles\n const cwd = process.cwd();\n\n if (existsSync(join(cwd, 'pnpm-lock.yaml'))) return 'pnpm';\n if (existsSync(join(cwd, 'yarn.lock'))) return 'yarn';\n if (existsSync(join(cwd, 'package-lock.json'))) return 'npm';\n\n // Check for global package manager\n try {\n execSync('pnpm --version', { stdio: 'ignore' });\n return 'pnpm';\n } catch {\n try {\n execSync('yarn --version', { stdio: 'ignore' });\n return 'yarn';\n } catch {\n return 'npm';\n }\n }\n } catch {\n return 'npm';\n }\n}\n\n/**\n * Build install command for a single package\n */\nexport function buildSingleInstallCommand(\n packageName: string,\n isDev: boolean,\n pm: 'pnpm' | 'yarn' | 'npm'\n): string {\n const devFlag = isDev ? '-D ' : '';\n switch (pm) {\n case 'pnpm':\n return `pnpm add ${devFlag}${packageName}`;\n case 'yarn':\n return `yarn add ${devFlag}${packageName}`;\n case 'npm':\n return `npm install ${devFlag}${packageName}`;\n }\n}\n\n/**\n * Build install command for multiple packages (for display)\n */\nexport function buildInstallCommand(packages: MissingPackage[], pm: 'pnpm' | 'yarn' | 'npm'): string {\n const devPackages = packages.filter(p => p.devDependency).map(p => p.name);\n const prodPackages = packages.filter(p => !p.devDependency).map(p => p.name);\n\n const commands: string[] = [];\n\n if (devPackages.length > 0) {\n switch (pm) {\n case 'pnpm':\n commands.push(`pnpm add -D ${devPackages.join(' ')}`);\n break;\n case 'yarn':\n commands.push(`yarn add -D ${devPackages.join(' ')}`);\n break;\n case 'npm':\n commands.push(`npm install -D ${devPackages.join(' ')}`);\n break;\n }\n }\n\n if (prodPackages.length > 0) {\n switch (pm) {\n case 'pnpm':\n commands.push(`pnpm add ${prodPackages.join(' ')}`);\n break;\n case 'yarn':\n commands.push(`yarn add ${prodPackages.join(' ')}`);\n break;\n case 'npm':\n commands.push(`npm install ${prodPackages.join(' ')}`);\n break;\n }\n }\n\n return commands.join(' && ');\n}\n\n/**\n * Ask user for confirmation via readline\n */\nasync function askConfirmation(question: string): Promise<boolean> {\n // Skip prompt in CI or non-TTY environments\n if (isCI || !process.stdin.isTTY) {\n return false;\n }\n\n return new Promise((resolve) => {\n const rl = createInterface({\n input: process.stdin,\n output: process.stdout,\n });\n\n rl.question(question, (answer) => {\n rl.close();\n const normalized = answer.toLowerCase().trim();\n resolve(normalized === '' || normalized === 'y' || normalized === 'yes');\n });\n });\n}\n\n/**\n * Create a simple spinner\n */\nfunction createSpinner(text: string) {\n let frameIndex = 0;\n let interval: NodeJS.Timeout | null = null;\n let currentText = text;\n\n const render = () => {\n const frame = SPINNER_FRAMES[frameIndex];\n process.stdout.write(`\\r${chalk.cyan(frame)} ${currentText}`);\n frameIndex = (frameIndex + 1) % SPINNER_FRAMES.length;\n };\n\n return {\n start() {\n if (process.stdout.isTTY) {\n interval = setInterval(render, 80);\n render();\n } else {\n console.log(` ${currentText}`);\n }\n return this;\n },\n text(newText: string) {\n currentText = newText;\n if (!process.stdout.isTTY) {\n console.log(` ${newText}`);\n }\n return this;\n },\n succeed(text?: string) {\n if (interval) clearInterval(interval);\n if (process.stdout.isTTY) {\n process.stdout.write(`\\r${chalk.green('✓')} ${text || currentText}\\n`);\n } else {\n console.log(` ${chalk.green('✓')} ${text || currentText}`);\n }\n return this;\n },\n fail(text?: string) {\n if (interval) clearInterval(interval);\n if (process.stdout.isTTY) {\n process.stdout.write(`\\r${chalk.red('✗')} ${text || currentText}\\n`);\n } else {\n console.log(` ${chalk.red('✗')} ${text || currentText}`);\n }\n return this;\n },\n stop() {\n if (interval) clearInterval(interval);\n if (process.stdout.isTTY) {\n process.stdout.write('\\r\\x1b[K'); // Clear line\n }\n return this;\n },\n };\n}\n\n/**\n * Install a single package with progress\n */\nasync function installSinglePackage(\n pkg: MissingPackage,\n pm: 'pnpm' | 'yarn' | 'npm',\n index: number,\n total: number\n): Promise<boolean> {\n const command = buildSingleInstallCommand(pkg.name, pkg.devDependency, pm);\n const progress = `[${index + 1}/${total}]`;\n const spinner = createSpinner(`${chalk.dim(progress)} Installing ${chalk.cyan(pkg.name)}...`);\n\n spinner.start();\n\n return new Promise((resolve) => {\n // `split` returns `string[]`, so `cmd` is `string | undefined` and no\n // `spawn` overload accepts that — which typed `proc` as `never` and made\n // every `proc.*` below an error. `buildSingleInstallCommand` never returns\n // an empty string; the fallback states that rather than asserting it.\n const [cmd = pm, ...args] = command.split(' ');\n const proc = spawn(cmd, args, {\n shell: true,\n cwd: process.cwd(),\n stdio: ['ignore', 'pipe', 'pipe'],\n });\n\n let stderr = '';\n\n proc.stderr?.on('data', (data) => {\n stderr += data.toString();\n });\n\n proc.on('close', (code) => {\n if (code === 0) {\n spinner.succeed(`${chalk.dim(progress)} ${chalk.cyan(pkg.name)} ${chalk.green('installed')}`);\n resolve(true);\n } else {\n spinner.fail(`${chalk.dim(progress)} ${chalk.cyan(pkg.name)} ${chalk.red('failed')}`);\n if (stderr) {\n console.log(chalk.dim(` ${stderr.split('\\n')[0]}`));\n }\n resolve(false);\n }\n });\n\n proc.on('error', () => {\n spinner.fail(`${chalk.dim(progress)} ${chalk.cyan(pkg.name)} ${chalk.red('failed')}`);\n resolve(false);\n });\n });\n}\n\n/**\n * Install packages with progress indication\n */\nexport async function installPackagesWithProgress(packages: MissingPackage[]): Promise<boolean> {\n if (packages.length === 0) return true;\n\n const pm = detectPackageManager();\n const total = packages.length;\n\n console.log('');\n console.log(chalk.bold(` Installing ${total} package${total > 1 ? 's' : ''}...`));\n console.log('');\n\n let successCount = 0;\n const failedPackages: string[] = [];\n\n for (const [i, pkg] of packages.entries()) {\n const success = await installSinglePackage(pkg, pm, i, total);\n if (success) {\n successCount++;\n } else {\n failedPackages.push(pkg.name);\n }\n }\n\n console.log('');\n\n if (failedPackages.length === 0) {\n consola.success(`All ${total} packages installed successfully!`);\n return true;\n } else if (successCount > 0) {\n consola.warn(`${successCount}/${total} packages installed. Failed: ${failedPackages.join(', ')}`);\n return false;\n } else {\n consola.error(`Failed to install packages: ${failedPackages.join(', ')}`);\n return false;\n }\n}\n\n/**\n * Install packages (simple version without per-package progress)\n */\nexport async function installPackages(packages: MissingPackage[]): Promise<boolean> {\n // Use progress version for multiple packages in TTY\n if (packages.length > 1 && process.stdout.isTTY) {\n return installPackagesWithProgress(packages);\n }\n\n // Simple installation for single package or non-TTY\n if (packages.length === 0) return true;\n\n const pm = detectPackageManager();\n const command = buildInstallCommand(packages, pm);\n\n consola.info(`Installing: ${chalk.cyan(packages.map(p => p.name).join(', '))}`);\n\n const spinner = createSpinner('Installing packages...');\n spinner.start();\n\n return new Promise((resolve) => {\n const proc = spawn(command, {\n shell: true,\n cwd: process.cwd(),\n stdio: ['ignore', 'pipe', 'pipe'],\n });\n\n proc.on('close', (code) => {\n if (code === 0) {\n spinner.succeed('Packages installed successfully!');\n resolve(true);\n } else {\n spinner.fail('Failed to install packages');\n resolve(false);\n }\n });\n\n proc.on('error', () => {\n spinner.fail('Installation failed');\n resolve(false);\n });\n });\n}\n\n/**\n * Check and prompt for missing packages\n *\n * Returns true if all packages are available (either already installed or just installed)\n */\nexport async function checkAndInstallPackages(options: InstallOptions = {}): Promise<boolean> {\n const missing = getMissingPackages();\n\n // Filter out skipped packages\n const skipList = options.skipPackages || installerCache.get('skipPackages') || [];\n const toInstall = missing.filter(p => !skipList.includes(p.name));\n\n if (toInstall.length === 0) {\n return true;\n }\n\n // Check cooldown (don't prompt too often)\n const lastPrompt = installerCache.get('lastPrompt') || 0;\n if (!options.force && (Date.now() - lastPrompt) < PROMPT_COOLDOWN_MS) {\n // Show info but don't prompt\n printMissingPackagesInfo(toInstall);\n return false;\n }\n\n // Auto-install if configured\n if (options.autoInstall || installerCache.get('autoInstall')) {\n return installPackages(toInstall);\n }\n\n // Show missing packages\n console.log('');\n consola.box('📦 Missing Optional Packages');\n console.log('');\n\n for (const pkg of toInstall) {\n console.log(` ${chalk.yellow('•')} ${chalk.bold(pkg.name)}`);\n console.log(` ${chalk.dim(pkg.description)}`);\n console.log(` ${chalk.dim(pkg.reason)}`);\n console.log('');\n }\n\n // Build install command for display\n const pm = detectPackageManager();\n const command = buildInstallCommand(toInstall, pm);\n\n console.log(` ${chalk.cyan('Command:')} ${command}`);\n console.log('');\n\n // Ask for confirmation\n installerCache.set('lastPrompt', Date.now());\n\n const shouldInstall = await askConfirmation(\n `${chalk.green('?')} Install these packages now? ${chalk.dim('[Y/n]')} `\n );\n\n if (shouldInstall) {\n const success = await installPackages(toInstall);\n\n // Ask if user wants to enable auto-install for future\n if (success) {\n const enableAuto = await askConfirmation(\n `${chalk.green('?')} Enable auto-install for future? ${chalk.dim('[y/N]')} `\n );\n if (enableAuto) {\n installerCache.set('autoInstall', true);\n consola.info('Auto-install enabled. Run with --no-auto-install to disable.');\n }\n }\n\n return success;\n }\n\n // User declined, ask if they want to skip these packages permanently\n const skipPermanently = await askConfirmation(\n `${chalk.green('?')} Skip these packages in future prompts? ${chalk.dim('[y/N]')} `\n );\n\n if (skipPermanently) {\n const currentSkip = installerCache.get('skipPackages') || [];\n installerCache.set('skipPackages', [...currentSkip, ...toInstall.map(p => p.name)]);\n consola.info('Packages added to skip list.');\n }\n\n return false;\n}\n\n/**\n * Print info about missing packages without prompting\n */\nfunction printMissingPackagesInfo(packages: MissingPackage[]): void {\n if (packages.length === 0) return;\n\n const pm = detectPackageManager();\n const command = buildInstallCommand(packages, pm);\n\n consola.warn(`Missing optional packages: ${packages.map(p => p.name).join(', ')}`);\n consola.info(`Install with: ${chalk.cyan(command)}`);\n}\n\n/**\n * Reset installer preferences\n */\nexport function resetInstallerPreferences(): void {\n installerCache.clear();\n consola.success('Installer preferences reset');\n}\n","/**\n * Package Updater\n *\n * Checks for outdated @djangocfg/* packages and offers to update them.\n */\n\nimport chalk from 'chalk';\nimport { spawn } from 'child_process';\nimport Conf from 'conf';\nimport consola from 'consola';\nimport { existsSync, readFileSync } from 'fs';\nimport { createRequire } from 'module';\nimport { join } from 'path';\nimport { createInterface } from 'readline';\nimport semver from 'semver';\n\nimport { DJANGOCFG_PACKAGES, PACKAGE_NAME } from '../constants';\nimport { isCI } from '../utils/env';\nimport { detectPackageManager } from './installer';\n\ntype UpdaterCacheShape = {\n autoUpdate?: boolean;\n lastCheck?: number;\n skippedVersions?: Record<string, string>;\n};\n\ninterface CacheLike<T extends Record<string, unknown>> {\n get<K extends keyof T>(key: K): T[K] | undefined;\n set<K extends keyof T>(key: K, value: T[K]): void;\n clear(): void;\n}\n\nfunction createMemoryCache<T extends Record<string, unknown>>(): CacheLike<T> {\n const store = new Map<string, unknown>();\n return {\n get<K extends keyof T>(key: K): T[K] | undefined {\n return store.get(String(key)) as T[K] | undefined;\n },\n set<K extends keyof T>(key: K, value: T[K]): void {\n store.set(String(key), value);\n },\n clear(): void {\n store.clear();\n },\n };\n}\n\nfunction createUpdaterCache(): CacheLike<UpdaterCacheShape> {\n try {\n return new Conf<UpdaterCacheShape>({\n projectName: 'djangocfg-nextjs-updater',\n projectVersion: '1.0.0',\n });\n } catch {\n // Fallback for corrupted conf file or invalid JSON in cache storage.\n return createMemoryCache<UpdaterCacheShape>();\n }\n}\n\n// Updater preferences cache\nconst updaterCache = createUpdaterCache();\n\n// Check for updates once per hour\nconst UPDATE_CHECK_COOLDOWN_MS = 60 * 60 * 1000;\n\n// Spinner frames\nconst SPINNER_FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];\n\nexport interface PackageVersion {\n name: string;\n current: string | null;\n latest: string | null;\n hasUpdate: boolean;\n}\n\nexport interface UpdateOptions {\n /** Auto-update without prompting */\n autoUpdate?: boolean;\n /** Force check even if recently checked (ignores cooldown) */\n force?: boolean;\n /** Force check even for workspace:* packages (for testing) */\n forceCheckWorkspace?: boolean;\n}\n\n/**\n * Check if package is a workspace dependency\n */\nfunction isWorkspacePackage(packageName: string): boolean {\n try {\n const pkgJsonPath = join(process.cwd(), 'package.json');\n const pkgJson = JSON.parse(readFileSync(pkgJsonPath, 'utf-8'));\n\n const deps = { ...pkgJson.dependencies, ...pkgJson.devDependencies };\n const version = deps[packageName];\n\n return version?.startsWith('workspace:') || false;\n } catch {\n return false;\n }\n}\n\n/**\n * Get installed version of a package from consumer's project\n * Uses multiple fallback strategies for monorepo compatibility\n */\nexport function getInstalledVersion(packageName: string): string | null {\n const cwd = process.cwd();\n\n // Strategy 1: Direct read from node_modules (works with symlinks)\n try {\n const directPath = join(cwd, 'node_modules', packageName, 'package.json');\n if (existsSync(directPath)) {\n const pkg = JSON.parse(readFileSync(directPath, 'utf-8'));\n return pkg.version || null;\n }\n } catch {\n // Continue to next strategy\n }\n\n // Strategy 2: Resolve from the consumer's project root. `createRequire` is the\n // ESM-safe way to do this — a bare `require()` here does not exist once tsup\n // emits an .mjs bundle, and its shim throws rather than falling through to the\n // next strategy.\n try {\n const consumerRequire = createRequire(join(cwd, 'package.json'));\n const pkgPath = consumerRequire.resolve(`${packageName}/package.json`);\n return JSON.parse(readFileSync(pkgPath, 'utf-8')).version || null;\n } catch {\n // Continue to next strategy\n }\n\n // Strategy 3: Resolve relative to THIS module, for the hoisted/global case.\n try {\n const localRequire = createRequire(import.meta.url);\n const pkgPath = localRequire.resolve(`${packageName}/package.json`);\n return JSON.parse(readFileSync(pkgPath, 'utf-8')).version || null;\n } catch {\n return null;\n }\n}\n\n/**\n * Check if we should skip update checking for this package\n * (e.g., workspace packages shouldn't be checked against npm)\n */\nexport function shouldCheckUpdates(packageName: string): boolean {\n return !isWorkspacePackage(packageName);\n}\n\n/**\n * Fetch latest version from npm registry\n */\nasync function fetchLatestVersion(packageName: string): Promise<string | null> {\n try {\n const https = await import('https');\n return new Promise((resolve) => {\n const req = https.get(\n `https://registry.npmjs.org/${packageName}/latest`,\n { timeout: 5000 },\n (res) => {\n let data = '';\n res.on('data', (chunk: string) => { data += chunk; });\n res.on('end', () => {\n try {\n const json = JSON.parse(data);\n resolve(json.version || null);\n } catch {\n resolve(null);\n }\n });\n }\n );\n req.on('error', () => resolve(null));\n req.on('timeout', () => { req.destroy(); resolve(null); });\n });\n } catch {\n return null;\n }\n}\n\n/**\n * Check all @djangocfg packages for updates\n * All packages are aligned to the version of @djangocfg/nextjs (master package)\n * Skips workspace:* packages unless forceCheckWorkspace is true\n */\nexport async function checkForUpdates(options: { forceCheckWorkspace?: boolean } = {}): Promise<PackageVersion[]> {\n const results: PackageVersion[] = [];\n\n // First, get the latest version of master package (@djangocfg/nextjs)\n // All other packages should align to this version\n const masterLatest = await fetchLatestVersion(PACKAGE_NAME);\n if (!masterLatest) {\n // Can't determine target version, skip update check\n return results;\n }\n\n // Check packages against master version\n const checks = DJANGOCFG_PACKAGES.map(async (name) => {\n // Skip workspace packages unless force mode\n const isWorkspace = !shouldCheckUpdates(name);\n if (!options.forceCheckWorkspace && isWorkspace) {\n return null;\n }\n\n const current = getInstalledVersion(name);\n if (!current) {\n return null;\n }\n\n // All packages should update to master package version\n const hasUpdate = !!(masterLatest && current && semver.gt(masterLatest, current));\n\n return { name, current, latest: masterLatest, hasUpdate };\n });\n\n const checkResults = await Promise.all(checks);\n\n for (const result of checkResults) {\n if (result) {\n results.push(result);\n }\n }\n\n return results;\n}\n\n/**\n * Get packages that need updating\n */\nexport async function getOutdatedPackages(options: { forceCheckWorkspace?: boolean } = {}): Promise<PackageVersion[]> {\n const all = await checkForUpdates(options);\n const skipped = updaterCache.get('skippedVersions') || {};\n\n return all.filter(pkg => {\n if (!pkg.hasUpdate) return false;\n // Skip if user chose to skip this specific version\n if (skipped[pkg.name] === pkg.latest) return false;\n return true;\n });\n}\n\n/**\n * Create a simple spinner\n */\nfunction createSpinner(text: string) {\n let frameIndex = 0;\n let interval: NodeJS.Timeout | null = null;\n let currentText = text;\n\n const render = () => {\n const frame = SPINNER_FRAMES[frameIndex];\n process.stdout.write(`\\r${chalk.cyan(frame)} ${currentText}`);\n frameIndex = (frameIndex + 1) % SPINNER_FRAMES.length;\n };\n\n return {\n start() {\n if (process.stdout.isTTY) {\n interval = setInterval(render, 80);\n render();\n } else {\n console.log(` ${currentText}`);\n }\n return this;\n },\n text(newText: string) {\n currentText = newText;\n return this;\n },\n succeed(text?: string) {\n if (interval) clearInterval(interval);\n if (process.stdout.isTTY) {\n process.stdout.write(`\\r${chalk.green('✓')} ${text || currentText}\\n`);\n } else {\n console.log(` ${chalk.green('✓')} ${text || currentText}`);\n }\n return this;\n },\n fail(text?: string) {\n if (interval) clearInterval(interval);\n if (process.stdout.isTTY) {\n process.stdout.write(`\\r${chalk.red('✗')} ${text || currentText}\\n`);\n } else {\n console.log(` ${chalk.red('✗')} ${text || currentText}`);\n }\n return this;\n },\n stop() {\n if (interval) clearInterval(interval);\n if (process.stdout.isTTY) {\n process.stdout.write('\\r\\x1b[K');\n }\n return this;\n },\n };\n}\n\n/**\n * Ask user for confirmation\n */\nasync function askConfirmation(question: string): Promise<boolean> {\n if (isCI || !process.stdin.isTTY) {\n return false;\n }\n\n return new Promise((resolve) => {\n const rl = createInterface({\n input: process.stdin,\n output: process.stdout,\n });\n\n rl.question(question, (answer) => {\n rl.close();\n const normalized = answer.toLowerCase().trim();\n resolve(normalized === '' || normalized === 'y' || normalized === 'yes');\n });\n });\n}\n\n/**\n * Update a single package with progress\n */\nasync function updateSinglePackage(\n pkg: PackageVersion,\n pm: 'pnpm' | 'yarn' | 'npm',\n index: number,\n total: number\n): Promise<boolean> {\n const progress = `[${index + 1}/${total}]`;\n const versionInfo = `${chalk.red(pkg.current)} → ${chalk.green(pkg.latest)}`;\n const spinner = createSpinner(\n `${chalk.dim(progress)} Updating ${chalk.cyan(pkg.name)} ${versionInfo}`\n );\n\n spinner.start();\n\n // Always install with @latest to keep package.json consistent\n const command = pm === 'pnpm'\n ? `pnpm add ${pkg.name}@latest`\n : pm === 'yarn'\n ? `yarn add ${pkg.name}@latest`\n : `npm install ${pkg.name}@latest`;\n\n return new Promise((resolve) => {\n const proc = spawn(command, {\n shell: true,\n cwd: process.cwd(),\n stdio: ['ignore', 'pipe', 'pipe'],\n });\n\n proc.on('close', (code) => {\n if (code === 0) {\n spinner.succeed(\n `${chalk.dim(progress)} ${chalk.cyan(pkg.name)} ${chalk.green(pkg.latest!)}`\n );\n resolve(true);\n } else {\n spinner.fail(\n `${chalk.dim(progress)} ${chalk.cyan(pkg.name)} ${chalk.red('failed')}`\n );\n resolve(false);\n }\n });\n\n proc.on('error', () => {\n spinner.fail(`${chalk.dim(progress)} ${chalk.cyan(pkg.name)} ${chalk.red('failed')}`);\n resolve(false);\n });\n });\n}\n\n/**\n * Update packages with progress\n */\nexport async function updatePackagesWithProgress(packages: PackageVersion[]): Promise<boolean> {\n if (packages.length === 0) return true;\n\n const pm = detectPackageManager();\n const total = packages.length;\n\n console.log('');\n console.log(chalk.bold(` Updating ${total} package${total > 1 ? 's' : ''}...`));\n console.log('');\n\n let successCount = 0;\n const failedPackages: string[] = [];\n\n for (const [i, pkg] of packages.entries()) {\n const success = await updateSinglePackage(pkg, pm, i, total);\n if (success) {\n successCount++;\n } else {\n failedPackages.push(pkg.name);\n }\n }\n\n console.log('');\n\n if (failedPackages.length === 0) {\n consola.success(`All ${total} packages updated successfully!`);\n return true;\n } else if (successCount > 0) {\n consola.warn(`${successCount}/${total} packages updated. Failed: ${failedPackages.join(', ')}`);\n return false;\n } else {\n consola.error(`Failed to update packages: ${failedPackages.join(', ')}`);\n return false;\n }\n}\n\n/**\n * Check and prompt for package updates\n */\nexport async function checkAndUpdatePackages(options: UpdateOptions = {}): Promise<boolean> {\n // Check cooldown\n const lastCheck = updaterCache.get('lastCheck') || 0;\n if (!options.force && (Date.now() - lastCheck) < UPDATE_CHECK_COOLDOWN_MS) {\n return true;\n }\n\n updaterCache.set('lastCheck', Date.now());\n\n // Check for updates\n const spinner = createSpinner('Checking for updates...');\n spinner.start();\n\n const outdated = await getOutdatedPackages({\n forceCheckWorkspace: options.forceCheckWorkspace,\n });\n\n spinner.stop();\n\n console.log(chalk.dim(` Found ${outdated.length} outdated package(s)`));\n\n if (outdated.length === 0) {\n console.log(chalk.green(' ✓ All packages are up to date'));\n return true;\n }\n\n // Auto-update if configured\n if (options.autoUpdate || updaterCache.get('autoUpdate')) {\n return updatePackagesWithProgress(outdated);\n }\n\n // Show outdated packages\n console.log('');\n consola.box('📦 Updates Available');\n console.log('');\n\n for (const pkg of outdated) {\n console.log(\n ` ${chalk.yellow('•')} ${chalk.bold(pkg.name)} ` +\n `${chalk.red(pkg.current)} → ${chalk.green(pkg.latest)}`\n );\n }\n console.log('');\n\n // Ask for confirmation\n const shouldUpdate = await askConfirmation(\n `${chalk.green('?')} Update these packages now? ${chalk.dim('[Y/n]')} `\n );\n\n if (shouldUpdate) {\n const success = await updatePackagesWithProgress(outdated);\n\n if (success) {\n const enableAuto = await askConfirmation(\n `${chalk.green('?')} Enable auto-update for future? ${chalk.dim('[y/N]')} `\n );\n if (enableAuto) {\n updaterCache.set('autoUpdate', true);\n consola.info('Auto-update enabled.');\n }\n }\n\n return success;\n }\n\n // User declined, ask if they want to skip these versions\n const skipVersions = await askConfirmation(\n `${chalk.green('?')} Skip these versions in future? ${chalk.dim('[y/N]')} `\n );\n\n if (skipVersions) {\n const skipped = updaterCache.get('skippedVersions') || {};\n for (const pkg of outdated) {\n if (pkg.latest) {\n skipped[pkg.name] = pkg.latest;\n }\n }\n updaterCache.set('skippedVersions', skipped);\n consola.info('Versions skipped. Will prompt again for newer versions.');\n }\n\n return false;\n}\n\n/**\n * Reset updater preferences\n */\nexport function resetUpdaterPreferences(): void {\n updaterCache.clear();\n consola.success('Updater preferences reset');\n}\n","/**\n * Version Checking Utilities\n */\n\nimport chalk from 'chalk';\nimport Conf from 'conf';\nimport consola from 'consola';\nimport { readFileSync } from 'fs';\nimport { createRequire } from 'module';\nimport semver from 'semver';\n\nimport { DJANGOCFG_PACKAGES, PACKAGE_NAME, VERSION_CACHE_TTL_MS } from '../constants';\n\ntype VersionCacheShape = {\n latestVersion?: string;\n lastCheck?: number;\n};\n\ninterface CacheLike<T extends Record<string, unknown>> {\n get<K extends keyof T>(key: K): T[K] | undefined;\n set<K extends keyof T>(key: K, value: T[K]): void;\n clear(): void;\n}\n\nfunction createMemoryCache<T extends Record<string, unknown>>(): CacheLike<T> {\n const store = new Map<string, unknown>();\n return {\n get<K extends keyof T>(key: K): T[K] | undefined {\n return store.get(String(key)) as T[K] | undefined;\n },\n set<K extends keyof T>(key: K, value: T[K]): void {\n store.set(String(key), value);\n },\n clear(): void {\n store.clear();\n },\n };\n}\n\nfunction createVersionCache(): CacheLike<VersionCacheShape> {\n try {\n return new Conf<VersionCacheShape>({\n projectName: 'djangocfg-nextjs',\n projectVersion: '1.0.0',\n });\n } catch {\n // Fallback for corrupted conf file or invalid JSON in cache storage.\n return createMemoryCache<VersionCacheShape>();\n }\n}\n\n// Version cache using conf (stores in ~/.config/djangocfg-nextjs/)\nconst versionCache = createVersionCache();\n\n/**\n * Get current package version from package.json\n */\nexport function getCurrentVersion(): string | null {\n try {\n // `createRequire(import.meta.url).resolve` rather than a bare `require`: the\n // published bundle is ESM, where `require` does not exist. Resolving is only\n // used to locate the file — the read itself goes through `fs`, so this does\n // not depend on JSON-module import support.\n const localRequire = createRequire(import.meta.url);\n const pkgPath = localRequire.resolve('../../../package.json');\n return JSON.parse(readFileSync(pkgPath, 'utf-8')).version || null;\n } catch {\n return null;\n }\n}\n\n/**\n * Fetch latest version from npm registry (with 1 hour caching via conf)\n */\nexport async function fetchLatestVersion(): Promise<string | null> {\n // Check cache first\n const lastCheck = versionCache.get('lastCheck') || 0;\n const cachedVersion = versionCache.get('latestVersion');\n\n if (cachedVersion && (Date.now() - lastCheck) < VERSION_CACHE_TTL_MS) {\n return cachedVersion;\n }\n\n // Fetch from npm registry\n try {\n const https = await import('https');\n return new Promise((resolve) => {\n const req = https.get(\n `https://registry.npmjs.org/${PACKAGE_NAME}/latest`,\n { timeout: 5000 },\n (res) => {\n let data = '';\n res.on('data', (chunk: string) => { data += chunk; });\n res.on('end', () => {\n try {\n const json = JSON.parse(data);\n const version = json.version || null;\n if (version) {\n versionCache.set('latestVersion', version);\n versionCache.set('lastCheck', Date.now());\n }\n resolve(version);\n } catch {\n resolve(cachedVersion || null);\n }\n });\n }\n );\n req.on('error', () => resolve(cachedVersion || null));\n req.on('timeout', () => { req.destroy(); resolve(cachedVersion || null); });\n });\n } catch {\n return cachedVersion || null;\n }\n}\n\n/**\n * Check if update is available\n */\nexport async function checkForUpdate(): Promise<{\n hasUpdate: boolean;\n currentVersion: string | null;\n latestVersion: string | null;\n}> {\n const currentVersion = getCurrentVersion();\n if (!currentVersion) {\n return { hasUpdate: false, currentVersion: null, latestVersion: null };\n }\n\n const latestVersion = await fetchLatestVersion();\n const hasUpdate = !!(latestVersion && semver.gt(latestVersion, currentVersion));\n\n return { hasUpdate, currentVersion, latestVersion };\n}\n\n/**\n * Get update command for all djangocfg packages\n */\nexport function getUpdateCommand(): string {\n return `pnpm add ${DJANGOCFG_PACKAGES.map(p => `${p}@latest`).join(' ')}`;\n}\n\n/**\n * Print version info and update notification\n */\nexport async function printVersionInfo(): Promise<void> {\n const { hasUpdate, currentVersion, latestVersion } = await checkForUpdate();\n\n if (!currentVersion) return;\n\n // Print current version\n consola.box(`📦 @djangocfg/nextjs v${currentVersion}`);\n\n // Show update notification if available\n if (hasUpdate && latestVersion) {\n consola.warn(`Update Available! ${chalk.red(currentVersion)} → ${chalk.green(latestVersion)}`);\n consola.info(`Run: ${chalk.cyan(getUpdateCommand())}`);\n console.log('');\n }\n}\n","/**\n * Deep Merge Utility\n *\n * Recursively merges objects, replacing arrays instead of merging them.\n */\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\n/**\n * `T extends object` rather than `Record<string, unknown>`: call sites pass\n * Next.js config interfaces, which have declared keys and no index signature —\n * they are not assignable to `Record<string, unknown>`.\n */\nexport function deepMerge<T extends object>(target: T, source: Partial<T>): T {\n const output: T = { ...target };\n\n for (const key of Object.keys(source) as Array<keyof T & string>) {\n const sourceValue: unknown = source[key];\n if (sourceValue === undefined) continue;\n\n const targetValue: unknown = output[key];\n\n // Objects: deep merge. Arrays and primitives: replace.\n // The recursion is instantiated at Record<string, unknown> — all the guards\n // prove — and the result cast back to the key's declared type.\n const merged =\n isPlainObject(sourceValue) && isPlainObject(targetValue)\n ? deepMerge(targetValue, sourceValue)\n : sourceValue;\n\n output[key] = merged as T[typeof key];\n }\n\n return output;\n}\n","/**\n * Base Next.js Configuration Factory\n *\n * Universal, reusable Next.js config for all DjangoCFG projects\n * Provides standard settings that can be extended per project\n *\n * @example\n * ```typescript\n * // In your project's next.config.ts\n * import { createBaseNextConfig } from '@djangocfg/nextjs/config';\n * import bundleAnalyzer from '@next/bundle-analyzer';\n *\n * const withBundleAnalyzer = bundleAnalyzer({\n * enabled: process.env.ANALYZE === 'true',\n * });\n *\n * export default withBundleAnalyzer(createBaseNextConfig({\n * // Your project-specific overrides\n * transpilePackages: ['my-custom-package'],\n * }));\n * ```\n */\n\nimport type { NextConfig } from 'next';\nimport type { Configuration as WebpackConfig } from 'webpack';\n\nimport { type I18nPluginOptions, withI18n } from '../i18n/plugin';\nimport { type PWAPluginOptions, withPWA } from '../pwa/plugin';\nimport { DEFAULT_OPTIMIZE_PACKAGES, DEFAULT_TRANSPILE_PACKAGES } from './constants';\nimport { CSP_BASELINE_VENDORS, type CspVendor, resolveCspVendors } from './csp';\nimport { addCompressionPlugins } from './plugins/compression';\nimport { DevStartupPlugin } from './plugins/devStartup';\nimport { deepMerge } from './utils/deepMerge';\nimport {\n getApiUrl,\n getBasePath,\n getSiteUrl,\n isDev,\n isStaticBuild,\n warnIfApiUrlMissing,\n} from './utils/env';\n\n// ─────────────────────────────────────────────────────────────────────────\n// Configuration Options\n// ─────────────────────────────────────────────────────────────────────────\n\n/**\n * Second argument Next.js passes to the `webpack` hook. Derived from Next's own\n * signature rather than re-declared, so it tracks upstream.\n */\ntype WebpackContext = Parameters<NonNullable<NextConfig['webpack']>>[1];\n\n/**\n * DjangoCFG-specific options. Every other Next.js option passes through via the\n * `NextConfig` half of `BaseNextConfigOptions`; the keys redeclared here are\n * omitted from it because this package narrows or re-purposes them.\n */\ninterface DjangoCfgNextOptions {\n /** Static build path (used when NEXT_PUBLIC_STATIC_BUILD=true) */\n isDefaultCfgAdmin?: boolean;\n /** Additional transpile packages (merged with defaults) */\n transpilePackages?: string[];\n /** Additional optimize package imports (merged with defaults) */\n optimizePackageImports?: string[];\n /** Check for @djangocfg/* package updates on startup (default: true) */\n checkUpdates?: boolean;\n /** Auto-update outdated packages without prompting (default: false) */\n autoUpdate?: boolean;\n /** Force check workspace:* packages for updates (for testing in monorepo) */\n forceCheckWorkspace?: boolean;\n /** Check for missing optional packages on startup (default: true) */\n checkPackages?: boolean;\n /** Auto-install missing packages without prompting (default: false) */\n autoInstall?: boolean;\n /**\n * Allow embedding this app in iframe from specified origins\n * Set to ['*'] to allow all origins, or specify domains like ['https://djangocfg.com']\n */\n allowIframeFrom?: string[];\n /**\n * Enable DPoP (RFC 9449) sender-constrained tokens on the generated API\n * client. When true, the client holds a non-extractable key and signs a\n * per-request `DPoP` proof so a stolen token is useless. Must be paired with\n * `jwt = JWTConfig(dpop_enabled=True)` on the Django backend. Default: false.\n *\n * Inlines `NEXT_PUBLIC_DPOP_ENABLED='true'` so the generated auth.ts activates.\n */\n dpop?: boolean;\n /**\n * Loosen the baseline CSP just enough for Stripe.js / Stripe Checkout &\n * Elements to work. When true, the required Stripe origins are added to\n * `script-src` (js.stripe.com — loads the SDK), `frame-src`\n * (js.stripe.com + checkout.stripe.com — Elements/Checkout mount iframes)\n * and `connect-src` (api.stripe.com — tokenization/confirm XHR). Without\n * this, the browser blocks Stripe.js and the payment form never renders.\n * Enable ONLY on apps that actually mount Stripe (e.g. the billing\n * dashboard), so every other app keeps the tighter default. Default: false.\n */\n stripe?: boolean;\n /**\n * Content-Security-Policy control.\n *\n * - `false` — do not emit the `Content-Security-Policy` header at all.\n * Use this on apps with no user auth/payment surface and unpredictable\n * third-party embeds (a pitch deck, a marketing microsite) where\n * naming every future embed's origin is not worth the maintenance —\n * `X-Content-Type-Options`/`Referrer-Policy`/`X-Frame-Options` below\n * still apply.\n * - object — keep the baseline CSP but additively loosen it. Prefer\n * `vendors` (declare WHAT you use by name; the package owns which origins\n * that implies) over hand-listing origins per directive. Every array is\n * additive; omit a key to leave that directive untouched.\n * - omitted — tightest default (`frame-src 'self'`, etc.), same as today.\n *\n * **Scripts nothing in this file can see are already handled.** Cloudflare\n * Web Analytics (injected at the edge) and Google Analytics (injected at\n * runtime by `BaseApp analytics={{ googleTrackingId }}`) are both switched on\n * outside `next.config.ts`, so their origins are in the baseline and you do\n * NOT need to list them — see `CSP_BASELINE_VENDORS`. Turn them off with\n * `baseline: false`.\n *\n * @example\n * ```ts\n * // Declare the third parties this app actually uses — origins come from\n * // the shared registry, so a vendor host change is fixed in one place.\n * // (Cloudflare + GA are already in the baseline; no need to list them.)\n * createBaseNextConfig({ csp: { vendors: ['stripe'] } })\n *\n * // Escape hatch for something not in the registry yet\n * createBaseNextConfig({ csp: { frameSrc: ['https://www.youtube-nocookie.com'] } })\n *\n * // No CSP at all — simplest option for a low-risk app with ad hoc embeds\n * createBaseNextConfig({ csp: false })\n * ```\n */\n csp?:\n | false\n | {\n /**\n * Named third-party vendors this app uses (`'stripe'`, `'cloudflare'`,\n * `'google-analytics'`, …). Each expands to the exact origins that\n * vendor needs across the relevant directives. See `CSP_VENDOR_PRESETS`\n * for the full list. Unknown names warn at build time, never throw.\n */\n vendors?: (CspVendor | (string & {}))[];\n /**\n * Drop the baseline vendors (see `CSP_BASELINE_VENDORS`: Cloudflare Web\n * Analytics + Google Analytics). They are on by default because each is\n * switched on somewhere `next.config.ts` cannot read — the Cloudflare\n * beacon is injected at the edge, and gtag.js is injected at runtime by\n * `BaseApp analytics={{ googleTrackingId }}` — so an opt-in flag would\n * only ever be discovered as a production console error.\n *\n * Set `false` for an app you know mounts neither, to get back the\n * tightest `script-src`.\n * @default true\n */\n baseline?: boolean;\n /** Extra `frame-src` origins (iframes this app embeds). */\n frameSrc?: string[];\n /** Extra `script-src` origins (third-party SDK scripts). */\n scriptSrc?: string[];\n /** Extra `connect-src` origins (XHR/fetch/WebSocket to a third party). */\n connectSrc?: string[];\n /** Extra `img-src` origins (hotlinked third-party images). */\n imgSrc?: string[];\n /** Extra `font-src` origins (third-party web fonts). */\n fontSrc?: string[];\n };\n /**\n * PWA configuration options\n * Set to false to disable PWA, or provide custom options\n * @default { enabled: true (in production), disable: true (in development) }\n */\n pwa?: PWAPluginOptions | false;\n /**\n * i18n configuration options for internationalization\n * Enables URL-based locale routing (e.g., /en/about, /ru/about)\n *\n * @example\n * ```ts\n * i18n: {\n * locales: ['en', 'ru', 'ko'],\n * defaultLocale: 'en',\n * }\n * ```\n */\n i18n?: I18nPluginOptions;\n /** Turbopack configuration (Next.js 16+ default bundler) */\n turbopack?: NextConfig['turbopack'];\n /** Custom webpack configuration function (called after base webpack logic) */\n webpack?: (\n config: WebpackConfig,\n options: WebpackContext\n ) => WebpackConfig | void;\n /** Custom experimental options (merged with defaults) */\n experimental?: NextConfig['experimental'];\n /** Custom env variables (merged with defaults) */\n env?: Record<string, string | undefined>;\n /** Additional redirects (merged with base URL normalization redirects) */\n redirects?: NextConfig['redirects'];\n /** Additional headers (merged with base CORS/security headers) */\n headers?: NextConfig['headers'];\n /** Additional rewrites */\n rewrites?: NextConfig['rewrites'];\n}\n\n/**\n * DjangoCFG options plus every remaining Next.js config option.\n */\nexport type BaseNextConfigOptions = DjangoCfgNextOptions &\n Omit<NextConfig, keyof DjangoCfgNextOptions>;\n\n// ─────────────────────────────────────────────────────────────────────────\n// Base Configuration Factory\n// ─────────────────────────────────────────────────────────────────────────\n\n/**\n * Create base Next.js configuration with standard DjangoCFG settings\n *\n * @param options - Custom configuration options to merge with base config\n * @returns Next.js configuration function\n */\nexport function createBaseNextConfig(\n options: BaseNextConfigOptions = {}\n): NextConfig {\n\n const basePath = getBasePath(options.isDefaultCfgAdmin);\n warnIfApiUrlMissing();\n const apiUrl = getApiUrl();\n const siteUrl = getSiteUrl();\n\n // Base configuration\n const baseConfig: NextConfig = {\n reactStrictMode: true,\n trailingSlash: true,\n\n // Dev indicator position — set explicitly so it's managed from one place\n // across every @djangocfg app. Apps can override via `devIndicators` in\n // their options (deep-merged over this). Kept bottom-left for now.\n devIndicators: { position: 'bottom-left' },\n\n // Static export configuration\n ...(isStaticBuild && {\n output: 'export' as const,\n distDir: 'out',\n basePath: basePath,\n // Fix for Next.js 15.5.4: prevent 500.html generation issue\n generateBuildId: async () => {\n return 'static-build';\n },\n }),\n\n // Standalone output for Docker (only in production, not dev)\n ...(!isStaticBuild && !isDev && {\n output: 'standalone' as const,\n }),\n\n // Environment variables\n env: {\n NEXT_PUBLIC_BASE_PATH: basePath,\n NEXT_PUBLIC_API_URL: apiUrl,\n NEXT_PUBLIC_SITE_URL: siteUrl,\n // DPoP toggle for the generated auth client (RFC 9449). Only inlined when\n // explicitly enabled, so non-DPoP apps stay on the plain Bearer path.\n ...(options.dpop ? { NEXT_PUBLIC_DPOP_ENABLED: 'true' } : {}),\n // Disable Next.js telemetry (Next.js 15+ uses env var instead of config option)\n NEXT_TELEMETRY_DISABLED: '1',\n ...options.env,\n },\n\n // Images configuration\n images: {\n unoptimized: true,\n },\n\n // CORS headers for static files and iframe embedding\n async headers() {\n const headers: { source: string; headers: { key: string; value: string }[] }[] = [\n {\n source: '/static/:path*',\n headers: [\n { key: 'Access-Control-Allow-Origin', value: '*' },\n { key: 'Access-Control-Allow-Methods', value: 'GET, OPTIONS' },\n { key: 'Access-Control-Allow-Headers', value: 'Origin, Content-Type, Accept' },\n ],\n },\n ];\n\n // ── Baseline security headers on every route ──────────────────────────\n // First line of defence against XSS/clickjacking/sniffing. The CSP here is\n // a PRAGMATIC baseline: it kills the high-value injection vectors\n // (plugins/objects, <base> hijacking, form-action exfiltration, iframe\n // embedding) while still allowing the inline/eval scripts a Next.js app\n // needs in dev. A fully strict nonce-based `script-src` is a follow-up\n // (needs middleware + per-request nonce wiring) — tracked in the\n // auth-hardening plan. `allowIframeFrom` (below) overrides frame-ancestors.\n const hasIframeAllowlist =\n !!options.allowIframeFrom && options.allowIframeFrom.length > 0;\n const frameAncestors = hasIframeAllowlist\n ? (options.allowIframeFrom!.includes('*')\n ? '*'\n : `'self' ${options.allowIframeFrom!.join(' ')}`)\n : \"'none'\";\n\n // `csp: false` opts an app out of the CSP header entirely (see the\n // option's doc comment) — the other baseline headers still apply.\n const cspDisabled = options.csp === false;\n const cspExtra = typeof options.csp === 'object' ? options.csp : undefined;\n\n // In dev the backend (Django) is plain http/ws on localhost,\n // so connect/img must allow http:/ws:. Production stays strict (https/wss\n // only) to forbid downgrade/cleartext exfiltration.\n //\n // Third-party origins come from the shared vendor registry (./csp), so\n // \"which hosts does Stripe need\" is answered in exactly one place. The\n // legacy `stripe: true` flag is just sugar for `vendors: ['stripe']` and\n // resolves through that same registry — no second copy of the origins.\n // Baseline vendors apply unless explicitly opted out. They are baseline\n // rather than per-app because each is enabled somewhere this file cannot\n // read — Cloudflare's beacon is injected by the edge after this header is\n // emitted, and gtag.js is injected at runtime by `BaseApp analytics={{\n // googleTrackingId }}` from the app's settings.ts. Leaving either opt-in\n // guarantees a silent production breakage in every app that forgets it\n // (which was all of them). See CSP_BASELINE_VENDORS for the full rule.\n const vendorNames = [\n ...(cspExtra?.baseline === false ? [] : CSP_BASELINE_VENDORS),\n ...(options.stripe ? ['stripe'] : []),\n ...(cspExtra?.vendors ?? []),\n ];\n const vendor = resolveCspVendors(vendorNames);\n\n // Vendor-derived origins and the per-directive escape hatch are equivalent\n // and additive; a host named by both is emitted once.\n const join = (...groups: (string[] | undefined)[]) => {\n const all = [...new Set(groups.flatMap((g) => g ?? []))];\n return all.length ? ' ' + all.join(' ') : '';\n };\n\n const extraScript = join(vendor.scriptSrc, cspExtra?.scriptSrc);\n const extraConnect = join(vendor.connectSrc, cspExtra?.connectSrc);\n const extraFrame = join(vendor.frameSrc, cspExtra?.frameSrc);\n const extraImg = join(vendor.imgSrc, cspExtra?.imgSrc);\n const extraFont = join(vendor.fontSrc, cspExtra?.fontSrc);\n\n const connectSrc = isDev\n ? `connect-src 'self' http: https: ws: wss:${extraConnect}`\n : `connect-src 'self' https: wss:${extraConnect}`;\n const imgSrc = isDev\n ? `img-src 'self' data: blob: http: https:${extraImg}`\n : `img-src 'self' data: blob: https:${extraImg}`;\n\n if (!cspDisabled) {\n const cspDirectives = [\n \"default-src 'self'\",\n // Next.js requires inline + eval for its runtime/HMR. Tighten to nonces later.\n `script-src 'self' 'unsafe-inline' 'unsafe-eval'${extraScript}`,\n \"style-src 'self' 'unsafe-inline'\",\n imgSrc,\n `font-src 'self' data:${extraFont}`,\n // API calls go cross-origin to Django.\n connectSrc,\n // Declared vendors (Stripe Elements/Checkout, video embeds…) and any\n // app-declared trusted origins mount iframes; add their hosts here.\n `frame-src 'self'${extraFrame}`,\n \"object-src 'none'\",\n \"base-uri 'self'\",\n \"form-action 'self'\",\n `frame-ancestors ${frameAncestors}`,\n ];\n\n headers.push({\n source: '/:path*',\n headers: [\n { key: 'X-Content-Type-Options', value: 'nosniff' },\n { key: 'Referrer-Policy', value: 'strict-origin-when-cross-origin' },\n { key: 'Content-Security-Policy', value: cspDirectives.join('; ') },\n // X-Frame-Options for older browsers. Omitted when a wildcard iframe\n // allowlist is set (browsers then rely on CSP frame-ancestors).\n ...(hasIframeAllowlist && options.allowIframeFrom!.includes('*')\n ? []\n : [{ key: 'X-Frame-Options', value: hasIframeAllowlist ? 'SAMEORIGIN' : 'DENY' }]),\n ],\n });\n } else {\n // Still ship the non-CSP baseline headers even with CSP disabled.\n headers.push({\n source: '/:path*',\n headers: [\n { key: 'X-Content-Type-Options', value: 'nosniff' },\n { key: 'Referrer-Policy', value: 'strict-origin-when-cross-origin' },\n ...(hasIframeAllowlist && options.allowIframeFrom!.includes('*')\n ? []\n : [{ key: 'X-Frame-Options', value: hasIframeAllowlist ? 'SAMEORIGIN' : 'DENY' }]),\n ],\n });\n }\n\n // Merge with user-provided headers\n const userHeaders = options.headers ? await options.headers() : [];\n return [...headers, ...userHeaders];\n },\n\n // Rewrites (user-provided only)\n ...(options.rewrites && {\n rewrites: options.rewrites,\n }),\n\n // Redirects for URL normalization\n // Fixes \"Failed to construct 'URL': Invalid URL\" error with double slashes\n async redirects() {\n const baseRedirects = [\n // Normalize double slashes in URL path (e.g., /foo//bar -> /foo/bar)\n {\n source: '/:path*(//)/:rest*',\n destination: '/:path*/:rest*',\n permanent: true,\n },\n // Handle root double slash (e.g., // -> /)\n {\n source: '//',\n destination: '/',\n permanent: true,\n },\n ];\n\n // Merge with user-provided redirects\n const userRedirects = options.redirects ? await options.redirects() : [];\n return [...baseRedirects, ...userRedirects];\n },\n\n // Transpile packages (merge with user-provided)\n transpilePackages: [\n ...DEFAULT_TRANSPILE_PACKAGES,\n ...(options.transpilePackages || []),\n ],\n\n // Turbopack configuration (Next.js 16+ default bundler)\n // Always set turbopack config to silence Next.js 16 warning about webpack config\n turbopack: options.turbopack || {},\n\n // Experimental features\n experimental: {\n // Optimize package imports (only in production)\n ...(!isDev && {\n optimizePackageImports: [\n ...DEFAULT_OPTIMIZE_PACKAGES,\n ...(options.optimizePackageImports || []),\n ],\n }),\n // Dev mode optimizations\n ...(isDev && {\n optimizeCss: false,\n }),\n // User experimental options applied last (can override base settings)\n ...options.experimental,\n },\n\n // Webpack configuration\n webpack: (config: WebpackConfig, webpackOptions: WebpackContext) => {\n const { isServer, dev } = webpackOptions;\n\n // Add dev startup plugin (client-side only in dev)\n if (dev && !isServer) {\n if (!config.plugins) {\n config.plugins = [];\n }\n config.plugins.push(\n new DevStartupPlugin({\n checkUpdates: options.checkUpdates,\n autoUpdate: options.autoUpdate,\n forceCheckWorkspace: options.forceCheckWorkspace,\n checkPackages: options.checkPackages,\n autoInstall: options.autoInstall,\n })\n );\n }\n\n // Dev mode optimizations\n if (dev) {\n config.optimization = {\n ...config.optimization,\n removeAvailableModules: false,\n removeEmptyChunks: false,\n splitChunks: false, // Disable code splitting in dev for faster compilation\n };\n }\n\n // Filesystem cache (dev and production)\n config.cache = {\n type: 'filesystem',\n buildDependencies: {},\n };\n\n // Compression plugins (only for static build in production, client-side)\n if (!isServer && isStaticBuild && !dev) {\n addCompressionPlugins(config);\n }\n\n // Call user's webpack function if provided\n if (options.webpack) {\n return options.webpack(config, webpackOptions);\n }\n\n return config;\n },\n };\n\n const { i18n: i18nOptions, pwa: pwaOptions } = options;\n\n // Keys already consumed above; they must not reach deepMerge, either because\n // Next.js does not understand them or because baseConfig already derived the\n // real value. `transpilePackages` is the load-bearing case: baseConfig merged\n // it with DEFAULT_TRANSPILE_PACKAGES, and leaving it here made deepMerge\n // REPLACE that merged array with the user's bare list (dropping the defaults,\n // so every @djangocfg source package stopped transpiling).\n const DJANGOCFG_ONLY_KEYS = [\n 'i18n',\n 'pwa',\n 'transpilePackages',\n 'optimizePackageImports',\n 'isDefaultCfgAdmin',\n 'checkUpdates',\n 'autoUpdate',\n 'forceCheckWorkspace',\n 'checkPackages',\n 'autoInstall',\n 'allowIframeFrom',\n 'dpop',\n 'stripe',\n 'csp',\n ] as const satisfies readonly (keyof BaseNextConfigOptions)[];\n\n const nextConfigOptions = Object.fromEntries(\n Object.entries(options).filter(\n ([key]) => !(DJANGOCFG_ONLY_KEYS as readonly string[]).includes(key)\n )\n ) as Partial<NextConfig>;\n\n // Deep merge only valid Next.js options with base config\n let finalConfig = deepMerge(baseConfig, nextConfigOptions);\n\n // Apply PWA wrapper only if explicitly configured (opt-in)\n // PWA requires sw.ts file in the app, so apps must explicitly enable it\n if (pwaOptions) {\n finalConfig = withPWA(finalConfig, pwaOptions);\n }\n\n // Apply i18n wrapper if configured\n // Enables next-intl for locale routing and translations\n if (i18nOptions) {\n finalConfig = withI18n(finalConfig, i18nOptions);\n }\n\n return finalConfig;\n}\n","/**\n * DjangoCFG Documentation Client\n *\n * HTTP client for accessing DjangoCFG documentation via MCP server API.\n */\n\nimport { API_INFO_ENDPOINT, API_SEARCH_ENDPOINT, DEFAULT_TIMEOUT, MCP_BASE_URL } from './constants';\n\nimport type { SearchResult, SearchOptions, McpConfig, ApiSearchResponse } from './types';\n\n/**\n * Client for DjangoCFG Documentation API.\n */\nexport class DjangoCfgDocsClient {\n private baseUrl: string;\n private timeout: number;\n\n constructor(baseUrl: string = MCP_BASE_URL, timeout: number = DEFAULT_TIMEOUT) {\n this.baseUrl = baseUrl.replace(/\\/$/, '');\n this.timeout = timeout;\n }\n\n private async makeRequest<T>(endpoint: string, params?: Record<string, string | number>): Promise<T> {\n let url = `${this.baseUrl}${endpoint}`;\n\n if (params) {\n const searchParams = new URLSearchParams();\n for (const [key, value] of Object.entries(params)) {\n searchParams.set(key, String(value));\n }\n url = `${url}?${searchParams.toString()}`;\n }\n\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), this.timeout);\n\n try {\n const response = await fetch(url, {\n method: 'GET',\n headers: {\n Accept: 'application/json',\n 'User-Agent': 'DjangoCFG-AI-Client/1.0',\n },\n signal: controller.signal,\n });\n\n if (!response.ok) {\n throw new Error(`HTTP Error ${response.status}: ${response.statusText}`);\n }\n\n return await response.json();\n } finally {\n clearTimeout(timeoutId);\n }\n }\n\n /**\n * Search documentation.\n */\n async search(query: string, options: SearchOptions = {}): Promise<SearchResult[]> {\n const { limit = 5, category } = options;\n\n const params: Record<string, string | number> = { q: query, limit };\n if (category) {\n params.category = category;\n }\n\n const data = await this.makeRequest<ApiSearchResponse>(API_SEARCH_ENDPOINT, params);\n\n const results = data.results || (Array.isArray(data) ? data : []);\n\n return results.map((item) => ({\n title: item.title || '',\n content: item.content || item.snippet || '',\n url: item.url || '',\n score: item.score || 0,\n category: item.category || '',\n }));\n }\n\n /**\n * Get detailed info about a specific topic.\n */\n async getInfo(topic: string): Promise<Record<string, unknown>> {\n return this.makeRequest(API_INFO_ENDPOINT, { topic });\n }\n\n /**\n * Get MCP server configuration for AI assistants.\n */\n getMcpConfig(): McpConfig {\n return {\n mcpServers: {\n 'djangocfg-docs': {\n url: `${this.baseUrl}/mcp`,\n },\n },\n };\n }\n}\n\n// Default client instance\nlet defaultClient: DjangoCfgDocsClient | null = null;\n\nfunction getClient(): DjangoCfgDocsClient {\n if (!defaultClient) {\n defaultClient = new DjangoCfgDocsClient();\n }\n return defaultClient;\n}\n\n/**\n * Search DjangoCFG documentation.\n *\n * @example\n * ```ts\n * const results = await search('database configuration');\n * results.forEach(r => console.log(r.title, r.url));\n * ```\n */\nexport async function search(query: string, options: SearchOptions = {}): Promise<SearchResult[]> {\n return getClient().search(query, options);\n}\n\n/**\n * Get documentation as formatted text.\n *\n * @example\n * ```ts\n * const docs = await getDocs('How to configure PostgreSQL?');\n * console.log(docs);\n * ```\n */\nexport async function getDocs(query: string, limit: number = 3): Promise<string> {\n const results = await search(query, { limit });\n\n if (results.length === 0) {\n return `No documentation found for: ${query}`;\n }\n\n const output: string[] = [];\n\n results.forEach((r, i) => {\n output.push(`## ${i + 1}. ${r.title}`);\n output.push(r.content);\n if (r.url) {\n output.push(`📖 Read more: ${r.url}`);\n }\n output.push('');\n });\n\n return output.join('\\n');\n}\n\n/**\n * Get detailed info about a topic.\n */\nexport async function getInfo(topic: string): Promise<Record<string, unknown>> {\n return getClient().getInfo(topic);\n}\n\n/**\n * Get MCP server configuration.\n */\nexport function getMcpConfig(): McpConfig {\n return getClient().getMcpConfig();\n}\n"],"mappings":"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","names":["createRequire","createRequire","chalk","existsSync","join","consola","join","join","isDev","text","consola","chalk","spawn","Conf","consola","existsSync","createRequire","join","createInterface","createMemoryCache","Conf","SPINNER_FRAMES","join","existsSync","createRequire","createSpinner","chalk","text","askConfirmation","createInterface","spawn","consola","chalk","Conf","consola","readFileSync","createRequire","semver","createMemoryCache","Conf","createRequire","readFileSync","fetchLatestVersion","semver","consola","chalk","chalk","join","existsSync","join"]}
@@ -288,11 +288,13 @@ declare function selectSources(index: SitemapIndex, select: SourceSelector | und
288
288
  declare function tenantSources(tenant: string): (name: string) => boolean;
289
289
 
290
290
  /**
291
- * Sitemap fetch helpers — never throw.
291
+ * Sitemap fetch helpers.
292
292
  *
293
- * Failures are converted to empty payloads so a transient Django outage
294
- * doesn't break `next build`. The next ISR cycle (`revalidate`) recovers
295
- * automatically.
293
+ * During `next build` a failure becomes an empty payload, so a Django outage
294
+ * does not fail the build. At runtime it throws: ISR then keeps serving the
295
+ * last good copy. An empty payload there would be cached for the whole
296
+ * revalidate window — a sitemap index with only the static chunk, which
297
+ * crawlers and IndexNow diffs read as every listing removed.
296
298
  */
297
299
 
298
300
  declare function fetchSitemapIndex(apiUrl: string, revalidate: number): Promise<SitemapIndex>;
@@ -17,6 +17,14 @@ async function parseAndCheckSize(r, url, label) {
17
17
  }
18
18
  return JSON.parse(text);
19
19
  }
20
+ var isBuilding = () => process.env.NEXT_PHASE === "phase-production-build";
21
+ function degrade(fallback, reason, err) {
22
+ if (!isBuilding()) {
23
+ throw new Error(`[sitemap] ${reason}; keeping the last good copy`, { cause: err });
24
+ }
25
+ console.warn(`[sitemap] ${reason}; falling back to empty during build`, err ?? "");
26
+ return fallback;
27
+ }
20
28
  var EMPTY_INDEX = {
21
29
  sources: [],
22
30
  generated_at: (/* @__PURE__ */ new Date(0)).toISOString(),
@@ -36,14 +44,10 @@ async function fetchSitemapIndex(apiUrl, revalidate) {
36
44
  const r = await fetch(url, {
37
45
  next: { revalidate }
38
46
  });
39
- if (!r.ok) {
40
- console.warn(`[sitemap] index fetch returned ${r.status}; falling back to empty`);
41
- return EMPTY_INDEX;
42
- }
47
+ if (!r.ok) return degrade(EMPTY_INDEX, `index fetch returned ${r.status}`);
43
48
  return await parseAndCheckSize(r, url, "index");
44
49
  } catch (err) {
45
- console.warn("[sitemap] index fetch failed:", err);
46
- return EMPTY_INDEX;
50
+ return degrade(EMPTY_INDEX, "index fetch failed", err);
47
51
  }
48
52
  }
49
53
  async function fetchSitemapFeed(apiUrl, source, cursor, revalidate) {
@@ -54,14 +58,10 @@ async function fetchSitemapFeed(apiUrl, source, cursor, revalidate) {
54
58
  const r = await fetch(url, {
55
59
  next: { revalidate }
56
60
  });
57
- if (!r.ok) {
58
- console.warn(`[sitemap] feed ${source} returned ${r.status}; falling back to empty`);
59
- return emptyFeed(source, cursor);
60
- }
61
+ if (!r.ok) return degrade(emptyFeed(source, cursor), `feed ${source} returned ${r.status}`);
61
62
  return await parseAndCheckSize(r, url, `feed ${source}`);
62
63
  } catch (err) {
63
- console.warn(`[sitemap] feed ${source} fetch failed:`, err);
64
- return emptyFeed(source, cursor);
64
+ return degrade(emptyFeed(source, cursor), `feed ${source} fetch failed`, err);
65
65
  }
66
66
  }
67
67
 
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/sitemap/constants.ts","../../src/sitemap/fetch.ts","../../src/sitemap/ids.ts","../../src/sitemap/select.ts","../../src/sitemap/sitemap.ts","../../src/sitemap/index-route.ts","../../src/sitemap/robots.ts"],"sourcesContent":["/**\n * Shared sitemap constants.\n *\n * Pulled out so both the chunk factory (`createDjangoSitemap`) and the\n * index Route Handler (`createSitemapIndex`) agree on the id of the\n * frontend-only static-routes chunk.\n */\n\nexport const STATIC_ID = 'static';\nexport const DEFAULT_INDEX_REVALIDATE = 600;\nexport const DEFAULT_FEED_REVALIDATE = 3600;\n","/**\n * Sitemap fetch helpers — never throw.\n *\n * Failures are converted to empty payloads so a transient Django outage\n * doesn't break `next build`. The next ISR cycle (`revalidate`) recovers\n * automatically.\n */\n\nimport type { SitemapFeedPage, SitemapIndex } from './types';\n\n/**\n * Next.js refuses to write a fetch response larger than 2MB into its data\n * cache. It logs one line and serves the response uncached, so `revalidate`\n * silently becomes a no-op and every regeneration refetches the full payload\n * from Django. Nothing else surfaces this, so we measure the body ourselves.\n */\nconst NEXT_DATA_CACHE_LIMIT = 2 * 1024 * 1024;\n\n// One warning per URL: `sitemap()` runs once per chunk per build, and a\n// repeated line for the same feed reads as a new problem rather than the\n// same one.\nconst oversizeWarned = new Set<string>();\n\n/**\n * Read the body as text so its exact size is known, then parse.\n *\n * `Content-Length` is not a substitute: it is absent under chunked transfer\n * and reports the compressed size when the response is gzipped, while the\n * 2MB ceiling applies to the decoded bytes Next.js would store.\n */\nasync function parseAndCheckSize<T>(r: Response, url: string, label: string): Promise<T> {\n const text = await r.text();\n const bytes = new TextEncoder().encode(text).length;\n if (bytes > NEXT_DATA_CACHE_LIMIT && !oversizeWarned.has(url)) {\n oversizeWarned.add(url);\n console.warn(\n `[sitemap] ${label} is ${bytes} bytes, over Next.js's 2MB data-cache ceiling: ` +\n `revalidate is inert for this feed and every regeneration refetches it from Django. ` +\n `Lower the backend page_size if the extra load matters — note 50,000 URLs is the ` +\n `sitemap protocol's own maximum, so a large payload here is not itself a bug.`,\n );\n }\n return JSON.parse(text) as T;\n}\n\nconst EMPTY_INDEX: SitemapIndex = {\n sources: [],\n generated_at: new Date(0).toISOString(),\n ttl_seconds: 60,\n};\n\nconst emptyFeed = (source: string, cursor: string | null): SitemapFeedPage => ({\n source,\n chunk_id: `${source}-empty`,\n count: 0,\n has_more: false,\n next_cursor: cursor,\n entries: [],\n});\n\nexport async function fetchSitemapIndex(\n apiUrl: string,\n revalidate: number,\n): Promise<SitemapIndex> {\n const url = `${apiUrl}/cfg/sitemap/index/`;\n try {\n const r = await fetch(url, {\n next: { revalidate },\n });\n if (!r.ok) {\n console.warn(`[sitemap] index fetch returned ${r.status}; falling back to empty`);\n return EMPTY_INDEX;\n }\n return await parseAndCheckSize<SitemapIndex>(r, url, 'index');\n } catch (err) {\n console.warn('[sitemap] index fetch failed:', err);\n return EMPTY_INDEX;\n }\n}\n\nexport async function fetchSitemapFeed(\n apiUrl: string,\n source: string,\n cursor: string | null,\n revalidate: number,\n): Promise<SitemapFeedPage> {\n const params = new URLSearchParams({ source });\n if (cursor) params.set('cursor', cursor);\n const url = `${apiUrl}/cfg/sitemap/feed/?${params}`;\n try {\n const r = await fetch(url, {\n next: { revalidate },\n });\n if (!r.ok) {\n console.warn(`[sitemap] feed ${source} returned ${r.status}; falling back to empty`);\n return emptyFeed(source, cursor);\n }\n return await parseAndCheckSize<SitemapFeedPage>(r, url, `feed ${source}`);\n } catch (err) {\n console.warn(`[sitemap] feed ${source} fetch failed:`, err);\n return emptyFeed(source, cursor);\n }\n}\n","/**\n * Sitemap chunk id encoding.\n *\n * Each Next.js sitemap file is addressed by an opaque string id. We\n * round-trip a `(source, cursor)` pair through that id so the default\n * sitemap handler knows which Django chunk to fetch.\n *\n * Separator `--` is unambiguous: backend cursors use URL-safe base64 (`-_`).\n */\n\nexport function encodeChunkId(source: string, cursor: string | null): string {\n return `${source}--${cursor ?? ''}`;\n}\n\nexport function decodeChunkId(id: string): { source: string; cursor: string | null } {\n const sep = id.indexOf('--');\n if (sep === -1) return { source: id, cursor: null };\n const source = id.slice(0, sep);\n const cursorRaw = id.slice(sep + 2);\n return { source, cursor: cursorRaw === '' ? null : cursorRaw };\n}\n","/**\n * Which backend sources a frontend claims.\n *\n * `GET /cfg/sitemap/index/` answers for the whole Django project — every\n * source any installed app registered. One frontend is not always entitled\n * to all of them: a backend serving several brands registers a source per\n * brand (`properties-caribbean`, `properties-bali`), and each brand's app\n * must emit only its own, or it advertises the other's URLs under its own\n * host — every one of them a 404, and the sitemap that carries them is\n * discredited whole.\n *\n * The selector lives here, shared, for the same reason `STATIC_ID` does:\n * `createDjangoSitemap` enumerates the chunks and `createSitemapIndex`\n * enumerates them again for the index document. If the two applied\n * different filters, the index would advertise shards the chunk route\n * refuses to build — and that mismatch is invisible until a crawler reads\n * both. One function, called by both, cannot drift.\n *\n * Source NAMES are the contract. The module ships no notion of a brand or\n * a tenant, and it should not: the backend chose what its source names\n * mean, and only the app knows which of them are its own.\n */\n\nimport type { SitemapIndex, SitemapSourceInfo, SourceSelector } from './types';\n\n/**\n * A predicate from whatever the caller passed.\n *\n * Omitted means every source — the single-tenant default, and what every\n * caller before this option existed already relied on.\n */\nexport function sourceFilter(select: SourceSelector | undefined): (name: string) => boolean {\n if (select === undefined) return () => true;\n if (typeof select === 'function') return select;\n const allowed = new Set(select);\n return (name) => allowed.has(name);\n}\n\n/** The index's sources, narrowed to those the caller claims. */\nexport function selectSources(\n index: SitemapIndex,\n select: SourceSelector | undefined,\n): SitemapSourceInfo[] {\n const keep = sourceFilter(select);\n return index.sources.filter((source) => keep(source.name));\n}\n\n/**\n * Every chunk id the selected sources produce, in index order.\n *\n * The one place `(source, chunk)` becomes an id, so the chunk route and the\n * index document enumerate the same set by construction.\n */\nexport function selectedChunkIds(\n index: SitemapIndex,\n select: SourceSelector | undefined,\n encode: (source: string, cursor: string | null) => string,\n): string[] {\n const ids: string[] = [];\n for (const source of selectSources(index, select)) {\n for (const chunk of source.chunks) {\n ids.push(encode(source.name, chunk.cursor_to));\n }\n }\n return ids;\n}\n\n/**\n * A selector for one tenant of a multi-brand backend.\n *\n * Matches the `<source>-<tenant>` names that a backend registering per\n * tenant emits. It splits on the LAST hyphen and compares the tail exactly,\n * rather than testing a suffix: `endsWith('-bali')` is correct for\n * `properties-bali` but the same shape reads `pricing-plans-premium` as\n * belonging to a `premium` tenant, and a substring match is not a contract.\n *\n * ```ts\n * sources: tenantSources('bali') // properties-bali, communities-bali, …\n * ```\n *\n * Sources with no hyphen never match: an untenanted source belongs to the\n * project rather than to a brand, and claiming it for one is the error this\n * function exists to prevent.\n */\nexport function tenantSources(tenant: string): (name: string) => boolean {\n return (name) => {\n const sep = name.lastIndexOf('-');\n return sep !== -1 && name.slice(sep + 1) === tenant;\n };\n}\n","/**\n * createDjangoSitemap — factory for Next.js `app/sitemap.ts`.\n *\n * Backed by `django_cfg.modules.django_sitemap`: the backend exposes a\n * paginated JSON feed, this factory turns each chunk into a Next.js\n * sitemap file via `generateSitemaps()`.\n *\n * Usage:\n *\n * ```ts\n * // apps/<app>/app/sitemap.ts\n * import { createDjangoSitemap } from '@djangocfg/nextjs/sitemap';\n *\n * const { generateSitemaps, sitemap } = createDjangoSitemap({\n * host: process.env.NEXT_PUBLIC_SITE_URL!,\n * apiUrl: process.env.NEXT_PUBLIC_API_URL!,\n * staticRoutes: [\n * { path: '/', changeFrequency: 'daily', priority: 1.0 },\n * { path: '/catalog', changeFrequency: 'daily', priority: 0.9 },\n * ],\n * });\n *\n * export { generateSitemaps, sitemap as default };\n * export const revalidate = 3600;\n * ```\n *\n * Without `apiUrl`, the sitemap emits only the static routes — handy for\n * marketing sites that don't have a backend-driven URL space.\n */\n\nimport type { MetadataRoute } from 'next';\n\nimport {\n DEFAULT_FEED_REVALIDATE,\n DEFAULT_INDEX_REVALIDATE,\n STATIC_ID,\n} from './constants';\nimport { fetchSitemapFeed, fetchSitemapIndex } from './fetch';\nimport { decodeChunkId, encodeChunkId } from './ids';\nimport { selectedChunkIds, sourceFilter } from './select';\n\nimport type { CreateDjangoSitemapOptions, StaticRoute } from './types';\n\ninterface SitemapApi {\n generateSitemaps: () => Promise<Array<{ id: string }>>;\n sitemap: (props: { id: Promise<string> }) => Promise<MetadataRoute.Sitemap>;\n}\n\nexport function createDjangoSitemap(opts: CreateDjangoSitemapOptions): SitemapApi {\n const {\n host,\n apiUrl,\n staticRoutes = [],\n sources,\n indexRevalidate = DEFAULT_INDEX_REVALIDATE,\n feedRevalidate = DEFAULT_FEED_REVALIDATE,\n } = opts;\n\n return {\n async generateSitemaps() {\n const ids: Array<{ id: string }> = [{ id: STATIC_ID }];\n if (!apiUrl) return ids;\n const index = await fetchSitemapIndex(apiUrl, indexRevalidate);\n for (const id of selectedChunkIds(index, sources, encodeChunkId)) {\n ids.push({ id });\n }\n return ids;\n },\n\n async sitemap({ id: idPromise }) {\n const id = await idPromise;\n\n if (id === STATIC_ID) {\n return renderStatic(host, staticRoutes);\n }\n\n if (!apiUrl) return [];\n\n const { source, cursor } = decodeChunkId(id);\n // A chunk this app does not claim renders empty rather than fetching.\n // `generateSitemaps` never mints such an id, but the route stays\n // reachable by hand, and serving another tenant's URLs from a guessed\n // address would undo the filter above.\n if (!sourceFilter(sources)(source)) return [];\n\n const feed = await fetchSitemapFeed(apiUrl, source, cursor, feedRevalidate);\n return feed.entries.map((e) => ({\n url: `${host}${e.loc}`,\n lastModified: e.lastmod ? new Date(e.lastmod) : undefined,\n }));\n },\n };\n}\n\nfunction renderStatic(host: string, routes: StaticRoute[]): MetadataRoute.Sitemap {\n return routes.map((r) => ({\n url: `${host}${r.path}`,\n changeFrequency: r.changeFrequency,\n priority: r.priority,\n }));\n}\n","/**\n * createSitemapIndex — Route Handler factory for the sitemap-index XML.\n *\n * Next.js's `generateSitemaps()` API ships chunks at `/sitemap/<id>.xml`\n * but does NOT emit a single sitemap-index document. Without one,\n * robots.txt + crawlers point at a 404 and never discover the chunks.\n * This factory closes that gap: it returns a `GET` Route Handler that\n * mints a `<sitemapindex>` XML listing every chunk the matching\n * `createDjangoSitemap` would produce.\n *\n * Mount it at **`app/sitemap_index.xml/route.ts`** — NOT `sitemap.xml`.\n * `app/sitemap.ts` (where `createDjangoSitemap` lives) already owns\n * `/sitemap.xml` in Next.js's metadata routing, even when its output is\n * fan-out via `generateSitemaps()` and the singular URL serves 404.\n * Putting a route handler at `app/sitemap.xml/route.ts` throws at build\n * time: *Conflicting route and metadata at /sitemap.xml*.\n *\n * Usage — the full pattern with the matching chunk + robots factories:\n *\n * ```ts\n * // apps/<app>/app/sitemap.ts — chunks\n * import { createDjangoSitemap } from '@djangocfg/nextjs/sitemap';\n * const { generateSitemaps, sitemap } = createDjangoSitemap({ ... });\n * export { generateSitemaps, sitemap as default };\n *\n * // apps/<app>/app/sitemap_index.xml/route.ts — the index\n * import { createSitemapIndex } from '@djangocfg/nextjs/sitemap';\n * export const { GET } = createSitemapIndex({\n * host: process.env.NEXT_PUBLIC_SITE_URL!,\n * apiUrl: process.env.NEXT_PUBLIC_API_URL!,\n * });\n * export const revalidate = 600;\n *\n * // apps/<app>/app/robots.ts — point crawlers at the index URL above\n * import { createRobots } from '@djangocfg/nextjs/sitemap';\n * const host = process.env.NEXT_PUBLIC_SITE_URL!;\n * export default createRobots({\n * host,\n * sitemap: `${host}/sitemap_index.xml`,\n * });\n * ```\n *\n * Keep `host` / `apiUrl` in sync with the same app's `app/sitemap.ts`\n * — the index just enumerates the chunks the other file emits.\n */\n\nimport {\n DEFAULT_INDEX_REVALIDATE,\n STATIC_ID,\n} from './constants';\nimport { fetchSitemapIndex } from './fetch';\nimport { encodeChunkId } from './ids';\nimport { selectedChunkIds } from './select';\n\nimport type { CreateSitemapIndexOptions } from './types';\n\ninterface SitemapIndexRoute {\n GET: () => Promise<Response>;\n}\n\nexport function createSitemapIndex(opts: CreateSitemapIndexOptions): SitemapIndexRoute {\n const {\n host,\n apiUrl,\n sources,\n indexRevalidate = DEFAULT_INDEX_REVALIDATE,\n includeStatic = true,\n } = opts;\n\n return {\n async GET() {\n const ids: string[] = includeStatic ? [STATIC_ID] : [];\n\n if (apiUrl) {\n const index = await fetchSitemapIndex(apiUrl, indexRevalidate);\n ids.push(...selectedChunkIds(index, sources, encodeChunkId));\n }\n\n const lastmod = new Date().toISOString();\n const body = renderIndex(host, ids, lastmod);\n\n return new Response(body, {\n status: 200,\n headers: {\n 'content-type': 'application/xml; charset=utf-8',\n // Mirror the per-chunk revalidate so a cached CDN edge expires\n // at the same cadence as the index it points at.\n 'cache-control': `public, s-maxage=${indexRevalidate}, stale-while-revalidate=${indexRevalidate}`,\n },\n });\n },\n };\n}\n\nfunction renderIndex(host: string, ids: string[], lastmod: string): string {\n const lines: string[] = [];\n lines.push('<?xml version=\"1.0\" encoding=\"UTF-8\"?>');\n lines.push('<sitemapindex xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">');\n for (const id of ids) {\n lines.push(' <sitemap>');\n lines.push(` <loc>${escapeXml(`${host}/sitemap/${id}.xml`)}</loc>`);\n lines.push(` <lastmod>${lastmod}</lastmod>`);\n lines.push(' </sitemap>');\n }\n lines.push('</sitemapindex>');\n return lines.join('\\n');\n}\n\nfunction escapeXml(s: string): string {\n return s\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;')\n .replace(/'/g, '&apos;');\n}\n","/**\n * createRobots — factory for Next.js `app/robots.ts`.\n *\n * Emits a single `User-Agent: *` block plus a Sitemap pointer. Disallow\n * defaults cover the common cases (auth, account, API surface) — override\n * per app if needed.\n *\n * The `sitemap` default `${host}/sitemap.xml` only makes sense for the\n * single-file case. When the app uses `createDjangoSitemap`\n * (`generateSitemaps()` → chunked output) **plus** `createSitemapIndex`\n * — the canonical pairing — pass an explicit\n * `sitemap: \\`${host}/sitemap_index.xml\\`` to point crawlers at the\n * index. The default `/sitemap.xml` will serve a 404 in that setup\n * (Next.js reserves it for the metadata route but doesn't emit content\n * there).\n *\n * Usage:\n *\n * ```ts\n * // apps/<app>/app/robots.ts\n * import { createRobots } from '@djangocfg/nextjs/sitemap';\n *\n * const host = process.env.NEXT_PUBLIC_SITE_URL!;\n *\n * export default createRobots({\n * host,\n * disallow: ['/account/', '/auth', '/api/', '/apix/'],\n * sitemap: `${host}/sitemap_index.xml`, // matches createSitemapIndex\n * });\n * ```\n */\n\nimport type { MetadataRoute } from 'next';\n\nimport type { CreateRobotsOptions } from './types';\n\nconst DEFAULT_DISALLOW = ['/account/', '/auth', '/api/'];\n\nexport function createRobots(opts: CreateRobotsOptions): () => MetadataRoute.Robots {\n const { host, disallow = DEFAULT_DISALLOW, sitemap } = opts;\n return () => ({\n rules: [{ userAgent: '*', allow: '/', disallow }],\n sitemap: sitemap ?? `${host}/sitemap.xml`,\n host,\n });\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/sitemap/constants.ts","../../src/sitemap/fetch.ts","../../src/sitemap/ids.ts","../../src/sitemap/select.ts","../../src/sitemap/sitemap.ts","../../src/sitemap/index-route.ts","../../src/sitemap/robots.ts"],"sourcesContent":["/**\n * Shared sitemap constants.\n *\n * Pulled out so both the chunk factory (`createDjangoSitemap`) and the\n * index Route Handler (`createSitemapIndex`) agree on the id of the\n * frontend-only static-routes chunk.\n */\n\nexport const STATIC_ID = 'static';\nexport const DEFAULT_INDEX_REVALIDATE = 600;\nexport const DEFAULT_FEED_REVALIDATE = 3600;\n","/**\n * Sitemap fetch helpers.\n *\n * During `next build` a failure becomes an empty payload, so a Django outage\n * does not fail the build. At runtime it throws: ISR then keeps serving the\n * last good copy. An empty payload there would be cached for the whole\n * revalidate window — a sitemap index with only the static chunk, which\n * crawlers and IndexNow diffs read as every listing removed.\n */\n\nimport type { SitemapFeedPage, SitemapIndex } from './types';\n\n/**\n * Next.js refuses to write a fetch response larger than 2MB into its data\n * cache. It logs one line and serves the response uncached, so `revalidate`\n * silently becomes a no-op and every regeneration refetches the full payload\n * from Django. Nothing else surfaces this, so we measure the body ourselves.\n */\nconst NEXT_DATA_CACHE_LIMIT = 2 * 1024 * 1024;\n\n// One warning per URL: `sitemap()` runs once per chunk per build, and a\n// repeated line for the same feed reads as a new problem rather than the\n// same one.\nconst oversizeWarned = new Set<string>();\n\n/**\n * Read the body as text so its exact size is known, then parse.\n *\n * `Content-Length` is not a substitute: it is absent under chunked transfer\n * and reports the compressed size when the response is gzipped, while the\n * 2MB ceiling applies to the decoded bytes Next.js would store.\n */\nasync function parseAndCheckSize<T>(r: Response, url: string, label: string): Promise<T> {\n const text = await r.text();\n const bytes = new TextEncoder().encode(text).length;\n if (bytes > NEXT_DATA_CACHE_LIMIT && !oversizeWarned.has(url)) {\n oversizeWarned.add(url);\n console.warn(\n `[sitemap] ${label} is ${bytes} bytes, over Next.js's 2MB data-cache ceiling: ` +\n `revalidate is inert for this feed and every regeneration refetches it from Django. ` +\n `Lower the backend page_size if the extra load matters — note 50,000 URLs is the ` +\n `sitemap protocol's own maximum, so a large payload here is not itself a bug.`,\n );\n }\n return JSON.parse(text) as T;\n}\n\n/** True while `next build` prerenders; false in the serving process. */\nconst isBuilding = (): boolean => process.env.NEXT_PHASE === 'phase-production-build';\n\nfunction degrade<T>(fallback: T, reason: string, err?: unknown): T {\n if (!isBuilding()) {\n throw new Error(`[sitemap] ${reason}; keeping the last good copy`, { cause: err });\n }\n console.warn(`[sitemap] ${reason}; falling back to empty during build`, err ?? '');\n return fallback;\n}\n\nconst EMPTY_INDEX: SitemapIndex = {\n sources: [],\n generated_at: new Date(0).toISOString(),\n ttl_seconds: 60,\n};\n\nconst emptyFeed = (source: string, cursor: string | null): SitemapFeedPage => ({\n source,\n chunk_id: `${source}-empty`,\n count: 0,\n has_more: false,\n next_cursor: cursor,\n entries: [],\n});\n\nexport async function fetchSitemapIndex(\n apiUrl: string,\n revalidate: number,\n): Promise<SitemapIndex> {\n const url = `${apiUrl}/cfg/sitemap/index/`;\n try {\n const r = await fetch(url, {\n next: { revalidate },\n });\n if (!r.ok) return degrade(EMPTY_INDEX, `index fetch returned ${r.status}`);\n return await parseAndCheckSize<SitemapIndex>(r, url, 'index');\n } catch (err) {\n return degrade(EMPTY_INDEX, 'index fetch failed', err);\n }\n}\n\nexport async function fetchSitemapFeed(\n apiUrl: string,\n source: string,\n cursor: string | null,\n revalidate: number,\n): Promise<SitemapFeedPage> {\n const params = new URLSearchParams({ source });\n if (cursor) params.set('cursor', cursor);\n const url = `${apiUrl}/cfg/sitemap/feed/?${params}`;\n try {\n const r = await fetch(url, {\n next: { revalidate },\n });\n if (!r.ok) return degrade(emptyFeed(source, cursor), `feed ${source} returned ${r.status}`);\n return await parseAndCheckSize<SitemapFeedPage>(r, url, `feed ${source}`);\n } catch (err) {\n return degrade(emptyFeed(source, cursor), `feed ${source} fetch failed`, err);\n }\n}\n","/**\n * Sitemap chunk id encoding.\n *\n * Each Next.js sitemap file is addressed by an opaque string id. We\n * round-trip a `(source, cursor)` pair through that id so the default\n * sitemap handler knows which Django chunk to fetch.\n *\n * Separator `--` is unambiguous: backend cursors use URL-safe base64 (`-_`).\n */\n\nexport function encodeChunkId(source: string, cursor: string | null): string {\n return `${source}--${cursor ?? ''}`;\n}\n\nexport function decodeChunkId(id: string): { source: string; cursor: string | null } {\n const sep = id.indexOf('--');\n if (sep === -1) return { source: id, cursor: null };\n const source = id.slice(0, sep);\n const cursorRaw = id.slice(sep + 2);\n return { source, cursor: cursorRaw === '' ? null : cursorRaw };\n}\n","/**\n * Which backend sources a frontend claims.\n *\n * `GET /cfg/sitemap/index/` answers for the whole Django project — every\n * source any installed app registered. One frontend is not always entitled\n * to all of them: a backend serving several brands registers a source per\n * brand (`properties-caribbean`, `properties-bali`), and each brand's app\n * must emit only its own, or it advertises the other's URLs under its own\n * host — every one of them a 404, and the sitemap that carries them is\n * discredited whole.\n *\n * The selector lives here, shared, for the same reason `STATIC_ID` does:\n * `createDjangoSitemap` enumerates the chunks and `createSitemapIndex`\n * enumerates them again for the index document. If the two applied\n * different filters, the index would advertise shards the chunk route\n * refuses to build — and that mismatch is invisible until a crawler reads\n * both. One function, called by both, cannot drift.\n *\n * Source NAMES are the contract. The module ships no notion of a brand or\n * a tenant, and it should not: the backend chose what its source names\n * mean, and only the app knows which of them are its own.\n */\n\nimport type { SitemapIndex, SitemapSourceInfo, SourceSelector } from './types';\n\n/**\n * A predicate from whatever the caller passed.\n *\n * Omitted means every source — the single-tenant default, and what every\n * caller before this option existed already relied on.\n */\nexport function sourceFilter(select: SourceSelector | undefined): (name: string) => boolean {\n if (select === undefined) return () => true;\n if (typeof select === 'function') return select;\n const allowed = new Set(select);\n return (name) => allowed.has(name);\n}\n\n/** The index's sources, narrowed to those the caller claims. */\nexport function selectSources(\n index: SitemapIndex,\n select: SourceSelector | undefined,\n): SitemapSourceInfo[] {\n const keep = sourceFilter(select);\n return index.sources.filter((source) => keep(source.name));\n}\n\n/**\n * Every chunk id the selected sources produce, in index order.\n *\n * The one place `(source, chunk)` becomes an id, so the chunk route and the\n * index document enumerate the same set by construction.\n */\nexport function selectedChunkIds(\n index: SitemapIndex,\n select: SourceSelector | undefined,\n encode: (source: string, cursor: string | null) => string,\n): string[] {\n const ids: string[] = [];\n for (const source of selectSources(index, select)) {\n for (const chunk of source.chunks) {\n ids.push(encode(source.name, chunk.cursor_to));\n }\n }\n return ids;\n}\n\n/**\n * A selector for one tenant of a multi-brand backend.\n *\n * Matches the `<source>-<tenant>` names that a backend registering per\n * tenant emits. It splits on the LAST hyphen and compares the tail exactly,\n * rather than testing a suffix: `endsWith('-bali')` is correct for\n * `properties-bali` but the same shape reads `pricing-plans-premium` as\n * belonging to a `premium` tenant, and a substring match is not a contract.\n *\n * ```ts\n * sources: tenantSources('bali') // properties-bali, communities-bali, …\n * ```\n *\n * Sources with no hyphen never match: an untenanted source belongs to the\n * project rather than to a brand, and claiming it for one is the error this\n * function exists to prevent.\n */\nexport function tenantSources(tenant: string): (name: string) => boolean {\n return (name) => {\n const sep = name.lastIndexOf('-');\n return sep !== -1 && name.slice(sep + 1) === tenant;\n };\n}\n","/**\n * createDjangoSitemap — factory for Next.js `app/sitemap.ts`.\n *\n * Backed by `django_cfg.modules.django_sitemap`: the backend exposes a\n * paginated JSON feed, this factory turns each chunk into a Next.js\n * sitemap file via `generateSitemaps()`.\n *\n * Usage:\n *\n * ```ts\n * // apps/<app>/app/sitemap.ts\n * import { createDjangoSitemap } from '@djangocfg/nextjs/sitemap';\n *\n * const { generateSitemaps, sitemap } = createDjangoSitemap({\n * host: process.env.NEXT_PUBLIC_SITE_URL!,\n * apiUrl: process.env.NEXT_PUBLIC_API_URL!,\n * staticRoutes: [\n * { path: '/', changeFrequency: 'daily', priority: 1.0 },\n * { path: '/catalog', changeFrequency: 'daily', priority: 0.9 },\n * ],\n * });\n *\n * export { generateSitemaps, sitemap as default };\n * export const revalidate = 3600;\n * ```\n *\n * Without `apiUrl`, the sitemap emits only the static routes — handy for\n * marketing sites that don't have a backend-driven URL space.\n */\n\nimport type { MetadataRoute } from 'next';\n\nimport {\n DEFAULT_FEED_REVALIDATE,\n DEFAULT_INDEX_REVALIDATE,\n STATIC_ID,\n} from './constants';\nimport { fetchSitemapFeed, fetchSitemapIndex } from './fetch';\nimport { decodeChunkId, encodeChunkId } from './ids';\nimport { selectedChunkIds, sourceFilter } from './select';\n\nimport type { CreateDjangoSitemapOptions, StaticRoute } from './types';\n\ninterface SitemapApi {\n generateSitemaps: () => Promise<Array<{ id: string }>>;\n sitemap: (props: { id: Promise<string> }) => Promise<MetadataRoute.Sitemap>;\n}\n\nexport function createDjangoSitemap(opts: CreateDjangoSitemapOptions): SitemapApi {\n const {\n host,\n apiUrl,\n staticRoutes = [],\n sources,\n indexRevalidate = DEFAULT_INDEX_REVALIDATE,\n feedRevalidate = DEFAULT_FEED_REVALIDATE,\n } = opts;\n\n return {\n async generateSitemaps() {\n const ids: Array<{ id: string }> = [{ id: STATIC_ID }];\n if (!apiUrl) return ids;\n const index = await fetchSitemapIndex(apiUrl, indexRevalidate);\n for (const id of selectedChunkIds(index, sources, encodeChunkId)) {\n ids.push({ id });\n }\n return ids;\n },\n\n async sitemap({ id: idPromise }) {\n const id = await idPromise;\n\n if (id === STATIC_ID) {\n return renderStatic(host, staticRoutes);\n }\n\n if (!apiUrl) return [];\n\n const { source, cursor } = decodeChunkId(id);\n // A chunk this app does not claim renders empty rather than fetching.\n // `generateSitemaps` never mints such an id, but the route stays\n // reachable by hand, and serving another tenant's URLs from a guessed\n // address would undo the filter above.\n if (!sourceFilter(sources)(source)) return [];\n\n const feed = await fetchSitemapFeed(apiUrl, source, cursor, feedRevalidate);\n return feed.entries.map((e) => ({\n url: `${host}${e.loc}`,\n lastModified: e.lastmod ? new Date(e.lastmod) : undefined,\n }));\n },\n };\n}\n\nfunction renderStatic(host: string, routes: StaticRoute[]): MetadataRoute.Sitemap {\n return routes.map((r) => ({\n url: `${host}${r.path}`,\n changeFrequency: r.changeFrequency,\n priority: r.priority,\n }));\n}\n","/**\n * createSitemapIndex — Route Handler factory for the sitemap-index XML.\n *\n * Next.js's `generateSitemaps()` API ships chunks at `/sitemap/<id>.xml`\n * but does NOT emit a single sitemap-index document. Without one,\n * robots.txt + crawlers point at a 404 and never discover the chunks.\n * This factory closes that gap: it returns a `GET` Route Handler that\n * mints a `<sitemapindex>` XML listing every chunk the matching\n * `createDjangoSitemap` would produce.\n *\n * Mount it at **`app/sitemap_index.xml/route.ts`** — NOT `sitemap.xml`.\n * `app/sitemap.ts` (where `createDjangoSitemap` lives) already owns\n * `/sitemap.xml` in Next.js's metadata routing, even when its output is\n * fan-out via `generateSitemaps()` and the singular URL serves 404.\n * Putting a route handler at `app/sitemap.xml/route.ts` throws at build\n * time: *Conflicting route and metadata at /sitemap.xml*.\n *\n * Usage — the full pattern with the matching chunk + robots factories:\n *\n * ```ts\n * // apps/<app>/app/sitemap.ts — chunks\n * import { createDjangoSitemap } from '@djangocfg/nextjs/sitemap';\n * const { generateSitemaps, sitemap } = createDjangoSitemap({ ... });\n * export { generateSitemaps, sitemap as default };\n *\n * // apps/<app>/app/sitemap_index.xml/route.ts — the index\n * import { createSitemapIndex } from '@djangocfg/nextjs/sitemap';\n * export const { GET } = createSitemapIndex({\n * host: process.env.NEXT_PUBLIC_SITE_URL!,\n * apiUrl: process.env.NEXT_PUBLIC_API_URL!,\n * });\n * export const revalidate = 600;\n *\n * // apps/<app>/app/robots.ts — point crawlers at the index URL above\n * import { createRobots } from '@djangocfg/nextjs/sitemap';\n * const host = process.env.NEXT_PUBLIC_SITE_URL!;\n * export default createRobots({\n * host,\n * sitemap: `${host}/sitemap_index.xml`,\n * });\n * ```\n *\n * Keep `host` / `apiUrl` in sync with the same app's `app/sitemap.ts`\n * — the index just enumerates the chunks the other file emits.\n */\n\nimport {\n DEFAULT_INDEX_REVALIDATE,\n STATIC_ID,\n} from './constants';\nimport { fetchSitemapIndex } from './fetch';\nimport { encodeChunkId } from './ids';\nimport { selectedChunkIds } from './select';\n\nimport type { CreateSitemapIndexOptions } from './types';\n\ninterface SitemapIndexRoute {\n GET: () => Promise<Response>;\n}\n\nexport function createSitemapIndex(opts: CreateSitemapIndexOptions): SitemapIndexRoute {\n const {\n host,\n apiUrl,\n sources,\n indexRevalidate = DEFAULT_INDEX_REVALIDATE,\n includeStatic = true,\n } = opts;\n\n return {\n async GET() {\n const ids: string[] = includeStatic ? [STATIC_ID] : [];\n\n if (apiUrl) {\n const index = await fetchSitemapIndex(apiUrl, indexRevalidate);\n ids.push(...selectedChunkIds(index, sources, encodeChunkId));\n }\n\n const lastmod = new Date().toISOString();\n const body = renderIndex(host, ids, lastmod);\n\n return new Response(body, {\n status: 200,\n headers: {\n 'content-type': 'application/xml; charset=utf-8',\n // Mirror the per-chunk revalidate so a cached CDN edge expires\n // at the same cadence as the index it points at.\n 'cache-control': `public, s-maxage=${indexRevalidate}, stale-while-revalidate=${indexRevalidate}`,\n },\n });\n },\n };\n}\n\nfunction renderIndex(host: string, ids: string[], lastmod: string): string {\n const lines: string[] = [];\n lines.push('<?xml version=\"1.0\" encoding=\"UTF-8\"?>');\n lines.push('<sitemapindex xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">');\n for (const id of ids) {\n lines.push(' <sitemap>');\n lines.push(` <loc>${escapeXml(`${host}/sitemap/${id}.xml`)}</loc>`);\n lines.push(` <lastmod>${lastmod}</lastmod>`);\n lines.push(' </sitemap>');\n }\n lines.push('</sitemapindex>');\n return lines.join('\\n');\n}\n\nfunction escapeXml(s: string): string {\n return s\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;')\n .replace(/'/g, '&apos;');\n}\n","/**\n * createRobots — factory for Next.js `app/robots.ts`.\n *\n * Emits a single `User-Agent: *` block plus a Sitemap pointer. Disallow\n * defaults cover the common cases (auth, account, API surface) — override\n * per app if needed.\n *\n * The `sitemap` default `${host}/sitemap.xml` only makes sense for the\n * single-file case. When the app uses `createDjangoSitemap`\n * (`generateSitemaps()` → chunked output) **plus** `createSitemapIndex`\n * — the canonical pairing — pass an explicit\n * `sitemap: \\`${host}/sitemap_index.xml\\`` to point crawlers at the\n * index. The default `/sitemap.xml` will serve a 404 in that setup\n * (Next.js reserves it for the metadata route but doesn't emit content\n * there).\n *\n * Usage:\n *\n * ```ts\n * // apps/<app>/app/robots.ts\n * import { createRobots } from '@djangocfg/nextjs/sitemap';\n *\n * const host = process.env.NEXT_PUBLIC_SITE_URL!;\n *\n * export default createRobots({\n * host,\n * disallow: ['/account/', '/auth', '/api/', '/apix/'],\n * sitemap: `${host}/sitemap_index.xml`, // matches createSitemapIndex\n * });\n * ```\n */\n\nimport type { MetadataRoute } from 'next';\n\nimport type { CreateRobotsOptions } from './types';\n\nconst DEFAULT_DISALLOW = ['/account/', '/auth', '/api/'];\n\nexport function createRobots(opts: CreateRobotsOptions): () => MetadataRoute.Robots {\n const { host, disallow = DEFAULT_DISALLOW, sitemap } = opts;\n return () => ({\n rules: [{ userAgent: '*', allow: '/', disallow }],\n sitemap: sitemap ?? `${host}/sitemap.xml`,\n host,\n });\n}\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@djangocfg/nextjs",
3
- "version": "2.1.569",
3
+ "version": "2.1.571",
4
4
  "description": "Next.js server utilities: sitemap, health, OG images, contact forms, navigation, config",
5
5
  "keywords": [
6
6
  "nextjs",
@@ -149,9 +149,9 @@
149
149
  "ai-docs": "tsx src/ai/cli.ts"
150
150
  },
151
151
  "peerDependencies": {
152
- "@djangocfg/devtools": "^2.1.569",
153
- "@djangocfg/i18n": "^2.1.569",
154
- "@djangocfg/ui-core": "^2.1.569",
152
+ "@djangocfg/devtools": "^2.1.571",
153
+ "@djangocfg/i18n": "^2.1.571",
154
+ "@djangocfg/ui-core": "^2.1.571",
155
155
  "next": "^16.2.2"
156
156
  },
157
157
  "peerDependenciesMeta": {
@@ -170,11 +170,11 @@
170
170
  "semver": "^7.8.5"
171
171
  },
172
172
  "devDependencies": {
173
- "@djangocfg/devtools": "^2.1.569",
174
- "@djangocfg/i18n": "^2.1.569",
175
- "@djangocfg/layouts": "^2.1.569",
176
- "@djangocfg/typescript-config": "^2.1.569",
177
- "@djangocfg/ui-core": "^2.1.569",
173
+ "@djangocfg/devtools": "^2.1.571",
174
+ "@djangocfg/i18n": "^2.1.571",
175
+ "@djangocfg/layouts": "^2.1.571",
176
+ "@djangocfg/typescript-config": "^2.1.571",
177
+ "@djangocfg/ui-core": "^2.1.571",
178
178
  "@types/node": "^25.9.5",
179
179
  "@types/react": "19.2.15",
180
180
  "@types/react-dom": "19.2.3",
@@ -195,15 +195,25 @@ on the Django side; every Redis key includes it.
195
195
 
196
196
  ## Failure mode
197
197
 
198
- `fetchSitemapIndex` and `fetchSitemapFeed` **never throw**. A transient
199
- Django outage during `next build` is downgraded to:
198
+ `fetchSitemapIndex` and `fetchSitemapFeed` behave differently in the two
199
+ phases.
200
+
201
+ **During `next build`** (`NEXT_PHASE=phase-production-build`) they never
202
+ throw. A Django outage is downgraded:
200
203
 
201
204
  - empty index → only the static chunk is emitted
202
205
  - empty feed → that chunk renders an empty `<urlset/>`
203
206
 
204
- `next build` completes, the bad cache entry expires on the next ISR
205
- cycle, and the sitemap heals automatically. No build-time coupling to
206
- Django uptime.
207
+ The build completes with no coupling to Django uptime, and the next ISR cycle
208
+ heals it.
209
+
210
+ **At runtime they throw.** On a failed regeneration Next keeps serving the
211
+ last good copy. The old runtime fallback cached the degraded answer for the
212
+ whole revalidate window: a Traefik 404 during a Django recreate became a
213
+ static-only sitemap index for ten minutes. A consumer that diffs the sitemap,
214
+ such as an IndexNow sweep, then read every listing as removed and resubmitted
215
+ the entire catalogue. A cold start with Django down answers 500 until Django
216
+ is back, which a crawler retries.
207
217
 
208
218
  ## Deployment
209
219
 
@@ -1,9 +1,11 @@
1
1
  /**
2
- * Sitemap fetch helpers — never throw.
2
+ * Sitemap fetch helpers.
3
3
  *
4
- * Failures are converted to empty payloads so a transient Django outage
5
- * doesn't break `next build`. The next ISR cycle (`revalidate`) recovers
6
- * automatically.
4
+ * During `next build` a failure becomes an empty payload, so a Django outage
5
+ * does not fail the build. At runtime it throws: ISR then keeps serving the
6
+ * last good copy. An empty payload there would be cached for the whole
7
+ * revalidate window — a sitemap index with only the static chunk, which
8
+ * crawlers and IndexNow diffs read as every listing removed.
7
9
  */
8
10
 
9
11
  import type { SitemapFeedPage, SitemapIndex } from './types';
@@ -43,6 +45,17 @@ async function parseAndCheckSize<T>(r: Response, url: string, label: string): Pr
43
45
  return JSON.parse(text) as T;
44
46
  }
45
47
 
48
+ /** True while `next build` prerenders; false in the serving process. */
49
+ const isBuilding = (): boolean => process.env.NEXT_PHASE === 'phase-production-build';
50
+
51
+ function degrade<T>(fallback: T, reason: string, err?: unknown): T {
52
+ if (!isBuilding()) {
53
+ throw new Error(`[sitemap] ${reason}; keeping the last good copy`, { cause: err });
54
+ }
55
+ console.warn(`[sitemap] ${reason}; falling back to empty during build`, err ?? '');
56
+ return fallback;
57
+ }
58
+
46
59
  const EMPTY_INDEX: SitemapIndex = {
47
60
  sources: [],
48
61
  generated_at: new Date(0).toISOString(),
@@ -67,14 +80,10 @@ export async function fetchSitemapIndex(
67
80
  const r = await fetch(url, {
68
81
  next: { revalidate },
69
82
  });
70
- if (!r.ok) {
71
- console.warn(`[sitemap] index fetch returned ${r.status}; falling back to empty`);
72
- return EMPTY_INDEX;
73
- }
83
+ if (!r.ok) return degrade(EMPTY_INDEX, `index fetch returned ${r.status}`);
74
84
  return await parseAndCheckSize<SitemapIndex>(r, url, 'index');
75
85
  } catch (err) {
76
- console.warn('[sitemap] index fetch failed:', err);
77
- return EMPTY_INDEX;
86
+ return degrade(EMPTY_INDEX, 'index fetch failed', err);
78
87
  }
79
88
  }
80
89
 
@@ -91,13 +100,9 @@ export async function fetchSitemapFeed(
91
100
  const r = await fetch(url, {
92
101
  next: { revalidate },
93
102
  });
94
- if (!r.ok) {
95
- console.warn(`[sitemap] feed ${source} returned ${r.status}; falling back to empty`);
96
- return emptyFeed(source, cursor);
97
- }
103
+ if (!r.ok) return degrade(emptyFeed(source, cursor), `feed ${source} returned ${r.status}`);
98
104
  return await parseAndCheckSize<SitemapFeedPage>(r, url, `feed ${source}`);
99
105
  } catch (err) {
100
- console.warn(`[sitemap] feed ${source} fetch failed:`, err);
101
- return emptyFeed(source, cursor);
106
+ return degrade(emptyFeed(source, cursor), `feed ${source} fetch failed`, err);
102
107
  }
103
108
  }