@djangocfg/nextjs 2.1.478 → 2.1.480

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.
@@ -5,6 +5,129 @@ import { P as PWAPluginOptions } from '../plugin-DdmK3zAE.mjs';
5
5
  export { A as AI_DOCS_HINT, M as MCP_API_URL, b as MCP_BASE_URL, c as MCP_SERVER_URL } from '../constants-BDS8GXHN.mjs';
6
6
  import '@djangocfg/i18n';
7
7
 
8
+ /**
9
+ * CSP Vendor Presets
10
+ *
11
+ * The problem this solves: every third-party script a DjangoCFG app loads needs
12
+ * its origin named in the CSP, and until now each app had to hand-list those
13
+ * origins (`csp: { scriptSrc: [...] }`) in its own `next.config.ts`. That
14
+ * repeats the same Stripe/Cloudflare/Google origin strings across every app,
15
+ * and — worse — nobody remembers which directive a given vendor needs, so the
16
+ * usual outcome is a production-only console error and a scramble.
17
+ *
18
+ * Instead, apps declare WHAT THEY USE by name (`csp: { vendors: ['stripe'] }`)
19
+ * and this registry owns WHICH ORIGINS that implies. When a vendor changes a
20
+ * host, it is fixed once here and every app inherits the fix on its next build.
21
+ *
22
+ * Adding a vendor: add an entry below, keyed by a short stable name, listing
23
+ * only the directives it genuinely needs. Keep the origins exact — never widen
24
+ * a directive to a bare `https:` to make a vendor work.
25
+ */
26
+ /** The CSP directives a vendor preset may contribute origins to. */
27
+ interface CspOrigins {
28
+ /** `script-src` — the vendor's SDK/tag `<script>` hosts. */
29
+ scriptSrc?: string[];
30
+ /** `frame-src` — hosts the vendor mounts iframes from. */
31
+ frameSrc?: string[];
32
+ /** `connect-src` — hosts the vendor makes XHR/fetch/WebSocket calls to. */
33
+ connectSrc?: string[];
34
+ /** `img-src` — hosts the vendor loads images/pixels from. */
35
+ imgSrc?: string[];
36
+ /** `font-src` — hosts the vendor loads web fonts from. */
37
+ fontSrc?: string[];
38
+ }
39
+ /**
40
+ * Known third-party vendors and the CSP origins each one requires.
41
+ *
42
+ * Every entry is OFF by default — an app opts in via `csp.vendors`, so an app
43
+ * that mounts nothing third-party keeps the tightest possible policy.
44
+ */
45
+ declare const CSP_VENDOR_PRESETS: {
46
+ /**
47
+ * Stripe.js / Checkout / Elements.
48
+ *
49
+ * `js.stripe.com` serves the SDK and also hosts the Elements iframes;
50
+ * `checkout.stripe.com` is the hosted Checkout iframe; `api.stripe.com`
51
+ * takes the tokenization/confirm XHR. Without all three the payment form
52
+ * silently fails to render.
53
+ */
54
+ readonly stripe: {
55
+ readonly scriptSrc: ["https://js.stripe.com"];
56
+ readonly frameSrc: ["https://js.stripe.com", "https://checkout.stripe.com"];
57
+ readonly connectSrc: ["https://api.stripe.com"];
58
+ };
59
+ /**
60
+ * Cloudflare Web Analytics (the RUM beacon).
61
+ *
62
+ * IMPORTANT: this script is NOT in the app's own HTML — when Web Analytics
63
+ * is enabled on the zone, Cloudflare INJECTS `beacon.min.js` into the
64
+ * response at the edge, after Next.js has already emitted the CSP header.
65
+ * So any app served through a Cloudflare zone with that feature on will
66
+ * violate its own CSP unless this preset is enabled, no matter what the
67
+ * source tree contains. The beacon also POSTs its payload back to
68
+ * `cloudflareinsights.com`, hence the `connect-src` entry.
69
+ */
70
+ readonly cloudflare: {
71
+ readonly scriptSrc: ["https://static.cloudflareinsights.com"];
72
+ readonly connectSrc: ["https://cloudflareinsights.com", "https://static.cloudflareinsights.com"];
73
+ };
74
+ /** Google Analytics 4 / gtag.js. */
75
+ readonly 'google-analytics': {
76
+ readonly scriptSrc: ["https://www.googletagmanager.com", "https://www.google-analytics.com"];
77
+ readonly connectSrc: ["https://www.google-analytics.com", "https://analytics.google.com", "https://region1.google-analytics.com"];
78
+ readonly imgSrc: ["https://www.google-analytics.com", "https://www.googletagmanager.com"];
79
+ };
80
+ /** Google Tag Manager (the container itself; tags it loads may need more). */
81
+ readonly 'google-tag-manager': {
82
+ readonly scriptSrc: ["https://www.googletagmanager.com"];
83
+ readonly connectSrc: ["https://www.googletagmanager.com"];
84
+ readonly imgSrc: ["https://www.googletagmanager.com"];
85
+ readonly frameSrc: ["https://www.googletagmanager.com"];
86
+ };
87
+ /** Google Fonts (stylesheet host + the font files themselves). */
88
+ readonly 'google-fonts': {
89
+ readonly fontSrc: ["https://fonts.gstatic.com"];
90
+ };
91
+ /** Sentry browser SDK — errors/traces are POSTed to the ingest host. */
92
+ readonly sentry: {
93
+ readonly connectSrc: ["https://*.ingest.sentry.io", "https://*.sentry.io"];
94
+ };
95
+ /** YouTube embeds (privacy-enhanced host included). */
96
+ readonly youtube: {
97
+ readonly frameSrc: ["https://www.youtube.com", "https://www.youtube-nocookie.com"];
98
+ readonly imgSrc: ["https://i.ytimg.com"];
99
+ };
100
+ /** Vimeo embeds. */
101
+ readonly vimeo: {
102
+ readonly frameSrc: ["https://player.vimeo.com"];
103
+ readonly imgSrc: ["https://i.vimeocdn.com"];
104
+ };
105
+ };
106
+ /** Name of a vendor preset in {@link CSP_VENDOR_PRESETS}. */
107
+ type CspVendor = keyof typeof CSP_VENDOR_PRESETS;
108
+ /**
109
+ * Vendors applied to every app unless explicitly disabled.
110
+ *
111
+ * Only for origins the DEPLOYMENT PLATFORM injects, which an app therefore
112
+ * cannot discover by reading its own source. Cloudflare's Web Analytics beacon
113
+ * is rewritten into the HTML at the edge, after Next.js has already sent the
114
+ * CSP header — so an opt-in flag here reliably produces a production-only
115
+ * console error that nobody notices until a user reports it. (Observed
116
+ * 2026-07-22: all six DjangoCFG-served hosts were violating their own CSP.)
117
+ *
118
+ * Do NOT add ordinary third parties here; those belong in `csp.vendors` on the
119
+ * app that actually uses them, so unrelated apps keep the tighter policy.
120
+ */
121
+ declare const CSP_BASELINE_VENDORS: CspVendor[];
122
+ /**
123
+ * Merge the named vendor presets into a single set of extra origins.
124
+ *
125
+ * Unknown names are ignored rather than thrown on: a typo must not break the
126
+ * production build of an app whose only mistake was a misspelled analytics
127
+ * vendor. `warnUnknown` surfaces them on the build log instead.
128
+ */
129
+ declare function resolveCspVendors(vendors: readonly string[] | undefined, warnUnknown?: boolean): CspOrigins;
130
+
8
131
  /**
9
132
  * Base Next.js Configuration Factory
10
133
  *
@@ -79,22 +202,48 @@ interface BaseNextConfigOptions {
79
202
  * naming every future embed's origin is not worth the maintenance —
80
203
  * `X-Content-Type-Options`/`Referrer-Policy`/`X-Frame-Options` below
81
204
  * still apply.
82
- * - object — keep the baseline CSP but additively loosen specific
83
- * directives for named trusted origins (the pattern `stripe` above is
84
- * a hardcoded special case of). Every array is additive; omit a key to
85
- * leave that directive untouched.
205
+ * - object — keep the baseline CSP but additively loosen it. Prefer
206
+ * `vendors` (declare WHAT you use by name; the package owns which origins
207
+ * that implies) over hand-listing origins per directive. Every array is
208
+ * additive; omit a key to leave that directive untouched.
86
209
  * - omitted — tightest default (`frame-src 'self'`, etc.), same as today.
87
210
  *
211
+ * **The deployment platform counts as a vendor.** Some origins are injected
212
+ * into the response by the EDGE, after Next.js has emitted this header —
213
+ * Cloudflare Web Analytics rewrites `beacon.min.js` into the HTML when the
214
+ * feature is on for the zone. Nothing in the source tree reveals that, so
215
+ * any app served through such a zone must name the platform in `vendors`
216
+ * (e.g. `'cloudflare'`) or it will violate its own CSP in production only.
217
+ *
88
218
  * @example
89
219
  * ```ts
90
- * // No CSP at all — simplest option for a low-risk app with ad hoc embeds
91
- * createBaseNextConfig({ csp: false })
220
+ * // Declare the third parties this app actually uses — origins come from
221
+ * // the shared registry, so a vendor host change is fixed in one place.
222
+ * createBaseNextConfig({ csp: { vendors: ['stripe', 'cloudflare'] } })
92
223
  *
93
- * // Keep CSP, just allow one trusted iframe origin
224
+ * // Escape hatch for something not in the registry yet
94
225
  * createBaseNextConfig({ csp: { frameSrc: ['https://www.youtube-nocookie.com'] } })
226
+ *
227
+ * // No CSP at all — simplest option for a low-risk app with ad hoc embeds
228
+ * createBaseNextConfig({ csp: false })
95
229
  * ```
96
230
  */
