@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.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/pwa/manifest.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"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@djangocfg/nextjs",
3
- "version": "2.1.514",
3
+ "version": "2.1.516",
4
4
  "description": "Next.js server utilities: sitemap, health, OG images, contact forms, navigation, config",
5
5
  "keywords": [
6
6
  "nextjs",
@@ -64,6 +64,11 @@
64
64
  "import": "./dist/pwa/index.mjs",
65
65
  "default": "./dist/pwa/index.mjs"
66
66
  },
67
+ "./pwa/manifest": {
68
+ "types": "./dist/pwa/manifest.d.mts",
69
+ "import": "./dist/pwa/manifest.mjs",
70
+ "default": "./dist/pwa/manifest.mjs"
71
+ },
67
72
  "./pwa/worker": {
68
73
  "types": "./dist/pwa/worker/index.d.mts",
69
74
  "import": "./dist/pwa/worker/index.mjs",
@@ -144,9 +149,9 @@
144
149
  "ai-docs": "tsx src/ai/cli.ts"
145
150
  },
146
151
  "peerDependencies": {
147
- "@djangocfg/devtools": "^2.1.514",
148
- "@djangocfg/i18n": "^2.1.514",
149
- "@djangocfg/ui-core": "^2.1.514",
152
+ "@djangocfg/devtools": "^2.1.516",
153
+ "@djangocfg/i18n": "^2.1.516",
154
+ "@djangocfg/ui-core": "^2.1.516",
150
155
  "next": "^16.2.2"
151
156
  },
152
157
  "peerDependenciesMeta": {
@@ -168,11 +173,11 @@
168
173
  "serwist": "^9.5.11"
169
174
  },
