@leadcms/sdk 1.2.85-pre

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,194 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.loadConfig = loadConfig;
7
+ exports.configure = configure;
8
+ exports.resetConfig = resetConfig;
9
+ exports.getConfig = getConfig;
10
+ exports.generateConfigFile = generateConfigFile;
11
+ const fs_1 = __importDefault(require("fs"));
12
+ const path_1 = __importDefault(require("path"));
13
+ let globalConfig = null;
14
+ /**
15
+ * Default configuration values
16
+ */
17
+ const DEFAULT_CONFIG = {
18
+ defaultLanguage: "en",
19
+ contentDir: ".leadcms/content",
20
+ mediaDir: "public/media",
21
+ enableDrafts: false,
22
+ };
23
+ /**
24
+ * Load configuration from multiple sources in priority order:
25
+ * 1. Programmatically set config (via configure())
26
+ * 2. Config file (leadcms.config.js/json)
27
+ * 3. Environment variables
28
+ * 4. Default values
29
+ */
30
+ function loadConfig(options = {}) {
31
+ const cwd = options.cwd || process.cwd();
32
+ // 1. Try to load from config file
33
+ const configFromFile = loadConfigFile(options.configPath, cwd);
34
+ // 2. Load from environment variables
35
+ const configFromEnv = loadConfigFromEnv();
36
+ // 3. Merge all sources with proper precedence
37
+ const mergedConfig = {
38
+ ...DEFAULT_CONFIG,
39
+ ...configFromEnv,
40
+ ...configFromFile,
41
+ ...globalConfig, // Programmatic config takes precedence
42
+ ...options, // Options passed to this function take highest precedence
43
+ };
44
+ // Remove undefined values and ensure required fields
45
+ const cleanConfig = {
46
+ url: mergedConfig.url || "",
47
+ apiKey: mergedConfig.apiKey || "",
48
+ defaultLanguage: mergedConfig.defaultLanguage || DEFAULT_CONFIG.defaultLanguage,
49
+ contentDir: mergedConfig.contentDir || DEFAULT_CONFIG.contentDir,
50
+ mediaDir: mergedConfig.mediaDir || DEFAULT_CONFIG.mediaDir,
51
+ enableDrafts: mergedConfig.enableDrafts || DEFAULT_CONFIG.enableDrafts,
52
+ };
53
+ validateConfig(cleanConfig);
54
+ return cleanConfig;
55
+ }
56
+ /**
57
+ * Set configuration programmatically
58
+ */
59
+ function configure(config) {
60
+ globalConfig = { ...globalConfig, ...config };
61
+ }
62
+ /**
63
+ * Reset configuration (useful for testing)
64
+ */
65
+ function resetConfig() {
66
+ globalConfig = null;
67
+ }
68
+ /**
69
+ * Get current configuration
70
+ */
71
+ function getConfig(options) {
72
+ return loadConfig(options);
73
+ }
74
+ /**
75
+ * Load configuration from file
76
+ */
77
+ function loadConfigFile(configPath, cwd = process.cwd()) {
78
+ const possiblePaths = [
79
+ configPath,
80
+ path_1.default.join(cwd, "leadcms.config.js"),
81
+ path_1.default.join(cwd, "leadcms.config.mjs"),
82
+ path_1.default.join(cwd, "leadcms.config.json"),
83
+ path_1.default.join(cwd, ".leadcmsrc.json"),
84
+ path_1.default.join(cwd, ".leadcmsrc"),
85
+ ].filter(Boolean);
86
+ for (const configFilePath of possiblePaths) {
87
+ try {
88
+ if (!fs_1.default.existsSync(configFilePath))
89
+ continue;
90
+ const ext = path_1.default.extname(configFilePath);
91
+ let config;
92
+ if (ext === ".json" || configFilePath.endsWith(".leadcmsrc")) {
93
+ // JSON config
94
+ const content = fs_1.default.readFileSync(configFilePath, "utf-8");
95
+ config = JSON.parse(content);
96
+ }
97
+ else if (ext === ".js" || ext === ".mjs") {
98
+ // JavaScript config (require won't work in ESM, but this is a starting point)
99
+ try {
100
+ // For now, we'll handle JS configs in the CLI layer
101
+ console.warn(`[LeadCMS] JavaScript config files not yet supported in browser environments: ${configFilePath}`);
102
+ continue;
103
+ }
104
+ catch (error) {
105
+ console.warn(`[LeadCMS] Failed to load JS config: ${configFilePath}`, error);
106
+ continue;
107
+ }
108
+ }
109
+ else {
110
+ continue;
111
+ }
112
+ console.log(`[LeadCMS] Loaded configuration from: ${configFilePath}`);
113
+ return config;
114
+ }
115
+ catch (error) {
116
+ console.warn(`[LeadCMS] Failed to load config from ${configFilePath}:`, error);
117
+ continue;
118
+ }
119
+ }
120
+ return {};
121
+ }
122
+ /**
123
+ * Load configuration from environment variables
124
+ */
125
+ function loadConfigFromEnv() {
126
+ const config = {};
127
+ // Support both generic and Next.js specific env vars
128
+ if (process.env.LEADCMS_URL || process.env.NEXT_PUBLIC_LEADCMS_URL) {
129
+ config.url = process.env.LEADCMS_URL || process.env.NEXT_PUBLIC_LEADCMS_URL;
130
+ }
131
+ if (process.env.LEADCMS_API_KEY) {
132
+ config.apiKey = process.env.LEADCMS_API_KEY;
133
+ }
134
+ if (process.env.LEADCMS_DEFAULT_LANGUAGE || process.env.NEXT_PUBLIC_LEADCMS_DEFAULT_LANGUAGE) {
135
+ config.defaultLanguage = process.env.LEADCMS_DEFAULT_LANGUAGE || process.env.NEXT_PUBLIC_LEADCMS_DEFAULT_LANGUAGE;
136
+ }
137
+ if (process.env.LEADCMS_CONTENT_DIR) {
138
+ config.contentDir = process.env.LEADCMS_CONTENT_DIR;
139
+ }
140
+ if (process.env.LEADCMS_MEDIA_DIR) {
141
+ config.mediaDir = process.env.LEADCMS_MEDIA_DIR;
142
+ }
143
+ if (process.env.LEADCMS_ENABLE_DRAFTS) {
144
+ config.enableDrafts = process.env.LEADCMS_ENABLE_DRAFTS === "true";
145
+ }
146
+ return config;
147
+ }
148
+ /**
149
+ * Validate configuration
150
+ */
151
+ function validateConfig(config) {
152
+ const errors = [];
153
+ if (!config.url) {
154
+ errors.push("Missing required configuration: url");
155
+ }
156
+ if (!config.apiKey) {
157
+ errors.push("Missing required configuration: apiKey");
158
+ }
159
+ if (config.url && !isValidUrl(config.url)) {
160
+ errors.push("Invalid URL format: url");
161
+ }
162
+ if (errors.length > 0) {
163
+ throw new Error(`LeadCMS configuration errors:\n${errors.join("\n")}`);
164
+ }
165
+ }
166
+ /**
167
+ * Simple URL validation
168
+ */
169
+ function isValidUrl(url) {
170
+ try {
171
+ new URL(url);
172
+ return true;
173
+ }
174
+ catch {
175
+ return false;
176
+ }
177
+ }
178
+ /**
179
+ * Generate a sample configuration file
180
+ */
181
+ function generateConfigFile(filePath = "leadcms.config.json") {
182
+ const sampleConfig = {
183
+ url: "https://your-leadcms-instance.com",
184
+ apiKey: "your-api-key-here",
185
+ defaultLanguage: "en",
186
+ contentDir: ".leadcms/content",
187
+ mediaDir: "public/media",
188
+ enableDrafts: false
189
+ };
190
+ const content = JSON.stringify(sampleConfig, null, 2);
191
+ fs_1.default.writeFileSync(filePath, content, "utf-8");
192
+ return filePath;
193
+ }
194
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/lib/config.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,361 @@
1
+ import "dotenv/config"
2
+ import fs from "fs/promises"
3
+ import path from "path"
4
+ import axios from "axios"
5
+ import {
6
+ extractMediaUrlsFromContent,
7
+ downloadMediaFileDirect,
8
+ saveContentFile,
9
+ leadCMSUrl,
10
+ leadCMSApiKey,
11
+ defaultLanguage,
12
+ CONTENT_DIR,
13
+ MEDIA_DIR,
14
+ fetchContentTypes,
15
+ } from "./leadcms-helpers.mjs"
16
+
17
+ // Add axios request/response interceptors for debugging
18
+ axios.interceptors.request.use(
19
+ (config) => {
20
+ console.log(`[AXIOS REQUEST] ${config.method?.toUpperCase()} ${config.url}`)
21
+
22
+ // Mask the Authorization header for security
23
+ const maskedHeaders = { ...config.headers }
24
+ if (maskedHeaders.Authorization && typeof maskedHeaders.Authorization === "string") {
25
+ const authParts = maskedHeaders.Authorization.split(" ")
26
+ if (authParts.length === 2 && authParts[0] === "Bearer") {
27
+ maskedHeaders.Authorization = `Bearer ${authParts[1].substring(0, 8)}...`
28
+ }
29
+ }
30
+
31
+ return config
32
+ },
33
+ (error) => {
34
+ console.error(`[AXIOS REQUEST ERROR]`, error)
35
+ return Promise.reject(error)
36
+ }
37
+ )
38
+
39
+ axios.interceptors.response.use(
40
+ (response) => {
41
+ return response
42
+ },
43
+ (error) => {
44
+ console.error(
45
+ `[AXIOS RESPONSE ERROR] ${error.response?.status || "NO_STATUS"} ${error.response?.statusText || "NO_STATUS_TEXT"} for ${error.config?.url || "NO_URL"}`
46
+ )
47
+ if (error.response) {
48
+ console.error(`[AXIOS RESPONSE ERROR] Response data:`, error.response.data)
49
+ console.error(
50
+ `[AXIOS RESPONSE ERROR] Response headers:`,
51
+ JSON.stringify(error.response.headers, null, 2)
52
+ )
53
+ }
54
+ console.error(`[AXIOS RESPONSE ERROR] Full error:`, error.message)
55
+ return Promise.reject(error)
56
+ }
57
+ )
58
+
59
+ const SYNC_TOKEN_PATH = path.resolve(".leadcms/sync-token.txt")
60
+ const MEDIA_SYNC_TOKEN_PATH = path.resolve(".leadcms/media-sync-token.txt")
61
+
62
+ async function readSyncToken() {
63
+ try {
64
+ return (await fs.readFile(SYNC_TOKEN_PATH, "utf8")).trim()
65
+ } catch {
66
+ return undefined
67
+ }
68
+ }
69
+
70
+ async function writeSyncToken(token) {
71
+ await fs.mkdir(path.dirname(SYNC_TOKEN_PATH), { recursive: true })
72
+ await fs.writeFile(SYNC_TOKEN_PATH, token, "utf8")
73
+ }
74
+
75
+ async function readMediaSyncToken() {
76
+ try {
77
+ return (await fs.readFile(MEDIA_SYNC_TOKEN_PATH, "utf8")).trim()
78
+ } catch {
79
+ return undefined
80
+ }
81
+ }
82
+
83
+ async function writeMediaSyncToken(token) {
84
+ await fs.mkdir(path.dirname(MEDIA_SYNC_TOKEN_PATH), { recursive: true })
85
+ await fs.writeFile(MEDIA_SYNC_TOKEN_PATH, token, "utf8")
86
+ }
87
+
88
+ async function fetchContentSync(syncToken) {
89
+ console.log(`[FETCH_CONTENT_SYNC] Starting with syncToken: ${syncToken || "NONE"}`)
90
+ let allItems = []
91
+ let allDeleted = []
92
+ let token = syncToken || ""
93
+ let nextSyncToken = undefined
94
+ let page = 0
95
+
96
+ while (true) {
97
+ const url = new URL("/api/content/sync", leadCMSUrl)
98
+ url.searchParams.set("filter[limit]", "100")
99
+ url.searchParams.set("syncToken", token)
100
+
101
+ console.log(`[FETCH_CONTENT_SYNC] Page ${page}, URL: ${url.toString()}`)
102
+
103
+ try {
104
+ const res = await axios.get(url.toString(), {
105
+ headers: { Authorization: `Bearer ${leadCMSApiKey}` },
106
+ })
107
+
108
+ if (res.status === 204) {
109
+ console.log(`[FETCH_CONTENT_SYNC] Got 204 No Content - ending sync`)
110
+ break
111
+ }
112
+
113
+ const data = res.data
114
+ console.log(
115
+ `[FETCH_CONTENT_SYNC] Page ${page} - Got ${data.items?.length || 0} items, ${data.deleted?.length || 0} deleted`
116
+ )
117
+
118
+ if (data.items && Array.isArray(data.items)) allItems.push(...data.items)
119
+
120
+ if (data.deleted && Array.isArray(data.deleted)) {
121
+ allDeleted.push(...data.deleted)
122
+ }
123
+
124
+ const newSyncToken = res.headers["x-next-sync-token"] || token
125
+ console.log(`[FETCH_CONTENT_SYNC] Next sync token: ${newSyncToken}`)
126
+
127
+ if (!newSyncToken || newSyncToken === token) {
128
+ nextSyncToken = newSyncToken || token
129
+ console.log(`[FETCH_CONTENT_SYNC] No new sync token - ending sync`)
130
+ break
131
+ }
132
+
133
+ nextSyncToken = newSyncToken
134
+ token = newSyncToken
135
+ page++
136
+ } catch (error) {
137
+ console.error(`[FETCH_CONTENT_SYNC] Failed on page ${page}:`, error.message)
138
+ throw error
139
+ }
140
+ }
141
+
142
+ console.log(
143
+ `[FETCH_CONTENT_SYNC] Completed - Total items: ${allItems.length}, deleted: ${allDeleted.length}`
144
+ )
145
+ return {
146
+ items: allItems,
147
+ deleted: allDeleted,
148
+ nextSyncToken: nextSyncToken || token,
149
+ }
150
+ }
151
+
152
+ async function fetchMediaSync(syncToken) {
153
+ console.log(`[FETCH_MEDIA_SYNC] Starting with syncToken: ${syncToken || "NONE"}`)
154
+ let allItems = []
155
+ let token = syncToken || ""
156
+ let nextSyncToken = undefined
157
+ let page = 0
158
+
159
+ while (true) {
160
+ const url = new URL("/api/media/sync", leadCMSUrl)
161
+ url.searchParams.set("filter[limit]", "100")
162
+ url.searchParams.set("syncToken", token)
163
+
164
+ console.log(`[FETCH_MEDIA_SYNC] Page ${page}, URL: ${url.toString()}`)
165
+
166
+ try {
167
+ const res = await axios.get(url.toString(), {
168
+ headers: { Authorization: `Bearer ${leadCMSApiKey}` },
169
+ })
170
+
171
+ if (res.status === 204) {
172
+ console.log(`[FETCH_MEDIA_SYNC] Got 204 No Content - ending sync`)
173
+ break
174
+ }
175
+
176
+ const data = res.data
177
+ console.log(
178
+ `[FETCH_MEDIA_SYNC] Page ${page} - Got ${data.items?.length || 0} items`
179
+ )
180
+
181
+ if (data.items && Array.isArray(data.items)) allItems.push(...data.items)
182
+
183
+ const newSyncToken = res.headers["x-next-sync-token"] || token
184
+ console.log(`[FETCH_MEDIA_SYNC] Next sync token: ${newSyncToken}`)
185
+
186
+ if (!newSyncToken || newSyncToken === token) {
187
+ nextSyncToken = newSyncToken || token
188
+ console.log(`[FETCH_MEDIA_SYNC] No new sync token - ending sync`)
189
+ break
190
+ }
191
+
192
+ nextSyncToken = newSyncToken
193
+ token = newSyncToken
194
+ page++
195
+ } catch (error) {
196
+ console.error(`[FETCH_MEDIA_SYNC] Failed on page ${page}:`, error.message)
197
+ throw error
198
+ }
199
+ }
200
+
201
+ console.log(
202
+ `[FETCH_MEDIA_SYNC] Completed - Total items: ${allItems.length}`
203
+ )
204
+ return {
205
+ items: allItems,
206
+ nextSyncToken: nextSyncToken || token,
207
+ }
208
+ }
209
+
210
+ async function main() {
211
+ // Log environment configuration for debugging
212
+ console.log(`[ENV] LeadCMS URL: ${leadCMSUrl}`)
213
+ console.log(
214
+ `[ENV] LeadCMS API Key: ${leadCMSApiKey ? `${leadCMSApiKey.substring(0, 8)}...` : "NOT_SET"}`
215
+ )
216
+ console.log(`[ENV] Default Language: ${defaultLanguage}`)
217
+ console.log(`[ENV] Content Dir: ${CONTENT_DIR}`)
218
+ console.log(`[ENV] Media Dir: ${MEDIA_DIR}`)
219
+
220
+ await fs.mkdir(CONTENT_DIR, { recursive: true })
221
+ await fs.mkdir(MEDIA_DIR, { recursive: true })
222
+
223
+ const typeMap = await fetchContentTypes()
224
+
225
+ const lastSyncToken = await readSyncToken()
226
+ const lastMediaSyncToken = await readMediaSyncToken()
227
+
228
+ let items = [],
229
+ deleted = [],
230
+ nextSyncToken
231
+
232
+ let mediaItems = [],
233
+ nextMediaSyncToken
234
+
235
+ // Sync content
236
+ try {
237
+ if (lastSyncToken) {
238
+ console.log(`Syncing content from LeadCMS using sync token: ${lastSyncToken}`)
239
+ ;({ items, deleted, nextSyncToken } = await fetchContentSync(lastSyncToken))
240
+ } else {
241
+ console.log("No content sync token found. Doing full fetch from LeadCMS...")
242
+ ;({ items, deleted, nextSyncToken } = await fetchContentSync(undefined))
243
+ }
244
+ } catch (error) {
245
+ console.error(`[MAIN] Failed to fetch content:`, error.message)
246
+ if (error.response?.status === 401) {
247
+ console.error(`[MAIN] Authentication failed - check your LEADCMS_API_KEY`)
248
+ }
249
+ throw error
250
+ }
251
+
252
+ // Sync media
253
+ try {
254
+ if (lastMediaSyncToken) {
255
+ console.log(`Syncing media from LeadCMS using sync token: ${lastMediaSyncToken}`)
256
+ ;({ items: mediaItems, nextSyncToken: nextMediaSyncToken } = await fetchMediaSync(lastMediaSyncToken))
257
+ } else {
258
+ console.log("No media sync token found. Doing full fetch from LeadCMS...")
259
+ ;({ items: mediaItems, nextSyncToken: nextMediaSyncToken } = await fetchMediaSync(undefined))
260
+ }
261
+ } catch (error) {
262
+ console.error(`[MAIN] Failed to fetch media:`, error.message)
263
+ if (error.response?.status === 401) {
264
+ console.error(`[MAIN] Authentication failed - check your LEADCMS_API_KEY`)
265
+ }
266
+ // Don't throw here, continue with content sync even if media sync fails
267
+ console.warn(`[MAIN] Continuing without media sync...`)
268
+ }
269
+
270
+ console.log(`Fetched ${items.length} content items, ${deleted.length} deleted.`)
271
+ console.log(`Fetched ${mediaItems.length} media items.`)
272
+
273
+ // Save content files and collect all media URLs from content
274
+ const allMediaUrls = new Set()
275
+ for (const content of items) {
276
+ if (content && typeof content === "object") {
277
+ await saveContentFile({
278
+ content,
279
+ typeMap,
280
+ contentDir: CONTENT_DIR,
281
+ })
282
+ for (const url of extractMediaUrlsFromContent(content)) {
283
+ allMediaUrls.add(url)
284
+ }
285
+ }
286
+ }
287
+
288
+ // Remove deleted content files from all language directories
289
+ for (const id of deleted) {
290
+ const idStr = String(id)
291
+
292
+ // Function to recursively search for files in a directory
293
+ async function findAndDeleteContentFile(dir) {
294
+ try {
295
+ const entries = await fs.readdir(dir, { withFileTypes: true })
296
+ for (const entry of entries) {
297
+ const fullPath = path.join(dir, entry.name)
298
+ if (entry.isDirectory()) {
299
+ // Recursively search subdirectories
300
+ await findAndDeleteContentFile(fullPath)
301
+ } else if (entry.isFile()) {
302
+ try {
303
+ const content = await fs.readFile(fullPath, "utf8")
304
+ // Exact-match YAML frontmatter: lines like `id: 10` or `id: '10'`
305
+ const yamlRegex = new RegExp(`(^|\\n)id:\\s*['\"]?${idStr}['\"]?(\\n|$)`)
306
+ // Exact-match JSON: "id": 10 or "id": "10"
307
+ const jsonRegex = new RegExp(`\\"id\\"\\s*:\\s*['\"]?${idStr}['\"]?\\s*(,|\\}|\\n|$)`)
308
+ if (yamlRegex.test(content) || jsonRegex.test(content)) {
309
+ await fs.unlink(fullPath)
310
+ console.log(`Deleted: ${fullPath}`)
311
+ }
312
+ } catch {}
313
+ }
314
+ }
315
+ } catch (err) {
316
+ // Directory might not exist, that's okay
317
+ if (err.code !== 'ENOENT') {
318
+ console.warn(`Warning: Could not read directory ${dir}:`, err.message)
319
+ }
320
+ }
321
+ }
322
+
323
+ await findAndDeleteContentFile(CONTENT_DIR)
324
+ }
325
+
326
+ // Handle media sync results
327
+ if (mediaItems.length > 0) {
328
+ console.log(`\nProcessing media changes...`)
329
+
330
+ // Download new/updated media files
331
+ let downloaded = 0
332
+ for (const mediaItem of mediaItems) {
333
+ if (mediaItem.location) {
334
+ const relPath = mediaItem.location.replace(/^\/api\/media\//, "")
335
+ const destPath = path.join(MEDIA_DIR, relPath)
336
+ const didDownload = await downloadMediaFileDirect(mediaItem.location, destPath, leadCMSUrl, leadCMSApiKey)
337
+ if (didDownload) {
338
+ console.log(`Downloaded: ${mediaItem.location} -> ${destPath}`)
339
+ downloaded++
340
+ }
341
+ }
342
+ }
343
+ console.log(`\nDone. ${downloaded} media files downloaded.\n`)
344
+ } else {
345
+ console.log(`\nNo media changes detected.\n`)
346
+ } // Save new sync tokens
347
+ if (nextSyncToken) {
348
+ await writeSyncToken(nextSyncToken)
349
+ console.log(`Content sync token updated: ${nextSyncToken}`)
350
+ }
351
+
352
+ if (nextMediaSyncToken) {
353
+ await writeMediaSyncToken(nextMediaSyncToken)
354
+ console.log(`Media sync token updated: ${nextMediaSyncToken}`)
355
+ }
356
+ }
357
+
358
+ main().catch((err) => {
359
+ console.error("Error in fetch-leadcms-content:", err)
360
+ process.exit(1)
361
+ })
@@ -0,0 +1,24 @@
1
+ // This script generates a __env.js file with all NEXT_PUBLIC_ env variables
2
+ // Usage: node ./scripts/generate-env-js.mjs
3
+ import fs from "fs"
4
+ import path from "path"
5
+ import dotenv from "dotenv"
6
+
7
+ // Load env from .env, .env.local, etc. (dotenv will not overwrite existing process.env)
8
+ dotenv.config({ path: path.resolve(process.cwd(), ".env") })
9
+ try {
10
+ dotenv.config({ path: path.resolve(process.cwd(), ".env.local") })
11
+ } catch {}
12
+
13
+ const envVars = Object.keys(process.env)
14
+ .filter((key) => key.startsWith("NEXT_PUBLIC_"))
15
+ .reduce((acc, key) => {
16
+ acc[key] = process.env[key]
17
+ return acc
18
+ }, {})
19
+
20
+ const jsContent = `window.__env = ${JSON.stringify(envVars, null, 2)};\n`
21
+
22
+ const outPath = path.resolve(process.cwd(), "public", "__env.js")
23
+ fs.writeFileSync(outPath, jsContent)
24
+ console.log("Generated public/__env.js with NEXT_PUBLIC_ env variables.")