97
231
  csp?: false | {
232
+ /**
233
+ * Named third-party vendors this app uses (`'stripe'`, `'cloudflare'`,
234
+ * `'google-analytics'`, …). Each expands to the exact origins that
235
+ * vendor needs across the relevant directives. See `CSP_VENDOR_PRESETS`
236
+ * for the full list. Unknown names warn at build time, never throw.
237
+ */
238
+ vendors?: (CspVendor | (string & {}))[];
239
+ /**
240
+ * Cloudflare Web Analytics origins are in the baseline (see
241
+ * `CSP_BASELINE_VENDORS`) because DjangoCFG apps are Cloudflare-served
242
+ * and the beacon is injected at the edge, where no app can see it.
243
+ * Set `false` only for an app served off Cloudflare entirely.
244
+ * @default true
245
+ */
246
+ cloudflare?: boolean;
98
247
  /** Extra `frame-src` origins (iframes this app embeds). */
99
248
  frameSrc?: string[];
100
249
  /** Extra `script-src` origins (third-party SDK scripts). */
@@ -430,4 +579,4 @@ declare function addCompressionPlugins(config: Configuration, options?: Compress
430
579
  */
431
580
  declare function isCompressionAvailable(): boolean;
432
581
 
433
- export { type BaseNextConfigOptions, type CompressionPluginOptions, DEFAULT_OPTIMIZE_PACKAGES, DEFAULT_TRANSPILE_PACKAGES, DJANGOCFG_PACKAGES, DJANGO_CFG_BANNER, DevStartupPlugin, type DevStartupPluginOptions, type InstallOptions, type InstallProgress, type MissingPackage, OPTIONAL_PACKAGES, PACKAGE_NAME, PEER_DEPENDENCIES, type PackageDefinition, type PackageVersion, type UpdateOptions, addCompressionPlugins, buildInstallCommand, buildSingleInstallCommand, checkAndInstallPackages, checkAndUpdatePackages, checkForUpdate, checkForUpdates, checkPackages, createBaseNextConfig, deepMerge, detectPackageManager, fetchLatestVersion, getApiUrl, getBasePath, getCurrentVersion, getInstalledVersion, getMissingPackages, getOutdatedPackages, getPackagesForContext, getSiteUrl, getUpdateCommand, installPackages, installPackagesWithProgress, isCI, isCompressionAvailable, isDev, isPackageInstalled, isProduction, isStaticBuild, printVersionInfo, resetDevStartupState, resetInstallerPreferences, resetUpdaterPreferences, updatePackagesWithProgress };
582
+ export { type BaseNextConfigOptions, CSP_BASELINE_VENDORS, CSP_VENDOR_PRESETS, type CompressionPluginOptions, type CspOrigins, type CspVendor, DEFAULT_OPTIMIZE_PACKAGES, DEFAULT_TRANSPILE_PACKAGES, DJANGOCFG_PACKAGES, DJANGO_CFG_BANNER, DevStartupPlugin, type DevStartupPluginOptions, type InstallOptions, type InstallProgress, type MissingPackage, OPTIONAL_PACKAGES, PACKAGE_NAME, PEER_DEPENDENCIES, type PackageDefinition, type PackageVersion, type UpdateOptions, addCompressionPlugins, buildInstallCommand, buildSingleInstallCommand, checkAndInstallPackages, checkAndUpdatePackages, checkForUpdate, checkForUpdates, checkPackages, createBaseNextConfig, deepMerge, detectPackageManager, fetchLatestVersion, getApiUrl, getBasePath, getCurrentVersion, getInstalledVersion, getMissingPackages, getOutdatedPackages, getPackagesForContext, getSiteUrl, getUpdateCommand, installPackages, installPackagesWithProgress, isCI, isCompressionAvailable, isDev, isPackageInstalled, isProduction, isStaticBuild, printVersionInfo, resetDevStartupState, resetInstallerPreferences, resetUpdaterPreferences, resolveCspVendors, updatePackagesWithProgress };
@@ -14,7 +14,7 @@ var require_package = __commonJS({
14
14
  "package.json"(exports, module) {
15
15
  module.exports = {
16
16
  name: "@djangocfg/nextjs",
17
- version: "2.1.478",
17
+ version: "2.1.480",
18
18
  description: "Next.js server utilities: sitemap, health, OG images, contact forms, navigation, config",
19
19
  keywords: [
20
20
  "nextjs",
@@ -289,6 +289,106 @@ var DEFAULT_OPTIMIZE_PACKAGES = [
289
289
  "recharts"
290
290
  ];
291
291
 
292
+ // src/config/csp.ts
293
+ var CSP_VENDOR_PRESETS = {
294
+ /**
295
+ * Stripe.js / Checkout / Elements.
296
+ *
297
+ * `js.stripe.com` serves the SDK and also hosts the Elements iframes;
298
+ * `checkout.stripe.com` is the hosted Checkout iframe; `api.stripe.com`
299
+ * takes the tokenization/confirm XHR. Without all three the payment form
300
+ * silently fails to render.
301
+ */
302
+ stripe: {
303
+ scriptSrc: ["https://js.stripe.com"],
304
+ frameSrc: ["https://js.stripe.com", "https://checkout.stripe.com"],
305
+ connectSrc: ["https://api.stripe.com"]
306
+ },
307
+ /**
308
+ * Cloudflare Web Analytics (the RUM beacon).
309
+ *
310
+ * IMPORTANT: this script is NOT in the app's own HTML — when Web Analytics
311
+ * is enabled on the zone, Cloudflare INJECTS `beacon.min.js` into the
312
+ * response at the edge, after Next.js has already emitted the CSP header.
313
+ * So any app served through a Cloudflare zone with that feature on will
314
+ * violate its own CSP unless this preset is enabled, no matter what the
315
+ * source tree contains. The beacon also POSTs its payload back to
316
+ * `cloudflareinsights.com`, hence the `connect-src` entry.
317
+ */
318
+ cloudflare: {
319
+ scriptSrc: ["https://static.cloudflareinsights.com"],
320
+ connectSrc: ["https://cloudflareinsights.com", "https://static.cloudflareinsights.com"]
321
+ },
322
+ /** Google Analytics 4 / gtag.js. */
323
+ "google-analytics": {
324
+ scriptSrc: ["https://www.googletagmanager.com", "https://www.google-analytics.com"],
325
+ connectSrc: [
326
+ "https://www.google-analytics.com",
327
+ "https://analytics.google.com",
328
+ "https://region1.google-analytics.com"
329
+ ],
330
+ imgSrc: ["https://www.google-analytics.com", "https://www.googletagmanager.com"]
331
+ },
332
+ /** Google Tag Manager (the container itself; tags it loads may need more). */
333
+ "google-tag-manager": {
334
+ scriptSrc: ["https://www.googletagmanager.com"],
335
+ connectSrc: ["https://www.googletagmanager.com"],
336
+ imgSrc: ["https://www.googletagmanager.com"],
337
+ frameSrc: ["https://www.googletagmanager.com"]
338
+ },
339
+ /** Google Fonts (stylesheet host + the font files themselves). */
340
+ "google-fonts": {
341
+ fontSrc: ["https://fonts.gstatic.com"]
342
+ },
343
+ /** Sentry browser SDK — errors/traces are POSTed to the ingest host. */
344
+ sentry: {
345
+ connectSrc: ["https://*.ingest.sentry.io", "https://*.sentry.io"]
346
+ },
347
+ /** YouTube embeds (privacy-enhanced host included). */
348
+ youtube: {
349
+ frameSrc: ["https://www.youtube.com", "https://www.youtube-nocookie.com"],
350
+ imgSrc: ["https://i.ytimg.com"]
351
+ },
352
+ /** Vimeo embeds. */
353
+ vimeo: {
354
+ frameSrc: ["https://player.vimeo.com"],
355
+ imgSrc: ["https://i.vimeocdn.com"]
356
+ }
357
+ };
358
+ var CSP_BASELINE_VENDORS = ["cloudflare"];
359
+ function resolveCspVendors(vendors, warnUnknown = true) {
360
+ const merged = {
361
+ scriptSrc: [],
362
+ frameSrc: [],
363
+ connectSrc: [],
364
+ imgSrc: [],
365
+ fontSrc: []
366
+ };
367
+ if (!vendors?.length) return merged;
368
+ const unknown = [];
369
+ for (const name of vendors) {
370
+ const preset = CSP_VENDOR_PRESETS[name];
371
+ if (!preset) {
372
+ unknown.push(name);
373
+ continue;
374
+ }
375
+ for (const key of Object.keys(merged)) {
376
+ const origins = preset[key];
377
+ if (origins) merged[key].push(...origins);
378
+ }
379
+ }
380
+ if (unknown.length && warnUnknown) {
381
+ const known = Object.keys(CSP_VENDOR_PRESETS).join(", ");
382
+ console.warn(
383
+ `[djangocfg] Unknown CSP vendor(s): ${unknown.join(", ")}. Known vendors: ${known}`
384
+ );
385
+ }
386
+ for (const key of Object.keys(merged)) {
387
+ merged[key] = [...new Set(merged[key])];
388
+ }
389
+ return merged;
390
+ }
391
+
292
392
  // src/config/packages/checker.ts
293
393
  import { createRequire } from "module";
294
394
  import { join } from "path";
@@ -1399,29 +1499,36 @@ function createBaseNextConfig(options = {}) {
1399
1499
  const frameAncestors = hasIframeAllowlist ? options.allowIframeFrom.includes("*") ? "*" : `'self' ${options.allowIframeFrom.join(" ")}` : "'none'";
1400
1500
  const cspDisabled = options.csp === false;
1401
1501
  const cspExtra = typeof options.csp === "object" ? options.csp : void 0;
1402
- const stripeScript = options.stripe ? " https://js.stripe.com" : "";
1403
- const stripeConnect = options.stripe ? " https://api.stripe.com" : "";
1404
- const stripeFrame = options.stripe ? " https://js.stripe.com https://checkout.stripe.com" : "";
1405
- const extraScript = cspExtra?.scriptSrc?.length ? " " + cspExtra.scriptSrc.join(" ") : "";
1406
- const extraConnect = cspExtra?.connectSrc?.length ? " " + cspExtra.connectSrc.join(" ") : "";
1407
- const extraFrame = cspExtra?.frameSrc?.length ? " " + cspExtra.frameSrc.join(" ") : "";
1408
- const extraImg = cspExtra?.imgSrc?.length ? " " + cspExtra.imgSrc.join(" ") : "";
1409
- const extraFont = cspExtra?.fontSrc?.length ? " " + cspExtra.fontSrc.join(" ") : "";
1410
- const connectSrc = isDev ? `connect-src 'self' http: https: ws: wss:${stripeConnect}${extraConnect}` : `connect-src 'self' https: wss:${stripeConnect}${extraConnect}`;
1502
+ const vendorNames = [
1503
+ ...cspExtra?.cloudflare === false ? [] : CSP_BASELINE_VENDORS,
1504
+ ...options.stripe ? ["stripe"] : [],
1505
+ ...cspExtra?.vendors ?? []
1506
+ ];
1507
+ const vendor = resolveCspVendors(vendorNames);
1508
+ const join3 = (...groups) => {
1509
+ const all = [...new Set(groups.flatMap((g) => g ?? []))];
1510
+ return all.length ? " " + all.join(" ") : "";
1511
+ };
1512
+ const extraScript = join3(vendor.scriptSrc, cspExtra?.scriptSrc);
1513
+ const extraConnect = join3(vendor.connectSrc, cspExtra?.connectSrc);
1514
+ const extraFrame = join3(vendor.frameSrc, cspExtra?.frameSrc);
1515
+ const extraImg = join3(vendor.imgSrc, cspExtra?.imgSrc);
1516
+ const extraFont = join3(vendor.fontSrc, cspExtra?.fontSrc);
1517
+ const connectSrc = isDev ? `connect-src 'self' http: https: ws: wss:${extraConnect}` : `connect-src 'self' https: wss:${extraConnect}`;
1411
1518
  const imgSrc = isDev ? `img-src 'self' data: blob: http: https:${extraImg}` : `img-src 'self' data: blob: https:${extraImg}`;
1412
1519
  if (!cspDisabled) {
1413
1520
  const cspDirectives = [
1414
1521
  "default-src 'self'",
1415
1522
  // Next.js requires inline + eval for its runtime/HMR. Tighten to nonces later.
1416
- `script-src 'self' 'unsafe-inline' 'unsafe-eval'${stripeScript}${extraScript}`,
1523
+ `script-src 'self' 'unsafe-inline' 'unsafe-eval'${extraScript}`,
1417
1524
  "style-src 'self' 'unsafe-inline'",
1418
1525
  imgSrc,
1419
1526
  `font-src 'self' data:${extraFont}`,
1420
1527
  // API/websocket calls go cross-origin to Django/Centrifugo.
1421
1528
  connectSrc,
1422
- // Stripe Elements/Checkout and any app-declared trusted origins
1423
- // (options.csp.frameSrc) mount iframes; add their hosts when enabled.
1424
- `frame-src 'self'${stripeFrame}${extraFrame}`,
1529
+ // Declared vendors (Stripe Elements/Checkout, video embeds…) and any
1530
+ // app-declared trusted origins mount iframes; add their hosts here.
1531
+ `frame-src 'self'${extraFrame}`,
1425
1532
  "object-src 'none'",
1426
1533
  "base-uri 'self'",
1427
1534
  "form-action 'self'",
@@ -1566,6 +1673,8 @@ function createBaseNextConfig(options = {}) {
1566
1673
  }
1567
1674
  export {
1568
1675
  AI_DOCS_HINT,
1676
+ CSP_BASELINE_VENDORS,
1677
+ CSP_VENDOR_PRESETS,
1569
1678
  DEFAULT_OPTIMIZE_PACKAGES,
1570
1679
  DEFAULT_TRANSPILE_PACKAGES,
1571
1680
  DJANGOCFG_PACKAGES,
@@ -1610,6 +1719,7 @@ export {
1610
1719
  resetDevStartupState,
1611
1720
  resetInstallerPreferences,
1612
1721
  resetUpdaterPreferences,
1722
+ resolveCspVendors,
1613
1723
  updatePackagesWithProgress
1614
1724
  };
1615
1725
  //# sourceMappingURL=index.mjs.map