@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.
- package/LICENSE +21 -0
- package/README.md +261 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +205 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +38 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/cms.d.ts +104 -0
- package/dist/lib/cms.d.ts.map +1 -0
- package/dist/lib/cms.js +532 -0
- package/dist/lib/cms.js.map +1 -0
- package/dist/lib/config.d.ts +45 -0
- package/dist/lib/config.d.ts.map +1 -0
- package/dist/lib/config.js +194 -0
- package/dist/lib/config.js.map +1 -0
- package/dist/scripts/fetch-leadcms-content.mjs +361 -0
- package/dist/scripts/generate-env-js.mjs +24 -0
- package/dist/scripts/leadcms-helpers.mjs +208 -0
- package/dist/scripts/sse-watcher.mjs +325 -0
- package/dist/templates/docker/Dockerfile +34 -0
- package/dist/templates/docker/nginx.conf +70 -0
- package/dist/templates/docker/preview/Dockerfile +75 -0
- package/dist/templates/docker/preview/nginx.conf +128 -0
- package/dist/templates/docker/preview/supervisord.conf +63 -0
- package/dist/templates/scripts/inject-runtime-env.sh +33 -0
- package/leadcms.config.json.sample +8 -0
- package/package.json +76 -0
|
@@ -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.")
|