@djangocfg/nextjs 2.1.514 → 2.1.516
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/config/index.mjs +45 -238
- package/dist/config/index.mjs.map +1 -1
- package/dist/i18n/routing.d.mts +2 -2
- package/dist/index.mjs +45 -238
- package/dist/index.mjs.map +1 -1
- package/dist/pwa/index.d.mts +2 -194
- package/dist/pwa/index.mjs +3 -8
- package/dist/pwa/index.mjs.map +1 -1
- package/dist/pwa/manifest.d.mts +194 -0
- package/dist/pwa/manifest.mjs +136 -0
- package/dist/pwa/manifest.mjs.map +1 -0
- package/package.json +14 -9
- package/src/config/packages/installer.ts +5 -5
- package/src/config/packages/updater.ts +13 -12
- package/src/config/plugins/compression.ts +8 -3
- package/src/config/plugins/devStartup.ts +6 -7
- package/src/config/utils/version.ts +9 -3
- package/src/pwa/plugin.ts +6 -1
package/dist/pwa/index.d.mts
CHANGED
|
@@ -1,195 +1,3 @@
|
|
|
1
|
-
|
|
1
|
+
export { IconPaths, ManifestConfig, ProtocolHandler, ScreenshotConfig, SmartScreenshotInput, createManifest, createManifestMetadata, createScreenshot, createScreenshots, createViewport, generateManifest } from './manifest.mjs';
|
|
2
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 };
|
|
3
|
+
import 'next';
|
package/dist/pwa/index.mjs
CHANGED
|
@@ -1,10 +1,3 @@
|
|
|
1
|
-
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
2
|
-
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
3
|
-
}) : x)(function(x) {
|
|
4
|
-
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
5
|
-
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
6
|
-
});
|
|
7
|
-
|
|
8
1
|
// src/pwa/manifest.ts
|
|
9
2
|
function createViewport(config) {
|
|
10
3
|
return {
|
|
@@ -135,6 +128,8 @@ function generateManifest(config) {
|
|
|
135
128
|
|
|
136
129
|
// src/pwa/plugin.ts
|
|
137
130
|
import { consola } from "consola";
|
|
131
|
+
import { createRequire } from "module";
|
|
132
|
+
var requirePeer = createRequire(import.meta.url);
|
|
138
133
|
function withPWA(nextConfig, options = {}) {
|
|
139
134
|
const isDev = process.env.NODE_ENV === "development";
|
|
140
135
|
const isStaticBuild = process.env.NEXT_PUBLIC_STATIC_BUILD === "true";
|
|
@@ -151,7 +146,7 @@ function withPWA(nextConfig, options = {}) {
|
|
|
151
146
|
if (!process.env.SERWIST_SUPPRESS_TURBOPACK_WARNING) {
|
|
152
147
|
process.env.SERWIST_SUPPRESS_TURBOPACK_WARNING = "1";
|
|
153
148
|
}
|
|
154
|
-
const withSerwistInit =
|
|
149
|
+
const withSerwistInit = requirePeer("@serwist/next").default;
|
|
155
150
|
const withSerwist = withSerwistInit({
|
|
156
151
|
swSrc: defaultOptions.swSrc,
|
|
157
152
|
swDest: defaultOptions.swDest,
|
package/dist/pwa/index.mjs.map
CHANGED
|
@@ -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';\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 = require('@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\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":[]}
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import { MetadataRoute, Metadata, Viewport } from 'next';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* PWA Manifest Metadata Utilities
|
|
5
|
+
*
|
|
6
|
+
* Helper functions for creating Next.js metadata for PWA manifest
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
interface ManifestConfig {
|
|
10
|
+
name: string;
|
|
11
|
+
shortName?: string;
|
|
12
|
+
description?: string;
|
|
13
|
+
themeColor?: string;
|
|
14
|
+
backgroundColor?: string;
|
|
15
|
+
display?: 'standalone' | 'fullscreen' | 'minimal-ui' | 'browser';
|
|
16
|
+
orientation?: 'portrait' | 'landscape' | 'any';
|
|
17
|
+
startUrl?: string;
|
|
18
|
+
scope?: string;
|
|
19
|
+
lang?: string;
|
|
20
|
+
dir?: 'ltr' | 'rtl' | 'auto';
|
|
21
|
+
icons?: {
|
|
22
|
+
src: string;
|
|
23
|
+
sizes: string;
|
|
24
|
+
type?: string;
|
|
25
|
+
purpose?: string;
|
|
26
|
+
}[];
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Icon paths configuration
|
|
30
|
+
*/
|
|
31
|
+
interface IconPaths {
|
|
32
|
+
logo192?: string;
|
|
33
|
+
logo384?: string;
|
|
34
|
+
logo512?: string;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Protocol handler configuration
|
|
38
|
+
* Allows your PWA to register as a handler for custom protocols
|
|
39
|
+
*
|
|
40
|
+
* @example
|
|
41
|
+
* ```typescript
|
|
42
|
+
* {
|
|
43
|
+
* protocol: "web+music",
|
|
44
|
+
* url: "/play?track=%s"
|
|
45
|
+
* }
|
|
46
|
+
* ```
|
|
47
|
+
*/
|
|
48
|
+
interface ProtocolHandler {
|
|
49
|
+
/** Protocol scheme (e.g., "web+music", "mailto", "magnet") */
|
|
50
|
+
protocol: string;
|
|
51
|
+
/** URL template with %s placeholder for the protocol parameter */
|
|
52
|
+
url: string;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Create viewport configuration for Next.js app
|
|
56
|
+
*
|
|
57
|
+
* @example
|
|
58
|
+
* ```typescript
|
|
59
|
+
* export const viewport: Viewport = createViewport({
|
|
60
|
+
* themeColor: '#ffffff',
|
|
61
|
+
* });
|
|
62
|
+
* ```
|
|
63
|
+
*/
|
|
64
|
+
declare function createViewport(config: {
|
|
65
|
+
themeColor?: string;
|
|
66
|
+
}): Viewport;
|
|
67
|
+
/**
|
|
68
|
+
* Create manifest metadata for Next.js app
|
|
69
|
+
*
|
|
70
|
+
* Note: themeColor and viewport should be exported separately using createViewport()
|
|
71
|
+
*
|
|
72
|
+
* @example
|
|
73
|
+
* ```typescript
|
|
74
|
+
* export const metadata: Metadata = {
|
|
75
|
+
* ...createManifestMetadata({
|
|
76
|
+
* name: 'My App',
|
|
77
|
+
* shortName: 'App',
|
|
78
|
+
* description: 'My awesome app',
|
|
79
|
+
* }),
|
|
80
|
+
* };
|
|
81
|
+
*
|
|
82
|
+
* export const viewport: Viewport = createViewport({
|
|
83
|
+
* themeColor: '#ffffff',
|
|
84
|
+
* });
|
|
85
|
+
* ```
|
|
86
|
+
*/
|
|
87
|
+
declare function createManifestMetadata(config: ManifestConfig): Metadata;
|
|
88
|
+
/**
|
|
89
|
+
* Create Next.js manifest function
|
|
90
|
+
*
|
|
91
|
+
* Use this in your app/manifest.ts file
|
|
92
|
+
*
|
|
93
|
+
* @example
|
|
94
|
+
* ```typescript
|
|
95
|
+
* // app/manifest.ts
|
|
96
|
+
* import { createManifest } from '@djangocfg/nextjs/config';
|
|
97
|
+
* import { settings } from '@core/settings';
|
|
98
|
+
*
|
|
99
|
+
* export default createManifest({
|
|
100
|
+
* name: settings.app.name,
|
|
101
|
+
* description: settings.app.description,
|
|
102
|
+
* icons: {
|
|
103
|
+
* logo192: settings.app.icons.logo192,
|
|
104
|
+
* logo384: settings.app.icons.logo384,
|
|
105
|
+
* logo512: settings.app.icons.logo512,
|
|
106
|
+
* },
|
|
107
|
+
* });
|
|
108
|
+
* ```
|
|
109
|
+
*/
|
|
110
|
+
interface ScreenshotConfig {
|
|
111
|
+
src: string;
|
|
112
|
+
sizes: string;
|
|
113
|
+
type?: string;
|
|
114
|
+
form_factor?: 'narrow' | 'wide';
|
|
115
|
+
label?: string;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Smart screenshot configuration
|
|
119
|
+
* Automatically detects everything from path or uses defaults
|
|
120
|
+
*/
|
|
121
|
+
interface SmartScreenshotInput {
|
|
122
|
+
src: string;
|
|
123
|
+
/** Form factor (auto-detected from filename if contains 'desktop'/'mobile', or use default) */
|
|
124
|
+
form_factor?: 'narrow' | 'wide';
|
|
125
|
+
/** Optional label (auto-generated from form_factor) */
|
|
126
|
+
label?: string;
|
|
127
|
+
/** Optional width (defaults based on form_factor) */
|
|
128
|
+
width?: number;
|
|
129
|
+
/** Optional height (defaults based on form_factor) */
|
|
130
|
+
height?: number;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Create screenshot config with smart defaults
|
|
134
|
+
* Automatically detects type, sizes, form_factor from path or uses sensible defaults
|
|
135
|
+
*
|
|
136
|
+
* @example
|
|
137
|
+
* ```typescript
|
|
138
|
+
* // Minimal - everything auto-detected
|
|
139
|
+
* createScreenshot({ src: '/screenshots/desktop-view.png' })
|
|
140
|
+
* // → form_factor: 'wide', sizes: '1920x1080', type: 'image/png', label: 'Desktop screenshot'
|
|
141
|
+
*
|
|
142
|
+
* createScreenshot({ src: '/screenshots/mobile.png' })
|
|
143
|
+
* // → form_factor: 'narrow', sizes: '390x844', type: 'image/png', label: 'Mobile screenshot'
|
|
144
|
+
*
|
|
145
|
+
* // With custom dimensions
|
|
146
|
+
* createScreenshot({ src: '/screenshots/tablet.png', width: 1024, height: 768 })
|
|
147
|
+
* ```
|
|
148
|
+
*/
|
|
149
|
+
declare function createScreenshot(input: SmartScreenshotInput | string): ScreenshotConfig;
|
|
150
|
+
/**
|
|
151
|
+
* Create multiple screenshots from array
|
|
152
|
+
* Supports string shorthand or full config objects
|
|
153
|
+
*
|
|
154
|
+
* @example
|
|
155
|
+
* ```typescript
|
|
156
|
+
* // Minimal - just paths
|
|
157
|
+
* createScreenshots([
|
|
158
|
+
* '/screenshots/desktop.png', // Auto: wide, 1920x1080
|
|
159
|
+
* '/screenshots/mobile.png', // Auto: narrow, 390x844
|
|
160
|
+
* ])
|
|
161
|
+
*
|
|
162
|
+
* // Mixed
|
|
163
|
+
* createScreenshots([
|
|
164
|
+
* '/screenshots/desktop.png',
|
|
165
|
+
* { src: '/screenshots/tablet.png', width: 1024, height: 768 },
|
|
166
|
+
* ])
|
|
167
|
+
* ```
|
|
168
|
+
*/
|
|
169
|
+
declare function createScreenshots(inputs: Array<SmartScreenshotInput | string>): ScreenshotConfig[];
|
|
170
|
+
declare function createManifest(config: {
|
|
171
|
+
name: string;
|
|
172
|
+
shortName?: string;
|
|
173
|
+
description?: string;
|
|
174
|
+
themeColor?: string;
|
|
175
|
+
backgroundColor?: string;
|
|
176
|
+
display?: 'standalone' | 'fullscreen' | 'minimal-ui' | 'browser';
|
|
177
|
+
orientation?: 'portrait' | 'landscape' | 'any';
|
|
178
|
+
id?: string;
|
|
179
|
+
startUrl?: string;
|
|
180
|
+
scope?: string;
|
|
181
|
+
lang?: string;
|
|
182
|
+
dir?: 'ltr' | 'rtl' | 'auto';
|
|
183
|
+
icons?: IconPaths | ManifestConfig['icons'];
|
|
184
|
+
screenshots?: ScreenshotConfig[];
|
|
185
|
+
protocol_handlers?: ProtocolHandler[];
|
|
186
|
+
}): () => MetadataRoute.Manifest;
|
|
187
|
+
/**
|
|
188
|
+
* Generate manifest.json content (legacy)
|
|
189
|
+
*
|
|
190
|
+
* @deprecated Use createManifest() instead
|
|
191
|
+
*/
|
|
192
|
+
declare function generateManifest(config: ManifestConfig): Record<string, any>;
|
|
193
|
+
|
|
194
|
+
export { type IconPaths, type ManifestConfig, type ProtocolHandler, type ScreenshotConfig, type SmartScreenshotInput, createManifest, createManifestMetadata, createScreenshot, createScreenshots, createViewport, generateManifest };
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
// src/pwa/manifest.ts
|
|
2
|
+
function createViewport(config) {
|
|
3
|
+
return {
|
|
4
|
+
width: "device-width",
|
|
5
|
+
initialScale: 1,
|
|
6
|
+
maximumScale: 1,
|
|
7
|
+
themeColor: config.themeColor || "#000000"
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
function createManifestMetadata(config) {
|
|
11
|
+
return {
|
|
12
|
+
manifest: "/manifest.json",
|
|
13
|
+
appleWebApp: {
|
|
14
|
+
capable: true,
|
|
15
|
+
statusBarStyle: "default",
|
|
16
|
+
title: config.shortName || config.name
|
|
17
|
+
},
|
|
18
|
+
applicationName: config.name,
|
|
19
|
+
formatDetection: {
|
|
20
|
+
telephone: false
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
function createScreenshot(input) {
|
|
25
|
+
const config = typeof input === "string" ? { src: input } : input;
|
|
26
|
+
let { src, width, height, label, form_factor } = config;
|
|
27
|
+
const ext = src.split(".").pop()?.toLowerCase();
|
|
28
|
+
const typeMap = {
|
|
29
|
+
png: "image/png",
|
|
30
|
+
jpg: "image/jpeg",
|
|
31
|
+
jpeg: "image/jpeg",
|
|
32
|
+
webp: "image/webp",
|
|
33
|
+
svg: "image/svg+xml"
|
|
34
|
+
};
|
|
35
|
+
const type = ext ? typeMap[ext] || "image/png" : "image/png";
|
|
36
|
+
const filename = src.toLowerCase();
|
|
37
|
+
const dimensionMatch = filename.match(/(\d{3,4})x(\d{3,4})/);
|
|
38
|
+
if (dimensionMatch && !width && !height) {
|
|
39
|
+
width = parseInt(dimensionMatch[1], 10);
|
|
40
|
+
height = parseInt(dimensionMatch[2], 10);
|
|
41
|
+
}
|
|
42
|
+
let autoFormFactor = "wide";
|
|
43
|
+
if (filename.includes("mobile") || filename.includes("phone") || filename.includes("narrow")) {
|
|
44
|
+
autoFormFactor = "narrow";
|
|
45
|
+
} else if (filename.includes("desktop") || filename.includes("laptop") || filename.includes("wide")) {
|
|
46
|
+
autoFormFactor = "wide";
|
|
47
|
+
} else if (width && height) {
|
|
48
|
+
const aspectRatio = width / height;
|
|
49
|
+
autoFormFactor = aspectRatio > 1.2 ? "wide" : "narrow";
|
|
50
|
+
}
|
|
51
|
+
const finalFormFactor = form_factor || autoFormFactor;
|
|
52
|
+
const defaultDimensions = finalFormFactor === "wide" ? { width: 1920, height: 1080 } : { width: 390, height: 844 };
|
|
53
|
+
const finalWidth = width || defaultDimensions.width;
|
|
54
|
+
const finalHeight = height || defaultDimensions.height;
|
|
55
|
+
const autoLabel = finalFormFactor === "wide" ? "Desktop screenshot" : "Mobile screenshot";
|
|
56
|
+
return {
|
|
57
|
+
src,
|
|
58
|
+
sizes: `${finalWidth}x${finalHeight}`,
|
|
59
|
+
type,
|
|
60
|
+
form_factor: finalFormFactor,
|
|
61
|
+
label: label || autoLabel
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
function createScreenshots(inputs) {
|
|
65
|
+
return inputs.map(createScreenshot);
|
|
66
|
+
}
|
|
67
|
+
function createManifest(config) {
|
|
68
|
+
return () => {
|
|
69
|
+
let manifestIcons;
|
|
70
|
+
if (Array.isArray(config.icons)) {
|
|
71
|
+
manifestIcons = config.icons;
|
|
72
|
+
} else if (config.icons) {
|
|
73
|
+
const { logo192, logo384, logo512 } = config.icons;
|
|
74
|
+
manifestIcons = [
|
|
75
|
+
...logo192 ? [
|
|
76
|
+
{
|
|
77
|
+
src: logo192,
|
|
78
|
+
sizes: "192x192",
|
|
79
|
+
type: "image/png",
|
|
80
|
+
purpose: "maskable"
|
|
81
|
+
}
|
|
82
|
+
] : [],
|
|
83
|
+
...logo384 ? [
|
|
84
|
+
{
|
|
85
|
+
src: logo384,
|
|
86
|
+
sizes: "384x384",
|
|
87
|
+
type: "image/png"
|
|
88
|
+
}
|
|
89
|
+
] : [],
|
|
90
|
+
...logo512 ? [
|
|
91
|
+
{
|
|
92
|
+
src: logo512,
|
|
93
|
+
sizes: "512x512",
|
|
94
|
+
type: "image/png"
|
|
95
|
+
}
|
|
96
|
+
] : []
|
|
97
|
+
];
|
|
98
|
+
}
|
|
99
|
+
const manifest = {
|
|
100
|
+
name: config.name,
|
|
101
|
+
short_name: config.shortName || config.name,
|
|
102
|
+
description: config.description || config.name,
|
|
103
|
+
id: config.id || config.startUrl || "/",
|
|
104
|
+
start_url: config.startUrl || "/",
|
|
105
|
+
scope: config.scope || "/",
|
|
106
|
+
display: config.display || "standalone",
|
|
107
|
+
orientation: config.orientation || "portrait",
|
|
108
|
+
background_color: config.backgroundColor || "#000000",
|
|
109
|
+
theme_color: config.themeColor || "#ffffff",
|
|
110
|
+
lang: config.lang || "en",
|
|
111
|
+
dir: config.dir || "ltr",
|
|
112
|
+
icons: manifestIcons
|
|
113
|
+
// Removed forced gcm_sender_id to avoid potential conflicts with VAPID
|
|
114
|
+
// gcm_sender_id: '103953800507',
|
|
115
|
+
};
|
|
116
|
+
if (config.screenshots && config.screenshots.length > 0) {
|
|
117
|
+
manifest.screenshots = config.screenshots;
|
|
118
|
+
}
|
|
119
|
+
if (config.protocol_handlers && config.protocol_handlers.length > 0) {
|
|
120
|
+
manifest.protocol_handlers = config.protocol_handlers;
|
|
121
|
+
}
|
|
122
|
+
return manifest;
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
function generateManifest(config) {
|
|
126
|
+
return createManifest(config)();
|
|
127
|
+
}
|
|
128
|
+
export {
|
|
129
|
+
createManifest,
|
|
130
|
+
createManifestMetadata,
|
|
131
|
+
createScreenshot,
|
|
132
|
+
createScreenshots,
|
|
133
|
+
createViewport,
|
|
134
|
+
generateManifest
|
|
135
|
+
};
|
|
136
|
+
//# sourceMappingURL=manifest.mjs.map
|