@djangocfg/nextjs 2.1.5 → 2.1.7
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/ai/cli.d.mts +1 -0
- package/dist/ai/cli.mjs +171 -0
- package/dist/ai/cli.mjs.map +1 -0
- package/dist/ai/index.d.mts +81 -0
- package/dist/ai/index.mjs +139 -0
- package/dist/ai/index.mjs.map +1 -0
- package/dist/config/index.d.mts +360 -0
- package/dist/config/index.mjs +1363 -0
- package/dist/config/index.mjs.map +1 -0
- package/dist/constants-HezbftFb.d.mts +10 -0
- package/dist/contact/index.d.mts +47 -0
- package/dist/contact/index.mjs +98 -0
- package/dist/contact/index.mjs.map +1 -0
- package/dist/contact/route.d.mts +35 -0
- package/dist/contact/route.mjs +99 -0
- package/dist/contact/route.mjs.map +1 -0
- package/dist/health/index.d.mts +43 -0
- package/dist/health/index.mjs +38 -0
- package/dist/health/index.mjs.map +1 -0
- package/dist/index.d.mts +19 -0
- package/dist/index.mjs +2565 -0
- package/dist/index.mjs.map +1 -0
- package/dist/navigation/index.d.mts +89 -0
- package/dist/navigation/index.mjs +63 -0
- package/dist/navigation/index.mjs.map +1 -0
- package/dist/og-image/components/index.d.mts +59 -0
- package/dist/og-image/components/index.mjs +325 -0
- package/dist/og-image/components/index.mjs.map +1 -0
- package/dist/og-image/index.d.mts +112 -0
- package/dist/og-image/index.mjs +823 -0
- package/dist/og-image/index.mjs.map +1 -0
- package/dist/og-image/utils/index.d.mts +302 -0
- package/dist/og-image/utils/index.mjs +317 -0
- package/dist/og-image/utils/index.mjs.map +1 -0
- package/dist/sitemap/index.d.mts +66 -0
- package/dist/sitemap/index.mjs +76 -0
- package/dist/sitemap/index.mjs.map +1 -0
- package/dist/types-CwhXnEbK.d.mts +30 -0
- package/package.json +7 -6
- package/src/config/createNextConfig.ts +31 -2
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,2565 @@
|
|
|
1
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
2
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
3
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
4
|
+
}) : x)(function(x) {
|
|
5
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
6
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
7
|
+
});
|
|
8
|
+
var __commonJS = (cb, mod) => function __require2() {
|
|
9
|
+
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
// package.json
|
|
13
|
+
var require_package = __commonJS({
|
|
14
|
+
"package.json"(exports, module) {
|
|
15
|
+
module.exports = {
|
|
16
|
+
name: "@djangocfg/nextjs",
|
|
17
|
+
version: "2.1.7",
|
|
18
|
+
description: "Next.js server utilities: sitemap, health, OG images, contact forms, navigation, config",
|
|
19
|
+
keywords: [
|
|
20
|
+
"nextjs",
|
|
21
|
+
"sitemap",
|
|
22
|
+
"health",
|
|
23
|
+
"og-image",
|
|
24
|
+
"contact",
|
|
25
|
+
"navigation",
|
|
26
|
+
"config",
|
|
27
|
+
"react",
|
|
28
|
+
"typescript"
|
|
29
|
+
],
|
|
30
|
+
author: {
|
|
31
|
+
name: "DjangoCFG",
|
|
32
|
+
url: "https://djangocfg.com"
|
|
33
|
+
},
|
|
34
|
+
homepage: "https://djangocfg.com",
|
|
35
|
+
repository: {
|
|
36
|
+
type: "git",
|
|
37
|
+
url: "https://github.com/markolofsen/django-cfg.git",
|
|
38
|
+
directory: "packages/nextjs"
|
|
39
|
+
},
|
|
40
|
+
bugs: {
|
|
41
|
+
url: "https://github.com/markolofsen/django-cfg/issues"
|
|
42
|
+
},
|
|
43
|
+
license: "MIT",
|
|
44
|
+
main: "./src/index.ts",
|
|
45
|
+
types: "./src/index.ts",
|
|
46
|
+
exports: {
|
|
47
|
+
".": {
|
|
48
|
+
types: "./src/index.ts",
|
|
49
|
+
import: "./src/index.ts",
|
|
50
|
+
default: "./src/index.ts"
|
|
51
|
+
},
|
|
52
|
+
"./sitemap": {
|
|
53
|
+
types: "./src/sitemap/index.ts",
|
|
54
|
+
import: "./src/sitemap/index.ts",
|
|
55
|
+
default: "./src/sitemap/index.ts"
|
|
56
|
+
},
|
|
57
|
+
"./health": {
|
|
58
|
+
types: "./src/health/index.ts",
|
|
59
|
+
import: "./src/health/index.ts",
|
|
60
|
+
default: "./src/health/index.ts"
|
|
61
|
+
},
|
|
62
|
+
"./og-image": {
|
|
63
|
+
types: "./src/og-image/index.ts",
|
|
64
|
+
import: "./src/og-image/index.ts",
|
|
65
|
+
default: "./src/og-image/index.ts"
|
|
66
|
+
},
|
|
67
|
+
"./og-image/utils": {
|
|
68
|
+
types: "./src/og-image/utils/index.ts",
|
|
69
|
+
import: "./src/og-image/utils/index.ts",
|
|
70
|
+
default: "./src/og-image/utils/index.ts"
|
|
71
|
+
},
|
|
72
|
+
"./og-image/components": {
|
|
73
|
+
types: "./src/og-image/components/index.ts",
|
|
74
|
+
import: "./src/og-image/components/index.ts",
|
|
75
|
+
default: "./src/og-image/components/index.ts"
|
|
76
|
+
},
|
|
77
|
+
"./contact": {
|
|
78
|
+
types: "./src/contact/index.ts",
|
|
79
|
+
import: "./src/contact/index.ts",
|
|
80
|
+
default: "./src/contact/index.ts"
|
|
81
|
+
},
|
|
82
|
+
"./navigation": {
|
|
83
|
+
types: "./src/navigation/index.ts",
|
|
84
|
+
import: "./src/navigation/index.ts",
|
|
85
|
+
default: "./src/navigation/index.ts"
|
|
86
|
+
},
|
|
87
|
+
"./config": {
|
|
88
|
+
types: "./src/config/index.ts",
|
|
89
|
+
import: "./src/config/index.ts",
|
|
90
|
+
default: "./src/config/index.ts"
|
|
91
|
+
},
|
|
92
|
+
"./scripts": {
|
|
93
|
+
types: "./src/scripts/index.ts",
|
|
94
|
+
import: "./src/scripts/index.ts",
|
|
95
|
+
default: "./src/scripts/index.ts"
|
|
96
|
+
},
|
|
97
|
+
"./ai": {
|
|
98
|
+
types: "./src/ai/index.ts",
|
|
99
|
+
import: "./src/ai/index.ts",
|
|
100
|
+
default: "./src/ai/index.ts"
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
files: [
|
|
104
|
+
"dist",
|
|
105
|
+
"src",
|
|
106
|
+
"README.md",
|
|
107
|
+
"LICENSE"
|
|
108
|
+
],
|
|
109
|
+
bin: {
|
|
110
|
+
"ai-docs": "./dist/ai/cli.mjs"
|
|
111
|
+
},
|
|
112
|
+
scripts: {
|
|
113
|
+
build: "tsup",
|
|
114
|
+
dev: "tsup --watch",
|
|
115
|
+
clean: "rm -rf dist",
|
|
116
|
+
lint: "eslint .",
|
|
117
|
+
check: "tsc --noEmit",
|
|
118
|
+
"check-links": "tsx src/scripts/check-links.ts",
|
|
119
|
+
"ai-docs": "tsx src/ai/cli.ts"
|
|
120
|
+
},
|
|
121
|
+
peerDependencies: {
|
|
122
|
+
"@djangocfg/api": "workspace:*",
|
|
123
|
+
next: "^15.5.7"
|
|
124
|
+
},
|
|
125
|
+
devDependencies: {
|
|
126
|
+
"@djangocfg/imgai": "workspace:*",
|
|
127
|
+
"@djangocfg/layouts": "workspace:*",
|
|
128
|
+
"@djangocfg/typescript-config": "workspace:*",
|
|
129
|
+
"@types/node": "^24.7.2",
|
|
130
|
+
"@types/react": "19.2.2",
|
|
131
|
+
"@types/react-dom": "19.2.1",
|
|
132
|
+
"@types/semver": "^7.7.1",
|
|
133
|
+
"@types/webpack": "^5.28.5",
|
|
134
|
+
"@vercel/og": "^0.8.5",
|
|
135
|
+
eslint: "^9.37.0",
|
|
136
|
+
linkinator: "^7.5.0",
|
|
137
|
+
"lucide-react": "^0.545.0",
|
|
138
|
+
picocolors: "^1.1.1",
|
|
139
|
+
prompts: "^2.4.2",
|
|
140
|
+
tsup: "^8.0.1",
|
|
141
|
+
tsx: "^4.19.2",
|
|
142
|
+
typescript: "^5.9.3"
|
|
143
|
+
},
|
|
144
|
+
publishConfig: {
|
|
145
|
+
access: "public"
|
|
146
|
+
},
|
|
147
|
+
dependencies: {
|
|
148
|
+
chalk: "^5.3.0",
|
|
149
|
+
conf: "^15.0.2",
|
|
150
|
+
consola: "^3.4.2",
|
|
151
|
+
semver: "^7.7.3"
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
// src/sitemap/route.ts
|
|
158
|
+
import { NextResponse } from "next/server";
|
|
159
|
+
|
|
160
|
+
// src/sitemap/generator.ts
|
|
161
|
+
function generateSitemapXml(urls) {
|
|
162
|
+
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
163
|
+
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
|
|
164
|
+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
165
|
+
xsi:schemaLocation="http://www.sitemaps.org/schemas/sitemap/0.9
|
|
166
|
+
http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd">
|
|
167
|
+
${urls.map(
|
|
168
|
+
({ loc, lastmod, changefreq, priority }) => ` <url>
|
|
169
|
+
<loc>${escapeXml(loc)}</loc>
|
|
170
|
+
${lastmod ? `<lastmod>${formatDate(lastmod)}</lastmod>` : ""}
|
|
171
|
+
${changefreq ? `<changefreq>${changefreq}</changefreq>` : ""}
|
|
172
|
+
${priority !== void 0 ? `<priority>${priority.toFixed(1)}</priority>` : ""}
|
|
173
|
+
</url>`
|
|
174
|
+
).join("\n")}
|
|
175
|
+
</urlset>`;
|
|
176
|
+
}
|
|
177
|
+
function formatDate(date) {
|
|
178
|
+
if (typeof date === "string") {
|
|
179
|
+
return date;
|
|
180
|
+
}
|
|
181
|
+
return date.toISOString().split("T")[0];
|
|
182
|
+
}
|
|
183
|
+
function escapeXml(unsafe) {
|
|
184
|
+
return unsafe.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
185
|
+
}
|
|
186
|
+
function normalizeUrl(url, siteUrl) {
|
|
187
|
+
if (url.startsWith("http://") || url.startsWith("https://")) {
|
|
188
|
+
return url;
|
|
189
|
+
}
|
|
190
|
+
const baseUrl = siteUrl.endsWith("/") ? siteUrl.slice(0, -1) : siteUrl;
|
|
191
|
+
const path = url.startsWith("/") ? url : `/${url}`;
|
|
192
|
+
return `${baseUrl}${path}`;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// src/sitemap/route.ts
|
|
196
|
+
function createSitemapHandler(options) {
|
|
197
|
+
const {
|
|
198
|
+
siteUrl,
|
|
199
|
+
staticPages = [],
|
|
200
|
+
dynamicPages = [],
|
|
201
|
+
cacheControl = "public, s-maxage=86400, stale-while-revalidate"
|
|
202
|
+
} = options;
|
|
203
|
+
return async function GET() {
|
|
204
|
+
const urls = [...staticPages];
|
|
205
|
+
if (dynamicPages) {
|
|
206
|
+
if (typeof dynamicPages === "function") {
|
|
207
|
+
const dynamicUrls = await dynamicPages();
|
|
208
|
+
urls.push(...dynamicUrls);
|
|
209
|
+
} else {
|
|
210
|
+
urls.push(...dynamicPages);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
const normalizedUrls = urls.map((url) => ({
|
|
214
|
+
...url,
|
|
215
|
+
loc: normalizeUrl(url.loc, siteUrl)
|
|
216
|
+
}));
|
|
217
|
+
const sitemap = generateSitemapXml(normalizedUrls);
|
|
218
|
+
return new NextResponse(sitemap, {
|
|
219
|
+
status: 200,
|
|
220
|
+
headers: {
|
|
221
|
+
"Content-Type": "application/xml",
|
|
222
|
+
"Cache-Control": cacheControl
|
|
223
|
+
}
|
|
224
|
+
});
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// src/health/route.ts
|
|
229
|
+
import { NextResponse as NextResponse2 } from "next/server";
|
|
230
|
+
function createHealthHandler(config = {}) {
|
|
231
|
+
const { version, checks = [], customData = {} } = config;
|
|
232
|
+
return async function GET() {
|
|
233
|
+
const startTime = Date.now();
|
|
234
|
+
let status = "ok";
|
|
235
|
+
const checkResults = {};
|
|
236
|
+
if (checks.length > 0) {
|
|
237
|
+
for (const check of checks) {
|
|
238
|
+
try {
|
|
239
|
+
const result = await check.check();
|
|
240
|
+
checkResults[check.name] = result;
|
|
241
|
+
if (!result) {
|
|
242
|
+
status = "error";
|
|
243
|
+
}
|
|
244
|
+
} catch (error) {
|
|
245
|
+
checkResults[check.name] = false;
|
|
246
|
+
status = "error";
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
const response = {
|
|
251
|
+
status,
|
|
252
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
253
|
+
uptime: process.uptime(),
|
|
254
|
+
...version && { version },
|
|
255
|
+
...Object.keys(checkResults).length > 0 && { checks: checkResults },
|
|
256
|
+
...customData
|
|
257
|
+
};
|
|
258
|
+
const statusCode = status === "ok" ? 200 : 503;
|
|
259
|
+
return NextResponse2.json(response, { status: statusCode });
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// src/og-image/route.tsx
|
|
264
|
+
import { ImageResponse } from "next/og";
|
|
265
|
+
import { NextRequest } from "next/server";
|
|
266
|
+
|
|
267
|
+
// src/og-image/utils/fonts.ts
|
|
268
|
+
async function loadGoogleFont(font, text, weight = 700) {
|
|
269
|
+
let url = `https://fonts.googleapis.com/css2?family=${font}:wght@${weight}`;
|
|
270
|
+
if (text) {
|
|
271
|
+
url += `&text=${encodeURIComponent(text)}`;
|
|
272
|
+
}
|
|
273
|
+
try {
|
|
274
|
+
const css = await fetch(url, {
|
|
275
|
+
headers: {
|
|
276
|
+
// Required to get TTF format instead of WOFF2
|
|
277
|
+
"User-Agent": "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_8; de-at) AppleWebKit/533.21.1 (KHTML, like Gecko) Version/5.0.5 Safari/533.21.1"
|
|
278
|
+
}
|
|
279
|
+
}).then((res) => res.text());
|
|
280
|
+
const resource = css.match(/src: url\((.+)\) format\('(opentype|truetype)'\)/);
|
|
281
|
+
if (!resource || !resource[1]) {
|
|
282
|
+
throw new Error(`Failed to parse font URL from CSS for font: ${font}`);
|
|
283
|
+
}
|
|
284
|
+
const response = await fetch(resource[1]);
|
|
285
|
+
if (response.status !== 200) {
|
|
286
|
+
throw new Error(`Failed to fetch font data: HTTP ${response.status}`);
|
|
287
|
+
}
|
|
288
|
+
return await response.arrayBuffer();
|
|
289
|
+
} catch (error) {
|
|
290
|
+
console.error(`Error loading Google Font "${font}":`, error);
|
|
291
|
+
throw new Error(`Failed to load font "${font}": ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
async function loadGoogleFonts(fonts) {
|
|
295
|
+
const fontConfigs = await Promise.all(
|
|
296
|
+
fonts.map(async ({ family, weight = 700, style = "normal", text }) => {
|
|
297
|
+
const data = await loadGoogleFont(family, text, weight);
|
|
298
|
+
return {
|
|
299
|
+
name: family,
|
|
300
|
+
weight,
|
|
301
|
+
style,
|
|
302
|
+
data
|
|
303
|
+
};
|
|
304
|
+
})
|
|
305
|
+
);
|
|
306
|
+
return fontConfigs;
|
|
307
|
+
}
|
|
308
|
+
function createFontLoader() {
|
|
309
|
+
const cache = /* @__PURE__ */ new Map();
|
|
310
|
+
return {
|
|
311
|
+
/**
|
|
312
|
+
* Load a font with caching
|
|
313
|
+
*/
|
|
314
|
+
async load(family, weight = 700, text) {
|
|
315
|
+
const cacheKey = `${family}-${weight}-${text || "all"}`;
|
|
316
|
+
if (!cache.has(cacheKey)) {
|
|
317
|
+
cache.set(cacheKey, loadGoogleFont(family, text, weight));
|
|
318
|
+
}
|
|
319
|
+
return cache.get(cacheKey);
|
|
320
|
+
},
|
|
321
|
+
/**
|
|
322
|
+
* Clear the cache
|
|
323
|
+
*/
|
|
324
|
+
clear() {
|
|
325
|
+
cache.clear();
|
|
326
|
+
},
|
|
327
|
+
/**
|
|
328
|
+
* Get cache size
|
|
329
|
+
*/
|
|
330
|
+
size() {
|
|
331
|
+
return cache.size;
|
|
332
|
+
}
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// src/og-image/utils/url.ts
|
|
337
|
+
var DEFAULT_OG_IMAGE_BASE_URL = "https://djangocfg.com/api/og";
|
|
338
|
+
function encodeBase64(str) {
|
|
339
|
+
if (typeof Buffer !== "undefined") {
|
|
340
|
+
return Buffer.from(str, "utf-8").toString("base64");
|
|
341
|
+
}
|
|
342
|
+
return btoa(unescape(encodeURIComponent(str)));
|
|
343
|
+
}
|
|
344
|
+
function decodeBase64(str) {
|
|
345
|
+
if (typeof Buffer !== "undefined") {
|
|
346
|
+
return Buffer.from(str, "base64").toString("utf-8");
|
|
347
|
+
}
|
|
348
|
+
try {
|
|
349
|
+
const binaryString = atob(str);
|
|
350
|
+
return decodeURIComponent(
|
|
351
|
+
binaryString.split("").map((c) => "%" + ("00" + c.charCodeAt(0).toString(16)).slice(-2)).join("")
|
|
352
|
+
);
|
|
353
|
+
} catch (error) {
|
|
354
|
+
return decodeURIComponent(escape(atob(str)));
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
function generateOgImageUrl(params, options = {}) {
|
|
358
|
+
const {
|
|
359
|
+
baseUrl = DEFAULT_OG_IMAGE_BASE_URL,
|
|
360
|
+
useBase64 = true
|
|
361
|
+
} = options;
|
|
362
|
+
if (useBase64) {
|
|
363
|
+
const cleanParams = {};
|
|
364
|
+
Object.entries(params).forEach(([key, value]) => {
|
|
365
|
+
if (value !== void 0 && value !== null && value !== "") {
|
|
366
|
+
cleanParams[key] = value;
|
|
367
|
+
}
|
|
368
|
+
});
|
|
369
|
+
const jsonString = JSON.stringify(cleanParams);
|
|
370
|
+
const base64Data = encodeBase64(jsonString);
|
|
371
|
+
return `${baseUrl}/${base64Data}`;
|
|
372
|
+
} else {
|
|
373
|
+
const searchParams = new URLSearchParams();
|
|
374
|
+
Object.entries(params).forEach(([key, value]) => {
|
|
375
|
+
if (value !== void 0 && value !== null && value !== "") {
|
|
376
|
+
searchParams.append(key, String(value));
|
|
377
|
+
}
|
|
378
|
+
});
|
|
379
|
+
const query = searchParams.toString();
|
|
380
|
+
return query ? `${baseUrl}?${query}` : baseUrl;
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
function getAbsoluteOgImageUrl(relativePath, siteUrl) {
|
|
384
|
+
if (relativePath.startsWith("http://") || relativePath.startsWith("https://")) {
|
|
385
|
+
return relativePath;
|
|
386
|
+
}
|
|
387
|
+
const cleanSiteUrl = siteUrl.replace(/\/$/, "");
|
|
388
|
+
const cleanPath = relativePath.startsWith("/") ? relativePath : `/${relativePath}`;
|
|
389
|
+
return `${cleanSiteUrl}${cleanPath}`;
|
|
390
|
+
}
|
|
391
|
+
function createOgImageUrlBuilder(defaults = {}, options = {}) {
|
|
392
|
+
return (params) => {
|
|
393
|
+
return generateOgImageUrl(
|
|
394
|
+
{ ...defaults, ...params },
|
|
395
|
+
options
|
|
396
|
+
);
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
function parseOgImageUrl(url) {
|
|
400
|
+
try {
|
|
401
|
+
const urlObj = new URL(url, "http://dummy.com");
|
|
402
|
+
const params = {};
|
|
403
|
+
urlObj.searchParams.forEach((value, key) => {
|
|
404
|
+
params[key] = value;
|
|
405
|
+
});
|
|
406
|
+
return params;
|
|
407
|
+
} catch {
|
|
408
|
+
return {};
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
function parseOgImageData(searchParams) {
|
|
412
|
+
try {
|
|
413
|
+
let params;
|
|
414
|
+
if (searchParams instanceof URLSearchParams) {
|
|
415
|
+
params = {};
|
|
416
|
+
for (const [key, value] of searchParams.entries()) {
|
|
417
|
+
params[key] = value;
|
|
418
|
+
}
|
|
419
|
+
} else {
|
|
420
|
+
params = searchParams;
|
|
421
|
+
}
|
|
422
|
+
if (process.env.NODE_ENV === "development") {
|
|
423
|
+
console.log("[parseOgImageData] Input params keys:", Object.keys(params));
|
|
424
|
+
console.log("[parseOgImageData] Input params:", params);
|
|
425
|
+
}
|
|
426
|
+
const dataParam = params.data;
|
|
427
|
+
if (dataParam && typeof dataParam === "string" && dataParam.trim() !== "") {
|
|
428
|
+
if (process.env.NODE_ENV === "development") {
|
|
429
|
+
console.log("[parseOgImageData] Found data param, length:", dataParam.length);
|
|
430
|
+
}
|
|
431
|
+
try {
|
|
432
|
+
const decoded = decodeBase64(dataParam);
|
|
433
|
+
if (process.env.NODE_ENV === "development") {
|
|
434
|
+
console.log("[parseOgImageData] Decoded string:", decoded.substring(0, 100));
|
|
435
|
+
}
|
|
436
|
+
const parsed = JSON.parse(decoded);
|
|
437
|
+
if (process.env.NODE_ENV === "development") {
|
|
438
|
+
console.log("[parseOgImageData] Parsed JSON:", parsed);
|
|
439
|
+
}
|
|
440
|
+
const result2 = {};
|
|
441
|
+
for (const [key, value] of Object.entries(parsed)) {
|
|
442
|
+
if (value !== void 0 && value !== null) {
|
|
443
|
+
result2[key] = String(value);
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
if (process.env.NODE_ENV === "development") {
|
|
447
|
+
console.log("[parseOgImageData] Result:", result2);
|
|
448
|
+
}
|
|
449
|
+
return result2;
|
|
450
|
+
} catch (decodeError) {
|
|
451
|
+
console.error("[parseOgImageData] Error decoding/parsing data param:", decodeError);
|
|
452
|
+
if (decodeError instanceof Error) {
|
|
453
|
+
console.error("[parseOgImageData] Error message:", decodeError.message);
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
} else {
|
|
457
|
+
if (process.env.NODE_ENV === "development") {
|
|
458
|
+
console.log("[parseOgImageData] No data param found or empty");
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
const result = {};
|
|
462
|
+
for (const [key, value] of Object.entries(params)) {
|
|
463
|
+
if (key !== "data" && value !== void 0 && value !== null) {
|
|
464
|
+
result[key] = Array.isArray(value) ? value[0] : String(value);
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
if (process.env.NODE_ENV === "development") {
|
|
468
|
+
console.log("[parseOgImageData] Fallback result:", result);
|
|
469
|
+
}
|
|
470
|
+
return result;
|
|
471
|
+
} catch (error) {
|
|
472
|
+
console.error("[parseOgImageData] Unexpected error:", error);
|
|
473
|
+
return {};
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
// src/og-image/utils/metadata.ts
|
|
478
|
+
function extractTitle(metadata) {
|
|
479
|
+
if (typeof metadata.title === "string") {
|
|
480
|
+
return metadata.title;
|
|
481
|
+
}
|
|
482
|
+
if (metadata.title) {
|
|
483
|
+
if ("default" in metadata.title) {
|
|
484
|
+
return metadata.title.default;
|
|
485
|
+
}
|
|
486
|
+
if ("absolute" in metadata.title) {
|
|
487
|
+
return metadata.title.absolute;
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
return "";
|
|
491
|
+
}
|
|
492
|
+
function extractDescription(metadata) {
|
|
493
|
+
if (typeof metadata.description === "string") {
|
|
494
|
+
return metadata.description;
|
|
495
|
+
}
|
|
496
|
+
return "";
|
|
497
|
+
}
|
|
498
|
+
function getSiteUrl() {
|
|
499
|
+
if (typeof process !== "undefined" && process.env.NEXT_PUBLIC_SITE_URL) {
|
|
500
|
+
return process.env.NEXT_PUBLIC_SITE_URL;
|
|
501
|
+
}
|
|
502
|
+
return "";
|
|
503
|
+
}
|
|
504
|
+
function generateOgImageMetadata(metadata, ogImageParams, options = {}) {
|
|
505
|
+
const {
|
|
506
|
+
ogImageBaseUrl = "https://djangocfg.com/api/og",
|
|
507
|
+
siteUrl: providedSiteUrl,
|
|
508
|
+
defaultParams = {},
|
|
509
|
+
useBase64 = true
|
|
510
|
+
} = options;
|
|
511
|
+
const siteUrl = providedSiteUrl && providedSiteUrl !== "undefined" ? providedSiteUrl : getSiteUrl();
|
|
512
|
+
const extractedTitle = extractTitle(metadata);
|
|
513
|
+
const extractedDescription = extractDescription(metadata);
|
|
514
|
+
const finalOgImageParams = {
|
|
515
|
+
...defaultParams,
|
|
516
|
+
title: ogImageParams?.title || extractedTitle || defaultParams.title || "",
|
|
517
|
+
description: ogImageParams?.description || extractedDescription || defaultParams.description || "",
|
|
518
|
+
...ogImageParams
|
|
519
|
+
};
|
|
520
|
+
const imageAlt = finalOgImageParams.title || finalOgImageParams.siteName;
|
|
521
|
+
const relativeOgImageUrl = generateOgImageUrl(
|
|
522
|
+
finalOgImageParams,
|
|
523
|
+
{ baseUrl: ogImageBaseUrl, useBase64 }
|
|
524
|
+
);
|
|
525
|
+
const ogImageUrl = siteUrl ? getAbsoluteOgImageUrl(relativeOgImageUrl, siteUrl) : relativeOgImageUrl;
|
|
526
|
+
const existingOgImages = metadata.openGraph?.images ? Array.isArray(metadata.openGraph.images) ? metadata.openGraph.images : [metadata.openGraph.images] : [];
|
|
527
|
+
const existingTwitterImages = metadata.twitter?.images ? Array.isArray(metadata.twitter.images) ? metadata.twitter.images : [metadata.twitter.images] : [];
|
|
528
|
+
const finalMetadata = {
|
|
529
|
+
...metadata,
|
|
530
|
+
openGraph: {
|
|
531
|
+
...metadata.openGraph,
|
|
532
|
+
images: [
|
|
533
|
+
...existingOgImages,
|
|
534
|
+
{
|
|
535
|
+
url: ogImageUrl,
|
|
536
|
+
width: 1200,
|
|
537
|
+
height: 630,
|
|
538
|
+
alt: imageAlt
|
|
539
|
+
}
|
|
540
|
+
]
|
|
541
|
+
},
|
|
542
|
+
twitter: {
|
|
543
|
+
...metadata.twitter,
|
|
544
|
+
card: "summary_large_image",
|
|
545
|
+
images: [
|
|
546
|
+
...existingTwitterImages,
|
|
547
|
+
{
|
|
548
|
+
url: ogImageUrl,
|
|
549
|
+
alt: imageAlt
|
|
550
|
+
}
|
|
551
|
+
]
|
|
552
|
+
}
|
|
553
|
+
};
|
|
554
|
+
if (!finalMetadata.metadataBase && siteUrl) {
|
|
555
|
+
if (siteUrl.startsWith("http://") || siteUrl.startsWith("https://")) {
|
|
556
|
+
try {
|
|
557
|
+
finalMetadata.metadataBase = new URL(siteUrl);
|
|
558
|
+
} catch (e) {
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
return finalMetadata;
|
|
563
|
+
}
|
|
564
|
+
function createOgImageMetadataGenerator(options) {
|
|
565
|
+
return (metadata, ogImageParams) => {
|
|
566
|
+
return generateOgImageMetadata(metadata, ogImageParams, options);
|
|
567
|
+
};
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
// src/og-image/components/DefaultTemplate.tsx
|
|
571
|
+
function DefaultTemplate({
|
|
572
|
+
title,
|
|
573
|
+
description,
|
|
574
|
+
siteName,
|
|
575
|
+
logo,
|
|
576
|
+
// Visibility flags
|
|
577
|
+
showLogo = true,
|
|
578
|
+
showSiteName = true,
|
|
579
|
+
// Background customization
|
|
580
|
+
backgroundType = "gradient",
|
|
581
|
+
gradientStart = "#667eea",
|
|
582
|
+
gradientEnd = "#764ba2",
|
|
583
|
+
backgroundColor = "#ffffff",
|
|
584
|
+
// Typography - Title
|
|
585
|
+
titleSize,
|
|
586
|
+
titleWeight = 800,
|
|
587
|
+
titleColor = "white",
|
|
588
|
+
// Typography - Description
|
|
589
|
+
descriptionSize = 32,
|
|
590
|
+
descriptionColor = "rgba(255, 255, 255, 0.85)",
|
|
591
|
+
// Typography - Site Name
|
|
592
|
+
siteNameSize = 28,
|
|
593
|
+
siteNameColor = "rgba(255, 255, 255, 0.95)",
|
|
594
|
+
// Layout
|
|
595
|
+
padding = 80,
|
|
596
|
+
logoSize = 48,
|
|
597
|
+
// Dev mode
|
|
598
|
+
devMode = false
|
|
599
|
+
}) {
|
|
600
|
+
const calculatedTitleSize = titleSize || (title.length > 60 ? 56 : 72);
|
|
601
|
+
const backgroundStyle = backgroundType === "gradient" ? `linear-gradient(135deg, ${gradientStart} 0%, ${gradientEnd} 100%)` : backgroundColor;
|
|
602
|
+
const gridOverlay = devMode ? /* @__PURE__ */ React.createElement(
|
|
603
|
+
"div",
|
|
604
|
+
{
|
|
605
|
+
style: {
|
|
606
|
+
position: "absolute",
|
|
607
|
+
top: 0,
|
|
608
|
+
left: 0,
|
|
609
|
+
right: 0,
|
|
610
|
+
bottom: 0,
|
|
611
|
+
backgroundImage: `
|
|
612
|
+
linear-gradient(rgba(0, 0, 0, 0.1) 1px, transparent 1px),
|
|
613
|
+
linear-gradient(90deg, rgba(0, 0, 0, 0.1) 1px, transparent 1px)
|
|
614
|
+
`,
|
|
615
|
+
backgroundSize: "20px 20px",
|
|
616
|
+
pointerEvents: "none",
|
|
617
|
+
zIndex: 10
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
) : null;
|
|
621
|
+
return /* @__PURE__ */ React.createElement(
|
|
622
|
+
"div",
|
|
623
|
+
{
|
|
624
|
+
style: {
|
|
625
|
+
height: "100%",
|
|
626
|
+
width: "100%",
|
|
627
|
+
display: "flex",
|
|
628
|
+
flexDirection: "column",
|
|
629
|
+
alignItems: "flex-start",
|
|
630
|
+
justifyContent: "space-between",
|
|
631
|
+
background: backgroundStyle,
|
|
632
|
+
padding: `${padding}px`,
|
|
633
|
+
fontFamily: "system-ui, -apple-system, sans-serif",
|
|
634
|
+
position: "relative"
|
|
635
|
+
}
|
|
636
|
+
},
|
|
637
|
+
gridOverlay,
|
|
638
|
+
(showLogo && logo || showSiteName && siteName) && /* @__PURE__ */ React.createElement(
|
|
639
|
+
"div",
|
|
640
|
+
{
|
|
641
|
+
style: {
|
|
642
|
+
display: "flex",
|
|
643
|
+
alignItems: "center",
|
|
644
|
+
gap: "16px"
|
|
645
|
+
}
|
|
646
|
+
},
|
|
647
|
+
showLogo && logo && // eslint-disable-next-line @next/next/no-img-element
|
|
648
|
+
/* @__PURE__ */ React.createElement(
|
|
649
|
+
"img",
|
|
650
|
+
{
|
|
651
|
+
src: logo,
|
|
652
|
+
alt: "Logo",
|
|
653
|
+
width: logoSize,
|
|
654
|
+
height: logoSize,
|
|
655
|
+
style: {
|
|
656
|
+
borderRadius: "8px"
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
),
|
|
660
|
+
showSiteName && siteName && /* @__PURE__ */ React.createElement(
|
|
661
|
+
"div",
|
|
662
|
+
{
|
|
663
|
+
style: {
|
|
664
|
+
fontSize: siteNameSize,
|
|
665
|
+
fontWeight: 600,
|
|
666
|
+
color: siteNameColor,
|
|
667
|
+
letterSpacing: "-0.02em"
|
|
668
|
+
}
|
|
669
|
+
},
|
|
670
|
+
siteName
|
|
671
|
+
)
|
|
672
|
+
),
|
|
673
|
+
/* @__PURE__ */ React.createElement(
|
|
674
|
+
"div",
|
|
675
|
+
{
|
|
676
|
+
style: {
|
|
677
|
+
display: "flex",
|
|
678
|
+
flexDirection: "column",
|
|
679
|
+
gap: "24px",
|
|
680
|
+
flex: 1,
|
|
681
|
+
justifyContent: "center"
|
|
682
|
+
}
|
|
683
|
+
},
|
|
684
|
+
/* @__PURE__ */ React.createElement(
|
|
685
|
+
"div",
|
|
686
|
+
{
|
|
687
|
+
style: {
|
|
688
|
+
fontSize: calculatedTitleSize,
|
|
689
|
+
fontWeight: titleWeight,
|
|
690
|
+
color: titleColor,
|
|
691
|
+
lineHeight: 1.1,
|
|
692
|
+
letterSpacing: "-0.03em",
|
|
693
|
+
textShadow: backgroundType === "gradient" ? "0 2px 20px rgba(0, 0, 0, 0.2)" : "none",
|
|
694
|
+
maxWidth: "100%",
|
|
695
|
+
wordWrap: "break-word"
|
|
696
|
+
}
|
|
697
|
+
},
|
|
698
|
+
title
|
|
699
|
+
),
|
|
700
|
+
description && /* @__PURE__ */ React.createElement(
|
|
701
|
+
"div",
|
|
702
|
+
{
|
|
703
|
+
style: {
|
|
704
|
+
fontSize: descriptionSize,
|
|
705
|
+
fontWeight: 400,
|
|
706
|
+
color: descriptionColor,
|
|
707
|
+
lineHeight: 1.5,
|
|
708
|
+
letterSpacing: "-0.01em",
|
|
709
|
+
maxWidth: "90%",
|
|
710
|
+
display: "-webkit-box",
|
|
711
|
+
WebkitLineClamp: 2,
|
|
712
|
+
WebkitBoxOrient: "vertical",
|
|
713
|
+
overflow: "hidden"
|
|
714
|
+
}
|
|
715
|
+
},
|
|
716
|
+
description
|
|
717
|
+
)
|
|
718
|
+
),
|
|
719
|
+
/* @__PURE__ */ React.createElement(
|
|
720
|
+
"div",
|
|
721
|
+
{
|
|
722
|
+
style: {
|
|
723
|
+
display: "flex",
|
|
724
|
+
width: "100%",
|
|
725
|
+
height: "4px",
|
|
726
|
+
background: backgroundType === "gradient" ? `linear-gradient(90deg, ${gradientStart} 0%, ${gradientEnd} 100%)` : gradientStart,
|
|
727
|
+
borderRadius: "2px"
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
)
|
|
731
|
+
);
|
|
732
|
+
}
|
|
733
|
+
function LightTemplate({
|
|
734
|
+
title,
|
|
735
|
+
description,
|
|
736
|
+
siteName,
|
|
737
|
+
logo,
|
|
738
|
+
// Visibility flags
|
|
739
|
+
showLogo = true,
|
|
740
|
+
showSiteName = true,
|
|
741
|
+
// Background customization (defaults to light theme)
|
|
742
|
+
backgroundType = "solid",
|
|
743
|
+
gradientStart = "#667eea",
|
|
744
|
+
gradientEnd = "#764ba2",
|
|
745
|
+
backgroundColor = "#ffffff",
|
|
746
|
+
// Typography - Title
|
|
747
|
+
titleSize,
|
|
748
|
+
titleWeight = 800,
|
|
749
|
+
titleColor = "#111",
|
|
750
|
+
// Typography - Description
|
|
751
|
+
descriptionSize = 32,
|
|
752
|
+
descriptionColor = "#666",
|
|
753
|
+
// Typography - Site Name
|
|
754
|
+
siteNameSize = 28,
|
|
755
|
+
siteNameColor = "#111",
|
|
756
|
+
// Layout
|
|
757
|
+
padding = 80,
|
|
758
|
+
logoSize = 48,
|
|
759
|
+
// Dev mode
|
|
760
|
+
devMode = false
|
|
761
|
+
}) {
|
|
762
|
+
const calculatedTitleSize = titleSize || (title.length > 60 ? 56 : 72);
|
|
763
|
+
const backgroundStyle = backgroundType === "gradient" ? `linear-gradient(135deg, ${gradientStart} 0%, ${gradientEnd} 100%)` : backgroundColor;
|
|
764
|
+
const gridOverlay = devMode ? /* @__PURE__ */ React.createElement(
|
|
765
|
+
"div",
|
|
766
|
+
{
|
|
767
|
+
style: {
|
|
768
|
+
position: "absolute",
|
|
769
|
+
top: 0,
|
|
770
|
+
left: 0,
|
|
771
|
+
right: 0,
|
|
772
|
+
bottom: 0,
|
|
773
|
+
backgroundImage: `
|
|
774
|
+
linear-gradient(rgba(0, 0, 0, 0.1) 1px, transparent 1px),
|
|
775
|
+
linear-gradient(90deg, rgba(0, 0, 0, 0.1) 1px, transparent 1px)
|
|
776
|
+
`,
|
|
777
|
+
backgroundSize: "20px 20px",
|
|
778
|
+
pointerEvents: "none",
|
|
779
|
+
zIndex: 10
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
) : null;
|
|
783
|
+
return /* @__PURE__ */ React.createElement(
|
|
784
|
+
"div",
|
|
785
|
+
{
|
|
786
|
+
style: {
|
|
787
|
+
height: "100%",
|
|
788
|
+
width: "100%",
|
|
789
|
+
display: "flex",
|
|
790
|
+
flexDirection: "column",
|
|
791
|
+
alignItems: "flex-start",
|
|
792
|
+
justifyContent: "space-between",
|
|
793
|
+
background: backgroundStyle,
|
|
794
|
+
padding: `${padding}px`,
|
|
795
|
+
fontFamily: "system-ui, -apple-system, sans-serif",
|
|
796
|
+
position: "relative"
|
|
797
|
+
}
|
|
798
|
+
},
|
|
799
|
+
gridOverlay,
|
|
800
|
+
(showLogo && logo || showSiteName && siteName) && /* @__PURE__ */ React.createElement(
|
|
801
|
+
"div",
|
|
802
|
+
{
|
|
803
|
+
style: {
|
|
804
|
+
display: "flex",
|
|
805
|
+
alignItems: "center",
|
|
806
|
+
gap: "16px"
|
|
807
|
+
}
|
|
808
|
+
},
|
|
809
|
+
showLogo && logo && // eslint-disable-next-line @next/next/no-img-element
|
|
810
|
+
/* @__PURE__ */ React.createElement(
|
|
811
|
+
"img",
|
|
812
|
+
{
|
|
813
|
+
src: logo,
|
|
814
|
+
alt: "Logo",
|
|
815
|
+
width: logoSize,
|
|
816
|
+
height: logoSize,
|
|
817
|
+
style: {
|
|
818
|
+
borderRadius: "8px"
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
),
|
|
822
|
+
showSiteName && siteName && /* @__PURE__ */ React.createElement(
|
|
823
|
+
"div",
|
|
824
|
+
{
|
|
825
|
+
style: {
|
|
826
|
+
fontSize: siteNameSize,
|
|
827
|
+
fontWeight: 600,
|
|
828
|
+
color: siteNameColor,
|
|
829
|
+
letterSpacing: "-0.02em"
|
|
830
|
+
}
|
|
831
|
+
},
|
|
832
|
+
siteName
|
|
833
|
+
)
|
|
834
|
+
),
|
|
835
|
+
/* @__PURE__ */ React.createElement(
|
|
836
|
+
"div",
|
|
837
|
+
{
|
|
838
|
+
style: {
|
|
839
|
+
display: "flex",
|
|
840
|
+
flexDirection: "column",
|
|
841
|
+
gap: "24px",
|
|
842
|
+
flex: 1,
|
|
843
|
+
justifyContent: "center"
|
|
844
|
+
}
|
|
845
|
+
},
|
|
846
|
+
/* @__PURE__ */ React.createElement(
|
|
847
|
+
"div",
|
|
848
|
+
{
|
|
849
|
+
style: {
|
|
850
|
+
fontSize: calculatedTitleSize,
|
|
851
|
+
fontWeight: titleWeight,
|
|
852
|
+
color: titleColor,
|
|
853
|
+
lineHeight: 1.1,
|
|
854
|
+
letterSpacing: "-0.03em",
|
|
855
|
+
maxWidth: "100%",
|
|
856
|
+
wordWrap: "break-word"
|
|
857
|
+
}
|
|
858
|
+
},
|
|
859
|
+
title
|
|
860
|
+
),
|
|
861
|
+
description && /* @__PURE__ */ React.createElement(
|
|
862
|
+
"div",
|
|
863
|
+
{
|
|
864
|
+
style: {
|
|
865
|
+
fontSize: descriptionSize,
|
|
866
|
+
fontWeight: 400,
|
|
867
|
+
color: descriptionColor,
|
|
868
|
+
lineHeight: 1.5,
|
|
869
|
+
letterSpacing: "-0.01em",
|
|
870
|
+
maxWidth: "90%"
|
|
871
|
+
}
|
|
872
|
+
},
|
|
873
|
+
description
|
|
874
|
+
)
|
|
875
|
+
),
|
|
876
|
+
/* @__PURE__ */ React.createElement(
|
|
877
|
+
"div",
|
|
878
|
+
{
|
|
879
|
+
style: {
|
|
880
|
+
display: "flex",
|
|
881
|
+
width: "100%",
|
|
882
|
+
height: "4px",
|
|
883
|
+
background: backgroundType === "gradient" ? `linear-gradient(90deg, ${gradientStart} 0%, ${gradientEnd} 100%)` : gradientStart,
|
|
884
|
+
borderRadius: "2px"
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
)
|
|
888
|
+
);
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
// src/og-image/route.tsx
|
|
892
|
+
function createOgImageHandler(config) {
|
|
893
|
+
const {
|
|
894
|
+
template: Template = DefaultTemplate,
|
|
895
|
+
defaultProps = {},
|
|
896
|
+
fonts: fontConfig = [],
|
|
897
|
+
size = { width: 1200, height: 630 },
|
|
898
|
+
debug = false
|
|
899
|
+
} = config;
|
|
900
|
+
async function GET(req) {
|
|
901
|
+
let searchParams = new URLSearchParams();
|
|
902
|
+
if (req.nextUrl?.searchParams && req.nextUrl.searchParams.size > 0) {
|
|
903
|
+
searchParams = req.nextUrl.searchParams;
|
|
904
|
+
} else if (req.nextUrl?.search && req.nextUrl.search.length > 1) {
|
|
905
|
+
searchParams = new URLSearchParams(req.nextUrl.search);
|
|
906
|
+
} else {
|
|
907
|
+
try {
|
|
908
|
+
const url = new URL(req.url);
|
|
909
|
+
if (url.searchParams.size > 0) {
|
|
910
|
+
searchParams = url.searchParams;
|
|
911
|
+
}
|
|
912
|
+
} catch (error) {
|
|
913
|
+
}
|
|
914
|
+
if (searchParams.size === 0 && req.url) {
|
|
915
|
+
const queryIndex = req.url.indexOf("?");
|
|
916
|
+
if (queryIndex !== -1) {
|
|
917
|
+
const queryString = req.url.substring(queryIndex + 1);
|
|
918
|
+
searchParams = new URLSearchParams(queryString);
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
if (searchParams.size === 0) {
|
|
922
|
+
const customParams = req.headers.get("x-og-search-params");
|
|
923
|
+
if (customParams) {
|
|
924
|
+
searchParams = new URLSearchParams(customParams);
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
let title = defaultProps.title || "Untitled";
|
|
929
|
+
let subtitle = defaultProps.subtitle || "";
|
|
930
|
+
let description = defaultProps.description || subtitle;
|
|
931
|
+
const dataParam = searchParams.get("data");
|
|
932
|
+
let decodedParams = {};
|
|
933
|
+
if (dataParam) {
|
|
934
|
+
try {
|
|
935
|
+
const paramsObj = { data: dataParam };
|
|
936
|
+
for (const [key, value] of searchParams.entries()) {
|
|
937
|
+
if (key !== "data") {
|
|
938
|
+
paramsObj[key] = value;
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
decodedParams = parseOgImageData(paramsObj);
|
|
942
|
+
if (decodedParams.title && typeof decodedParams.title === "string" && decodedParams.title.trim() !== "") {
|
|
943
|
+
title = decodedParams.title.trim();
|
|
944
|
+
}
|
|
945
|
+
if (decodedParams.subtitle && typeof decodedParams.subtitle === "string" && decodedParams.subtitle.trim() !== "") {
|
|
946
|
+
subtitle = decodedParams.subtitle.trim();
|
|
947
|
+
}
|
|
948
|
+
if (decodedParams.description && typeof decodedParams.description === "string" && decodedParams.description.trim() !== "") {
|
|
949
|
+
description = decodedParams.description.trim();
|
|
950
|
+
}
|
|
951
|
+
} catch (error) {
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
if (!title || title === "Untitled") {
|
|
955
|
+
const titleParam = searchParams.get("title");
|
|
956
|
+
if (titleParam) {
|
|
957
|
+
title = titleParam;
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
if (!subtitle) {
|
|
961
|
+
const subtitleParam = searchParams.get("subtitle");
|
|
962
|
+
if (subtitleParam) {
|
|
963
|
+
subtitle = subtitleParam;
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
if (!description || description === subtitle) {
|
|
967
|
+
const descParam = searchParams.get("description");
|
|
968
|
+
if (descParam) {
|
|
969
|
+
description = descParam;
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
let fonts = [];
|
|
973
|
+
if (fontConfig.length > 0) {
|
|
974
|
+
fonts = await loadGoogleFonts(fontConfig);
|
|
975
|
+
}
|
|
976
|
+
const parseValue = (value, type = "string") => {
|
|
977
|
+
if (value === void 0 || value === null || value === "") {
|
|
978
|
+
return void 0;
|
|
979
|
+
}
|
|
980
|
+
if (type === "number") {
|
|
981
|
+
const num = Number(value);
|
|
982
|
+
return isNaN(num) ? void 0 : num;
|
|
983
|
+
}
|
|
984
|
+
if (type === "boolean") {
|
|
985
|
+
if (typeof value === "boolean") return value;
|
|
986
|
+
if (typeof value === "string") {
|
|
987
|
+
return value.toLowerCase() === "true" || value === "1";
|
|
988
|
+
}
|
|
989
|
+
return Boolean(value);
|
|
990
|
+
}
|
|
991
|
+
return String(value);
|
|
992
|
+
};
|
|
993
|
+
const templateProps = {
|
|
994
|
+
...defaultProps,
|
|
995
|
+
// Content
|
|
996
|
+
title,
|
|
997
|
+
subtitle,
|
|
998
|
+
description,
|
|
999
|
+
// Override with decoded params if present
|
|
1000
|
+
siteName: decodedParams.siteName || defaultProps.siteName,
|
|
1001
|
+
logo: decodedParams.logo || defaultProps.logo,
|
|
1002
|
+
// Background
|
|
1003
|
+
backgroundType: decodedParams.backgroundType || defaultProps.backgroundType,
|
|
1004
|
+
gradientStart: decodedParams.gradientStart || defaultProps.gradientStart,
|
|
1005
|
+
gradientEnd: decodedParams.gradientEnd || defaultProps.gradientEnd,
|
|
1006
|
+
backgroundColor: decodedParams.backgroundColor || defaultProps.backgroundColor,
|
|
1007
|
+
// Typography - Title
|
|
1008
|
+
titleSize: parseValue(decodedParams.titleSize, "number") ?? defaultProps.titleSize,
|
|
1009
|
+
titleWeight: parseValue(decodedParams.titleWeight, "number") ?? defaultProps.titleWeight,
|
|
1010
|
+
titleColor: decodedParams.titleColor || defaultProps.titleColor,
|
|
1011
|
+
// Typography - Description
|
|
1012
|
+
descriptionSize: parseValue(decodedParams.descriptionSize, "number") ?? defaultProps.descriptionSize,
|
|
1013
|
+
descriptionColor: decodedParams.descriptionColor || defaultProps.descriptionColor,
|
|
1014
|
+
// Typography - Site Name
|
|
1015
|
+
siteNameSize: parseValue(decodedParams.siteNameSize, "number") ?? defaultProps.siteNameSize,
|
|
1016
|
+
siteNameColor: decodedParams.siteNameColor || defaultProps.siteNameColor,
|
|
1017
|
+
// Layout
|
|
1018
|
+
padding: parseValue(decodedParams.padding, "number") ?? defaultProps.padding,
|
|
1019
|
+
logoSize: parseValue(decodedParams.logoSize, "number") ?? defaultProps.logoSize,
|
|
1020
|
+
// Visibility flags
|
|
1021
|
+
showLogo: parseValue(decodedParams.showLogo, "boolean") ?? defaultProps.showLogo,
|
|
1022
|
+
showSiteName: parseValue(decodedParams.showSiteName, "boolean") ?? defaultProps.showSiteName
|
|
1023
|
+
};
|
|
1024
|
+
return new ImageResponse(
|
|
1025
|
+
/* @__PURE__ */ React.createElement(Template, { ...templateProps }),
|
|
1026
|
+
{
|
|
1027
|
+
width: size.width,
|
|
1028
|
+
height: size.height,
|
|
1029
|
+
fonts,
|
|
1030
|
+
debug: debug || process.env.NODE_ENV === "development"
|
|
1031
|
+
}
|
|
1032
|
+
);
|
|
1033
|
+
}
|
|
1034
|
+
return {
|
|
1035
|
+
GET,
|
|
1036
|
+
runtime: "edge"
|
|
1037
|
+
};
|
|
1038
|
+
}
|
|
1039
|
+
function createOgImageDynamicRoute(config) {
|
|
1040
|
+
const handler = createOgImageHandler(config);
|
|
1041
|
+
const isStaticBuild2 = typeof process !== "undefined" && process.env.NEXT_PUBLIC_STATIC_BUILD === "true";
|
|
1042
|
+
async function GET(request, context) {
|
|
1043
|
+
if (isStaticBuild2) {
|
|
1044
|
+
return new Response("OG Image generation is not available in static export mode", {
|
|
1045
|
+
status: 404,
|
|
1046
|
+
headers: { "Content-Type": "text/plain" }
|
|
1047
|
+
});
|
|
1048
|
+
}
|
|
1049
|
+
const params = await context.params;
|
|
1050
|
+
const dataParam = params.data;
|
|
1051
|
+
const url = new URL(request.url);
|
|
1052
|
+
url.searchParams.set("data", dataParam);
|
|
1053
|
+
const modifiedRequest = new NextRequest(url.toString(), {
|
|
1054
|
+
method: request.method,
|
|
1055
|
+
headers: request.headers
|
|
1056
|
+
});
|
|
1057
|
+
return handler.GET(modifiedRequest);
|
|
1058
|
+
}
|
|
1059
|
+
async function generateStaticParams() {
|
|
1060
|
+
return [];
|
|
1061
|
+
}
|
|
1062
|
+
return {
|
|
1063
|
+
GET,
|
|
1064
|
+
generateStaticParams
|
|
1065
|
+
};
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
// src/contact/submit.ts
|
|
1069
|
+
import { API, MemoryStorageAdapter, Fetchers } from "@djangocfg/api/server";
|
|
1070
|
+
async function submitContactForm({
|
|
1071
|
+
data,
|
|
1072
|
+
apiUrl,
|
|
1073
|
+
siteUrl
|
|
1074
|
+
}) {
|
|
1075
|
+
if (!data.name || !data.email || !data.message) {
|
|
1076
|
+
throw new Error("Missing required fields: name, email, message");
|
|
1077
|
+
}
|
|
1078
|
+
if (!apiUrl) {
|
|
1079
|
+
throw new Error("API URL is required");
|
|
1080
|
+
}
|
|
1081
|
+
const serverApi = new API(apiUrl, {
|
|
1082
|
+
storage: new MemoryStorageAdapter()
|
|
1083
|
+
});
|
|
1084
|
+
const submissionData = {
|
|
1085
|
+
...data,
|
|
1086
|
+
site_url: data.site_url || siteUrl
|
|
1087
|
+
};
|
|
1088
|
+
try {
|
|
1089
|
+
const result = await Fetchers.createLeadsSubmitCreate(submissionData, serverApi);
|
|
1090
|
+
return {
|
|
1091
|
+
success: result.success ?? true,
|
|
1092
|
+
message: result.message || "Contact form submitted successfully",
|
|
1093
|
+
lead_id: result.lead_id
|
|
1094
|
+
};
|
|
1095
|
+
} catch (error) {
|
|
1096
|
+
if (error instanceof Error) {
|
|
1097
|
+
throw new Error(`Failed to submit contact form: ${error.message}`);
|
|
1098
|
+
}
|
|
1099
|
+
throw new Error("An unexpected error occurred while submitting the contact form");
|
|
1100
|
+
}
|
|
1101
|
+
}
|
|
1102
|
+
|
|
1103
|
+
// src/contact/route.ts
|
|
1104
|
+
import { NextResponse as NextResponse3 } from "next/server";
|
|
1105
|
+
function createContactRoute(options) {
|
|
1106
|
+
return async function POST2(request) {
|
|
1107
|
+
try {
|
|
1108
|
+
const body = await request.json();
|
|
1109
|
+
const apiUrl = body._apiUrl && body._apiUrl !== "" ? body._apiUrl : options?.apiUrl || process.env.NEXT_PUBLIC_API_URL || "";
|
|
1110
|
+
if (!apiUrl) {
|
|
1111
|
+
return NextResponse3.json(
|
|
1112
|
+
{
|
|
1113
|
+
success: false,
|
|
1114
|
+
message: "API URL not configured. Set NEXT_PUBLIC_API_URL, provide apiUrl option, or pass _apiUrl in request body."
|
|
1115
|
+
},
|
|
1116
|
+
{ status: 500 }
|
|
1117
|
+
);
|
|
1118
|
+
}
|
|
1119
|
+
const { _apiUrl, ...submissionData } = body;
|
|
1120
|
+
const response = await submitContactForm({
|
|
1121
|
+
data: submissionData,
|
|
1122
|
+
apiUrl,
|
|
1123
|
+
siteUrl: request.headers.get("origin") || void 0
|
|
1124
|
+
});
|
|
1125
|
+
return NextResponse3.json(response);
|
|
1126
|
+
} catch (error) {
|
|
1127
|
+
console.error("Contact form submission error:", error);
|
|
1128
|
+
if (error instanceof Error && error.message.includes("Missing required fields")) {
|
|
1129
|
+
return NextResponse3.json(
|
|
1130
|
+
{
|
|
1131
|
+
success: false,
|
|
1132
|
+
message: error.message
|
|
1133
|
+
},
|
|
1134
|
+
{ status: 400 }
|
|
1135
|
+
);
|
|
1136
|
+
}
|
|
1137
|
+
if (error instanceof Error) {
|
|
1138
|
+
return NextResponse3.json(
|
|
1139
|
+
{
|
|
1140
|
+
success: false,
|
|
1141
|
+
message: error.message || "Failed to submit contact form"
|
|
1142
|
+
},
|
|
1143
|
+
{ status: 500 }
|
|
1144
|
+
);
|
|
1145
|
+
}
|
|
1146
|
+
return NextResponse3.json(
|
|
1147
|
+
{
|
|
1148
|
+
success: false,
|
|
1149
|
+
message: "An unexpected error occurred"
|
|
1150
|
+
},
|
|
1151
|
+
{ status: 500 }
|
|
1152
|
+
);
|
|
1153
|
+
}
|
|
1154
|
+
};
|
|
1155
|
+
}
|
|
1156
|
+
async function POST(request) {
|
|
1157
|
+
const handler = createContactRoute();
|
|
1158
|
+
return handler(request);
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1161
|
+
// src/navigation/utils.ts
|
|
1162
|
+
function defineRoute(path, metadata) {
|
|
1163
|
+
return {
|
|
1164
|
+
path,
|
|
1165
|
+
metadata
|
|
1166
|
+
};
|
|
1167
|
+
}
|
|
1168
|
+
function getUnauthenticatedRedirect(path, authPath = "/auth") {
|
|
1169
|
+
if (path.startsWith("/private") || path.startsWith("/admin")) {
|
|
1170
|
+
return authPath;
|
|
1171
|
+
}
|
|
1172
|
+
return null;
|
|
1173
|
+
}
|
|
1174
|
+
function redirectToAuth(authPath = "/auth") {
|
|
1175
|
+
return authPath;
|
|
1176
|
+
}
|
|
1177
|
+
function findRoute(routes, path) {
|
|
1178
|
+
return routes.find((r) => r.path === path);
|
|
1179
|
+
}
|
|
1180
|
+
function findRouteByPattern(routes, path) {
|
|
1181
|
+
const exact = findRoute(routes, path);
|
|
1182
|
+
if (exact) return exact;
|
|
1183
|
+
const segments = path.split("/").filter(Boolean);
|
|
1184
|
+
for (let i = segments.length; i > 0; i--) {
|
|
1185
|
+
const parentPath = "/" + segments.slice(0, i).join("/");
|
|
1186
|
+
const parent = findRoute(routes, parentPath);
|
|
1187
|
+
if (parent) return parent;
|
|
1188
|
+
}
|
|
1189
|
+
return void 0;
|
|
1190
|
+
}
|
|
1191
|
+
function getPageTitle(routes, path, fallback = "Dashboard") {
|
|
1192
|
+
const route = findRouteByPattern(routes, path);
|
|
1193
|
+
return route?.metadata.label || fallback;
|
|
1194
|
+
}
|
|
1195
|
+
function isActive(current, target, allRoutes) {
|
|
1196
|
+
const matches = current === target || target !== "/" && current.startsWith(target + "/");
|
|
1197
|
+
if (matches && allRoutes) {
|
|
1198
|
+
return !allRoutes.some(
|
|
1199
|
+
(otherRoute) => otherRoute.path !== target && otherRoute.path.startsWith(target + "/") && (current === otherRoute.path || current.startsWith(otherRoute.path + "/"))
|
|
1200
|
+
);
|
|
1201
|
+
}
|
|
1202
|
+
return matches;
|
|
1203
|
+
}
|
|
1204
|
+
function routesToMenuItems(routes, groupName) {
|
|
1205
|
+
return routes.filter(
|
|
1206
|
+
(r) => r.metadata.group === groupName && r.metadata.icon && (r.metadata.show === void 0 || r.metadata.show === true)
|
|
1207
|
+
).sort((a, b) => (a.metadata.order || 0) - (b.metadata.order || 0)).map((r) => ({
|
|
1208
|
+
path: r.path,
|
|
1209
|
+
label: r.metadata.label,
|
|
1210
|
+
icon: r.metadata.icon
|
|
1211
|
+
}));
|
|
1212
|
+
}
|
|
1213
|
+
|
|
1214
|
+
// src/config/constants.ts
|
|
1215
|
+
var PACKAGE_NAME = "@djangocfg/nextjs";
|
|
1216
|
+
var VERSION_CACHE_TTL_MS = 60 * 60 * 1e3;
|
|
1217
|
+
var DJANGO_CFG_BANNER = `
|
|
1218
|
+
\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557
|
|
1219
|
+
\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D \u2588\u2588\u2554\u2550\u2550\u2550\u2588\u2588\u2557 \u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D
|
|
1220
|
+
\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2554\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551 \u2588\u2588\u2588\u2557
|
|
1221
|
+
\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2551\u255A\u2588\u2588\u2557\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u255D \u2588\u2588\u2551 \u2588\u2588\u2551
|
|
1222
|
+
\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u255A\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2551\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D \u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D
|
|
1223
|
+
\u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D
|
|
1224
|
+
`;
|
|
1225
|
+
var DJANGOCFG_PACKAGES = [
|
|
1226
|
+
"@djangocfg/ui-core",
|
|
1227
|
+
"@djangocfg/ui-nextjs",
|
|
1228
|
+
"@djangocfg/layouts",
|
|
1229
|
+
"@djangocfg/nextjs",
|
|
1230
|
+
"@djangocfg/api",
|
|
1231
|
+
"@djangocfg/centrifugo",
|
|
1232
|
+
"@djangocfg/eslint-config",
|
|
1233
|
+
"@djangocfg/typescript-config"
|
|
1234
|
+
];
|
|
1235
|
+
var DEFAULT_TRANSPILE_PACKAGES = [
|
|
1236
|
+
"@djangocfg/ui-core",
|
|
1237
|
+
"@djangocfg/ui-nextjs",
|
|
1238
|
+
"@djangocfg/layouts",
|
|
1239
|
+
"@djangocfg/nextjs",
|
|
1240
|
+
"@djangocfg/api",
|
|
1241
|
+
"@djangocfg/centrifugo"
|
|
1242
|
+
];
|
|
1243
|
+
var DEFAULT_OPTIMIZE_PACKAGES = [
|
|
1244
|
+
"@djangocfg/ui-core",
|
|
1245
|
+
"@djangocfg/ui-nextjs",
|
|
1246
|
+
"@djangocfg/layouts",
|
|
1247
|
+
"lucide-react",
|
|
1248
|
+
"recharts"
|
|
1249
|
+
];
|
|
1250
|
+
|
|
1251
|
+
// src/config/utils/deepMerge.ts
|
|
1252
|
+
function deepMerge(target, source) {
|
|
1253
|
+
const output = { ...target };
|
|
1254
|
+
for (const key in source) {
|
|
1255
|
+
if (source[key] === void 0) continue;
|
|
1256
|
+
if (Array.isArray(source[key])) {
|
|
1257
|
+
output[key] = source[key];
|
|
1258
|
+
} else if (source[key] && typeof source[key] === "object" && !Array.isArray(source[key])) {
|
|
1259
|
+
const targetValue = output[key];
|
|
1260
|
+
if (targetValue && typeof targetValue === "object" && !Array.isArray(targetValue)) {
|
|
1261
|
+
output[key] = deepMerge(targetValue, source[key]);
|
|
1262
|
+
} else {
|
|
1263
|
+
output[key] = source[key];
|
|
1264
|
+
}
|
|
1265
|
+
} else {
|
|
1266
|
+
output[key] = source[key];
|
|
1267
|
+
}
|
|
1268
|
+
}
|
|
1269
|
+
return output;
|
|
1270
|
+
}
|
|
1271
|
+
|
|
1272
|
+
// src/config/utils/env.ts
|
|
1273
|
+
var isStaticBuild = process.env.NEXT_PUBLIC_STATIC_BUILD === "true";
|
|
1274
|
+
var isDev = process.env.NODE_ENV === "development";
|
|
1275
|
+
var isProduction = process.env.NODE_ENV === "production";
|
|
1276
|
+
var isCI = process.env.CI === "true";
|
|
1277
|
+
function getBasePath(isDefaultCfgAdmin) {
|
|
1278
|
+
if (!isStaticBuild) return "";
|
|
1279
|
+
return isDefaultCfgAdmin ? "/cfg/admin" : "/cfg/nextjs-admin";
|
|
1280
|
+
}
|
|
1281
|
+
function getApiUrl() {
|
|
1282
|
+
return isStaticBuild ? "" : process.env.NEXT_PUBLIC_API_URL || "";
|
|
1283
|
+
}
|
|
1284
|
+
function getSiteUrl2() {
|
|
1285
|
+
return isStaticBuild ? "" : process.env.NEXT_PUBLIC_SITE_URL || "";
|
|
1286
|
+
}
|
|
1287
|
+
|
|
1288
|
+
// src/config/plugins/devStartup.ts
|
|
1289
|
+
import chalk4 from "chalk";
|
|
1290
|
+
|
|
1291
|
+
// src/ai/constants.ts
|
|
1292
|
+
var MCP_BASE_URL = "https://mcp.djangocfg.com";
|
|
1293
|
+
var MCP_SERVER_URL = `${MCP_BASE_URL}/mcp`;
|
|
1294
|
+
var MCP_API_URL = `${MCP_BASE_URL}/api/search`;
|
|
1295
|
+
var API_SEARCH_ENDPOINT = "/api/search";
|
|
1296
|
+
var API_INFO_ENDPOINT = "/api/info";
|
|
1297
|
+
var DEFAULT_TIMEOUT = 1e4;
|
|
1298
|
+
var AI_DOCS_HINT = `\u{1F916} AI Docs: ${MCP_SERVER_URL} | pnpm ai-docs search "query"`;
|
|
1299
|
+
var AI_HINT = `
|
|
1300
|
+
DjangoCFG Documentation is available via MCP server.
|
|
1301
|
+
|
|
1302
|
+
To get help with DjangoCFG configuration:
|
|
1303
|
+
1. Use the MCP server: ${MCP_SERVER_URL}
|
|
1304
|
+
2. Or search directly: ${MCP_API_URL}?q=YOUR_QUERY&limit=5
|
|
1305
|
+
|
|
1306
|
+
Example queries:
|
|
1307
|
+
- "How to configure PostgreSQL database?"
|
|
1308
|
+
- "What is DatabaseConfig?"
|
|
1309
|
+
- "How to setup Redis cache?"
|
|
1310
|
+
- "Email configuration with SMTP"
|
|
1311
|
+
`;
|
|
1312
|
+
|
|
1313
|
+
// src/config/utils/version.ts
|
|
1314
|
+
import chalk from "chalk";
|
|
1315
|
+
import semver from "semver";
|
|
1316
|
+
import consola from "consola";
|
|
1317
|
+
import Conf from "conf";
|
|
1318
|
+
var versionCache = new Conf({
|
|
1319
|
+
projectName: "djangocfg-nextjs",
|
|
1320
|
+
projectVersion: "1.0.0"
|
|
1321
|
+
});
|
|
1322
|
+
function getCurrentVersion() {
|
|
1323
|
+
try {
|
|
1324
|
+
const packageJson = require_package();
|
|
1325
|
+
return packageJson.version || null;
|
|
1326
|
+
} catch {
|
|
1327
|
+
return null;
|
|
1328
|
+
}
|
|
1329
|
+
}
|
|
1330
|
+
async function fetchLatestVersion() {
|
|
1331
|
+
const lastCheck = versionCache.get("lastCheck") || 0;
|
|
1332
|
+
const cachedVersion = versionCache.get("latestVersion");
|
|
1333
|
+
if (cachedVersion && Date.now() - lastCheck < VERSION_CACHE_TTL_MS) {
|
|
1334
|
+
return cachedVersion;
|
|
1335
|
+
}
|
|
1336
|
+
try {
|
|
1337
|
+
const https = await import("https");
|
|
1338
|
+
return new Promise((resolve) => {
|
|
1339
|
+
const req = https.get(
|
|
1340
|
+
`https://registry.npmjs.org/${PACKAGE_NAME}/latest`,
|
|
1341
|
+
{ timeout: 5e3 },
|
|
1342
|
+
(res) => {
|
|
1343
|
+
let data = "";
|
|
1344
|
+
res.on("data", (chunk) => {
|
|
1345
|
+
data += chunk;
|
|
1346
|
+
});
|
|
1347
|
+
res.on("end", () => {
|
|
1348
|
+
try {
|
|
1349
|
+
const json = JSON.parse(data);
|
|
1350
|
+
const version = json.version || null;
|
|
1351
|
+
if (version) {
|
|
1352
|
+
versionCache.set("latestVersion", version);
|
|
1353
|
+
versionCache.set("lastCheck", Date.now());
|
|
1354
|
+
}
|
|
1355
|
+
resolve(version);
|
|
1356
|
+
} catch {
|
|
1357
|
+
resolve(cachedVersion || null);
|
|
1358
|
+
}
|
|
1359
|
+
});
|
|
1360
|
+
}
|
|
1361
|
+
);
|
|
1362
|
+
req.on("error", () => resolve(cachedVersion || null));
|
|
1363
|
+
req.on("timeout", () => {
|
|
1364
|
+
req.destroy();
|
|
1365
|
+
resolve(cachedVersion || null);
|
|
1366
|
+
});
|
|
1367
|
+
});
|
|
1368
|
+
} catch {
|
|
1369
|
+
return cachedVersion || null;
|
|
1370
|
+
}
|
|
1371
|
+
}
|
|
1372
|
+
async function checkForUpdate() {
|
|
1373
|
+
const currentVersion = getCurrentVersion();
|
|
1374
|
+
if (!currentVersion) {
|
|
1375
|
+
return { hasUpdate: false, currentVersion: null, latestVersion: null };
|
|
1376
|
+
}
|
|
1377
|
+
const latestVersion = await fetchLatestVersion();
|
|
1378
|
+
const hasUpdate = !!(latestVersion && semver.gt(latestVersion, currentVersion));
|
|
1379
|
+
return { hasUpdate, currentVersion, latestVersion };
|
|
1380
|
+
}
|
|
1381
|
+
function getUpdateCommand() {
|
|
1382
|
+
return `pnpm add ${DJANGOCFG_PACKAGES.map((p) => `${p}@latest`).join(" ")}`;
|
|
1383
|
+
}
|
|
1384
|
+
async function printVersionInfo() {
|
|
1385
|
+
const { hasUpdate, currentVersion, latestVersion } = await checkForUpdate();
|
|
1386
|
+
if (!currentVersion) return;
|
|
1387
|
+
consola.box(`\u{1F4E6} @djangocfg/nextjs v${currentVersion}`);
|
|
1388
|
+
if (hasUpdate && latestVersion) {
|
|
1389
|
+
consola.warn(`Update Available! ${chalk.red(currentVersion)} \u2192 ${chalk.green(latestVersion)}`);
|
|
1390
|
+
consola.info(`Run: ${chalk.cyan(getUpdateCommand())}`);
|
|
1391
|
+
console.log("");
|
|
1392
|
+
}
|
|
1393
|
+
}
|
|
1394
|
+
|
|
1395
|
+
// src/config/packages/installer.ts
|
|
1396
|
+
import { execSync, spawn } from "child_process";
|
|
1397
|
+
import { createInterface } from "readline";
|
|
1398
|
+
import chalk2 from "chalk";
|
|
1399
|
+
import consola2 from "consola";
|
|
1400
|
+
import Conf2 from "conf";
|
|
1401
|
+
|
|
1402
|
+
// src/config/packages/checker.ts
|
|
1403
|
+
import { createRequire } from "module";
|
|
1404
|
+
import { join } from "path";
|
|
1405
|
+
|
|
1406
|
+
// src/config/packages/definitions.ts
|
|
1407
|
+
var OPTIONAL_PACKAGES = [
|
|
1408
|
+
{
|
|
1409
|
+
name: "compression-webpack-plugin",
|
|
1410
|
+
description: "Gzip and Brotli compression for static builds",
|
|
1411
|
+
condition: "static-build",
|
|
1412
|
+
devDependency: true
|
|
1413
|
+
},
|
|
1414
|
+
{
|
|
1415
|
+
name: "@next/bundle-analyzer",
|
|
1416
|
+
description: "Bundle analyzer for analyzing build output",
|
|
1417
|
+
condition: "dev",
|
|
1418
|
+
devDependency: true,
|
|
1419
|
+
featureFlag: "ANALYZE"
|
|
1420
|
+
}
|
|
1421
|
+
];
|
|
1422
|
+
var PEER_DEPENDENCIES = [
|
|
1423
|
+
"next",
|
|
1424
|
+
"react",
|
|
1425
|
+
"react-dom"
|
|
1426
|
+
];
|
|
1427
|
+
function getPackagesForContext(options) {
|
|
1428
|
+
return OPTIONAL_PACKAGES.filter((pkg) => {
|
|
1429
|
+
switch (pkg.condition) {
|
|
1430
|
+
case "static-build":
|
|
1431
|
+
return options.isStaticBuild;
|
|
1432
|
+
case "dev":
|
|
1433
|
+
return options.isDev;
|
|
1434
|
+
case "always":
|
|
1435
|
+
return true;
|
|
1436
|
+
default:
|
|
1437
|
+
return false;
|
|
1438
|
+
}
|
|
1439
|
+
});
|
|
1440
|
+
}
|
|
1441
|
+
|
|
1442
|
+
// src/config/packages/checker.ts
|
|
1443
|
+
function isPackageInstalled(packageName) {
|
|
1444
|
+
try {
|
|
1445
|
+
const consumerRequire = createRequire(join(process.cwd(), "package.json"));
|
|
1446
|
+
consumerRequire.resolve(packageName);
|
|
1447
|
+
return true;
|
|
1448
|
+
} catch {
|
|
1449
|
+
try {
|
|
1450
|
+
__require.resolve(packageName);
|
|
1451
|
+
return true;
|
|
1452
|
+
} catch {
|
|
1453
|
+
return false;
|
|
1454
|
+
}
|
|
1455
|
+
}
|
|
1456
|
+
}
|
|
1457
|
+
function getMissingPackages() {
|
|
1458
|
+
const neededPackages = getPackagesForContext({ isStaticBuild, isDev });
|
|
1459
|
+
const missing = [];
|
|
1460
|
+
for (const pkg of neededPackages) {
|
|
1461
|
+
if (!isPackageInstalled(pkg.name)) {
|
|
1462
|
+
missing.push({
|
|
1463
|
+
...pkg,
|
|
1464
|
+
reason: getReasonText(pkg)
|
|
1465
|
+
});
|
|
1466
|
+
}
|
|
1467
|
+
}
|
|
1468
|
+
return missing;
|
|
1469
|
+
}
|
|
1470
|
+
function checkPackages(packageNames) {
|
|
1471
|
+
const missing = [];
|
|
1472
|
+
for (const name of packageNames) {
|
|
1473
|
+
if (!isPackageInstalled(name)) {
|
|
1474
|
+
const definition = OPTIONAL_PACKAGES.find((p) => p.name === name);
|
|
1475
|
+
missing.push({
|
|
1476
|
+
name,
|
|
1477
|
+
description: definition?.description || "Optional package",
|
|
1478
|
+
condition: definition?.condition || "always",
|
|
1479
|
+
devDependency: definition?.devDependency ?? true,
|
|
1480
|
+
reason: definition ? getReasonText(definition) : "Requested by configuration"
|
|
1481
|
+
});
|
|
1482
|
+
}
|
|
1483
|
+
}
|
|
1484
|
+
return missing;
|
|
1485
|
+
}
|
|
1486
|
+
function getReasonText(pkg) {
|
|
1487
|
+
switch (pkg.condition) {
|
|
1488
|
+
case "static-build":
|
|
1489
|
+
return "Required for static build (NEXT_PUBLIC_STATIC_BUILD=true)";
|
|
1490
|
+
case "dev":
|
|
1491
|
+
return "Required for development mode";
|
|
1492
|
+
case "always":
|
|
1493
|
+
return "Required for all builds";
|
|
1494
|
+
default:
|
|
1495
|
+
return "Optional feature";
|
|
1496
|
+
}
|
|
1497
|
+
}
|
|
1498
|
+
|
|
1499
|
+
// src/config/packages/installer.ts
|
|
1500
|
+
var installerCache = new Conf2({
|
|
1501
|
+
projectName: "djangocfg-nextjs-installer",
|
|
1502
|
+
projectVersion: "1.0.0"
|
|
1503
|
+
});
|
|
1504
|
+
var PROMPT_COOLDOWN_MS = 60 * 60 * 1e3;
|
|
1505
|
+
var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
1506
|
+
function detectPackageManager() {
|
|
1507
|
+
try {
|
|
1508
|
+
const fs = __require("fs");
|
|
1509
|
+
const path = __require("path");
|
|
1510
|
+
const cwd = process.cwd();
|
|
1511
|
+
if (fs.existsSync(path.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
|
|
1512
|
+
if (fs.existsSync(path.join(cwd, "yarn.lock"))) return "yarn";
|
|
1513
|
+
if (fs.existsSync(path.join(cwd, "package-lock.json"))) return "npm";
|
|
1514
|
+
try {
|
|
1515
|
+
execSync("pnpm --version", { stdio: "ignore" });
|
|
1516
|
+
return "pnpm";
|
|
1517
|
+
} catch {
|
|
1518
|
+
try {
|
|
1519
|
+
execSync("yarn --version", { stdio: "ignore" });
|
|
1520
|
+
return "yarn";
|
|
1521
|
+
} catch {
|
|
1522
|
+
return "npm";
|
|
1523
|
+
}
|
|
1524
|
+
}
|
|
1525
|
+
} catch {
|
|
1526
|
+
return "npm";
|
|
1527
|
+
}
|
|
1528
|
+
}
|
|
1529
|
+
function buildSingleInstallCommand(packageName, isDev2, pm) {
|
|
1530
|
+
const devFlag = isDev2 ? "-D " : "";
|
|
1531
|
+
switch (pm) {
|
|
1532
|
+
case "pnpm":
|
|
1533
|
+
return `pnpm add ${devFlag}${packageName}`;
|
|
1534
|
+
case "yarn":
|
|
1535
|
+
return `yarn add ${devFlag}${packageName}`;
|
|
1536
|
+
case "npm":
|
|
1537
|
+
return `npm install ${devFlag}${packageName}`;
|
|
1538
|
+
}
|
|
1539
|
+
}
|
|
1540
|
+
function buildInstallCommand(packages, pm) {
|
|
1541
|
+
const devPackages = packages.filter((p) => p.devDependency).map((p) => p.name);
|
|
1542
|
+
const prodPackages = packages.filter((p) => !p.devDependency).map((p) => p.name);
|
|
1543
|
+
const commands = [];
|
|
1544
|
+
if (devPackages.length > 0) {
|
|
1545
|
+
switch (pm) {
|
|
1546
|
+
case "pnpm":
|
|
1547
|
+
commands.push(`pnpm add -D ${devPackages.join(" ")}`);
|
|
1548
|
+
break;
|
|
1549
|
+
case "yarn":
|
|
1550
|
+
commands.push(`yarn add -D ${devPackages.join(" ")}`);
|
|
1551
|
+
break;
|
|
1552
|
+
case "npm":
|
|
1553
|
+
commands.push(`npm install -D ${devPackages.join(" ")}`);
|
|
1554
|
+
break;
|
|
1555
|
+
}
|
|
1556
|
+
}
|
|
1557
|
+
if (prodPackages.length > 0) {
|
|
1558
|
+
switch (pm) {
|
|
1559
|
+
case "pnpm":
|
|
1560
|
+
commands.push(`pnpm add ${prodPackages.join(" ")}`);
|
|
1561
|
+
break;
|
|
1562
|
+
case "yarn":
|
|
1563
|
+
commands.push(`yarn add ${prodPackages.join(" ")}`);
|
|
1564
|
+
break;
|
|
1565
|
+
case "npm":
|
|
1566
|
+
commands.push(`npm install ${prodPackages.join(" ")}`);
|
|
1567
|
+
break;
|
|
1568
|
+
}
|
|
1569
|
+
}
|
|
1570
|
+
return commands.join(" && ");
|
|
1571
|
+
}
|
|
1572
|
+
async function askConfirmation(question) {
|
|
1573
|
+
if (isCI || !process.stdin.isTTY) {
|
|
1574
|
+
return false;
|
|
1575
|
+
}
|
|
1576
|
+
return new Promise((resolve) => {
|
|
1577
|
+
const rl = createInterface({
|
|
1578
|
+
input: process.stdin,
|
|
1579
|
+
output: process.stdout
|
|
1580
|
+
});
|
|
1581
|
+
rl.question(question, (answer) => {
|
|
1582
|
+
rl.close();
|
|
1583
|
+
const normalized = answer.toLowerCase().trim();
|
|
1584
|
+
resolve(normalized === "" || normalized === "y" || normalized === "yes");
|
|
1585
|
+
});
|
|
1586
|
+
});
|
|
1587
|
+
}
|
|
1588
|
+
function createSpinner(text) {
|
|
1589
|
+
let frameIndex = 0;
|
|
1590
|
+
let interval = null;
|
|
1591
|
+
let currentText = text;
|
|
1592
|
+
const render = () => {
|
|
1593
|
+
const frame = SPINNER_FRAMES[frameIndex];
|
|
1594
|
+
process.stdout.write(`\r${chalk2.cyan(frame)} ${currentText}`);
|
|
1595
|
+
frameIndex = (frameIndex + 1) % SPINNER_FRAMES.length;
|
|
1596
|
+
};
|
|
1597
|
+
return {
|
|
1598
|
+
start() {
|
|
1599
|
+
if (process.stdout.isTTY) {
|
|
1600
|
+
interval = setInterval(render, 80);
|
|
1601
|
+
render();
|
|
1602
|
+
} else {
|
|
1603
|
+
console.log(` ${currentText}`);
|
|
1604
|
+
}
|
|
1605
|
+
return this;
|
|
1606
|
+
},
|
|
1607
|
+
text(newText) {
|
|
1608
|
+
currentText = newText;
|
|
1609
|
+
if (!process.stdout.isTTY) {
|
|
1610
|
+
console.log(` ${newText}`);
|
|
1611
|
+
}
|
|
1612
|
+
return this;
|
|
1613
|
+
},
|
|
1614
|
+
succeed(text2) {
|
|
1615
|
+
if (interval) clearInterval(interval);
|
|
1616
|
+
if (process.stdout.isTTY) {
|
|
1617
|
+
process.stdout.write(`\r${chalk2.green("\u2713")} ${text2 || currentText}
|
|
1618
|
+
`);
|
|
1619
|
+
} else {
|
|
1620
|
+
console.log(` ${chalk2.green("\u2713")} ${text2 || currentText}`);
|
|
1621
|
+
}
|
|
1622
|
+
return this;
|
|
1623
|
+
},
|
|
1624
|
+
fail(text2) {
|
|
1625
|
+
if (interval) clearInterval(interval);
|
|
1626
|
+
if (process.stdout.isTTY) {
|
|
1627
|
+
process.stdout.write(`\r${chalk2.red("\u2717")} ${text2 || currentText}
|
|
1628
|
+
`);
|
|
1629
|
+
} else {
|
|
1630
|
+
console.log(` ${chalk2.red("\u2717")} ${text2 || currentText}`);
|
|
1631
|
+
}
|
|
1632
|
+
return this;
|
|
1633
|
+
},
|
|
1634
|
+
stop() {
|
|
1635
|
+
if (interval) clearInterval(interval);
|
|
1636
|
+
if (process.stdout.isTTY) {
|
|
1637
|
+
process.stdout.write("\r\x1B[K");
|
|
1638
|
+
}
|
|
1639
|
+
return this;
|
|
1640
|
+
}
|
|
1641
|
+
};
|
|
1642
|
+
}
|
|
1643
|
+
async function installSinglePackage(pkg, pm, index, total) {
|
|
1644
|
+
const command = buildSingleInstallCommand(pkg.name, pkg.devDependency, pm);
|
|
1645
|
+
const progress = `[${index + 1}/${total}]`;
|
|
1646
|
+
const spinner = createSpinner(`${chalk2.dim(progress)} Installing ${chalk2.cyan(pkg.name)}...`);
|
|
1647
|
+
spinner.start();
|
|
1648
|
+
return new Promise((resolve) => {
|
|
1649
|
+
const [cmd, ...args] = command.split(" ");
|
|
1650
|
+
const proc = spawn(cmd, args, {
|
|
1651
|
+
shell: true,
|
|
1652
|
+
cwd: process.cwd(),
|
|
1653
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
1654
|
+
});
|
|
1655
|
+
let stderr = "";
|
|
1656
|
+
proc.stderr?.on("data", (data) => {
|
|
1657
|
+
stderr += data.toString();
|
|
1658
|
+
});
|
|
1659
|
+
proc.on("close", (code) => {
|
|
1660
|
+
if (code === 0) {
|
|
1661
|
+
spinner.succeed(`${chalk2.dim(progress)} ${chalk2.cyan(pkg.name)} ${chalk2.green("installed")}`);
|
|
1662
|
+
resolve(true);
|
|
1663
|
+
} else {
|
|
1664
|
+
spinner.fail(`${chalk2.dim(progress)} ${chalk2.cyan(pkg.name)} ${chalk2.red("failed")}`);
|
|
1665
|
+
if (stderr) {
|
|
1666
|
+
console.log(chalk2.dim(` ${stderr.split("\n")[0]}`));
|
|
1667
|
+
}
|
|
1668
|
+
resolve(false);
|
|
1669
|
+
}
|
|
1670
|
+
});
|
|
1671
|
+
proc.on("error", () => {
|
|
1672
|
+
spinner.fail(`${chalk2.dim(progress)} ${chalk2.cyan(pkg.name)} ${chalk2.red("failed")}`);
|
|
1673
|
+
resolve(false);
|
|
1674
|
+
});
|
|
1675
|
+
});
|
|
1676
|
+
}
|
|
1677
|
+
async function installPackagesWithProgress(packages) {
|
|
1678
|
+
if (packages.length === 0) return true;
|
|
1679
|
+
const pm = detectPackageManager();
|
|
1680
|
+
const total = packages.length;
|
|
1681
|
+
console.log("");
|
|
1682
|
+
console.log(chalk2.bold(` Installing ${total} package${total > 1 ? "s" : ""}...`));
|
|
1683
|
+
console.log("");
|
|
1684
|
+
let successCount = 0;
|
|
1685
|
+
let failedPackages = [];
|
|
1686
|
+
for (let i = 0; i < packages.length; i++) {
|
|
1687
|
+
const success = await installSinglePackage(packages[i], pm, i, total);
|
|
1688
|
+
if (success) {
|
|
1689
|
+
successCount++;
|
|
1690
|
+
} else {
|
|
1691
|
+
failedPackages.push(packages[i].name);
|
|
1692
|
+
}
|
|
1693
|
+
}
|
|
1694
|
+
console.log("");
|
|
1695
|
+
if (failedPackages.length === 0) {
|
|
1696
|
+
consola2.success(`All ${total} packages installed successfully!`);
|
|
1697
|
+
return true;
|
|
1698
|
+
} else if (successCount > 0) {
|
|
1699
|
+
consola2.warn(`${successCount}/${total} packages installed. Failed: ${failedPackages.join(", ")}`);
|
|
1700
|
+
return false;
|
|
1701
|
+
} else {
|
|
1702
|
+
consola2.error(`Failed to install packages: ${failedPackages.join(", ")}`);
|
|
1703
|
+
return false;
|
|
1704
|
+
}
|
|
1705
|
+
}
|
|
1706
|
+
async function installPackages(packages) {
|
|
1707
|
+
if (packages.length > 1 && process.stdout.isTTY) {
|
|
1708
|
+
return installPackagesWithProgress(packages);
|
|
1709
|
+
}
|
|
1710
|
+
if (packages.length === 0) return true;
|
|
1711
|
+
const pm = detectPackageManager();
|
|
1712
|
+
const command = buildInstallCommand(packages, pm);
|
|
1713
|
+
consola2.info(`Installing: ${chalk2.cyan(packages.map((p) => p.name).join(", "))}`);
|
|
1714
|
+
const spinner = createSpinner("Installing packages...");
|
|
1715
|
+
spinner.start();
|
|
1716
|
+
return new Promise((resolve) => {
|
|
1717
|
+
const proc = spawn(command, {
|
|
1718
|
+
shell: true,
|
|
1719
|
+
cwd: process.cwd(),
|
|
1720
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
1721
|
+
});
|
|
1722
|
+
proc.on("close", (code) => {
|
|
1723
|
+
if (code === 0) {
|
|
1724
|
+
spinner.succeed("Packages installed successfully!");
|
|
1725
|
+
resolve(true);
|
|
1726
|
+
} else {
|
|
1727
|
+
spinner.fail("Failed to install packages");
|
|
1728
|
+
resolve(false);
|
|
1729
|
+
}
|
|
1730
|
+
});
|
|
1731
|
+
proc.on("error", () => {
|
|
1732
|
+
spinner.fail("Installation failed");
|
|
1733
|
+
resolve(false);
|
|
1734
|
+
});
|
|
1735
|
+
});
|
|
1736
|
+
}
|
|
1737
|
+
async function checkAndInstallPackages(options = {}) {
|
|
1738
|
+
const missing = getMissingPackages();
|
|
1739
|
+
const skipList = options.skipPackages || installerCache.get("skipPackages") || [];
|
|
1740
|
+
const toInstall = missing.filter((p) => !skipList.includes(p.name));
|
|
1741
|
+
if (toInstall.length === 0) {
|
|
1742
|
+
return true;
|
|
1743
|
+
}
|
|
1744
|
+
const lastPrompt = installerCache.get("lastPrompt") || 0;
|
|
1745
|
+
if (!options.force && Date.now() - lastPrompt < PROMPT_COOLDOWN_MS) {
|
|
1746
|
+
printMissingPackagesInfo(toInstall);
|
|
1747
|
+
return false;
|
|
1748
|
+
}
|
|
1749
|
+
if (options.autoInstall || installerCache.get("autoInstall")) {
|
|
1750
|
+
return installPackages(toInstall);
|
|
1751
|
+
}
|
|
1752
|
+
console.log("");
|
|
1753
|
+
consola2.box("\u{1F4E6} Missing Optional Packages");
|
|
1754
|
+
console.log("");
|
|
1755
|
+
for (const pkg of toInstall) {
|
|
1756
|
+
console.log(` ${chalk2.yellow("\u2022")} ${chalk2.bold(pkg.name)}`);
|
|
1757
|
+
console.log(` ${chalk2.dim(pkg.description)}`);
|
|
1758
|
+
console.log(` ${chalk2.dim(pkg.reason)}`);
|
|
1759
|
+
console.log("");
|
|
1760
|
+
}
|
|
1761
|
+
const pm = detectPackageManager();
|
|
1762
|
+
const command = buildInstallCommand(toInstall, pm);
|
|
1763
|
+
console.log(` ${chalk2.cyan("Command:")} ${command}`);
|
|
1764
|
+
console.log("");
|
|
1765
|
+
installerCache.set("lastPrompt", Date.now());
|
|
1766
|
+
const shouldInstall = await askConfirmation(
|
|
1767
|
+
`${chalk2.green("?")} Install these packages now? ${chalk2.dim("[Y/n]")} `
|
|
1768
|
+
);
|
|
1769
|
+
if (shouldInstall) {
|
|
1770
|
+
const success = await installPackages(toInstall);
|
|
1771
|
+
if (success) {
|
|
1772
|
+
const enableAuto = await askConfirmation(
|
|
1773
|
+
`${chalk2.green("?")} Enable auto-install for future? ${chalk2.dim("[y/N]")} `
|
|
1774
|
+
);
|
|
1775
|
+
if (enableAuto) {
|
|
1776
|
+
installerCache.set("autoInstall", true);
|
|
1777
|
+
consola2.info("Auto-install enabled. Run with --no-auto-install to disable.");
|
|
1778
|
+
}
|
|
1779
|
+
}
|
|
1780
|
+
return success;
|
|
1781
|
+
}
|
|
1782
|
+
const skipPermanently = await askConfirmation(
|
|
1783
|
+
`${chalk2.green("?")} Skip these packages in future prompts? ${chalk2.dim("[y/N]")} `
|
|
1784
|
+
);
|
|
1785
|
+
if (skipPermanently) {
|
|
1786
|
+
const currentSkip = installerCache.get("skipPackages") || [];
|
|
1787
|
+
installerCache.set("skipPackages", [...currentSkip, ...toInstall.map((p) => p.name)]);
|
|
1788
|
+
consola2.info("Packages added to skip list.");
|
|
1789
|
+
}
|
|
1790
|
+
return false;
|
|
1791
|
+
}
|
|
1792
|
+
function printMissingPackagesInfo(packages) {
|
|
1793
|
+
if (packages.length === 0) return;
|
|
1794
|
+
const pm = detectPackageManager();
|
|
1795
|
+
const command = buildInstallCommand(packages, pm);
|
|
1796
|
+
consola2.warn(`Missing optional packages: ${packages.map((p) => p.name).join(", ")}`);
|
|
1797
|
+
consola2.info(`Install with: ${chalk2.cyan(command)}`);
|
|
1798
|
+
}
|
|
1799
|
+
function resetInstallerPreferences() {
|
|
1800
|
+
installerCache.clear();
|
|
1801
|
+
consola2.success("Installer preferences reset");
|
|
1802
|
+
}
|
|
1803
|
+
|
|
1804
|
+
// src/config/packages/updater.ts
|
|
1805
|
+
import { spawn as spawn2 } from "child_process";
|
|
1806
|
+
import { createInterface as createInterface2 } from "readline";
|
|
1807
|
+
import { createRequire as createRequire2 } from "module";
|
|
1808
|
+
import { join as join2 } from "path";
|
|
1809
|
+
import chalk3 from "chalk";
|
|
1810
|
+
import consola3 from "consola";
|
|
1811
|
+
import Conf3 from "conf";
|
|
1812
|
+
import semver2 from "semver";
|
|
1813
|
+
var updaterCache = new Conf3({
|
|
1814
|
+
projectName: "djangocfg-nextjs-updater",
|
|
1815
|
+
projectVersion: "1.0.0"
|
|
1816
|
+
});
|
|
1817
|
+
var UPDATE_CHECK_COOLDOWN_MS = 60 * 60 * 1e3;
|
|
1818
|
+
var SPINNER_FRAMES2 = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
1819
|
+
function isWorkspacePackage(packageName) {
|
|
1820
|
+
try {
|
|
1821
|
+
const fs = __require("fs");
|
|
1822
|
+
const pkgJsonPath = join2(process.cwd(), "package.json");
|
|
1823
|
+
const pkgJson = JSON.parse(fs.readFileSync(pkgJsonPath, "utf-8"));
|
|
1824
|
+
const deps = { ...pkgJson.dependencies, ...pkgJson.devDependencies };
|
|
1825
|
+
const version = deps[packageName];
|
|
1826
|
+
return version?.startsWith("workspace:") || false;
|
|
1827
|
+
} catch {
|
|
1828
|
+
return false;
|
|
1829
|
+
}
|
|
1830
|
+
}
|
|
1831
|
+
function getInstalledVersion(packageName) {
|
|
1832
|
+
const fs = __require("fs");
|
|
1833
|
+
const cwd = process.cwd();
|
|
1834
|
+
try {
|
|
1835
|
+
const directPath = join2(cwd, "node_modules", packageName, "package.json");
|
|
1836
|
+
if (fs.existsSync(directPath)) {
|
|
1837
|
+
const pkg = JSON.parse(fs.readFileSync(directPath, "utf-8"));
|
|
1838
|
+
return pkg.version || null;
|
|
1839
|
+
}
|
|
1840
|
+
} catch {
|
|
1841
|
+
}
|
|
1842
|
+
try {
|
|
1843
|
+
const consumerRequire = createRequire2(join2(cwd, "package.json"));
|
|
1844
|
+
const pkgPath = consumerRequire.resolve(`${packageName}/package.json`);
|
|
1845
|
+
const pkg = __require(pkgPath);
|
|
1846
|
+
return pkg.version || null;
|
|
1847
|
+
} catch {
|
|
1848
|
+
}
|
|
1849
|
+
try {
|
|
1850
|
+
const pkgPath = __require.resolve(`${packageName}/package.json`);
|
|
1851
|
+
const pkg = __require(pkgPath);
|
|
1852
|
+
return pkg.version || null;
|
|
1853
|
+
} catch {
|
|
1854
|
+
return null;
|
|
1855
|
+
}
|
|
1856
|
+
}
|
|
1857
|
+
function shouldCheckUpdates(packageName) {
|
|
1858
|
+
return !isWorkspacePackage(packageName);
|
|
1859
|
+
}
|
|
1860
|
+
async function fetchLatestVersion2(packageName) {
|
|
1861
|
+
try {
|
|
1862
|
+
const https = await import("https");
|
|
1863
|
+
return new Promise((resolve) => {
|
|
1864
|
+
const req = https.get(
|
|
1865
|
+
`https://registry.npmjs.org/${packageName}/latest`,
|
|
1866
|
+
{ timeout: 5e3 },
|
|
1867
|
+
(res) => {
|
|
1868
|
+
let data = "";
|
|
1869
|
+
res.on("data", (chunk) => {
|
|
1870
|
+
data += chunk;
|
|
1871
|
+
});
|
|
1872
|
+
res.on("end", () => {
|
|
1873
|
+
try {
|
|
1874
|
+
const json = JSON.parse(data);
|
|
1875
|
+
resolve(json.version || null);
|
|
1876
|
+
} catch {
|
|
1877
|
+
resolve(null);
|
|
1878
|
+
}
|
|
1879
|
+
});
|
|
1880
|
+
}
|
|
1881
|
+
);
|
|
1882
|
+
req.on("error", () => resolve(null));
|
|
1883
|
+
req.on("timeout", () => {
|
|
1884
|
+
req.destroy();
|
|
1885
|
+
resolve(null);
|
|
1886
|
+
});
|
|
1887
|
+
});
|
|
1888
|
+
} catch {
|
|
1889
|
+
return null;
|
|
1890
|
+
}
|
|
1891
|
+
}
|
|
1892
|
+
async function checkForUpdates(options = {}) {
|
|
1893
|
+
const results = [];
|
|
1894
|
+
const masterLatest = await fetchLatestVersion2(PACKAGE_NAME);
|
|
1895
|
+
if (!masterLatest) {
|
|
1896
|
+
return results;
|
|
1897
|
+
}
|
|
1898
|
+
const checks = DJANGOCFG_PACKAGES.map(async (name) => {
|
|
1899
|
+
const isWorkspace = !shouldCheckUpdates(name);
|
|
1900
|
+
if (!options.forceCheckWorkspace && isWorkspace) {
|
|
1901
|
+
return null;
|
|
1902
|
+
}
|
|
1903
|
+
const current = getInstalledVersion(name);
|
|
1904
|
+
if (!current) {
|
|
1905
|
+
return null;
|
|
1906
|
+
}
|
|
1907
|
+
const hasUpdate = !!(masterLatest && current && semver2.gt(masterLatest, current));
|
|
1908
|
+
return { name, current, latest: masterLatest, hasUpdate };
|
|
1909
|
+
});
|
|
1910
|
+
const checkResults = await Promise.all(checks);
|
|
1911
|
+
for (const result of checkResults) {
|
|
1912
|
+
if (result) {
|
|
1913
|
+
results.push(result);
|
|
1914
|
+
}
|
|
1915
|
+
}
|
|
1916
|
+
return results;
|
|
1917
|
+
}
|
|
1918
|
+
async function getOutdatedPackages(options = {}) {
|
|
1919
|
+
const all = await checkForUpdates(options);
|
|
1920
|
+
const skipped = updaterCache.get("skippedVersions") || {};
|
|
1921
|
+
return all.filter((pkg) => {
|
|
1922
|
+
if (!pkg.hasUpdate) return false;
|
|
1923
|
+
if (skipped[pkg.name] === pkg.latest) return false;
|
|
1924
|
+
return true;
|
|
1925
|
+
});
|
|
1926
|
+
}
|
|
1927
|
+
function createSpinner2(text) {
|
|
1928
|
+
let frameIndex = 0;
|
|
1929
|
+
let interval = null;
|
|
1930
|
+
let currentText = text;
|
|
1931
|
+
const render = () => {
|
|
1932
|
+
const frame = SPINNER_FRAMES2[frameIndex];
|
|
1933
|
+
process.stdout.write(`\r${chalk3.cyan(frame)} ${currentText}`);
|
|
1934
|
+
frameIndex = (frameIndex + 1) % SPINNER_FRAMES2.length;
|
|
1935
|
+
};
|
|
1936
|
+
return {
|
|
1937
|
+
start() {
|
|
1938
|
+
if (process.stdout.isTTY) {
|
|
1939
|
+
interval = setInterval(render, 80);
|
|
1940
|
+
render();
|
|
1941
|
+
} else {
|
|
1942
|
+
console.log(` ${currentText}`);
|
|
1943
|
+
}
|
|
1944
|
+
return this;
|
|
1945
|
+
},
|
|
1946
|
+
text(newText) {
|
|
1947
|
+
currentText = newText;
|
|
1948
|
+
return this;
|
|
1949
|
+
},
|
|
1950
|
+
succeed(text2) {
|
|
1951
|
+
if (interval) clearInterval(interval);
|
|
1952
|
+
if (process.stdout.isTTY) {
|
|
1953
|
+
process.stdout.write(`\r${chalk3.green("\u2713")} ${text2 || currentText}
|
|
1954
|
+
`);
|
|
1955
|
+
} else {
|
|
1956
|
+
console.log(` ${chalk3.green("\u2713")} ${text2 || currentText}`);
|
|
1957
|
+
}
|
|
1958
|
+
return this;
|
|
1959
|
+
},
|
|
1960
|
+
fail(text2) {
|
|
1961
|
+
if (interval) clearInterval(interval);
|
|
1962
|
+
if (process.stdout.isTTY) {
|
|
1963
|
+
process.stdout.write(`\r${chalk3.red("\u2717")} ${text2 || currentText}
|
|
1964
|
+
`);
|
|
1965
|
+
} else {
|
|
1966
|
+
console.log(` ${chalk3.red("\u2717")} ${text2 || currentText}`);
|
|
1967
|
+
}
|
|
1968
|
+
return this;
|
|
1969
|
+
},
|
|
1970
|
+
stop() {
|
|
1971
|
+
if (interval) clearInterval(interval);
|
|
1972
|
+
if (process.stdout.isTTY) {
|
|
1973
|
+
process.stdout.write("\r\x1B[K");
|
|
1974
|
+
}
|
|
1975
|
+
return this;
|
|
1976
|
+
}
|
|
1977
|
+
};
|
|
1978
|
+
}
|
|
1979
|
+
async function askConfirmation2(question) {
|
|
1980
|
+
if (isCI || !process.stdin.isTTY) {
|
|
1981
|
+
return false;
|
|
1982
|
+
}
|
|
1983
|
+
return new Promise((resolve) => {
|
|
1984
|
+
const rl = createInterface2({
|
|
1985
|
+
input: process.stdin,
|
|
1986
|
+
output: process.stdout
|
|
1987
|
+
});
|
|
1988
|
+
rl.question(question, (answer) => {
|
|
1989
|
+
rl.close();
|
|
1990
|
+
const normalized = answer.toLowerCase().trim();
|
|
1991
|
+
resolve(normalized === "" || normalized === "y" || normalized === "yes");
|
|
1992
|
+
});
|
|
1993
|
+
});
|
|
1994
|
+
}
|
|
1995
|
+
async function updateSinglePackage(pkg, pm, index, total) {
|
|
1996
|
+
const progress = `[${index + 1}/${total}]`;
|
|
1997
|
+
const versionInfo = `${chalk3.red(pkg.current)} \u2192 ${chalk3.green(pkg.latest)}`;
|
|
1998
|
+
const spinner = createSpinner2(
|
|
1999
|
+
`${chalk3.dim(progress)} Updating ${chalk3.cyan(pkg.name)} ${versionInfo}`
|
|
2000
|
+
);
|
|
2001
|
+
spinner.start();
|
|
2002
|
+
const command = pm === "pnpm" ? `pnpm add ${pkg.name}@latest` : pm === "yarn" ? `yarn add ${pkg.name}@latest` : `npm install ${pkg.name}@latest`;
|
|
2003
|
+
return new Promise((resolve) => {
|
|
2004
|
+
const proc = spawn2(command, {
|
|
2005
|
+
shell: true,
|
|
2006
|
+
cwd: process.cwd(),
|
|
2007
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
2008
|
+
});
|
|
2009
|
+
proc.on("close", (code) => {
|
|
2010
|
+
if (code === 0) {
|
|
2011
|
+
spinner.succeed(
|
|
2012
|
+
`${chalk3.dim(progress)} ${chalk3.cyan(pkg.name)} ${chalk3.green(pkg.latest)}`
|
|
2013
|
+
);
|
|
2014
|
+
resolve(true);
|
|
2015
|
+
} else {
|
|
2016
|
+
spinner.fail(
|
|
2017
|
+
`${chalk3.dim(progress)} ${chalk3.cyan(pkg.name)} ${chalk3.red("failed")}`
|
|
2018
|
+
);
|
|
2019
|
+
resolve(false);
|
|
2020
|
+
}
|
|
2021
|
+
});
|
|
2022
|
+
proc.on("error", () => {
|
|
2023
|
+
spinner.fail(`${chalk3.dim(progress)} ${chalk3.cyan(pkg.name)} ${chalk3.red("failed")}`);
|
|
2024
|
+
resolve(false);
|
|
2025
|
+
});
|
|
2026
|
+
});
|
|
2027
|
+
}
|
|
2028
|
+
async function updatePackagesWithProgress(packages) {
|
|
2029
|
+
if (packages.length === 0) return true;
|
|
2030
|
+
const pm = detectPackageManager();
|
|
2031
|
+
const total = packages.length;
|
|
2032
|
+
console.log("");
|
|
2033
|
+
console.log(chalk3.bold(` Updating ${total} package${total > 1 ? "s" : ""}...`));
|
|
2034
|
+
console.log("");
|
|
2035
|
+
let successCount = 0;
|
|
2036
|
+
const failedPackages = [];
|
|
2037
|
+
for (let i = 0; i < packages.length; i++) {
|
|
2038
|
+
const success = await updateSinglePackage(packages[i], pm, i, total);
|
|
2039
|
+
if (success) {
|
|
2040
|
+
successCount++;
|
|
2041
|
+
} else {
|
|
2042
|
+
failedPackages.push(packages[i].name);
|
|
2043
|
+
}
|
|
2044
|
+
}
|
|
2045
|
+
console.log("");
|
|
2046
|
+
if (failedPackages.length === 0) {
|
|
2047
|
+
consola3.success(`All ${total} packages updated successfully!`);
|
|
2048
|
+
return true;
|
|
2049
|
+
} else if (successCount > 0) {
|
|
2050
|
+
consola3.warn(`${successCount}/${total} packages updated. Failed: ${failedPackages.join(", ")}`);
|
|
2051
|
+
return false;
|
|
2052
|
+
} else {
|
|
2053
|
+
consola3.error(`Failed to update packages: ${failedPackages.join(", ")}`);
|
|
2054
|
+
return false;
|
|
2055
|
+
}
|
|
2056
|
+
}
|
|
2057
|
+
async function checkAndUpdatePackages(options = {}) {
|
|
2058
|
+
const lastCheck = updaterCache.get("lastCheck") || 0;
|
|
2059
|
+
if (!options.force && Date.now() - lastCheck < UPDATE_CHECK_COOLDOWN_MS) {
|
|
2060
|
+
return true;
|
|
2061
|
+
}
|
|
2062
|
+
updaterCache.set("lastCheck", Date.now());
|
|
2063
|
+
const spinner = createSpinner2("Checking for updates...");
|
|
2064
|
+
spinner.start();
|
|
2065
|
+
const outdated = await getOutdatedPackages({
|
|
2066
|
+
forceCheckWorkspace: options.forceCheckWorkspace
|
|
2067
|
+
});
|
|
2068
|
+
spinner.stop();
|
|
2069
|
+
console.log(chalk3.dim(` Found ${outdated.length} outdated package(s)`));
|
|
2070
|
+
if (outdated.length === 0) {
|
|
2071
|
+
console.log(chalk3.green(" \u2713 All packages are up to date"));
|
|
2072
|
+
return true;
|
|
2073
|
+
}
|
|
2074
|
+
if (options.autoUpdate || updaterCache.get("autoUpdate")) {
|
|
2075
|
+
return updatePackagesWithProgress(outdated);
|
|
2076
|
+
}
|
|
2077
|
+
console.log("");
|
|
2078
|
+
consola3.box("\u{1F4E6} Updates Available");
|
|
2079
|
+
console.log("");
|
|
2080
|
+
for (const pkg of outdated) {
|
|
2081
|
+
console.log(
|
|
2082
|
+
` ${chalk3.yellow("\u2022")} ${chalk3.bold(pkg.name)} ${chalk3.red(pkg.current)} \u2192 ${chalk3.green(pkg.latest)}`
|
|
2083
|
+
);
|
|
2084
|
+
}
|
|
2085
|
+
console.log("");
|
|
2086
|
+
const shouldUpdate = await askConfirmation2(
|
|
2087
|
+
`${chalk3.green("?")} Update these packages now? ${chalk3.dim("[Y/n]")} `
|
|
2088
|
+
);
|
|
2089
|
+
if (shouldUpdate) {
|
|
2090
|
+
const success = await updatePackagesWithProgress(outdated);
|
|
2091
|
+
if (success) {
|
|
2092
|
+
const enableAuto = await askConfirmation2(
|
|
2093
|
+
`${chalk3.green("?")} Enable auto-update for future? ${chalk3.dim("[y/N]")} `
|
|
2094
|
+
);
|
|
2095
|
+
if (enableAuto) {
|
|
2096
|
+
updaterCache.set("autoUpdate", true);
|
|
2097
|
+
consola3.info("Auto-update enabled.");
|
|
2098
|
+
}
|
|
2099
|
+
}
|
|
2100
|
+
return success;
|
|
2101
|
+
}
|
|
2102
|
+
const skipVersions = await askConfirmation2(
|
|
2103
|
+
`${chalk3.green("?")} Skip these versions in future? ${chalk3.dim("[y/N]")} `
|
|
2104
|
+
);
|
|
2105
|
+
if (skipVersions) {
|
|
2106
|
+
const skipped = updaterCache.get("skippedVersions") || {};
|
|
2107
|
+
for (const pkg of outdated) {
|
|
2108
|
+
if (pkg.latest) {
|
|
2109
|
+
skipped[pkg.name] = pkg.latest;
|
|
2110
|
+
}
|
|
2111
|
+
}
|
|
2112
|
+
updaterCache.set("skippedVersions", skipped);
|
|
2113
|
+
consola3.info("Versions skipped. Will prompt again for newer versions.");
|
|
2114
|
+
}
|
|
2115
|
+
return false;
|
|
2116
|
+
}
|
|
2117
|
+
function resetUpdaterPreferences() {
|
|
2118
|
+
updaterCache.clear();
|
|
2119
|
+
consola3.success("Updater preferences reset");
|
|
2120
|
+
}
|
|
2121
|
+
|
|
2122
|
+
// src/config/plugins/devStartup.ts
|
|
2123
|
+
var startupDone = false;
|
|
2124
|
+
var browserOpened = false;
|
|
2125
|
+
var DevStartupPlugin = class {
|
|
2126
|
+
constructor(options = {}) {
|
|
2127
|
+
this.options = options;
|
|
2128
|
+
}
|
|
2129
|
+
apply(compiler) {
|
|
2130
|
+
compiler.hooks.done.tapPromise("DevStartupPlugin", async () => {
|
|
2131
|
+
if (!startupDone) {
|
|
2132
|
+
startupDone = true;
|
|
2133
|
+
await this.runStartupTasks();
|
|
2134
|
+
}
|
|
2135
|
+
if (this.options.openBrowser && !browserOpened) {
|
|
2136
|
+
browserOpened = true;
|
|
2137
|
+
this.openBrowser();
|
|
2138
|
+
}
|
|
2139
|
+
});
|
|
2140
|
+
}
|
|
2141
|
+
async runStartupTasks() {
|
|
2142
|
+
console.log("\n" + chalk4.yellowBright.bold(DJANGO_CFG_BANNER));
|
|
2143
|
+
const version = getCurrentVersion();
|
|
2144
|
+
if (version) {
|
|
2145
|
+
console.log(chalk4.dim(` \u{1F4E6} @djangocfg/nextjs v${version}`));
|
|
2146
|
+
}
|
|
2147
|
+
console.log(chalk4.magenta(` ${AI_DOCS_HINT}
|
|
2148
|
+
`));
|
|
2149
|
+
if (this.options.checkUpdates !== false) {
|
|
2150
|
+
try {
|
|
2151
|
+
await checkAndUpdatePackages({
|
|
2152
|
+
autoUpdate: this.options.autoUpdate,
|
|
2153
|
+
forceCheckWorkspace: this.options.forceCheckWorkspace,
|
|
2154
|
+
force: true
|
|
2155
|
+
// Force check ignoring cooldown
|
|
2156
|
+
});
|
|
2157
|
+
} catch (err) {
|
|
2158
|
+
console.log(chalk4.red(" Update check failed:"), err);
|
|
2159
|
+
}
|
|
2160
|
+
}
|
|
2161
|
+
if (this.options.checkPackages !== false) {
|
|
2162
|
+
await checkAndInstallPackages({
|
|
2163
|
+
autoInstall: this.options.autoInstall
|
|
2164
|
+
});
|
|
2165
|
+
}
|
|
2166
|
+
}
|
|
2167
|
+
openBrowser() {
|
|
2168
|
+
setTimeout(async () => {
|
|
2169
|
+
try {
|
|
2170
|
+
const { exec } = await import("child_process");
|
|
2171
|
+
const port = process.env.PORT || "3000";
|
|
2172
|
+
const url = `http://localhost:${port}`;
|
|
2173
|
+
const command = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
|
|
2174
|
+
exec(`${command} ${url}`, (error) => {
|
|
2175
|
+
if (error) {
|
|
2176
|
+
console.warn(`Failed to open browser: ${error.message}`);
|
|
2177
|
+
}
|
|
2178
|
+
});
|
|
2179
|
+
} catch (error) {
|
|
2180
|
+
console.warn("Failed to open browser");
|
|
2181
|
+
}
|
|
2182
|
+
}, 2e3);
|
|
2183
|
+
}
|
|
2184
|
+
};
|
|
2185
|
+
function resetDevStartupState() {
|
|
2186
|
+
startupDone = false;
|
|
2187
|
+
browserOpened = false;
|
|
2188
|
+
}
|
|
2189
|
+
|
|
2190
|
+
// src/config/plugins/compression.ts
|
|
2191
|
+
var DEFAULT_OPTIONS = {
|
|
2192
|
+
gzip: true,
|
|
2193
|
+
brotli: true,
|
|
2194
|
+
threshold: 8192,
|
|
2195
|
+
minRatio: 0.8,
|
|
2196
|
+
brotliLevel: 8
|
|
2197
|
+
};
|
|
2198
|
+
function addCompressionPlugins(config, options = {}) {
|
|
2199
|
+
if (!isPackageInstalled("compression-webpack-plugin")) {
|
|
2200
|
+
return false;
|
|
2201
|
+
}
|
|
2202
|
+
const opts = { ...DEFAULT_OPTIONS, ...options };
|
|
2203
|
+
try {
|
|
2204
|
+
const CompressionPlugin = __require("compression-webpack-plugin");
|
|
2205
|
+
if (!config.plugins) {
|
|
2206
|
+
config.plugins = [];
|
|
2207
|
+
}
|
|
2208
|
+
if (opts.gzip) {
|
|
2209
|
+
config.plugins.push(
|
|
2210
|
+
new CompressionPlugin({
|
|
2211
|
+
filename: "[path][base].gz",
|
|
2212
|
+
algorithm: "gzip",
|
|
2213
|
+
test: /\.(js|css|html|svg|json)$/,
|
|
2214
|
+
threshold: opts.threshold,
|
|
2215
|
+
minRatio: opts.minRatio
|
|
2216
|
+
})
|
|
2217
|
+
);
|
|
2218
|
+
}
|
|
2219
|
+
if (opts.brotli) {
|
|
2220
|
+
config.plugins.push(
|
|
2221
|
+
new CompressionPlugin({
|
|
2222
|
+
filename: "[path][base].br",
|
|
2223
|
+
algorithm: "brotliCompress",
|
|
2224
|
+
test: /\.(js|css|html|svg|json)$/,
|
|
2225
|
+
threshold: opts.threshold,
|
|
2226
|
+
minRatio: opts.minRatio,
|
|
2227
|
+
compressionOptions: {
|
|
2228
|
+
level: opts.brotliLevel
|
|
2229
|
+
}
|
|
2230
|
+
})
|
|
2231
|
+
);
|
|
2232
|
+
}
|
|
2233
|
+
return true;
|
|
2234
|
+
} catch (error) {
|
|
2235
|
+
return false;
|
|
2236
|
+
}
|
|
2237
|
+
}
|
|
2238
|
+
function isCompressionAvailable() {
|
|
2239
|
+
return isPackageInstalled("compression-webpack-plugin");
|
|
2240
|
+
}
|
|
2241
|
+
|
|
2242
|
+
// src/config/createNextConfig.ts
|
|
2243
|
+
function createBaseNextConfig(options = {}) {
|
|
2244
|
+
const basePath = getBasePath(options.isDefaultCfgAdmin);
|
|
2245
|
+
const apiUrl = getApiUrl();
|
|
2246
|
+
const siteUrl = getSiteUrl2();
|
|
2247
|
+
const baseConfig = {
|
|
2248
|
+
reactStrictMode: true,
|
|
2249
|
+
trailingSlash: true,
|
|
2250
|
+
// Static export configuration
|
|
2251
|
+
...isStaticBuild && {
|
|
2252
|
+
output: "export",
|
|
2253
|
+
distDir: "out",
|
|
2254
|
+
basePath,
|
|
2255
|
+
// Fix for Next.js 15.5.4: prevent 500.html generation issue
|
|
2256
|
+
generateBuildId: async () => {
|
|
2257
|
+
return "static-build";
|
|
2258
|
+
}
|
|
2259
|
+
},
|
|
2260
|
+
// Standalone output for Docker (only in production, not dev)
|
|
2261
|
+
...!isStaticBuild && !isDev && {
|
|
2262
|
+
output: "standalone"
|
|
2263
|
+
},
|
|
2264
|
+
// Environment variables
|
|
2265
|
+
env: {
|
|
2266
|
+
NEXT_PUBLIC_BASE_PATH: basePath,
|
|
2267
|
+
NEXT_PUBLIC_API_URL: apiUrl,
|
|
2268
|
+
NEXT_PUBLIC_SITE_URL: siteUrl,
|
|
2269
|
+
...options.env
|
|
2270
|
+
},
|
|
2271
|
+
// Images configuration
|
|
2272
|
+
images: {
|
|
2273
|
+
unoptimized: true
|
|
2274
|
+
},
|
|
2275
|
+
// CORS headers for static files and iframe embedding
|
|
2276
|
+
async headers() {
|
|
2277
|
+
const headers = [
|
|
2278
|
+
{
|
|
2279
|
+
source: "/static/:path*",
|
|
2280
|
+
headers: [
|
|
2281
|
+
{ key: "Access-Control-Allow-Origin", value: "*" },
|
|
2282
|
+
{ key: "Access-Control-Allow-Methods", value: "GET, OPTIONS" },
|
|
2283
|
+
{ key: "Access-Control-Allow-Headers", value: "Origin, Content-Type, Accept" }
|
|
2284
|
+
]
|
|
2285
|
+
}
|
|
2286
|
+
];
|
|
2287
|
+
if (options.allowIframeFrom && options.allowIframeFrom.length > 0) {
|
|
2288
|
+
const frameAncestors = options.allowIframeFrom.includes("*") ? "*" : `'self' ${options.allowIframeFrom.join(" ")}`;
|
|
2289
|
+
headers.push({
|
|
2290
|
+
source: "/:path*",
|
|
2291
|
+
headers: [
|
|
2292
|
+
// Content-Security-Policy frame-ancestors directive
|
|
2293
|
+
{ key: "Content-Security-Policy", value: `frame-ancestors ${frameAncestors}` },
|
|
2294
|
+
// X-Frame-Options for older browsers (ALLOW-FROM is deprecated, use CSP instead)
|
|
2295
|
+
// Only set SAMEORIGIN if allowing all, otherwise browsers will use CSP
|
|
2296
|
+
...options.allowIframeFrom.includes("*") ? [] : [{ key: "X-Frame-Options", value: "SAMEORIGIN" }]
|
|
2297
|
+
]
|
|
2298
|
+
});
|
|
2299
|
+
}
|
|
2300
|
+
return headers;
|
|
2301
|
+
},
|
|
2302
|
+
// Transpile packages (merge with user-provided)
|
|
2303
|
+
transpilePackages: [
|
|
2304
|
+
...DEFAULT_TRANSPILE_PACKAGES,
|
|
2305
|
+
...options.transpilePackages || []
|
|
2306
|
+
],
|
|
2307
|
+
// Experimental features
|
|
2308
|
+
experimental: {
|
|
2309
|
+
// Optimize package imports (only in production)
|
|
2310
|
+
...!isDev && {
|
|
2311
|
+
optimizePackageImports: [
|
|
2312
|
+
...DEFAULT_OPTIMIZE_PACKAGES,
|
|
2313
|
+
...options.optimizePackageImports || []
|
|
2314
|
+
]
|
|
2315
|
+
},
|
|
2316
|
+
// Dev mode optimizations
|
|
2317
|
+
...isDev && {
|
|
2318
|
+
optimizeCss: false
|
|
2319
|
+
},
|
|
2320
|
+
// User experimental options applied last (can override base settings)
|
|
2321
|
+
...options.experimental
|
|
2322
|
+
},
|
|
2323
|
+
// Webpack configuration
|
|
2324
|
+
webpack: (config, webpackOptions) => {
|
|
2325
|
+
const { isServer, dev } = webpackOptions;
|
|
2326
|
+
if (dev && !isServer) {
|
|
2327
|
+
if (!config.plugins) {
|
|
2328
|
+
config.plugins = [];
|
|
2329
|
+
}
|
|
2330
|
+
config.plugins.push(
|
|
2331
|
+
new DevStartupPlugin({
|
|
2332
|
+
openBrowser: options.openBrowser,
|
|
2333
|
+
checkUpdates: options.checkUpdates,
|
|
2334
|
+
autoUpdate: options.autoUpdate,
|
|
2335
|
+
forceCheckWorkspace: options.forceCheckWorkspace,
|
|
2336
|
+
checkPackages: options.checkPackages,
|
|
2337
|
+
autoInstall: options.autoInstall
|
|
2338
|
+
})
|
|
2339
|
+
);
|
|
2340
|
+
}
|
|
2341
|
+
if (dev) {
|
|
2342
|
+
config.optimization = {
|
|
2343
|
+
...config.optimization,
|
|
2344
|
+
removeAvailableModules: false,
|
|
2345
|
+
removeEmptyChunks: false,
|
|
2346
|
+
splitChunks: false
|
|
2347
|
+
// Disable code splitting in dev for faster compilation
|
|
2348
|
+
};
|
|
2349
|
+
}
|
|
2350
|
+
config.cache = {
|
|
2351
|
+
type: "filesystem",
|
|
2352
|
+
buildDependencies: {}
|
|
2353
|
+
};
|
|
2354
|
+
if (!isServer && isStaticBuild && !dev) {
|
|
2355
|
+
addCompressionPlugins(config);
|
|
2356
|
+
}
|
|
2357
|
+
if (options.webpack) {
|
|
2358
|
+
return options.webpack(config, webpackOptions);
|
|
2359
|
+
}
|
|
2360
|
+
return config;
|
|
2361
|
+
}
|
|
2362
|
+
};
|
|
2363
|
+
const finalConfig = deepMerge(baseConfig, options);
|
|
2364
|
+
delete finalConfig.optimizePackageImports;
|
|
2365
|
+
delete finalConfig.isDefaultCfgAdmin;
|
|
2366
|
+
delete finalConfig.openBrowser;
|
|
2367
|
+
delete finalConfig.checkUpdates;
|
|
2368
|
+
delete finalConfig.autoUpdate;
|
|
2369
|
+
delete finalConfig.forceCheckWorkspace;
|
|
2370
|
+
delete finalConfig.checkPackages;
|
|
2371
|
+
delete finalConfig.autoInstall;
|
|
2372
|
+
delete finalConfig.allowIframeFrom;
|
|
2373
|
+
return finalConfig;
|
|
2374
|
+
}
|
|
2375
|
+
|
|
2376
|
+
// src/ai/client.ts
|
|
2377
|
+
var DjangoCfgDocsClient = class {
|
|
2378
|
+
constructor(baseUrl = MCP_BASE_URL, timeout = DEFAULT_TIMEOUT) {
|
|
2379
|
+
this.baseUrl = baseUrl.replace(/\/$/, "");
|
|
2380
|
+
this.timeout = timeout;
|
|
2381
|
+
}
|
|
2382
|
+
async makeRequest(endpoint, params) {
|
|
2383
|
+
let url = `${this.baseUrl}${endpoint}`;
|
|
2384
|
+
if (params) {
|
|
2385
|
+
const searchParams = new URLSearchParams();
|
|
2386
|
+
for (const [key, value] of Object.entries(params)) {
|
|
2387
|
+
searchParams.set(key, String(value));
|
|
2388
|
+
}
|
|
2389
|
+
url = `${url}?${searchParams.toString()}`;
|
|
2390
|
+
}
|
|
2391
|
+
const controller = new AbortController();
|
|
2392
|
+
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
2393
|
+
try {
|
|
2394
|
+
const response = await fetch(url, {
|
|
2395
|
+
method: "GET",
|
|
2396
|
+
headers: {
|
|
2397
|
+
Accept: "application/json",
|
|
2398
|
+
"User-Agent": "DjangoCFG-AI-Client/1.0"
|
|
2399
|
+
},
|
|
2400
|
+
signal: controller.signal
|
|
2401
|
+
});
|
|
2402
|
+
if (!response.ok) {
|
|
2403
|
+
throw new Error(`HTTP Error ${response.status}: ${response.statusText}`);
|
|
2404
|
+
}
|
|
2405
|
+
return await response.json();
|
|
2406
|
+
} finally {
|
|
2407
|
+
clearTimeout(timeoutId);
|
|
2408
|
+
}
|
|
2409
|
+
}
|
|
2410
|
+
/**
|
|
2411
|
+
* Search documentation.
|
|
2412
|
+
*/
|
|
2413
|
+
async search(query, options = {}) {
|
|
2414
|
+
const { limit = 5, category } = options;
|
|
2415
|
+
const params = { q: query, limit };
|
|
2416
|
+
if (category) {
|
|
2417
|
+
params.category = category;
|
|
2418
|
+
}
|
|
2419
|
+
const data = await this.makeRequest(API_SEARCH_ENDPOINT, params);
|
|
2420
|
+
const results = data.results || (Array.isArray(data) ? data : []);
|
|
2421
|
+
return results.map((item) => ({
|
|
2422
|
+
title: item.title || "",
|
|
2423
|
+
content: item.content || item.snippet || "",
|
|
2424
|
+
url: item.url || "",
|
|
2425
|
+
score: item.score || 0,
|
|
2426
|
+
category: item.category || ""
|
|
2427
|
+
}));
|
|
2428
|
+
}
|
|
2429
|
+
/**
|
|
2430
|
+
* Get detailed info about a specific topic.
|
|
2431
|
+
*/
|
|
2432
|
+
async getInfo(topic) {
|
|
2433
|
+
return this.makeRequest(API_INFO_ENDPOINT, { topic });
|
|
2434
|
+
}
|
|
2435
|
+
/**
|
|
2436
|
+
* Get MCP server configuration for AI assistants.
|
|
2437
|
+
*/
|
|
2438
|
+
getMcpConfig() {
|
|
2439
|
+
return {
|
|
2440
|
+
mcpServers: {
|
|
2441
|
+
"djangocfg-docs": {
|
|
2442
|
+
url: `${this.baseUrl}/mcp`
|
|
2443
|
+
}
|
|
2444
|
+
}
|
|
2445
|
+
};
|
|
2446
|
+
}
|
|
2447
|
+
};
|
|
2448
|
+
var defaultClient = null;
|
|
2449
|
+
function getClient() {
|
|
2450
|
+
if (!defaultClient) {
|
|
2451
|
+
defaultClient = new DjangoCfgDocsClient();
|
|
2452
|
+
}
|
|
2453
|
+
return defaultClient;
|
|
2454
|
+
}
|
|
2455
|
+
async function search(query, options = {}) {
|
|
2456
|
+
return getClient().search(query, options);
|
|
2457
|
+
}
|
|
2458
|
+
async function getDocs(query, limit = 3) {
|
|
2459
|
+
const results = await search(query, { limit });
|
|
2460
|
+
if (results.length === 0) {
|
|
2461
|
+
return `No documentation found for: ${query}`;
|
|
2462
|
+
}
|
|
2463
|
+
const output = [];
|
|
2464
|
+
results.forEach((r, i) => {
|
|
2465
|
+
output.push(`## ${i + 1}. ${r.title}`);
|
|
2466
|
+
output.push(r.content);
|
|
2467
|
+
if (r.url) {
|
|
2468
|
+
output.push(`\u{1F4D6} Read more: ${r.url}`);
|
|
2469
|
+
}
|
|
2470
|
+
output.push("");
|
|
2471
|
+
});
|
|
2472
|
+
return output.join("\n");
|
|
2473
|
+
}
|
|
2474
|
+
async function getInfo(topic) {
|
|
2475
|
+
return getClient().getInfo(topic);
|
|
2476
|
+
}
|
|
2477
|
+
function getMcpConfig() {
|
|
2478
|
+
return getClient().getMcpConfig();
|
|
2479
|
+
}
|
|
2480
|
+
export {
|
|
2481
|
+
AI_DOCS_HINT,
|
|
2482
|
+
AI_HINT,
|
|
2483
|
+
DEFAULT_OPTIMIZE_PACKAGES,
|
|
2484
|
+
DEFAULT_TRANSPILE_PACKAGES,
|
|
2485
|
+
DJANGOCFG_PACKAGES,
|
|
2486
|
+
DJANGO_CFG_BANNER,
|
|
2487
|
+
DefaultTemplate,
|
|
2488
|
+
DevStartupPlugin,
|
|
2489
|
+
DjangoCfgDocsClient,
|
|
2490
|
+
LightTemplate,
|
|
2491
|
+
MCP_API_URL,
|
|
2492
|
+
MCP_BASE_URL,
|
|
2493
|
+
MCP_SERVER_URL,
|
|
2494
|
+
OPTIONAL_PACKAGES,
|
|
2495
|
+
PACKAGE_NAME,
|
|
2496
|
+
PEER_DEPENDENCIES,
|
|
2497
|
+
POST,
|
|
2498
|
+
addCompressionPlugins,
|
|
2499
|
+
buildInstallCommand,
|
|
2500
|
+
buildSingleInstallCommand,
|
|
2501
|
+
checkAndInstallPackages,
|
|
2502
|
+
checkAndUpdatePackages,
|
|
2503
|
+
checkForUpdate,
|
|
2504
|
+
checkForUpdates,
|
|
2505
|
+
checkPackages,
|
|
2506
|
+
createBaseNextConfig,
|
|
2507
|
+
createContactRoute,
|
|
2508
|
+
createFontLoader,
|
|
2509
|
+
createHealthHandler,
|
|
2510
|
+
createOgImageDynamicRoute,
|
|
2511
|
+
createOgImageHandler,
|
|
2512
|
+
createOgImageMetadataGenerator,
|
|
2513
|
+
createOgImageUrlBuilder,
|
|
2514
|
+
createSitemapHandler,
|
|
2515
|
+
decodeBase64,
|
|
2516
|
+
deepMerge,
|
|
2517
|
+
defineRoute,
|
|
2518
|
+
detectPackageManager,
|
|
2519
|
+
encodeBase64,
|
|
2520
|
+
fetchLatestVersion,
|
|
2521
|
+
findRoute,
|
|
2522
|
+
findRouteByPattern,
|
|
2523
|
+
generateOgImageMetadata,
|
|
2524
|
+
generateOgImageUrl,
|
|
2525
|
+
generateSitemapXml,
|
|
2526
|
+
getAbsoluteOgImageUrl,
|
|
2527
|
+
getApiUrl,
|
|
2528
|
+
getBasePath,
|
|
2529
|
+
getCurrentVersion,
|
|
2530
|
+
getDocs,
|
|
2531
|
+
getInfo,
|
|
2532
|
+
getInstalledVersion,
|
|
2533
|
+
getMcpConfig,
|
|
2534
|
+
getMissingPackages,
|
|
2535
|
+
getOutdatedPackages,
|
|
2536
|
+
getPackagesForContext,
|
|
2537
|
+
getPageTitle,
|
|
2538
|
+
getSiteUrl2 as getSiteUrl,
|
|
2539
|
+
getUnauthenticatedRedirect,
|
|
2540
|
+
getUpdateCommand,
|
|
2541
|
+
installPackages,
|
|
2542
|
+
installPackagesWithProgress,
|
|
2543
|
+
isActive,
|
|
2544
|
+
isCI,
|
|
2545
|
+
isCompressionAvailable,
|
|
2546
|
+
isDev,
|
|
2547
|
+
isPackageInstalled,
|
|
2548
|
+
isProduction,
|
|
2549
|
+
isStaticBuild,
|
|
2550
|
+
loadGoogleFont,
|
|
2551
|
+
loadGoogleFonts,
|
|
2552
|
+
normalizeUrl,
|
|
2553
|
+
parseOgImageData,
|
|
2554
|
+
parseOgImageUrl,
|
|
2555
|
+
printVersionInfo,
|
|
2556
|
+
redirectToAuth,
|
|
2557
|
+
resetDevStartupState,
|
|
2558
|
+
resetInstallerPreferences,
|
|
2559
|
+
resetUpdaterPreferences,
|
|
2560
|
+
routesToMenuItems,
|
|
2561
|
+
search,
|
|
2562
|
+
submitContactForm,
|
|
2563
|
+
updatePackagesWithProgress
|
|
2564
|
+
};
|
|
2565
|
+
//# sourceMappingURL=index.mjs.map
|