@djangocfg/nextjs 2.1.515 → 2.1.518

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.
@@ -0,0 +1,79 @@
1
+ import { NextConfig } from 'next';
2
+
3
+ /**
4
+ * PWA (Progressive Web App) Plugin — dependency-free.
5
+ *
6
+ * History: this used to wrap `@serwist/next`. Serwist is a **webpack** plugin,
7
+ * and Next.js 16 defaults to Turbopack, which does not run webpack loaders and
8
+ * chokes on serwist's `fileURLToPath(new URL(".", import.meta.url))` during
9
+ * static tracing — failing the whole build even when PWA was never enabled
10
+ * (the mere `require('@serwist/next')` reference was enough). Since no app in
11
+ * this monorepo actually turned PWA on (`withPWA` is opt-in and unused), we
12
+ * dropped serwist entirely.
13
+ *
14
+ * What remains:
15
+ * - `createManifest` (in `./manifest`) — the web app manifest, which Next.js
16
+ * supports natively via `app/manifest.ts`; needs no library at all.
17
+ * - `withPWA` — a thin, Turbopack-safe pass-through kept for API stability.
18
+ * - `getServiceWorkerTemplate` — a vanilla service worker you can drop into
19
+ * `app/sw.ts`, no framework runtime required.
20
+ *
21
+ * If you want a full precache/offline story back, wire a real service worker
22
+ * (the template below, or Workbox) yourself — but do it in an app, not here, so
23
+ * one app's PWA choice can't break every consumer's build.
24
+ */
25
+
26
+ interface PWAPluginOptions {
27
+ /**
28
+ * Path to service worker source file (relative to project root).
29
+ * @default 'app/sw.ts'
30
+ */
31
+ swSrc?: string;
32
+ /**
33
+ * Destination for the compiled service worker.
34
+ * @default 'public/sw.js'
35
+ */
36
+ swDest?: string;
37
+ /**
38
+ * Disable PWA completely.
39
+ * @default true in development / static export, false otherwise
40
+ */
41
+ disable?: boolean;
42
+ /** Cache pages on navigation. Consumed by a custom SW, if any. @default true */
43
+ cacheOnNavigation?: boolean;
44
+ /** Reload the app when the device comes back online. @default true */
45
+ reloadOnOnline?: boolean;
46
+ }
47
+ /**
48
+ * Turbopack-safe PWA pass-through.
49
+ *
50
+ * This no longer injects a build plugin — Next.js's native manifest support
51
+ * plus a hand-written `app/sw.ts` (see {@link getServiceWorkerTemplate}) cover
52
+ * the PWA surface without a webpack-only dependency. Kept as a named export so
53
+ * existing `withPWA(config)` / `createBaseNextConfig({ pwa })` call sites keep
54
+ * type-checking and behave predictably (they return the config unchanged).
55
+ *
56
+ * @example
57
+ * ```ts
58
+ * import { createBaseNextConfig } from '@djangocfg/nextjs/config';
59
+ * export default createBaseNextConfig({ pwa: { swSrc: 'app/sw.ts' } });
60
+ * ```
61
+ */
62
+ declare function withPWA(nextConfig: NextConfig, _options?: PWAPluginOptions): NextConfig;
63
+ /**
64
+ * Vanilla service-worker template — no library.
65
+ *
66
+ * Returns ready-to-use, framework-free service worker source. Copy it into
67
+ * `app/sw.ts` (or generate it) if you want offline support. It uses only the
68
+ * standard Service Worker + Cache APIs, so it builds under Turbopack with no
69
+ * extra dependency.
70
+ *
71
+ * @example
72
+ * ```ts
73
+ * import { getServiceWorkerTemplate } from '@djangocfg/nextjs/config';
74
+ * console.log(getServiceWorkerTemplate()); // → paste into app/sw.ts
75
+ * ```
76
+ */
77
+ declare function getServiceWorkerTemplate(): string;
78
+
79
+ export { type PWAPluginOptions as P, getServiceWorkerTemplate as g, withPWA as w };
@@ -1,195 +1,3 @@
1
- import { MetadataRoute, Metadata, Viewport } from 'next';
2
- export { P as PWAPluginOptions, c as createApiCacheRule, a as createCdnCacheRule, b as createStaticAssetRule, d as defaultRuntimeCaching, g as getServiceWorkerTemplate, w as withPWA } from '../plugin-DdmK3zAE.mjs';
3
-
4
- /**
5
- * PWA Manifest Metadata Utilities
6
- *
7
- * Helper functions for creating Next.js metadata for PWA manifest
8
- */
9
-
10
- interface ManifestConfig {
11
- name: string;
12
- shortName?: string;
13
- description?: string;
14
- themeColor?: string;
15
- backgroundColor?: string;
16
- display?: 'standalone' | 'fullscreen' | 'minimal-ui' | 'browser';
17
- orientation?: 'portrait' | 'landscape' | 'any';
18
- startUrl?: string;
19
- scope?: string;
20
- lang?: string;
21
- dir?: 'ltr' | 'rtl' | 'auto';
22
- icons?: {
23
- src: string;
24
- sizes: string;
25
- type?: string;
26
- purpose?: string;
27
- }[];
28
- }
29
- /**
30
- * Icon paths configuration
31
- */
32
- interface IconPaths {
33
- logo192?: string;
34
- logo384?: string;
35
- logo512?: string;
36
- }
37
- /**
38
- * Protocol handler configuration
39
- * Allows your PWA to register as a handler for custom protocols
40
- *
41
- * @example
42
- * ```typescript
43
- * {
44
- * protocol: "web+music",
45
- * url: "/play?track=%s"
46
- * }
47
- * ```
48
- */
49
- interface ProtocolHandler {
50
- /** Protocol scheme (e.g., "web+music", "mailto", "magnet") */
51
- protocol: string;
52
- /** URL template with %s placeholder for the protocol parameter */
53
- url: string;
54
- }
55
- /**
56
- * Create viewport configuration for Next.js app
57
- *
58
- * @example
59
- * ```typescript
60
- * export const viewport: Viewport = createViewport({
61
- * themeColor: '#ffffff',
62
- * });
63
- * ```
64
- */
65
- declare function createViewport(config: {
66
- themeColor?: string;
67
- }): Viewport;
68
- /**
69
- * Create manifest metadata for Next.js app
70
- *
71
- * Note: themeColor and viewport should be exported separately using createViewport()
72
- *
73
- * @example
74
- * ```typescript
75
- * export const metadata: Metadata = {
76
- * ...createManifestMetadata({
77
- * name: 'My App',
78
- * shortName: 'App',
79
- * description: 'My awesome app',
80
- * }),
81
- * };
82
- *
83
- * export const viewport: Viewport = createViewport({
84
- * themeColor: '#ffffff',
85
- * });
86
- * ```
87
- */
88
- declare function createManifestMetadata(config: ManifestConfig): Metadata;
89
- /**
90
- * Create Next.js manifest function
91
- *
92
- * Use this in your app/manifest.ts file
93
- *
94
- * @example
95
- * ```typescript
96
- * // app/manifest.ts
97
- * import { createManifest } from '@djangocfg/nextjs/config';
98
- * import { settings } from '@core/settings';
99
- *
100
- * export default createManifest({
101
- * name: settings.app.name,
102
- * description: settings.app.description,
103
- * icons: {
104
- * logo192: settings.app.icons.logo192,
105
- * logo384: settings.app.icons.logo384,
106
- * logo512: settings.app.icons.logo512,
107
- * },
108
- * });
109
- * ```
110
- */
111
- interface ScreenshotConfig {
112
- src: string;
113
- sizes: string;
114
- type?: string;
115
- form_factor?: 'narrow' | 'wide';
116
- label?: string;
117
- }
118
- /**
119
- * Smart screenshot configuration
120
- * Automatically detects everything from path or uses defaults
121
- */
122
- interface SmartScreenshotInput {
123
- src: string;
124
- /** Form factor (auto-detected from filename if contains 'desktop'/'mobile', or use default) */
125
- form_factor?: 'narrow' | 'wide';
126
- /** Optional label (auto-generated from form_factor) */
127
- label?: string;
128
- /** Optional width (defaults based on form_factor) */
129
- width?: number;
130
- /** Optional height (defaults based on form_factor) */
131
- height?: number;
132
- }
133
- /**
134
- * Create screenshot config with smart defaults
135
- * Automatically detects type, sizes, form_factor from path or uses sensible defaults
136
- *
137
- * @example
138
- * ```typescript
139
- * // Minimal - everything auto-detected
140
- * createScreenshot({ src: '/screenshots/desktop-view.png' })
141
- * // → form_factor: 'wide', sizes: '1920x1080', type: 'image/png', label: 'Desktop screenshot'
142
- *
143
- * createScreenshot({ src: '/screenshots/mobile.png' })
144
- * // → form_factor: 'narrow', sizes: '390x844', type: 'image/png', label: 'Mobile screenshot'
145
- *
146
- * // With custom dimensions
147
- * createScreenshot({ src: '/screenshots/tablet.png', width: 1024, height: 768 })
148
- * ```
149
- */
150
- declare function createScreenshot(input: SmartScreenshotInput | string): ScreenshotConfig;
151
- /**
152
- * Create multiple screenshots from array
153
- * Supports string shorthand or full config objects
154
- *
155
- * @example
156
- * ```typescript
157
- * // Minimal - just paths
158
- * createScreenshots([
159
- * '/screenshots/desktop.png', // Auto: wide, 1920x1080
160
- * '/screenshots/mobile.png', // Auto: narrow, 390x844
161
- * ])
162
- *
163
- * // Mixed
164
- * createScreenshots([
165
- * '/screenshots/desktop.png',
166
- * { src: '/screenshots/tablet.png', width: 1024, height: 768 },
167
- * ])
168
- * ```
169
- */
170
- declare function createScreenshots(inputs: Array<SmartScreenshotInput | string>): ScreenshotConfig[];
171
- declare function createManifest(config: {
172
- name: string;
173
- shortName?: string;
174
- description?: string;
175
- themeColor?: string;
176
- backgroundColor?: string;
177
- display?: 'standalone' | 'fullscreen' | 'minimal-ui' | 'browser';
178
- orientation?: 'portrait' | 'landscape' | 'any';
179
- id?: string;
180
- startUrl?: string;
181
- scope?: string;
182
- lang?: string;
183
- dir?: 'ltr' | 'rtl' | 'auto';
184
- icons?: IconPaths | ManifestConfig['icons'];
185
- screenshots?: ScreenshotConfig[];
186
- protocol_handlers?: ProtocolHandler[];
187
- }): () => MetadataRoute.Manifest;
188
- /**
189
- * Generate manifest.json content (legacy)
190
- *
191
- * @deprecated Use createManifest() instead
192
- */
193
- declare function generateManifest(config: ManifestConfig): Record<string, any>;
194
-
195
- export { type IconPaths, type ManifestConfig, type ProtocolHandler, type ScreenshotConfig, type SmartScreenshotInput, createManifest, createManifestMetadata, createScreenshot, createScreenshots, createViewport, generateManifest };
1
+ export { IconPaths, ManifestConfig, ProtocolHandler, ScreenshotConfig, SmartScreenshotInput, createManifest, createManifestMetadata, createScreenshot, createScreenshots, createViewport, generateManifest } from './manifest.mjs';
2
+ export { P as PWAPluginOptions, g as getServiceWorkerTemplate, w as withPWA } from '../plugin-yhoLWYh6.mjs';
3
+ import 'next';
@@ -127,108 +127,57 @@ function generateManifest(config) {
127
127
  }
