@djangocfg/nextjs 1.0.6 → 2.1.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@djangocfg/nextjs",
3
- "version": "1.0.6",
3
+ "version": "2.1.1",
4
4
  "description": "Next.js server utilities: sitemap, health, OG images, contact forms, navigation, config",
5
5
  "keywords": [
6
6
  "nextjs",
@@ -95,21 +95,22 @@
95
95
  "check-links": "tsx src/scripts/check-links.ts"
96
96
  },
97
97
  "peerDependencies": {
98
- "@djangocfg/api": "^1.4.36",
99
- "next": "^15.4.4"
98
+ "@djangocfg/api": "^2.1.1",
99
+ "next": "^15.5.7"
100
100
  },
101
101
  "devDependencies": {
102
- "@djangocfg/imgai": "^1.0.22",
103
- "@djangocfg/layouts": "^2.0.6",
104
- "@djangocfg/typescript-config": "^1.4.36",
102
+ "@djangocfg/imgai": "^2.1.1",
103
+ "@djangocfg/layouts": "^2.1.1",
104
+ "@djangocfg/typescript-config": "^2.1.1",
105
105
  "@types/node": "^24.7.2",
106
106
  "@types/react": "19.2.2",
107
107
  "@types/react-dom": "19.2.1",
108
+ "@types/semver": "^7.7.1",
108
109
  "@types/webpack": "^5.28.5",
109
110
  "@vercel/og": "^0.8.5",
110
111
  "eslint": "^9.37.0",
111
112
  "linkinator": "^7.5.0",
112
- "lucide-react": "^0.469.0",
113
+ "lucide-react": "^0.545.0",
113
114
  "picocolors": "^1.1.1",
114
115
  "prompts": "^2.4.2",
115
116
  "tsup": "^8.0.1",
@@ -120,6 +121,9 @@
120
121
  "access": "public"
121
122
  },
122
123
  "dependencies": {
123
- "chalk": "^5.3.0"
124
+ "chalk": "^5.3.0",
125
+ "conf": "^15.0.2",
126
+ "consola": "^3.4.2",
127
+ "semver": "^7.7.3"
124
128
  }