170
175
  "devDependencies": {
171
- "@djangocfg/devtools": "^2.1.514",
172
- "@djangocfg/i18n": "^2.1.514",
173
- "@djangocfg/layouts": "^2.1.514",
174
- "@djangocfg/typescript-config": "^2.1.514",
175
- "@djangocfg/ui-core": "^2.1.514",
176
+ "@djangocfg/devtools": "^2.1.516",
177
+ "@djangocfg/i18n": "^2.1.516",
178
+ "@djangocfg/layouts": "^2.1.516",
179
+ "@djangocfg/typescript-config": "^2.1.516",
180
+ "@djangocfg/ui-core": "^2.1.516",
176
181
  "@types/node": "^25.9.5",
177
182
  "@types/react": "19.2.15",
178
183
  "@types/react-dom": "19.2.3",
@@ -8,6 +8,8 @@ import chalk from 'chalk';
8
8
  import { execSync, spawn } from 'child_process';
9
9
  import Conf from 'conf';
10
10
  import consola from 'consola';
11
+ import { existsSync } from 'fs';
12
+ import { join } from 'path';
11
13
  import { createInterface } from 'readline';
12
14
 
13
15
  import { isCI } from '../utils/env';
@@ -84,13 +86,11 @@ export interface InstallProgress {
84
86
  export function detectPackageManager(): 'pnpm' | 'yarn' | 'npm' {
85
87
  try {
86
88
  // Check for lockfiles
87
- const fs = require('fs');
88
- const path = require('path');
89
89
  const cwd = process.cwd();
90
90
 
91
- if (fs.existsSync(path.join(cwd, 'pnpm-lock.yaml'))) return 'pnpm';
92
- if (fs.existsSync(path.join(cwd, 'yarn.lock'))) return 'yarn';
93
- if (fs.existsSync(path.join(cwd, 'package-lock.json'))) return 'npm';
91
+ if (existsSync(join(cwd, 'pnpm-lock.yaml'))) return 'pnpm';
92
+ if (existsSync(join(cwd, 'yarn.lock'))) return 'yarn';
93
+ if (existsSync(join(cwd, 'package-lock.json'))) return 'npm';
94
94
 
95
95
  // Check for global package manager
96
96
  try {
@@ -8,6 +8,7 @@ import chalk from 'chalk';
8
8
  import { spawn } from 'child_process';
9
9
  import Conf from 'conf';
10
10
  import consola from 'consola';
11
+ import { existsSync, readFileSync } from 'fs';
11
12
  import { createRequire } from 'module';
12
13
  import { join } from 'path';
13
14
  import { createInterface } from 'readline';
@@ -86,9 +87,8 @@ export interface UpdateOptions {
86
87
  */
87
88
  function isWorkspacePackage(packageName: string): boolean {
88
89
  try {
89
- const fs = require('fs');
90
90
  const pkgJsonPath = join(process.cwd(), 'package.json');
91
- const pkgJson = JSON.parse(fs.readFileSync(pkgJsonPath, 'utf-8'));
91
+ const pkgJson = JSON.parse(readFileSync(pkgJsonPath, 'utf-8'));
92
92
 
93
93
  const deps = { ...pkgJson.dependencies, ...pkgJson.devDependencies };
94
94
  const version = deps[packageName];
@@ -104,35 +104,36 @@ function isWorkspacePackage(packageName: string): boolean {
104
104
  * Uses multiple fallback strategies for monorepo compatibility
105
105
  */
106
106
  export function getInstalledVersion(packageName: string): string | null {
107
- const fs = require('fs');
108
107
  const cwd = process.cwd();
109
108
 
110
109
  // Strategy 1: Direct read from node_modules (works with symlinks)
111
110
  try {
112
111
  const directPath = join(cwd, 'node_modules', packageName, 'package.json');
113
- if (fs.existsSync(directPath)) {
114
- const pkg = JSON.parse(fs.readFileSync(directPath, 'utf-8'));
112
+ if (existsSync(directPath)) {
113
+ const pkg = JSON.parse(readFileSync(directPath, 'utf-8'));
115
114
  return pkg.version || null;
116
115
  }
117
116
  } catch {
118
117
  // Continue to next strategy
119
118
  }
120
119
 
121
- // Strategy 2: Use createRequire from cwd
120
+ // Strategy 2: Resolve from the consumer's project root. `createRequire` is the
121
+ // ESM-safe way to do this — a bare `require()` here does not exist once tsup
122
+ // emits an .mjs bundle, and its shim throws rather than falling through to the
123
+ // next strategy.
122
124
  try {
123
125
  const consumerRequire = createRequire(join(cwd, 'package.json'));
124
126
  const pkgPath = consumerRequire.resolve(`${packageName}/package.json`);
125
- const pkg = require(pkgPath);
126
- return pkg.version || null;
127
+ return JSON.parse(readFileSync(pkgPath, 'utf-8')).version || null;
127
128
  } catch {
128
129
  // Continue to next strategy
129
130
  }
130
131
 
131
- // Strategy 3: Try global require
132
+ // Strategy 3: Resolve relative to THIS module, for the hoisted/global case.
132
133
  try {
133
- const pkgPath = require.resolve(`${packageName}/package.json`);
134
- const pkg = require(pkgPath);
135
- return pkg.version || null;
134
+ const localRequire = createRequire(import.meta.url);
135
+ const pkgPath = localRequire.resolve(`${packageName}/package.json`);
136
+ return JSON.parse(readFileSync(pkgPath, 'utf-8')).version || null;
136
137
  } catch {
137
138
  return null;
138
139
  }
@@ -5,8 +5,14 @@
5
5
  */
6
6
 
7
7
  import type { Configuration as WebpackConfig } from 'webpack';
8
+ import { createRequire } from 'module';
9
+
8
10
  import { isPackageInstalled } from '../packages/checker';
9
11
 
12
+ // Optional peer dependencies are loaded synchronously at config time, which
13
+ // `import()` cannot do. `createRequire` is the ESM-safe equivalent of `require`.
14
+ const requirePeer = createRequire(import.meta.url);
15
+
10
16
  export interface CompressionPluginOptions {
11
17
  /** Enable gzip compression */
12
18
  gzip?: boolean;
@@ -45,9 +51,8 @@ export function addCompressionPlugins(
45
51
  const opts = { ...DEFAULT_OPTIONS, ...options };
46
52
 
47
53
  try {
48
- // Dynamic import to avoid bundling if not installed
49
- // eslint-disable-next-line @typescript-eslint/no-require-imports
50
- const CompressionPlugin = require('compression-webpack-plugin');
54
+ // Resolved at call time to avoid bundling it when it is not installed.
55
+ const CompressionPlugin = requirePeer('compression-webpack-plugin');
51
56
 
52
57
  if (!config.plugins) {
53
58
  config.plugins = [];
@@ -6,6 +6,8 @@
6
6
 
7
7
  import type { Compiler } from 'webpack';
8
8
  import chalk from 'chalk';
9
+ import { existsSync } from 'fs';
10
+ import { join } from 'path';
9
11
 
10
12
  import { AI_DOCS_HINT } from '../../ai/constants';
11
13
  import { DJANGO_CFG_BANNER } from '../constants';
@@ -88,15 +90,12 @@ export class DevStartupPlugin {
88
90
  }
89
91
 
90
92
  private checkPWASetup(): void {
91
- const fs = require('fs');
92
- const path = require('path');
93
-
94
93
  const cwd = process.cwd();
95
- const swPath = path.join(cwd, 'app', 'sw.ts');
96
- const manifestPath = path.join(cwd, 'app', 'manifest.ts');
94
+ const swPath = join(cwd, 'app', 'sw.ts');
95
+ const manifestPath = join(cwd, 'app', 'manifest.ts');
97
96
 
98
- const hasSW = fs.existsSync(swPath);
99
- const hasManifest = fs.existsSync(manifestPath);
97
+ const hasSW = existsSync(swPath);
98
+ const hasManifest = existsSync(manifestPath);
100
99
 
101
100
  if (hasSW || hasManifest) {
102
101
  console.log(chalk.cyan(' 📱 PWA Configuration:'));
@@ -5,6 +5,8 @@
5
5
  import chalk from 'chalk';
6
6
  import Conf from 'conf';
7
7
  import consola from 'consola';
8
+ import { readFileSync } from 'fs';
9
+ import { createRequire } from 'module';
8
10
  import semver from 'semver';
9
11
 
10
12
  import { DJANGOCFG_PACKAGES, PACKAGE_NAME, VERSION_CACHE_TTL_MS } from '../constants';
@@ -55,9 +57,13 @@ const versionCache = createVersionCache();
55
57
  */
56
58
  export function getCurrentVersion(): string | null {
57
59
  try {
58
- // eslint-disable-next-line @typescript-eslint/no-require-imports
59
- const packageJson = require('../../../package.json');
60
- return packageJson.version || null;
60
+ // `createRequire(import.meta.url).resolve` rather than a bare `require`: the
61
+ // published bundle is ESM, where `require` does not exist. Resolving is only
62
+ // used to locate the file — the read itself goes through `fs`, so this does
63
+ // not depend on JSON-module import support.
64
+ const localRequire = createRequire(import.meta.url);
65
+ const pkgPath = localRequire.resolve('../../../package.json');
66
+ return JSON.parse(readFileSync(pkgPath, 'utf-8')).version || null;
61
67
  } catch {
62
68
  return null;
63
69
  }
package/src/pwa/plugin.ts CHANGED
@@ -9,6 +9,11 @@
9
9
 
10
10
  import type { NextConfig } from 'next';
11
11
  import { consola } from 'consola';
12
+ import { createRequire } from 'module';
13
+
14
+ // Serwist is an optional peer loaded synchronously at config time, which
15
+ // `import()` cannot do. `createRequire` is the ESM-safe equivalent of `require`.
16
+ const requirePeer = createRequire(import.meta.url);
12
17
 
13
18
  export interface PWAPluginOptions {
14
19
  /**
@@ -117,7 +122,7 @@ export function withPWA(
117
122
  process.env.SERWIST_SUPPRESS_TURBOPACK_WARNING = '1';
118
123
  }
119
124
 
120
- const withSerwistInit = require('@serwist/next').default;
125
+ const withSerwistInit = requirePeer('@serwist/next').default;
121
126
 
122
127
  const withSerwist = withSerwistInit({
123
128
  swSrc: defaultOptions.swSrc,