128
128
 
129
129
  // src/pwa/plugin.ts
130
- import { consola } from "consola";
131
- import { createRequire } from "module";
132
- var requirePeer = createRequire(import.meta.url);
133
- function withPWA(nextConfig, options = {}) {
134
- const isDev = process.env.NODE_ENV === "development";
135
- const isStaticBuild = process.env.NEXT_PUBLIC_STATIC_BUILD === "true";
136
- const shouldDisable = options.disable !== void 0 ? options.disable : isDev || isStaticBuild;
137
- const defaultOptions = {
138
- swSrc: "app/sw.ts",
139
- swDest: "public/sw.js",
140
- disable: shouldDisable,
141
- cacheOnNavigation: true,
142
- reloadOnOnline: true,
143
- ...options
144
- };
145
- try {
146
- if (!process.env.SERWIST_SUPPRESS_TURBOPACK_WARNING) {
147
- process.env.SERWIST_SUPPRESS_TURBOPACK_WARNING = "1";
148
- }
149
- const withSerwistInit = requirePeer("@serwist/next").default;
150
- const withSerwist = withSerwistInit({
151
- swSrc: defaultOptions.swSrc,
152
- swDest: defaultOptions.swDest,
153
- disable: defaultOptions.disable,
154
- cacheOnNavigation: defaultOptions.cacheOnNavigation,
155
- reloadOnOnline: defaultOptions.reloadOnOnline,
156
- ...defaultOptions.serwistOptions
157
- });
158
- return withSerwist(nextConfig);
159
- } catch (error) {
160
- consola.error("Failed to configure Serwist:", error);
161
- return nextConfig;
162
- }
130
+ function withPWA(nextConfig, _options = {}) {
131
+ return nextConfig;
163
132
  }
164
133
  function getServiceWorkerTemplate() {
165
134
  return `/**
166
- * Service Worker (Serwist)
167
- *
168
- * Modern PWA service worker using Serwist
135
+ * Service Worker (vanilla) \u2014 standard Cache + Fetch APIs, no library.
169
136
  */
170
137
 
171
- import { defaultCache } from '@serwist/next/worker';
172
- import { Serwist } from 'serwist';
173
-
138
+ /// <reference lib="webworker" />
174
139
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
175
140
  declare const self: any;
176
141
 
177
- const serwist = new Serwist({
178
- // Precache entries injected by Serwist build plugin
179
- precacheEntries: self.__SW_MANIFEST,
180
-
181
- // Skip waiting - activate new SW immediately
182
- skipWaiting: true,
183
-
184
- // Take control of all clients immediately
185
- clientsClaim: true,
142
+ const CACHE = 'app-cache-v1';
143
+ const OFFLINE_URL = '/_offline';
186
144
 
187
- // Enable navigation preload for faster loads
188
- navigationPreload: true,
189
-
190
- // Use default Next.js runtime caching strategies
191
- runtimeCaching: defaultCache,
145
+ // Precache the offline fallback on install.
146
+ self.addEventListener('install', (event: any) => {
147
+ event.waitUntil(
148
+ caches.open(CACHE).then((cache: Cache) => cache.addAll([OFFLINE_URL]))
149
+ );
150
+ self.skipWaiting();
151
+ });
192
152
 
193
- // Fallback pages for offline
194
- fallbacks: {
195
- entries: [
196
- {
197
- url: '/_offline',
198
- matcher({ request }) {
199
- return request.destination === 'document';
200
- },
201
- },
202
- ],
203
- },
153
+ // Drop stale caches on activate and take control immediately.
154
+ self.addEventListener('activate', (event: any) => {
155
+ event.waitUntil(
156
+ caches.keys().then((keys: string[]) =>
157
+ Promise.all(keys.filter((k) => k !== CACHE).map((k) => caches.delete(k)))
158
+ )
159
+ );
160
+ self.clients.claim();
204
161
  });
205
162
 
206
- serwist.addEventListeners();
163
+ // Network-first for navigations, with an offline fallback.
164
+ self.addEventListener('fetch', (event: any) => {
165
+ const { request } = event;
166
+ if (request.mode !== 'navigate') return;
167
+ event.respondWith(
168
+ fetch(request).catch(() =>
169
+ caches.match(OFFLINE_URL).then((r: Response | undefined) => r ?? Response.error())
170
+ )
171
+ );
172
+ });
207
173
  `;
208
174
  }
209
- var defaultRuntimeCaching = [];
210
- function createApiCacheRule() {
211
- consola.warn("createApiCacheRule is deprecated with Serwist. Use defaultCache from @serwist/next/worker");
212
- return {};
213
- }
214
- function createStaticAssetRule() {
215
- consola.warn("createStaticAssetRule is deprecated with Serwist. Use defaultCache from @serwist/next/worker");
216
- return {};
217
- }
218
- function createCdnCacheRule() {
219
- consola.warn("createCdnCacheRule is deprecated with Serwist. Use defaultCache from @serwist/next/worker");
220
- return {};
221
- }
222
175
  export {
223
- createApiCacheRule,
224
- createCdnCacheRule,
225
176
  createManifest,
226
177
  createManifestMetadata,
227
178
  createScreenshot,
228
179
  createScreenshots,
229
- createStaticAssetRule,
230
180
  createViewport,
231
- defaultRuntimeCaching,
232
181
  generateManifest,
233
182
  getServiceWorkerTemplate,
234
183
  withPWA
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/pwa/manifest.ts","../../src/pwa/plugin.ts"],"sourcesContent":["/**\n * PWA Manifest Metadata Utilities\n *\n * Helper functions for creating Next.js metadata for PWA manifest\n */\n\nimport type { Metadata, MetadataRoute, Viewport } from 'next';\n\nexport interface ManifestConfig {\n name: string;\n shortName?: string;\n description?: string;\n themeColor?: string;\n backgroundColor?: string;\n display?: 'standalone' | 'fullscreen' | 'minimal-ui' | 'browser';\n orientation?: 'portrait' | 'landscape' | 'any';\n startUrl?: string;\n scope?: string;\n lang?: string;\n dir?: 'ltr' | 'rtl' | 'auto';\n icons?: {\n src: string;\n sizes: string;\n type?: string;\n purpose?: string;\n }[];\n}\n\n/**\n * Icon paths configuration\n */\nexport interface IconPaths {\n logo192?: string;\n logo384?: string;\n logo512?: string;\n}\n\n/**\n * Protocol handler configuration\n * Allows your PWA to register as a handler for custom protocols\n *\n * @example\n * ```typescript\n * {\n * protocol: \"web+music\",\n * url: \"/play?track=%s\"\n * }\n * ```\n */\nexport interface ProtocolHandler {\n /** Protocol scheme (e.g., \"web+music\", \"mailto\", \"magnet\") */\n protocol: string;\n /** URL template with %s placeholder for the protocol parameter */\n url: string;\n}\n\n/**\n * Create viewport configuration for Next.js app\n *\n * @example\n * ```typescript\n * export const viewport: Viewport = createViewport({\n * themeColor: '#ffffff',\n * });\n * ```\n */\nexport function createViewport(config: { themeColor?: string }): Viewport {\n return {\n width: 'device-width',\n initialScale: 1,\n maximumScale: 1,\n themeColor: config.themeColor || '#000000',\n };\n}\n\n/**\n * Create manifest metadata for Next.js app\n *\n * Note: themeColor and viewport should be exported separately using createViewport()\n *\n * @example\n * ```typescript\n * export const metadata: Metadata = {\n * ...createManifestMetadata({\n * name: 'My App',\n * shortName: 'App',\n * description: 'My awesome app',\n * }),\n * };\n *\n * export const viewport: Viewport = createViewport({\n * themeColor: '#ffffff',\n * });\n * ```\n */\nexport function createManifestMetadata(config: ManifestConfig): Metadata {\n return {\n manifest: '/manifest.json',\n appleWebApp: {\n capable: true,\n statusBarStyle: 'default',\n title: config.shortName || config.name,\n },\n applicationName: config.name,\n formatDetection: {\n telephone: false,\n },\n };\n}\n\n/**\n * Create Next.js manifest function\n *\n * Use this in your app/manifest.ts file\n *\n * @example\n * ```typescript\n * // app/manifest.ts\n * import { createManifest } from '@djangocfg/nextjs/config';\n * import { settings } from '@core/settings';\n *\n * export default createManifest({\n * name: settings.app.name,\n * description: settings.app.description,\n * icons: {\n * logo192: settings.app.icons.logo192,\n * logo384: settings.app.icons.logo384,\n * logo512: settings.app.icons.logo512,\n * },\n * });\n * ```\n */\nexport interface ScreenshotConfig {\n src: string;\n sizes: string;\n type?: string;\n form_factor?: 'narrow' | 'wide';\n label?: string;\n}\n\n/**\n * Smart screenshot configuration\n * Automatically detects everything from path or uses defaults\n */\nexport interface SmartScreenshotInput {\n src: string;\n /** Form factor (auto-detected from filename if contains 'desktop'/'mobile', or use default) */\n form_factor?: 'narrow' | 'wide';\n /** Optional label (auto-generated from form_factor) */\n label?: string;\n /** Optional width (defaults based on form_factor) */\n width?: number;\n /** Optional height (defaults based on form_factor) */\n height?: number;\n}\n\n/**\n * Create screenshot config with smart defaults\n * Automatically detects type, sizes, form_factor from path or uses sensible defaults\n *\n * @example\n * ```typescript\n * // Minimal - everything auto-detected\n * createScreenshot({ src: '/screenshots/desktop-view.png' })\n * // → form_factor: 'wide', sizes: '1920x1080', type: 'image/png', label: 'Desktop screenshot'\n *\n * createScreenshot({ src: '/screenshots/mobile.png' })\n * // → form_factor: 'narrow', sizes: '390x844', type: 'image/png', label: 'Mobile screenshot'\n *\n * // With custom dimensions\n * createScreenshot({ src: '/screenshots/tablet.png', width: 1024, height: 768 })\n * ```\n */\nexport function createScreenshot(input: SmartScreenshotInput | string): ScreenshotConfig {\n // Allow string shorthand\n const config = typeof input === 'string' ? { src: input } : input;\n let { src, width, height, label, form_factor } = config;\n\n // Auto-detect image type from extension\n const ext = src.split('.').pop()?.toLowerCase();\n const typeMap: Record<string, string> = {\n png: 'image/png',\n jpg: 'image/jpeg',\n jpeg: 'image/jpeg',\n webp: 'image/webp',\n svg: 'image/svg+xml',\n };\n const type = ext ? typeMap[ext] || 'image/png' : 'image/png';\n\n // Try to parse dimensions from filename (e.g., \"1920x1080.png\" or \"screenshot-390x844.png\")\n const filename = src.toLowerCase();\n const dimensionMatch = filename.match(/(\\d{3,4})x(\\d{3,4})/);\n if (dimensionMatch && !width && !height) {\n width = parseInt(dimensionMatch[1], 10);\n height = parseInt(dimensionMatch[2], 10);\n }\n\n // Auto-detect form_factor from filename if not provided\n let autoFormFactor: 'narrow' | 'wide' = 'wide'; // Default to wide\n if (filename.includes('mobile') || filename.includes('phone') || filename.includes('narrow')) {\n autoFormFactor = 'narrow';\n } else if (filename.includes('desktop') || filename.includes('laptop') || filename.includes('wide')) {\n autoFormFactor = 'wide';\n } else if (width && height) {\n // Calculate from dimensions if provided or parsed\n const aspectRatio = width / height;\n autoFormFactor = aspectRatio > 1.2 ? 'wide' : 'narrow';\n }\n\n const finalFormFactor = form_factor || autoFormFactor;\n\n // Default dimensions based on form_factor (only if not parsed from filename)\n const defaultDimensions = finalFormFactor === 'wide'\n ? { width: 1920, height: 1080 } // Desktop default\n : { width: 390, height: 844 }; // Mobile default (iPhone 14)\n\n const finalWidth = width || defaultDimensions.width;\n const finalHeight = height || defaultDimensions.height;\n\n // Auto-generate label\n const autoLabel = finalFormFactor === 'wide'\n ? 'Desktop screenshot'\n : 'Mobile screenshot';\n\n return {\n src,\n sizes: `${finalWidth}x${finalHeight}`,\n type,\n form_factor: finalFormFactor,\n label: label || autoLabel,\n };\n}\n\n/**\n * Create multiple screenshots from array\n * Supports string shorthand or full config objects\n *\n * @example\n * ```typescript\n * // Minimal - just paths\n * createScreenshots([\n * '/screenshots/desktop.png', // Auto: wide, 1920x1080\n * '/screenshots/mobile.png', // Auto: narrow, 390x844\n * ])\n *\n * // Mixed\n * createScreenshots([\n * '/screenshots/desktop.png',\n * { src: '/screenshots/tablet.png', width: 1024, height: 768 },\n * ])\n * ```\n */\nexport function createScreenshots(inputs: Array<SmartScreenshotInput | string>): ScreenshotConfig[] {\n return inputs.map(createScreenshot);\n}\n\nexport function createManifest(config: {\n name: string;\n shortName?: string;\n description?: string;\n themeColor?: string;\n backgroundColor?: string;\n display?: 'standalone' | 'fullscreen' | 'minimal-ui' | 'browser';\n orientation?: 'portrait' | 'landscape' | 'any';\n id?: string;\n startUrl?: string;\n scope?: string;\n lang?: string;\n dir?: 'ltr' | 'rtl' | 'auto';\n icons?: IconPaths | ManifestConfig['icons'];\n screenshots?: ScreenshotConfig[];\n protocol_handlers?: ProtocolHandler[];\n}): () => MetadataRoute.Manifest {\n return () => {\n // Convert IconPaths to manifest icons format\n let manifestIcons: MetadataRoute.Manifest['icons'];\n\n if (Array.isArray(config.icons)) {\n // Already in manifest format\n manifestIcons = config.icons as MetadataRoute.Manifest['icons'];\n } else if (config.icons) {\n // Convert IconPaths to manifest icons\n const { logo192, logo384, logo512 } = config.icons as IconPaths;\n manifestIcons = [\n ...(logo192\n ? [\n {\n src: logo192,\n sizes: '192x192',\n type: 'image/png',\n purpose: 'maskable' as const,\n },\n ]\n : []),\n ...(logo384\n ? [\n {\n src: logo384,\n sizes: '384x384',\n type: 'image/png',\n },\n ]\n : []),\n ...(logo512\n ? [\n {\n src: logo512,\n sizes: '512x512',\n type: 'image/png',\n },\n ]\n : []),\n ];\n }\n\n const manifest: MetadataRoute.Manifest = {\n name: config.name,\n short_name: config.shortName || config.name,\n description: config.description || config.name,\n id: config.id || config.startUrl || '/',\n start_url: config.startUrl || '/',\n scope: config.scope || '/',\n display: config.display || 'standalone',\n orientation: config.orientation || 'portrait',\n background_color: config.backgroundColor || '#000000',\n theme_color: config.themeColor || '#ffffff',\n lang: config.lang || 'en',\n dir: config.dir || 'ltr',\n icons: manifestIcons,\n // Removed forced gcm_sender_id to avoid potential conflicts with VAPID\n // gcm_sender_id: '103953800507',\n };\n\n // Add screenshots if provided (for Richer PWA Install UI)\n if (config.screenshots && config.screenshots.length > 0) {\n (manifest as any).screenshots = config.screenshots;\n }\n\n // Add protocol handlers if provided\n if (config.protocol_handlers && config.protocol_handlers.length > 0) {\n (manifest as any).protocol_handlers = config.protocol_handlers;\n }\n\n return manifest;\n };\n}\n\n/**\n * Generate manifest.json content (legacy)\n *\n * @deprecated Use createManifest() instead\n */\nexport function generateManifest(config: ManifestConfig): Record<string, any> {\n return createManifest(config)();\n}\n","/**\n * PWA (Progressive Web App) Plugin\n *\n * Configures Serwist for service worker and offline support\n * Modern PWA solution for Next.js 15+ with App Router\n *\n * @see https://serwist.pages.dev/\n */\n\nimport type { NextConfig } from 'next';\nimport { consola } from 'consola';\nimport { createRequire } from 'module';\n\n// Serwist is an optional peer 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 PWAPluginOptions {\n /**\n * Destination directory for service worker files\n * @default 'public'\n * @deprecated Use swDest instead\n */\n dest?: string;\n\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 compiled service worker\n * @default 'public/sw.js'\n */\n swDest?: string;\n\n /**\n * Disable PWA completely\n * @default false in production, true in development\n * @example disable: process.env.NODE_ENV === 'development'\n */\n disable?: boolean;\n\n /**\n * Cache on navigation - cache pages when navigating\n * @default true\n */\n cacheOnNavigation?: boolean;\n\n /**\n * Reload app when device goes back online\n * @default true\n */\n reloadOnOnline?: boolean;\n\n /**\n * Additional Serwist options\n * @see https://serwist.pages.dev/docs/next/configuring\n */\n serwistOptions?: Record<string, any>;\n}\n\n/**\n * Add PWA configuration to Next.js config using Serwist\n *\n * @example Basic usage\n * ```ts\n * import { createBaseNextConfig, withPWA } from '@djangocfg/nextjs/config';\n *\n * const nextConfig = createBaseNextConfig({...});\n *\n * export default withPWA(nextConfig, {\n * swSrc: 'app/sw.ts',\n * disable: process.env.NODE_ENV === 'development',\n * });\n * ```\n *\n * @example Integrated with createBaseNextConfig\n * ```ts\n * import { createBaseNextConfig } from '@djangocfg/nextjs/config';\n *\n * const config = createBaseNextConfig({\n * pwa: {\n * swSrc: 'app/sw.ts',\n * disable: false,\n * },\n * });\n *\n * export default config;\n * ```\n */\nexport function withPWA(\n nextConfig: NextConfig,\n options: PWAPluginOptions = {}\n): NextConfig {\n const isDev = process.env.NODE_ENV === 'development';\n const isStaticBuild = process.env.NEXT_PUBLIC_STATIC_BUILD === 'true';\n\n // Determine if PWA should be disabled:\n // - Explicitly disabled via options\n // - In development mode (default)\n // - Static build (output: 'export' doesn't support service workers)\n const shouldDisable = options.disable !== undefined\n ? options.disable\n : (isDev || isStaticBuild);\n\n const defaultOptions: PWAPluginOptions = {\n swSrc: 'app/sw.ts',\n swDest: 'public/sw.js',\n disable: shouldDisable,\n cacheOnNavigation: true,\n reloadOnOnline: true,\n ...options,\n };\n\n try {\n // Suppress Turbopack warning - it only applies to dev mode, not production builds\n // The warning is misleading when running `next build` with Turbopack\n // See: https://github.com/serwist/serwist/issues/54\n if (!process.env.SERWIST_SUPPRESS_TURBOPACK_WARNING) {\n process.env.SERWIST_SUPPRESS_TURBOPACK_WARNING = '1';\n }\n\n const withSerwistInit = requirePeer('@serwist/next').default;\n\n const withSerwist = withSerwistInit({\n swSrc: defaultOptions.swSrc,\n swDest: defaultOptions.swDest,\n disable: defaultOptions.disable,\n cacheOnNavigation: defaultOptions.cacheOnNavigation,\n reloadOnOnline: defaultOptions.reloadOnOnline,\n ...defaultOptions.serwistOptions,\n });\n\n return withSerwist(nextConfig);\n } catch (error) {\n consola.error('Failed to configure Serwist:', error);\n return nextConfig;\n }\n}\n\n/**\n * Get service worker template content\n *\n * Returns ready-to-use service worker code for app/sw.ts\n *\n * @example\n * ```ts\n * import { getServiceWorkerTemplate } from '@djangocfg/nextjs/config';\n *\n * // Copy this to your app/sw.ts file\n * console.log(getServiceWorkerTemplate());\n * ```\n */\nexport function getServiceWorkerTemplate(): string {\n return `/**\n * Service Worker (Serwist)\n *\n * Modern PWA service worker using Serwist\n */\n\nimport { defaultCache } from '@serwist/next/worker';\nimport { Serwist } from 'serwist';\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ndeclare const self: any;\n\nconst serwist = new Serwist({\n // Precache entries injected by Serwist build plugin\n precacheEntries: self.__SW_MANIFEST,\n\n // Skip waiting - activate new SW immediately\n skipWaiting: true,\n\n // Take control of all clients immediately\n clientsClaim: true,\n\n // Enable navigation preload for faster loads\n navigationPreload: true,\n\n // Use default Next.js runtime caching strategies\n runtimeCaching: defaultCache,\n\n // Fallback pages for offline\n fallbacks: {\n entries: [\n {\n url: '/_offline',\n matcher({ request }) {\n return request.destination === 'document';\n },\n },\n ],\n },\n});\n\nserwist.addEventListeners();\n`;\n}\n\n// Backward compatibility exports (deprecated)\nexport const defaultRuntimeCaching = [];\nexport function createApiCacheRule() {\n consola.warn('createApiCacheRule is deprecated with Serwist. Use defaultCache from @serwist/next/worker');\n return {};\n}\nexport function createStaticAssetRule() {\n consola.warn('createStaticAssetRule is deprecated with Serwist. Use defaultCache from @serwist/next/worker');\n return {};\n}\nexport function createCdnCacheRule() {\n consola.warn('createCdnCacheRule is deprecated with Serwist. Use defaultCache from @serwist/next/worker');\n return {};\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/pwa/manifest.ts","../../src/pwa/plugin.ts"],"sourcesContent":["/**\n * PWA Manifest Metadata Utilities\n *\n * Helper functions for creating Next.js metadata for PWA manifest\n */\n\nimport type { Metadata, MetadataRoute, Viewport } from 'next';\n\nexport interface ManifestConfig {\n name: string;\n shortName?: string;\n description?: string;\n themeColor?: string;\n backgroundColor?: string;\n display?: 'standalone' | 'fullscreen' | 'minimal-ui' | 'browser';\n orientation?: 'portrait' | 'landscape' | 'any';\n startUrl?: string;\n scope?: string;\n lang?: string;\n dir?: 'ltr' | 'rtl' | 'auto';\n icons?: {\n src: string;\n sizes: string;\n type?: string;\n purpose?: string;\n }[];\n}\n\n/**\n * Icon paths configuration\n */\nexport interface IconPaths {\n logo192?: string;\n logo384?: string;\n logo512?: string;\n}\n\n/**\n * Protocol handler configuration\n * Allows your PWA to register as a handler for custom protocols\n *\n * @example\n * ```typescript\n * {\n * protocol: \"web+music\",\n * url: \"/play?track=%s\"\n * }\n * ```\n */\nexport interface ProtocolHandler {\n /** Protocol scheme (e.g., \"web+music\", \"mailto\", \"magnet\") */\n protocol: string;\n /** URL template with %s placeholder for the protocol parameter */\n url: string;\n}\n\n/**\n * Create viewport configuration for Next.js app\n *\n * @example\n * ```typescript\n * export const viewport: Viewport = createViewport({\n * themeColor: '#ffffff',\n * });\n * ```\n */\nexport function createViewport(config: { themeColor?: string }): Viewport {\n return {\n width: 'device-width',\n initialScale: 1,\n maximumScale: 1,\n themeColor: config.themeColor || '#000000',\n };\n}\n\n/**\n * Create manifest metadata for Next.js app\n *\n * Note: themeColor and viewport should be exported separately using createViewport()\n *\n * @example\n * ```typescript\n * export const metadata: Metadata = {\n * ...createManifestMetadata({\n * name: 'My App',\n * shortName: 'App',\n * description: 'My awesome app',\n * }),\n * };\n *\n * export const viewport: Viewport = createViewport({\n * themeColor: '#ffffff',\n * });\n * ```\n */\nexport function createManifestMetadata(config: ManifestConfig): Metadata {\n return {\n manifest: '/manifest.json',\n appleWebApp: {\n capable: true,\n statusBarStyle: 'default',\n title: config.shortName || config.name,\n },\n applicationName: config.name,\n formatDetection: {\n telephone: false,\n },\n };\n}\n\n/**\n * Create Next.js manifest function\n *\n * Use this in your app/manifest.ts file\n *\n * @example\n * ```typescript\n * // app/manifest.ts\n * import { createManifest } from '@djangocfg/nextjs/config';\n * import { settings } from '@core/settings';\n *\n * export default createManifest({\n * name: settings.app.name,\n * description: settings.app.description,\n * icons: {\n * logo192: settings.app.icons.logo192,\n * logo384: settings.app.icons.logo384,\n * logo512: settings.app.icons.logo512,\n * },\n * });\n * ```\n */\nexport interface ScreenshotConfig {\n src: string;\n sizes: string;\n type?: string;\n form_factor?: 'narrow' | 'wide';\n label?: string;\n}\n\n/**\n * Smart screenshot configuration\n * Automatically detects everything from path or uses defaults\n */\nexport interface SmartScreenshotInput {\n src: string;\n /** Form factor (auto-detected from filename if contains 'desktop'/'mobile', or use default) */\n form_factor?: 'narrow' | 'wide';\n /** Optional label (auto-generated from form_factor) */\n label?: string;\n /** Optional width (defaults based on form_factor) */\n width?: number;\n /** Optional height (defaults based on form_factor) */\n height?: number;\n}\n\n/**\n * Create screenshot config with smart defaults\n * Automatically detects type, sizes, form_factor from path or uses sensible defaults\n *\n * @example\n * ```typescript\n * // Minimal - everything auto-detected\n * createScreenshot({ src: '/screenshots/desktop-view.png' })\n * // → form_factor: 'wide', sizes: '1920x1080', type: 'image/png', label: 'Desktop screenshot'\n *\n * createScreenshot({ src: '/screenshots/mobile.png' })\n * // → form_factor: 'narrow', sizes: '390x844', type: 'image/png', label: 'Mobile screenshot'\n *\n * // With custom dimensions\n * createScreenshot({ src: '/screenshots/tablet.png', width: 1024, height: 768 })\n * ```\n */\nexport function createScreenshot(input: SmartScreenshotInput | string): ScreenshotConfig {\n // Allow string shorthand\n const config = typeof input === 'string' ? { src: input } : input;\n let { src, width, height, label, form_factor } = config;\n\n // Auto-detect image type from extension\n const ext = src.split('.').pop()?.toLowerCase();\n const typeMap: Record<string, string> = {\n png: 'image/png',\n jpg: 'image/jpeg',\n jpeg: 'image/jpeg',\n webp: 'image/webp',\n svg: 'image/svg+xml',\n };\n const type = ext ? typeMap[ext] || 'image/png' : 'image/png';\n\n // Try to parse dimensions from filename (e.g., \"1920x1080.png\" or \"screenshot-390x844.png\")\n const filename = src.toLowerCase();\n const dimensionMatch = filename.match(/(\\d{3,4})x(\\d{3,4})/);\n if (dimensionMatch && !width && !height) {\n width = parseInt(dimensionMatch[1], 10);\n height = parseInt(dimensionMatch[2], 10);\n }\n\n // Auto-detect form_factor from filename if not provided\n let autoFormFactor: 'narrow' | 'wide' = 'wide'; // Default to wide\n if (filename.includes('mobile') || filename.includes('phone') || filename.includes('narrow')) {\n autoFormFactor = 'narrow';\n } else if (filename.includes('desktop') || filename.includes('laptop') || filename.includes('wide')) {\n autoFormFactor = 'wide';\n } else if (width && height) {\n // Calculate from dimensions if provided or parsed\n const aspectRatio = width / height;\n autoFormFactor = aspectRatio > 1.2 ? 'wide' : 'narrow';\n }\n\n const finalFormFactor = form_factor || autoFormFactor;\n\n // Default dimensions based on form_factor (only if not parsed from filename)\n const defaultDimensions = finalFormFactor === 'wide'\n ? { width: 1920, height: 1080 } // Desktop default\n : { width: 390, height: 844 }; // Mobile default (iPhone 14)\n\n const finalWidth = width || defaultDimensions.width;\n const finalHeight = height || defaultDimensions.height;\n\n // Auto-generate label\n const autoLabel = finalFormFactor === 'wide'\n ? 'Desktop screenshot'\n : 'Mobile screenshot';\n\n return {\n src,\n sizes: `${finalWidth}x${finalHeight}`,\n type,\n form_factor: finalFormFactor,\n label: label || autoLabel,\n };\n}\n\n/**\n * Create multiple screenshots from array\n * Supports string shorthand or full config objects\n *\n * @example\n * ```typescript\n * // Minimal - just paths\n * createScreenshots([\n * '/screenshots/desktop.png', // Auto: wide, 1920x1080\n * '/screenshots/mobile.png', // Auto: narrow, 390x844\n * ])\n *\n * // Mixed\n * createScreenshots([\n * '/screenshots/desktop.png',\n * { src: '/screenshots/tablet.png', width: 1024, height: 768 },\n * ])\n * ```\n */\nexport function createScreenshots(inputs: Array<SmartScreenshotInput | string>): ScreenshotConfig[] {\n return inputs.map(createScreenshot);\n}\n\nexport function createManifest(config: {\n name: string;\n shortName?: string;\n description?: string;\n themeColor?: string;\n backgroundColor?: string;\n display?: 'standalone' | 'fullscreen' | 'minimal-ui' | 'browser';\n orientation?: 'portrait' | 'landscape' | 'any';\n id?: string;\n startUrl?: string;\n scope?: string;\n lang?: string;\n dir?: 'ltr' | 'rtl' | 'auto';\n icons?: IconPaths | ManifestConfig['icons'];\n screenshots?: ScreenshotConfig[];\n protocol_handlers?: ProtocolHandler[];\n}): () => MetadataRoute.Manifest {\n return () => {\n // Convert IconPaths to manifest icons format\n let manifestIcons: MetadataRoute.Manifest['icons'];\n\n if (Array.isArray(config.icons)) {\n // Already in manifest format\n manifestIcons = config.icons as MetadataRoute.Manifest['icons'];\n } else if (config.icons) {\n // Convert IconPaths to manifest icons\n const { logo192, logo384, logo512 } = config.icons as IconPaths;\n manifestIcons = [\n ...(logo192\n ? [\n {\n src: logo192,\n sizes: '192x192',\n type: 'image/png',\n purpose: 'maskable' as const,\n },\n ]\n : []),\n ...(logo384\n ? [\n {\n src: logo384,\n sizes: '384x384',\n type: 'image/png',\n },\n ]\n : []),\n ...(logo512\n ? [\n {\n src: logo512,\n sizes: '512x512',\n type: 'image/png',\n },\n ]\n : []),\n ];\n }\n\n const manifest: MetadataRoute.Manifest = {\n name: config.name,\n short_name: config.shortName || config.name,\n description: config.description || config.name,\n id: config.id || config.startUrl || '/',\n start_url: config.startUrl || '/',\n scope: config.scope || '/',\n display: config.display || 'standalone',\n orientation: config.orientation || 'portrait',\n background_color: config.backgroundColor || '#000000',\n theme_color: config.themeColor || '#ffffff',\n lang: config.lang || 'en',\n dir: config.dir || 'ltr',\n icons: manifestIcons,\n // Removed forced gcm_sender_id to avoid potential conflicts with VAPID\n // gcm_sender_id: '103953800507',\n };\n\n // Add screenshots if provided (for Richer PWA Install UI)\n if (config.screenshots && config.screenshots.length > 0) {\n (manifest as any).screenshots = config.screenshots;\n }\n\n // Add protocol handlers if provided\n if (config.protocol_handlers && config.protocol_handlers.length > 0) {\n (manifest as any).protocol_handlers = config.protocol_handlers;\n }\n\n return manifest;\n };\n}\n\n/**\n * Generate manifest.json content (legacy)\n *\n * @deprecated Use createManifest() instead\n */\nexport function generateManifest(config: ManifestConfig): Record<string, any> {\n return createManifest(config)();\n}\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"],"mappings":"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","names":[]}