125
129
  }
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Shared Constants for DjangoCFG Next.js Configuration
3
+ */
4
+
5
+ // Package name for version checks
6
+ export const PACKAGE_NAME = '@djangocfg/nextjs';
7
+
8
+ // Version cache TTL (1 hour)
9
+ export const VERSION_CACHE_TTL_MS = 60 * 60 * 1000;
10
+
11
+ // ASCII Art Banner for Django CFG
12
+ export const DJANGO_CFG_BANNER = `
13
+ 888 d8b .d888
14
+ 888 Y8P d88P"
15
+ 888 888
16
+ .d88888 8888 8888b. 88888b. .d88b. .d88b. .d8888b 888888 .d88b.
17
+ d88" 888 "888 "88b 888 "88b d88P"88b d88""88b d88P" 888 d88P"88b
18
+ 888 888 888 .d888888 888 888 888 888 888 888 888 888 888 888
19
+ Y88b 888 888 888 888 888 888 Y88b 888 Y88..88P Y88b. 888 Y88b 888
20
+ "Y88888 888 "Y888888 888 888 "Y88888 "Y88P" "Y8888P 888 "Y88888
21
+ 888 888 888
22
+ d88P Y8b d88P Y8b d88P
23
+ 888P" "Y88P" "Y88P"
24
+ `;
25
+
26
+ // All @djangocfg packages that can be updated together
27
+ export const DJANGOCFG_PACKAGES = [
28
+ '@djangocfg/ui-core',
29
+ '@djangocfg/ui-nextjs',
30
+ '@djangocfg/layouts',
31
+ '@djangocfg/nextjs',
32
+ '@djangocfg/api',
33
+ '@djangocfg/centrifugo',
34
+ '@djangocfg/eslint-config',
35
+ '@djangocfg/typescript-config',
36
+ ] as const;
37
+
38
+ // Default packages to transpile
39
+ export const DEFAULT_TRANSPILE_PACKAGES = [
40
+ '@djangocfg/ui-core',
41
+ '@djangocfg/ui-nextjs',
42
+ '@djangocfg/layouts',
43
+ '@djangocfg/nextjs',
44
+ '@djangocfg/api',
45
+ '@djangocfg/centrifugo',
46
+ ] as const;
47
+
48
+ // Default packages to optimize imports
49
+ export const DEFAULT_OPTIMIZE_PACKAGES = [
50
+ '@djangocfg/ui-core',
51
+ '@djangocfg/ui-nextjs',
52
+ '@djangocfg/layouts',
53
+ 'lucide-react',
54
+ 'recharts',
55
+ ] as const;
@@ -0,0 +1,209 @@
1
+ /**
2
+ * Base Next.js Configuration Factory
3
+ *
4
+ * Universal, reusable Next.js config for all DjangoCFG projects
5
+ * Provides standard settings that can be extended per project
6
+ *
7
+ * @example
8
+ * ```typescript
9
+ * // In your project's next.config.ts
10
+ * import { createBaseNextConfig } from '@djangocfg/nextjs/config';
11
+ * import bundleAnalyzer from '@next/bundle-analyzer';
12
+ *
13
+ * const withBundleAnalyzer = bundleAnalyzer({
14
+ * enabled: process.env.ANALYZE === 'true',
15
+ * });
16
+ *
17
+ * export default withBundleAnalyzer(createBaseNextConfig({
18
+ * // Your project-specific overrides
19
+ * transpilePackages: ['my-custom-package'],
20
+ * }));
21
+ * ```
22
+ */
23
+
24
+ import type { NextConfig } from 'next';
25
+ import type { Configuration as WebpackConfig } from 'webpack';
26
+
27
+ import { DEFAULT_TRANSPILE_PACKAGES, DEFAULT_OPTIMIZE_PACKAGES } from './constants';
28
+ import { deepMerge } from './utils/deepMerge';
29
+ import { isStaticBuild, isDev, getBasePath, getApiUrl, getSiteUrl } from './utils/env';
30
+ import { DevStartupPlugin } from './plugins/devStartup';
31
+ import { addCompressionPlugins } from './plugins/compression';
32
+
33
+ // ─────────────────────────────────────────────────────────────────────────
34
+ // Configuration Options
35
+ // ─────────────────────────────────────────────────────────────────────────
36
+
37
+ export interface BaseNextConfigOptions {
38
+ /** Static build path (used when NEXT_PUBLIC_STATIC_BUILD=true) */
39
+ isDefaultCfgAdmin?: boolean;
40
+ /** Additional transpile packages (merged with defaults) */
41
+ transpilePackages?: string[];
42
+ /** Additional optimize package imports (merged with defaults) */
43
+ optimizePackageImports?: string[];
44
+ /** Automatically open browser in dev mode (default: false) */
45
+ openBrowser?: boolean;
46
+ /** Check for @djangocfg/* package updates on startup (default: true) */
47
+ checkUpdates?: boolean;
48
+ /** Auto-update outdated packages without prompting (default: false) */
49
+ autoUpdate?: boolean;
50
+ /** Force check workspace:* packages for updates (for testing in monorepo) */
51
+ forceCheckWorkspace?: boolean;
52
+ /** Check for missing optional packages on startup (default: true) */
53
+ checkPackages?: boolean;
54
+ /** Auto-install missing packages without prompting (default: false) */
55
+ autoInstall?: boolean;
56
+ /** Custom webpack configuration function (called after base webpack logic) */
57
+ webpack?: (
58
+ config: WebpackConfig,
59
+ options: { isServer: boolean; dev: boolean; [key: string]: any }
60
+ ) => WebpackConfig | void;
61
+ /** Custom experimental options (merged with defaults) */
62
+ experimental?: NextConfig['experimental'];
63
+ /** Custom env variables (merged with defaults) */
64
+ env?: Record<string, string | undefined>;
65
+ /** Override any Next.js config option */
66
+ [key: string]: any;
67
+ }
68
+
69
+ // ─────────────────────────────────────────────────────────────────────────
70
+ // Base Configuration Factory
71
+ // ─────────────────────────────────────────────────────────────────────────
72
+
73
+ /**
74
+ * Create base Next.js configuration with standard DjangoCFG settings
75
+ *
76
+ * @param options - Custom configuration options to merge with base config
77
+ * @returns Next.js configuration function
78
+ */
79
+ export function createBaseNextConfig(
80
+ options: BaseNextConfigOptions = {}
81
+ ): NextConfig {
82
+
83
+ const basePath = getBasePath(options.isDefaultCfgAdmin);
84
+ const apiUrl = getApiUrl();
85
+ const siteUrl = getSiteUrl();
86
+
87
+ // Base configuration
88
+ const baseConfig: NextConfig = {
89
+ reactStrictMode: true,
90
+ trailingSlash: true,
91
+
92
+ // Static export configuration
93
+ ...(isStaticBuild && {
94
+ output: 'export' as const,
95
+ distDir: 'out',
96
+ basePath: basePath,
97
+ // Fix for Next.js 15.5.4: prevent 500.html generation issue
98
+ generateBuildId: async () => {
99
+ return 'static-build';
100
+ },
101
+ }),
102
+
103
+ // Standalone output for Docker (only in production, not dev)
104
+ ...(!isStaticBuild && !isDev && {
105
+ output: 'standalone' as const,
106
+ }),
107
+
108
+ // Environment variables
109
+ env: {
110
+ NEXT_PUBLIC_BASE_PATH: basePath,
111
+ NEXT_PUBLIC_API_URL: apiUrl,
112
+ NEXT_PUBLIC_SITE_URL: siteUrl,
113
+ ...options.env,
114
+ },
115
+
116
+ // Images configuration
117
+ images: {
118
+ unoptimized: true,
119
+ },
120
+
121
+ // Transpile packages (merge with user-provided)
122
+ transpilePackages: [
123
+ ...DEFAULT_TRANSPILE_PACKAGES,
124
+ ...(options.transpilePackages || []),
125
+ ],
126
+
127
+ // Experimental features
128
+ experimental: {
129
+ // Optimize package imports (only in production)
130
+ ...(!isDev && {
131
+ optimizePackageImports: [
132
+ ...DEFAULT_OPTIMIZE_PACKAGES,
133
+ ...(options.optimizePackageImports || []),
134
+ ],
135
+ }),
136
+ // Dev mode optimizations
137
+ ...(isDev && {
138
+ optimizeCss: false,
139
+ }),
140
+ // User experimental options applied last (can override base settings)
141
+ ...options.experimental,
142
+ },
143
+
144
+ // Webpack configuration
145
+ webpack: (config: WebpackConfig, webpackOptions: { isServer: boolean; dev: boolean; [key: string]: any }) => {
146
+ const { isServer, dev } = webpackOptions;
147
+
148
+ // Add dev startup plugin (client-side only in dev)
149
+ if (dev && !isServer) {
150
+ if (!config.plugins) {
151
+ config.plugins = [];
152
+ }
153
+ config.plugins.push(
154
+ new DevStartupPlugin({
155
+ openBrowser: options.openBrowser,
156
+ checkUpdates: options.checkUpdates,
157
+ autoUpdate: options.autoUpdate,
158
+ forceCheckWorkspace: options.forceCheckWorkspace,
159
+ checkPackages: options.checkPackages,
160
+ autoInstall: options.autoInstall,
161
+ })
162
+ );
163
+ }
164
+
165
+ // Dev mode optimizations
166
+ if (dev) {
167
+ config.optimization = {
168
+ ...config.optimization,
169
+ removeAvailableModules: false,
170
+ removeEmptyChunks: false,
171
+ splitChunks: false, // Disable code splitting in dev for faster compilation
172
+ };
173
+ }
174
+
175
+ // Filesystem cache (dev and production)
176
+ config.cache = {
177
+ type: 'filesystem',
178
+ buildDependencies: {},
179
+ };
180
+
181
+ // Compression plugins (only for static build in production, client-side)
182
+ if (!isServer && isStaticBuild && !dev) {
183
+ addCompressionPlugins(config);
184
+ }
185
+
186
+ // Call user's webpack function if provided
187
+ if (options.webpack) {
188
+ return options.webpack(config, webpackOptions);
189
+ }
190
+
191
+ return config;
192
+ },
193
+ };
194
+
195
+ // Deep merge user options with base config
196
+ const finalConfig = deepMerge(baseConfig, options);
197
+
198
+ // Cleanup: Remove custom options that are not part of NextConfig
199
+ delete (finalConfig as any).optimizePackageImports;
200
+ delete (finalConfig as any).isDefaultCfgAdmin;
201
+ delete (finalConfig as any).openBrowser;
202
+ delete (finalConfig as any).checkUpdates;
203
+ delete (finalConfig as any).autoUpdate;
204
+ delete (finalConfig as any).forceCheckWorkspace;
205
+ delete (finalConfig as any).checkPackages;
206
+ delete (finalConfig as any).autoInstall;
207
+
208
+ return finalConfig;
209
+ }
@@ -1,6 +1,91 @@
1
1
  /**
2
2
  * Next.js Configuration Utilities
3
+ *
4
+ * @example
5
+ * ```typescript
6
+ * import { createBaseNextConfig } from '@djangocfg/nextjs/config';
7
+ *
8
+ * export default createBaseNextConfig({
9
+ * openBrowser: true,
10
+ * checkPackages: true,
11
+ * });
12
+ * ```
3
13
  */
