@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 +12 -8
- package/src/config/constants.ts +55 -0
- package/src/config/createNextConfig.ts +209 -0
- package/src/config/index.ts +86 -1
- package/src/config/packages/checker.ts +94 -0
- package/src/config/packages/definitions.ts +67 -0
- package/src/config/packages/index.ts +27 -0
- package/src/config/packages/installer.ts +457 -0
- package/src/config/packages/updater.ts +469 -0
- package/src/config/plugins/compression.ts +97 -0
- package/src/config/plugins/devStartup.ts +123 -0
- package/src/config/plugins/index.ts +6 -0
- package/src/config/utils/deepMerge.ts +33 -0
- package/src/config/utils/env.ts +30 -0
- package/src/config/utils/index.ts +7 -0
- package/src/config/utils/version.ts +121 -0
- package/src/og-image/utils/metadata.ts +1 -2
- package/src/og-image/utils/url.ts +57 -44
- package/src/config/base-next-config.ts +0 -303
- package/src/config/deepMerge.ts +0 -33
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deep Merge Utility
|
|
3
|
+
*
|
|
4
|
+
* Recursively merges objects, replacing arrays instead of merging them.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export function deepMerge<T extends Record<string, any>>(target: T, source: Partial<T>): T {
|
|
8
|
+
const output = { ...target };
|
|
9
|
+
|
|
10
|
+
for (const key in source) {
|
|
11
|
+
if (source[key] === undefined) continue;
|
|
12
|
+
|
|
13
|
+
// Arrays: replace (don't merge arrays)
|
|
14
|
+
if (Array.isArray(source[key])) {
|
|
15
|
+
output[key] = source[key] as any;
|
|
16
|
+
}
|
|
17
|
+
// Objects: deep merge
|
|
18
|
+
else if (source[key] && typeof source[key] === 'object' && !Array.isArray(source[key])) {
|
|
19
|
+
const targetValue = output[key];
|
|
20
|
+
if (targetValue && typeof targetValue === 'object' && !Array.isArray(targetValue)) {
|
|
21
|
+
output[key] = deepMerge(targetValue, source[key] as any);
|
|
22
|
+
} else {
|
|
23
|
+
output[key] = source[key] as any;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
// Primitives: replace
|
|
27
|
+
else {
|
|
28
|
+
output[key] = source[key] as any;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return output;
|
|
33
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Environment Variable Utilities
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export const isStaticBuild = process.env.NEXT_PUBLIC_STATIC_BUILD === 'true';
|
|
6
|
+
export const isDev = process.env.NODE_ENV === 'development';
|
|
7
|
+
export const isProduction = process.env.NODE_ENV === 'production';
|
|
8
|
+
export const isCI = process.env.CI === 'true';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Get base path for static builds
|
|
12
|
+
*/
|
|
13
|
+
export function getBasePath(isDefaultCfgAdmin?: boolean): string {
|
|
14
|
+
if (!isStaticBuild) return '';
|
|
15
|
+
return isDefaultCfgAdmin ? '/cfg/admin' : '/cfg/nextjs-admin';
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Get API URL (empty for static builds)
|
|
20
|
+
*/
|
|
21
|
+
export function getApiUrl(): string {
|
|
22
|
+
return isStaticBuild ? '' : (process.env.NEXT_PUBLIC_API_URL || '');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Get Site URL (empty for static builds)
|
|
27
|
+
*/
|
|
28
|
+
export function getSiteUrl(): string {
|
|
29
|
+
return isStaticBuild ? '' : (process.env.NEXT_PUBLIC_SITE_URL || '');
|
|
30
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Version Checking Utilities
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import chalk from 'chalk';
|
|
6
|
+
import semver from 'semver';
|
|
7
|
+
import consola from 'consola';
|
|
8
|
+
import Conf from 'conf';
|
|
9
|
+
import { PACKAGE_NAME, VERSION_CACHE_TTL_MS, DJANGOCFG_PACKAGES } from '../constants';
|
|
10
|
+
|
|
11
|
+
// Version cache using conf (stores in ~/.config/djangocfg-nextjs/)
|
|
12
|
+
const versionCache = new Conf<{
|
|
13
|
+
latestVersion?: string;
|
|
14
|
+
lastCheck?: number;
|
|
15
|
+
}>({
|
|
16
|
+
projectName: 'djangocfg-nextjs',
|
|
17
|
+
projectVersion: '1.0.0',
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Get current package version from package.json
|
|
22
|
+
*/
|
|
23
|
+
export function getCurrentVersion(): string | null {
|
|
24
|
+
try {
|
|
25
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
26
|
+
const packageJson = require('../../../package.json');
|
|
27
|
+
return packageJson.version || null;
|
|
28
|
+
} catch {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Fetch latest version from npm registry (with 1 hour caching via conf)
|
|
35
|
+
*/
|
|
36
|
+
export async function fetchLatestVersion(): Promise<string | null> {
|
|
37
|
+
// Check cache first
|
|
38
|
+
const lastCheck = versionCache.get('lastCheck') || 0;
|
|
39
|
+
const cachedVersion = versionCache.get('latestVersion');
|
|
40
|
+
|
|
41
|
+
if (cachedVersion && (Date.now() - lastCheck) < VERSION_CACHE_TTL_MS) {
|
|
42
|
+
return cachedVersion;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Fetch from npm registry
|
|
46
|
+
try {
|
|
47
|
+
const https = await import('https');
|
|
48
|
+
return new Promise((resolve) => {
|
|
49
|
+
const req = https.get(
|
|
50
|
+
`https://registry.npmjs.org/${PACKAGE_NAME}/latest`,
|
|
51
|
+
{ timeout: 5000 },
|
|
52
|
+
(res: any) => {
|
|
53
|
+
let data = '';
|
|
54
|
+
res.on('data', (chunk: string) => { data += chunk; });
|
|
55
|
+
res.on('end', () => {
|
|
56
|
+
try {
|
|
57
|
+
const json = JSON.parse(data);
|
|
58
|
+
const version = json.version || null;
|
|
59
|
+
if (version) {
|
|
60
|
+
versionCache.set('latestVersion', version);
|
|
61
|
+
versionCache.set('lastCheck', Date.now());
|
|
62
|
+
}
|
|
63
|
+
resolve(version);
|
|
64
|
+
} catch {
|
|
65
|
+
resolve(cachedVersion || null);
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
);
|
|
70
|
+
req.on('error', () => resolve(cachedVersion || null));
|
|
71
|
+
req.on('timeout', () => { req.destroy(); resolve(cachedVersion || null); });
|
|
72
|
+
});
|
|
73
|
+
} catch {
|
|
74
|
+
return cachedVersion || null;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Check if update is available
|
|
80
|
+
*/
|
|
81
|
+
export async function checkForUpdate(): Promise<{
|
|
82
|
+
hasUpdate: boolean;
|
|
83
|
+
currentVersion: string | null;
|
|
84
|
+
latestVersion: string | null;
|
|
85
|
+
}> {
|
|
86
|
+
const currentVersion = getCurrentVersion();
|
|
87
|
+
if (!currentVersion) {
|
|
88
|
+
return { hasUpdate: false, currentVersion: null, latestVersion: null };
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const latestVersion = await fetchLatestVersion();
|
|
92
|
+
const hasUpdate = !!(latestVersion && semver.gt(latestVersion, currentVersion));
|
|
93
|
+
|
|
94
|
+
return { hasUpdate, currentVersion, latestVersion };
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Get update command for all djangocfg packages
|
|
99
|
+
*/
|
|
100
|
+
export function getUpdateCommand(): string {
|
|
101
|
+
return `pnpm add ${DJANGOCFG_PACKAGES.map(p => `${p}@latest`).join(' ')}`;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Print version info and update notification
|
|
106
|
+
*/
|
|
107
|
+
export async function printVersionInfo(): Promise<void> {
|
|
108
|
+
const { hasUpdate, currentVersion, latestVersion } = await checkForUpdate();
|
|
109
|
+
|
|
110
|
+
if (!currentVersion) return;
|
|
111
|
+
|
|
112
|
+
// Print current version
|
|
113
|
+
consola.box(`📦 @djangocfg/nextjs v${currentVersion}`);
|
|
114
|
+
|
|
115
|
+
// Show update notification if available
|
|
116
|
+
if (hasUpdate && latestVersion) {
|
|
117
|
+
consola.warn(`Update Available! ${chalk.red(currentVersion)} → ${chalk.green(latestVersion)}`);
|
|
118
|
+
consola.info(`Run: ${chalk.cyan(getUpdateCommand())}`);
|
|
119
|
+
console.log('');
|
|
120
|
+
}
|
|
121
|
+
}
|
|
@@ -131,9 +131,8 @@ export function generateOgImageMetadata(
|
|
|
131
131
|
|
|
132
132
|
// Generate relative OG image URL
|
|
133
133
|
const relativeOgImageUrl = generateOgImageUrl(
|
|
134
|
-
ogImageBaseUrl,
|
|
135
134
|
finalOgImageParams,
|
|
136
|
-
useBase64
|
|
135
|
+
{ baseUrl: ogImageBaseUrl, useBase64 }
|
|
137
136
|
);
|
|
138
137
|
|
|
139
138
|
// CRITICAL: Use absolute URL to ensure query params are preserved
|
|
@@ -4,6 +4,9 @@
|
|
|
4
4
|
* Utilities to generate OG image URLs with proper query parameters
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
+
/** Default OG Image API base URL */
|
|
8
|
+
const DEFAULT_OG_IMAGE_BASE_URL = 'https://djangocfg.com/api/og';
|
|
9
|
+
|
|
7
10
|
/**
|
|
8
11
|
* Encode string to base64 with Unicode support
|
|
9
12
|
* Works in both browser and Node.js environments
|
|
@@ -90,47 +93,53 @@ export interface OgImageUrlParams {
|
|
|
90
93
|
[key: string]: string | number | boolean | undefined;
|
|
91
94
|
}
|
|
92
95
|
|
|
96
|
+
/**
|
|
97
|
+
* Options for generating OG image URL
|
|
98
|
+
*/
|
|
99
|
+
export interface GenerateOgImageUrlOptions {
|
|
100
|
+
/**
|
|
101
|
+
* Base URL of the OG image API route
|
|
102
|
+
* @default 'https://djangocfg.com/api/og'
|
|
103
|
+
*/
|
|
104
|
+
baseUrl?: string;
|
|
105
|
+
/**
|
|
106
|
+
* If true, encode params as base64 for safer URLs
|
|
107
|
+
* @default true
|
|
108
|
+
*/
|
|
109
|
+
useBase64?: boolean;
|
|
110
|
+
}
|
|
111
|
+
|
|
93
112
|
/**
|
|
94
113
|
* Generate OG image URL with query parameters or base64 encoding
|
|
95
114
|
*
|
|
96
|
-
* @param baseUrl - Base URL of the OG image API route (e.g., '/api/og' or 'https://example.com/api/og')
|
|
97
115
|
* @param params - URL parameters for the OG image
|
|
98
|
-
* @param
|
|
116
|
+
* @param options - Generation options (baseUrl, useBase64)
|
|
99
117
|
* @returns Complete OG image URL with encoded parameters
|
|
100
118
|
*
|
|
101
119
|
* @example
|
|
102
120
|
* ```typescript
|
|
103
|
-
* //
|
|
104
|
-
* const url = generateOgImageUrl(
|
|
121
|
+
* // Using default baseUrl (https://djangocfg.com/api/og)
|
|
122
|
+
* const url = generateOgImageUrl({
|
|
105
123
|
* title: 'My Page Title',
|
|
106
124
|
* description: 'Page description here',
|
|
107
|
-
* siteName: 'My Site',
|
|
108
|
-
* logo: '/logo.svg',
|
|
109
|
-
* backgroundType: 'gradient',
|
|
110
|
-
* gradientStart: '#0f172a',
|
|
111
|
-
* gradientEnd: '#334155',
|
|
112
|
-
* titleSize: 80,
|
|
113
|
-
* titleWeight: 800,
|
|
114
|
-
* titleColor: 'white',
|
|
115
|
-
* descriptionSize: 36,
|
|
116
|
-
* descriptionColor: 'rgba(226, 232, 240, 0.9)',
|
|
117
|
-
* siteNameSize: 32,
|
|
118
|
-
* siteNameColor: 'rgba(255, 255, 255, 0.95)',
|
|
119
|
-
* padding: 80,
|
|
120
|
-
* logoSize: 56,
|
|
121
125
|
* });
|
|
122
|
-
* // Result: /api/og/[base64-encoded-json]
|
|
123
126
|
*
|
|
124
|
-
* //
|
|
125
|
-
* const url = generateOgImageUrl(
|
|
126
|
-
*
|
|
127
|
+
* // With custom baseUrl
|
|
128
|
+
* const url = generateOgImageUrl(
|
|
129
|
+
* { title: 'My Page' },
|
|
130
|
+
* { baseUrl: '/api/og' }
|
|
131
|
+
* );
|
|
127
132
|
* ```
|
|
128
133
|
*/
|
|
129
134
|
export function generateOgImageUrl(
|
|
130
|
-
baseUrl: string,
|
|
131
135
|
params: OgImageUrlParams,
|
|
132
|
-
|
|
136
|
+
options: GenerateOgImageUrlOptions = {}
|
|
133
137
|
): string {
|
|
138
|
+
const {
|
|
139
|
+
baseUrl = DEFAULT_OG_IMAGE_BASE_URL,
|
|
140
|
+
useBase64 = true
|
|
141
|
+
} = options;
|
|
142
|
+
|
|
134
143
|
if (useBase64) {
|
|
135
144
|
// Clean params - remove undefined/null/empty values
|
|
136
145
|
const cleanParams: Record<string, string | number | boolean> = {};
|
|
@@ -186,6 +195,11 @@ export function getAbsoluteOgImageUrl(
|
|
|
186
195
|
relativePath: string,
|
|
187
196
|
siteUrl: string
|
|
188
197
|
): string {
|
|
198
|
+
// If path is already an absolute URL, return as-is
|
|
199
|
+
if (relativePath.startsWith('http://') || relativePath.startsWith('https://')) {
|
|
200
|
+
return relativePath;
|
|
201
|
+
}
|
|
202
|
+
|
|
189
203
|
// Remove trailing slash from site URL
|
|
190
204
|
const cleanSiteUrl = siteUrl.replace(/\/$/, '');
|
|
191
205
|
|
|
@@ -202,30 +216,30 @@ export function getAbsoluteOgImageUrl(
|
|
|
202
216
|
*
|
|
203
217
|
* Useful when you want to reuse the same base URL and default parameters
|
|
204
218
|
*
|
|
205
|
-
* @param baseUrl - Base URL of the OG image API route
|
|
206
219
|
* @param defaults - Default parameters to merge with each URL generation
|
|
220
|
+
* @param options - Default options (baseUrl, useBase64)
|
|
207
221
|
* @returns URL builder function
|
|
208
222
|
*
|
|
209
223
|
* @example
|
|
210
224
|
* ```typescript
|
|
211
|
-
* const buildOgUrl = createOgImageUrlBuilder(
|
|
212
|
-
* siteName: 'My Site',
|
|
213
|
-
*
|
|
214
|
-
*
|
|
225
|
+
* const buildOgUrl = createOgImageUrlBuilder(
|
|
226
|
+
* { siteName: 'My Site', logo: '/logo.png' },
|
|
227
|
+
* { baseUrl: '/api/og' }
|
|
228
|
+
* );
|
|
215
229
|
*
|
|
216
230
|
* const url1 = buildOgUrl({ title: 'Page 1' });
|
|
217
231
|
* const url2 = buildOgUrl({ title: 'Page 2', description: 'Custom desc' });
|
|
218
232
|
* ```
|
|
219
233
|
*/
|
|
220
234
|
export function createOgImageUrlBuilder(
|
|
221
|
-
|
|
222
|
-
|
|
235
|
+
defaults: Partial<OgImageUrlParams> = {},
|
|
236
|
+
options: GenerateOgImageUrlOptions = {}
|
|
223
237
|
) {
|
|
224
238
|
return (params: OgImageUrlParams): string => {
|
|
225
|
-
return generateOgImageUrl(
|
|
226
|
-
...defaults,
|
|
227
|
-
|
|
228
|
-
|
|
239
|
+
return generateOgImageUrl(
|
|
240
|
+
{ ...defaults, ...params },
|
|
241
|
+
options
|
|
242
|
+
);
|
|
229
243
|
};
|
|
230
244
|
}
|
|
231
245
|
|
|
@@ -287,7 +301,7 @@ export function parseOgImageData(
|
|
|
287
301
|
try {
|
|
288
302
|
// Handle URLSearchParams
|
|
289
303
|
let params: Record<string, string | undefined>;
|
|
290
|
-
|
|
304
|
+
|
|
291
305
|
if (searchParams instanceof URLSearchParams) {
|
|
292
306
|
// Convert URLSearchParams to object
|
|
293
307
|
params = {};
|
|
@@ -310,18 +324,18 @@ export function parseOgImageData(
|
|
|
310
324
|
if (process.env.NODE_ENV === 'development') {
|
|
311
325
|
console.log('[parseOgImageData] Found data param, length:', dataParam.length);
|
|
312
326
|
}
|
|
313
|
-
|
|
327
|
+
|
|
314
328
|
try {
|
|
315
329
|
const decoded = decodeBase64(dataParam);
|
|
316
330
|
if (process.env.NODE_ENV === 'development') {
|
|
317
331
|
console.log('[parseOgImageData] Decoded string:', decoded.substring(0, 100));
|
|
318
332
|
}
|
|
319
|
-
|
|
333
|
+
|
|
320
334
|
const parsed = JSON.parse(decoded);
|
|
321
335
|
if (process.env.NODE_ENV === 'development') {
|
|
322
336
|
console.log('[parseOgImageData] Parsed JSON:', parsed);
|
|
323
337
|
}
|
|
324
|
-
|
|
338
|
+
|
|
325
339
|
// Ensure all values are strings
|
|
326
340
|
const result: Record<string, string> = {};
|
|
327
341
|
for (const [key, value] of Object.entries(parsed)) {
|
|
@@ -329,11 +343,11 @@ export function parseOgImageData(
|
|
|
329
343
|
result[key] = String(value);
|
|
330
344
|
}
|
|
331
345
|
}
|
|
332
|
-
|
|
346
|
+
|
|
333
347
|
if (process.env.NODE_ENV === 'development') {
|
|
334
348
|
console.log('[parseOgImageData] Result:', result);
|
|
335
349
|
}
|
|
336
|
-
|
|
350
|
+
|
|
337
351
|
return result;
|
|
338
352
|
} catch (decodeError) {
|
|
339
353
|
console.error('[parseOgImageData] Error decoding/parsing data param:', decodeError);
|
|
@@ -355,11 +369,11 @@ export function parseOgImageData(
|
|
|
355
369
|
result[key] = Array.isArray(value) ? value[0] : String(value);
|
|
356
370
|
}
|
|
357
371
|
}
|
|
358
|
-
|
|
372
|
+
|
|
359
373
|
if (process.env.NODE_ENV === 'development') {
|
|
360
374
|
console.log('[parseOgImageData] Fallback result:', result);
|
|
361
375
|
}
|
|
362
|
-
|
|
376
|
+
|
|
363
377
|
return result;
|
|
364
378
|
} catch (error) {
|
|
365
379
|
console.error('[parseOgImageData] Unexpected error:', error);
|
|
@@ -369,4 +383,3 @@ export function parseOgImageData(
|
|
|
369
383
|
|
|
370
384
|
// Export base64 utilities for advanced use cases
|
|
371
385
|
export { encodeBase64, decodeBase64 };
|
|
372
|
-
|