4
14
 
5
- export { createBaseNextConfig, type BaseNextConfigOptions } from './base-next-config';
15
+ // Main config factory
16
+ export { createBaseNextConfig, type BaseNextConfigOptions } from './createNextConfig';
6
17
 
18
+ // Constants
19
+ export {
20
+ PACKAGE_NAME,
21
+ DJANGOCFG_PACKAGES,
22
+ DEFAULT_TRANSPILE_PACKAGES,
23
+ DEFAULT_OPTIMIZE_PACKAGES,
24
+ DJANGO_CFG_BANNER,
25
+ } from './constants';
26
+
27
+ // Utils
28
+ export { deepMerge } from './utils/deepMerge';
29
+ export {
30
+ isStaticBuild,
31
+ isDev,
32
+ isProduction,
33
+ isCI,
34
+ getBasePath,
35
+ getApiUrl,
36
+ getSiteUrl,
37
+ } from './utils/env';
38
+ export {
39
+ getCurrentVersion,
40
+ fetchLatestVersion,
41
+ checkForUpdate,
42
+ getUpdateCommand,
43
+ printVersionInfo,
44
+ } from './utils/version';
45
+
46
+ // Package management
47
+ export {
48
+ OPTIONAL_PACKAGES,
49
+ PEER_DEPENDENCIES,
50
+ getPackagesForContext,
51
+ type PackageDefinition,
52
+ } from './packages/definitions';
53
+ export {
54
+ isPackageInstalled,
55
+ getMissingPackages,
56
+ checkPackages,
57
+ type MissingPackage,
58
+ } from './packages/checker';
59
+ export {
60
+ detectPackageManager,
61
+ buildSingleInstallCommand,
62
+ buildInstallCommand,
63
+ installPackages,
64
+ installPackagesWithProgress,
65
+ checkAndInstallPackages,
66
+ resetInstallerPreferences,
67
+ type InstallOptions,
68
+ type InstallProgress,
69
+ } from './packages/installer';
70
+ export {
71
+ getInstalledVersion,
72
+ checkForUpdates,
73
+ getOutdatedPackages,
74
+ updatePackagesWithProgress,
75
+ checkAndUpdatePackages,
76
+ resetUpdaterPreferences,
77
+ type PackageVersion,
78
+ type UpdateOptions,
79
+ } from './packages/updater';
80
+
81
+ // Plugins
82
+ export {
83
+ DevStartupPlugin,
84
+ resetDevStartupState,
85
+ type DevStartupPluginOptions,
86
+ } from './plugins/devStartup';
87
+ export {
88
+ addCompressionPlugins,
89
+ isCompressionAvailable,
90
+ type CompressionPluginOptions,
91
+ } from './plugins/compression';
@@ -0,0 +1,94 @@
1
+ /**
2
+ * Package Checker
3
+ *
4
+ * Checks which optional packages are missing and need to be installed.
5
+ */
6
+
7
+ import { createRequire } from 'module';
8
+ import { join } from 'path';
9
+ import type { PackageDefinition } from './definitions';
10
+ import { getPackagesForContext, OPTIONAL_PACKAGES } from './definitions';
11
+ import { isStaticBuild, isDev } from '../utils/env';
12
+
13
+ export interface MissingPackage extends PackageDefinition {
14
+ /** Why is this package needed? */
15
+ reason: string;
16
+ }
17
+
18
+ /**
19
+ * Check if a package is installed in the consumer's project
20
+ * Uses process.cwd() to check from the consumer's context, not the library's
21
+ */
22
+ export function isPackageInstalled(packageName: string): boolean {
23
+ try {
24
+ // Create require from consumer's cwd to check their node_modules
25
+ const consumerRequire = createRequire(join(process.cwd(), 'package.json'));
26
+ consumerRequire.resolve(packageName);
27
+ return true;
28
+ } catch {
29
+ // Fallback: try regular require (for packages hoisted to root)
30
+ try {
31
+ require.resolve(packageName);
32
+ return true;
33
+ } catch {
34
+ return false;
35
+ }
36
+ }
37
+ }
38
+
39
+ /**
40
+ * Get all missing packages for current context
41
+ */
42
+ export function getMissingPackages(): MissingPackage[] {
43
+ const neededPackages = getPackagesForContext({ isStaticBuild, isDev });
44
+ const missing: MissingPackage[] = [];
45
+
46
+ for (const pkg of neededPackages) {
47
+ if (!isPackageInstalled(pkg.name)) {
48
+ missing.push({
49
+ ...pkg,
50
+ reason: getReasonText(pkg),
51
+ });
52
+ }
53
+ }
54
+
55
+ return missing;
56
+ }
57
+
58
+ /**
59
+ * Check specific packages
60
+ */
61
+ export function checkPackages(packageNames: string[]): MissingPackage[] {
62
+ const missing: MissingPackage[] = [];
63
+
64
+ for (const name of packageNames) {
65
+ if (!isPackageInstalled(name)) {
66
+ const definition = OPTIONAL_PACKAGES.find(p => p.name === name);
67
+ missing.push({
68
+ name,
69
+ description: definition?.description || 'Optional package',
70
+ condition: definition?.condition || 'always',
71
+ devDependency: definition?.devDependency ?? true,
72
+ reason: definition ? getReasonText(definition) : 'Requested by configuration',
73
+ });
74
+ }
75
+ }
76
+
77
+ return missing;
78
+ }
79
+
80
+ /**
81
+ * Get human-readable reason for package requirement
82
+ */
83
+ function getReasonText(pkg: PackageDefinition): string {
84
+ switch (pkg.condition) {
85
+ case 'static-build':
86
+ return 'Required for static build (NEXT_PUBLIC_STATIC_BUILD=true)';
87
+ case 'dev':
88
+ return 'Required for development mode';
89
+ case 'always':
90
+ return 'Required for all builds';
91
+ default:
92
+ return 'Optional feature';
93
+ }
94
+ }
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Package Definitions
3
+ *
4
+ * Defines optional packages that can be auto-installed when needed.
5
+ */
6
+
7
+ export interface PackageDefinition {
8
+ /** npm package name */
9
+ name: string;
10
+ /** Human-readable description */
11
+ description: string;
12
+ /** When is this package needed? */
13
+ condition: 'static-build' | 'dev' | 'always';
14
+ /** Is this a dev dependency? */
15
+ devDependency: boolean;
16
+ /** Feature flag that enables this package */
17
+ featureFlag?: string;
18
+ }
19
+
20
+ /**
21
+ * Optional packages that can be auto-installed
22
+ */
23
+ export const OPTIONAL_PACKAGES: PackageDefinition[] = [
24
+ {
25
+ name: 'compression-webpack-plugin',
26
+ description: 'Gzip and Brotli compression for static builds',
27
+ condition: 'static-build',
28
+ devDependency: true,
29
+ },
30
+ {
31
+ name: '@next/bundle-analyzer',
32
+ description: 'Bundle analyzer for analyzing build output',
33
+ condition: 'dev',
34
+ devDependency: true,
35
+ featureFlag: 'ANALYZE',
36
+ },
37
+ ];
38
+
39
+ /**
40
+ * Required peer dependencies (should already be installed)
41
+ */
42
+ export const PEER_DEPENDENCIES = [
43
+ 'next',
44
+ 'react',
45
+ 'react-dom',
46
+ ] as const;
47
+
48
+ /**
49
+ * Get packages needed for current build context
50
+ */
51
+ export function getPackagesForContext(options: {
52
+ isStaticBuild: boolean;
53
+ isDev: boolean;
54
+ }): PackageDefinition[] {
55
+ return OPTIONAL_PACKAGES.filter(pkg => {
56
+ switch (pkg.condition) {
57
+ case 'static-build':
58
+ return options.isStaticBuild;
59
+ case 'dev':
60
+ return options.isDev;
61
+ case 'always':
62
+ return true;
63
+ default:
64
+ return false;
65
+ }
66
+ });
67
+ }
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Packages module exports
3
+ */
4
+
5
+ export * from './definitions';
6
+ export * from './checker';
7
+ export {
8
+ detectPackageManager,
9
+ buildSingleInstallCommand,
10
+ buildInstallCommand,
11
+ installPackages,
12
+ installPackagesWithProgress,
13
+ checkAndInstallPackages,
14
+ resetInstallerPreferences,
15
+ type InstallOptions,
16
+ type InstallProgress,
17
+ } from './installer';
18
+ export {
19
+ getInstalledVersion,
20
+ checkForUpdates,
21
+ getOutdatedPackages,
22
+ updatePackagesWithProgress,
23
+ checkAndUpdatePackages,
24
+ resetUpdaterPreferences,
25
+ type PackageVersion,
26
+ type UpdateOptions,
27
+ } from './updater';