@leadcms/sdk 3.3.12 ā 3.5.0
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/README.md +2 -1
- package/dist/cli/bin/content-status-args.d.ts +7 -0
- package/dist/cli/bin/content-status-args.d.ts.map +1 -0
- package/dist/cli/bin/content-status-args.js +27 -0
- package/dist/cli/bin/content-status-args.js.map +1 -0
- package/dist/cli/bin/login.js +1 -1
- package/dist/cli/bin/login.js.map +1 -1
- package/dist/cli/bin/pull-all.js +4 -2
- package/dist/cli/bin/pull-all.js.map +1 -1
- package/dist/cli/bin/pull-comments.js +3 -1
- package/dist/cli/bin/pull-comments.js.map +1 -1
- package/dist/cli/bin/pull-content.js +6 -2
- package/dist/cli/bin/pull-content.js.map +1 -1
- package/dist/cli/bin/pull-email-templates.js +3 -1
- package/dist/cli/bin/pull-email-templates.js.map +1 -1
- package/dist/cli/bin/pull-media.js +3 -1
- package/dist/cli/bin/pull-media.js.map +1 -1
- package/dist/cli/bin/pull-settings.js +2 -0
- package/dist/cli/bin/pull-settings.js.map +1 -1
- package/dist/cli/bin/push-all.js +12 -1
- package/dist/cli/bin/push-all.js.map +1 -1
- package/dist/cli/bin/push-comments.d.ts +6 -0
- package/dist/cli/bin/push-comments.d.ts.map +1 -0
- package/dist/cli/bin/push-comments.js +35 -0
- package/dist/cli/bin/push-comments.js.map +1 -0
- package/dist/cli/bin/push-content.js +7 -4
- package/dist/cli/bin/push-content.js.map +1 -1
- package/dist/cli/bin/push-email-templates.js +3 -1
- package/dist/cli/bin/push-email-templates.js.map +1 -1
- package/dist/cli/bin/push-media.js +2 -0
- package/dist/cli/bin/push-media.js.map +1 -1
- package/dist/cli/bin/push-settings.js +2 -0
- package/dist/cli/bin/push-settings.js.map +1 -1
- package/dist/cli/bin/push.js +5 -3
- package/dist/cli/bin/push.js.map +1 -1
- package/dist/cli/bin/remote-flag.d.ts +17 -0
- package/dist/cli/bin/remote-flag.d.ts.map +1 -0
- package/dist/cli/bin/remote-flag.js +50 -0
- package/dist/cli/bin/remote-flag.js.map +1 -0
- package/dist/cli/bin/remote.d.ts +14 -0
- package/dist/cli/bin/remote.d.ts.map +1 -0
- package/dist/cli/bin/remote.js +291 -0
- package/dist/cli/bin/remote.js.map +1 -0
- package/dist/cli/bin/status-all.js +67 -4
- package/dist/cli/bin/status-all.js.map +1 -1
- package/dist/cli/bin/status-comments.d.ts +6 -0
- package/dist/cli/bin/status-comments.d.ts.map +1 -0
- package/dist/cli/bin/status-comments.js +30 -0
- package/dist/cli/bin/status-comments.js.map +1 -0
- package/dist/cli/bin/status-content.js +6 -2
- package/dist/cli/bin/status-content.js.map +1 -1
- package/dist/cli/bin/status-email-templates.js +3 -1
- package/dist/cli/bin/status-email-templates.js.map +1 -1
- package/dist/cli/bin/status-media.js +2 -0
- package/dist/cli/bin/status-media.js.map +1 -1
- package/dist/cli/bin/status-settings.js +2 -0
- package/dist/cli/bin/status-settings.js.map +1 -1
- package/dist/cli/bin/status.js +5 -2
- package/dist/cli/bin/status.js.map +1 -1
- package/dist/cli/bin/watch.js +6 -3
- package/dist/cli/bin/watch.js.map +1 -1
- package/dist/cli/index.js +65 -10
- package/dist/cli/index.js.map +1 -1
- package/dist/index.js +0 -1
- package/dist/index.js.map +1 -1
- package/dist/lib/auth.d.ts +1 -1
- package/dist/lib/auth.d.ts.map +1 -1
- package/dist/lib/auth.js +5 -5
- package/dist/lib/auth.js.map +1 -1
- package/dist/lib/cms.d.ts +7 -7
- package/dist/lib/cms.d.ts.map +1 -1
- package/dist/lib/cms.js +41 -7
- package/dist/lib/cms.js.map +1 -1
- package/dist/lib/comment-types.d.ts +4 -0
- package/dist/lib/comment-types.d.ts.map +1 -1
- package/dist/lib/config.d.ts +6 -1
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +37 -8
- package/dist/lib/config.js.map +1 -1
- package/dist/lib/content-merge.d.ts.map +1 -1
- package/dist/lib/content-merge.js +102 -19
- package/dist/lib/content-merge.js.map +1 -1
- package/dist/lib/content-transformation.d.ts +5 -0
- package/dist/lib/content-transformation.d.ts.map +1 -1
- package/dist/lib/content-transformation.js +49 -2
- package/dist/lib/content-transformation.js.map +1 -1
- package/dist/lib/data-service.d.ts +47 -1
- package/dist/lib/data-service.d.ts.map +1 -1
- package/dist/lib/data-service.js +143 -0
- package/dist/lib/data-service.js.map +1 -1
- package/dist/lib/remote-context.d.ts +98 -0
- package/dist/lib/remote-context.d.ts.map +1 -0
- package/dist/lib/remote-context.js +297 -0
- package/dist/lib/remote-context.js.map +1 -0
- package/dist/scripts/init-leadcms.d.ts.map +1 -1
- package/dist/scripts/init-leadcms.js +127 -15
- package/dist/scripts/init-leadcms.js.map +1 -1
- package/dist/scripts/leadcms-helpers.d.ts +16 -5
- package/dist/scripts/leadcms-helpers.d.ts.map +1 -1
- package/dist/scripts/leadcms-helpers.js +31 -8
- package/dist/scripts/leadcms-helpers.js.map +1 -1
- package/dist/scripts/login-leadcms.d.ts +4 -1
- package/dist/scripts/login-leadcms.d.ts.map +1 -1
- package/dist/scripts/login-leadcms.js +45 -6
- package/dist/scripts/login-leadcms.js.map +1 -1
- package/dist/scripts/pull-all.d.ts +14 -6
- package/dist/scripts/pull-all.d.ts.map +1 -1
- package/dist/scripts/pull-all.js +86 -33
- package/dist/scripts/pull-all.js.map +1 -1
- package/dist/scripts/pull-comments.d.ts +3 -0
- package/dist/scripts/pull-comments.d.ts.map +1 -1
- package/dist/scripts/pull-comments.js +4 -4
- package/dist/scripts/pull-comments.js.map +1 -1
- package/dist/scripts/pull-content.d.ts +14 -0
- package/dist/scripts/pull-content.d.ts.map +1 -1
- package/dist/scripts/pull-content.js +85 -8
- package/dist/scripts/pull-content.js.map +1 -1
- package/dist/scripts/pull-email-templates.d.ts +3 -0
- package/dist/scripts/pull-email-templates.d.ts.map +1 -1
- package/dist/scripts/pull-email-templates.js +4 -4
- package/dist/scripts/pull-email-templates.js.map +1 -1
- package/dist/scripts/{fetch-leadcms-comments.d.ts ā pull-leadcms-comments.d.ts} +10 -4
- package/dist/scripts/pull-leadcms-comments.d.ts.map +1 -0
- package/dist/scripts/{fetch-leadcms-comments.js ā pull-leadcms-comments.js} +62 -32
- package/dist/scripts/pull-leadcms-comments.js.map +1 -0
- package/dist/scripts/{fetch-leadcms-content.d.ts ā pull-leadcms-content.d.ts} +13 -19
- package/dist/scripts/pull-leadcms-content.d.ts.map +1 -0
- package/dist/scripts/{fetch-leadcms-content.js ā pull-leadcms-content.js} +121 -250
- package/dist/scripts/pull-leadcms-content.js.map +1 -0
- package/dist/scripts/{fetch-leadcms-email-templates.d.ts ā pull-leadcms-email-templates.d.ts} +5 -4
- package/dist/scripts/pull-leadcms-email-templates.d.ts.map +1 -0
- package/dist/scripts/{fetch-leadcms-email-templates.js ā pull-leadcms-email-templates.js} +89 -11
- package/dist/scripts/pull-leadcms-email-templates.js.map +1 -0
- package/dist/scripts/pull-leadcms-media.d.ts +32 -0
- package/dist/scripts/pull-leadcms-media.d.ts.map +1 -0
- package/dist/scripts/pull-leadcms-media.js +229 -0
- package/dist/scripts/pull-leadcms-media.js.map +1 -0
- package/dist/scripts/pull-media.d.ts +4 -1
- package/dist/scripts/pull-media.d.ts.map +1 -1
- package/dist/scripts/pull-media.js +4 -4
- package/dist/scripts/pull-media.js.map +1 -1
- package/dist/scripts/push-comments.d.ts +48 -0
- package/dist/scripts/push-comments.d.ts.map +1 -0
- package/dist/scripts/push-comments.js +433 -0
- package/dist/scripts/push-comments.js.map +1 -0
- package/dist/scripts/push-email-templates.d.ts +6 -1
- package/dist/scripts/push-email-templates.d.ts.map +1 -1
- package/dist/scripts/push-email-templates.js +96 -21
- package/dist/scripts/push-email-templates.js.map +1 -1
- package/dist/scripts/push-leadcms-content.d.ts +24 -8
- package/dist/scripts/push-leadcms-content.d.ts.map +1 -1
- package/dist/scripts/push-leadcms-content.js +192 -56
- package/dist/scripts/push-leadcms-content.js.map +1 -1
- package/dist/scripts/sse-watcher.d.ts +9 -8
- package/dist/scripts/sse-watcher.d.ts.map +1 -1
- package/dist/scripts/sse-watcher.js +59 -52
- package/dist/scripts/sse-watcher.js.map +1 -1
- package/leadcms.config.json.sample +12 -1
- package/package.json +2 -3
- package/dist/scripts/fetch-leadcms-comments.d.ts.map +0 -1
- package/dist/scripts/fetch-leadcms-comments.js.map +0 -1
- package/dist/scripts/fetch-leadcms-content.d.ts.map +0 -1
- package/dist/scripts/fetch-leadcms-content.js.map +0 -1
- package/dist/scripts/fetch-leadcms-email-templates.d.ts.map +0 -1
- package/dist/scripts/fetch-leadcms-email-templates.js.map +0 -1
|
@@ -6,10 +6,60 @@ import matter from "gray-matter";
|
|
|
6
6
|
import * as Diff from "diff";
|
|
7
7
|
import { defaultLanguage, CONTENT_DIR, } from "./leadcms-helpers.js";
|
|
8
8
|
import { leadCMSDataService } from "../lib/data-service.js";
|
|
9
|
-
import { transformRemoteToLocalFormat, transformRemoteForComparison, hasContentDifferences } from "../lib/content-transformation.js";
|
|
9
|
+
import { transformRemoteToLocalFormat, transformRemoteForComparison, hasContentDifferences, stripTimestampMetadata } from "../lib/content-transformation.js";
|
|
10
10
|
import { formatContentForAPI } from '../lib/content-api-formatting.js';
|
|
11
11
|
import { colorConsole, statusColors, diffColors } from '../lib/console-colors.js';
|
|
12
12
|
import { logger } from '../lib/logger.js';
|
|
13
|
+
const CONTENT_STATUS_ALIASES = {
|
|
14
|
+
create: 'create',
|
|
15
|
+
created: 'create',
|
|
16
|
+
new: 'create',
|
|
17
|
+
update: 'update',
|
|
18
|
+
updated: 'update',
|
|
19
|
+
modify: 'update',
|
|
20
|
+
modified: 'update',
|
|
21
|
+
change: 'update',
|
|
22
|
+
changed: 'update',
|
|
23
|
+
rename: 'rename',
|
|
24
|
+
renamed: 'rename',
|
|
25
|
+
typechange: 'typeChange',
|
|
26
|
+
'type-change': 'typeChange',
|
|
27
|
+
typechanged: 'typeChange',
|
|
28
|
+
'type-changed': 'typeChange',
|
|
29
|
+
conflict: 'conflict',
|
|
30
|
+
conflicts: 'conflict',
|
|
31
|
+
delete: 'delete',
|
|
32
|
+
deleted: 'delete',
|
|
33
|
+
remove: 'delete',
|
|
34
|
+
removed: 'delete'
|
|
35
|
+
};
|
|
36
|
+
function normalizeStatusAlias(value) {
|
|
37
|
+
return value.trim().toLowerCase().replace(/[_\s]+/g, '-');
|
|
38
|
+
}
|
|
39
|
+
export function normalizeContentStatusFilters(statusFilters) {
|
|
40
|
+
if (!statusFilters || statusFilters.length === 0) {
|
|
41
|
+
return undefined;
|
|
42
|
+
}
|
|
43
|
+
const normalized = new Set();
|
|
44
|
+
const invalid = [];
|
|
45
|
+
for (const rawFilter of statusFilters) {
|
|
46
|
+
const filter = normalizeStatusAlias(rawFilter);
|
|
47
|
+
if (!filter) {
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
const operationKey = CONTENT_STATUS_ALIASES[filter];
|
|
51
|
+
if (operationKey) {
|
|
52
|
+
normalized.add(operationKey);
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
invalid.push(rawFilter);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
if (invalid.length > 0) {
|
|
59
|
+
throw new Error(`Unsupported content status filter: ${invalid.join(', ')}`);
|
|
60
|
+
}
|
|
61
|
+
return normalized.size > 0 ? Array.from(normalized) : undefined;
|
|
62
|
+
}
|
|
13
63
|
// Create readline interface for user prompts
|
|
14
64
|
const rl = readline.createInterface({
|
|
15
65
|
input: process.stdin,
|
|
@@ -166,10 +216,10 @@ async function fetchRemoteContent() {
|
|
|
166
216
|
async function hasActualContentChanges(local, remote, typeMap) {
|
|
167
217
|
try {
|
|
168
218
|
// Read the local file content as-is
|
|
169
|
-
const localFileContent = await fs.readFile(local.filePath, 'utf-8');
|
|
219
|
+
const localFileContent = stripTimestampMetadata(await fs.readFile(local.filePath, 'utf-8'));
|
|
170
220
|
// Transform remote content for comparison, only including fields that exist in local content
|
|
171
221
|
// This prevents false positives when remote has additional fields like updatedAt
|
|
172
|
-
const transformedRemoteContent = await transformRemoteForComparison(remote, localFileContent, typeMap);
|
|
222
|
+
const transformedRemoteContent = stripTimestampMetadata(await transformRemoteForComparison(remote, localFileContent, typeMap));
|
|
173
223
|
// Compare the raw file contents using shared normalization logic
|
|
174
224
|
const hasFileContentChanges = hasContentDifferences(localFileContent, transformedRemoteContent);
|
|
175
225
|
return hasFileContentChanges;
|
|
@@ -183,7 +233,7 @@ async function hasActualContentChanges(local, remote, typeMap) {
|
|
|
183
233
|
/**
|
|
184
234
|
* Match local content with remote content
|
|
185
235
|
*/
|
|
186
|
-
async function matchContent(localContent, remoteContent, typeMap, allowDelete = false) {
|
|
236
|
+
async function matchContent(localContent, remoteContent, typeMap, allowDelete = false, metadataMap) {
|
|
187
237
|
const operations = {
|
|
188
238
|
create: [],
|
|
189
239
|
update: [],
|
|
@@ -194,9 +244,14 @@ async function matchContent(localContent, remoteContent, typeMap, allowDelete =
|
|
|
194
244
|
};
|
|
195
245
|
for (const local of localContent) {
|
|
196
246
|
let match = undefined;
|
|
197
|
-
// First try to match by ID
|
|
198
|
-
|
|
199
|
-
|
|
247
|
+
// First try to match by ID ā use remote-specific id-map when available,
|
|
248
|
+
// falling back to frontmatter ID for single-remote backward compatibility
|
|
249
|
+
const remoteId = metadataMap
|
|
250
|
+
? (await import('../lib/remote-context.js')).lookupRemoteId(metadataMap, local.locale, local.slug)
|
|
251
|
+
: undefined;
|
|
252
|
+
const matchId = remoteId ?? local.metadata.id;
|
|
253
|
+
if (matchId) {
|
|
254
|
+
match = remoteContent.find(remote => remote.id === matchId);
|
|
200
255
|
}
|
|
201
256
|
// If no ID match, try to match by current filename slug and locale
|
|
202
257
|
if (!match) {
|
|
@@ -214,8 +269,12 @@ async function matchContent(localContent, remoteContent, typeMap, allowDelete =
|
|
|
214
269
|
(remote.language || defaultLanguage) === local.locale);
|
|
215
270
|
}
|
|
216
271
|
if (match) {
|
|
217
|
-
// Check for conflicts by comparing updatedAt timestamps
|
|
218
|
-
|
|
272
|
+
// Check for conflicts by comparing updatedAt timestamps.
|
|
273
|
+
// Use remote-specific metadata-map when available, falling back to frontmatter.
|
|
274
|
+
const localUpdatedStr = metadataMap
|
|
275
|
+
? (await import('../lib/remote-context.js')).getMetadataForContent(metadataMap, local.locale, local.slug)?.updatedAt
|
|
276
|
+
: local.metadata.updatedAt;
|
|
277
|
+
const localUpdated = localUpdatedStr ? new Date(localUpdatedStr) : new Date(0);
|
|
219
278
|
const remoteUpdated = match.updatedAt ? new Date(match.updatedAt) : new Date(0);
|
|
220
279
|
// Detect different types of changes
|
|
221
280
|
const slugChanged = match.slug !== local.slug;
|
|
@@ -286,8 +345,24 @@ async function matchContent(localContent, remoteContent, typeMap, allowDelete =
|
|
|
286
345
|
}
|
|
287
346
|
// Check for deleted content (remote but not local) if deletion is allowed
|
|
288
347
|
if (allowDelete) {
|
|
289
|
-
//
|
|
290
|
-
const localIds = new Set(
|
|
348
|
+
// Build a set of IDs from the remote-specific id-map (or frontmatter for single-remote)
|
|
349
|
+
const localIds = new Set();
|
|
350
|
+
if (metadataMap) {
|
|
351
|
+
// Multi-remote: use the target remote's metadata values
|
|
352
|
+
for (const slugMap of Object.values(metadataMap.content)) {
|
|
353
|
+
for (const entry of Object.values(slugMap)) {
|
|
354
|
+
if (entry.id != null)
|
|
355
|
+
localIds.add(entry.id);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
else {
|
|
360
|
+
// Single-remote: use frontmatter IDs
|
|
361
|
+
for (const c of localContent) {
|
|
362
|
+
if (c.metadata.id != null)
|
|
363
|
+
localIds.add(c.metadata.id);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
291
366
|
const localSlugs = new Map();
|
|
292
367
|
for (const local of localContent) {
|
|
293
368
|
const key = `${local.slug}:${local.locale}`;
|
|
@@ -460,11 +535,15 @@ async function createContentTypeInteractive(typeName, localContent, dryRun = fal
|
|
|
460
535
|
/**
|
|
461
536
|
* Filter content operations to only include specific content by ID or slug
|
|
462
537
|
*/
|
|
463
|
-
function filterContentOperations(operations, targetId, targetSlug) {
|
|
464
|
-
|
|
538
|
+
function filterContentOperations(operations, targetId, targetSlug, statusFilters) {
|
|
539
|
+
const normalizedStatusFilters = normalizeContentStatusFilters(statusFilters);
|
|
540
|
+
if (!targetId && !targetSlug && !normalizedStatusFilters) {
|
|
465
541
|
return operations; // No filtering needed
|
|
466
542
|
}
|
|
467
543
|
const matchesTarget = (op) => {
|
|
544
|
+
if (!targetId && !targetSlug) {
|
|
545
|
+
return true;
|
|
546
|
+
}
|
|
468
547
|
if (targetId) {
|
|
469
548
|
// Check if local content has the target ID
|
|
470
549
|
if (op.local.metadata.id?.toString() === targetId)
|
|
@@ -486,19 +565,25 @@ function filterContentOperations(operations, targetId, targetSlug) {
|
|
|
486
565
|
}
|
|
487
566
|
return false;
|
|
488
567
|
};
|
|
568
|
+
const includesStatus = (operationType) => {
|
|
569
|
+
if (!normalizedStatusFilters || normalizedStatusFilters.length === 0) {
|
|
570
|
+
return true;
|
|
571
|
+
}
|
|
572
|
+
return normalizedStatusFilters.includes(operationType);
|
|
573
|
+
};
|
|
489
574
|
return {
|
|
490
|
-
create: operations.create.filter(matchesTarget),
|
|
491
|
-
update: operations.update.filter(matchesTarget),
|
|
492
|
-
rename: operations.rename.filter(matchesTarget),
|
|
493
|
-
typeChange: operations.typeChange.filter(matchesTarget),
|
|
494
|
-
conflict: operations.conflict.filter(matchesTarget),
|
|
495
|
-
delete: operations.delete.filter(matchesTarget)
|
|
575
|
+
create: includesStatus('create') ? operations.create.filter(matchesTarget) : [],
|
|
576
|
+
update: includesStatus('update') ? operations.update.filter(matchesTarget) : [],
|
|
577
|
+
rename: includesStatus('rename') ? operations.rename.filter(matchesTarget) : [],
|
|
578
|
+
typeChange: includesStatus('typeChange') ? operations.typeChange.filter(matchesTarget) : [],
|
|
579
|
+
conflict: includesStatus('conflict') ? operations.conflict.filter(matchesTarget) : [],
|
|
580
|
+
delete: includesStatus('delete') ? operations.delete.filter(matchesTarget) : []
|
|
496
581
|
};
|
|
497
582
|
}
|
|
498
583
|
/**
|
|
499
584
|
* Display detailed diff for a single content item
|
|
500
585
|
*/
|
|
501
|
-
async function displayDetailedDiff(operation, operationType, typeMap) {
|
|
586
|
+
async function displayDetailedDiff(operation, operationType, typeMap, options = {}) {
|
|
502
587
|
const { local, remote } = operation;
|
|
503
588
|
console.log(`\nš Detailed Changes for: ${local.slug} [${local.locale}]`);
|
|
504
589
|
console.log(` Operation: ${operationType}`);
|
|
@@ -511,9 +596,9 @@ async function displayDetailedDiff(operation, operationType, typeMap) {
|
|
|
511
596
|
console.log('\nš Content Changes:');
|
|
512
597
|
try {
|
|
513
598
|
// Read local file content as-is
|
|
514
|
-
const localFileContent = await fs.readFile(local.filePath, 'utf-8');
|
|
599
|
+
const localFileContent = stripTimestampMetadata(await fs.readFile(local.filePath, 'utf-8'));
|
|
515
600
|
// Transform remote content to local format for comparison
|
|
516
|
-
const transformedRemoteContent = remote ? await transformRemoteToLocalFormat(remote, typeMap) : '';
|
|
601
|
+
const transformedRemoteContent = remote ? stripTimestampMetadata(await transformRemoteToLocalFormat(remote, typeMap)) : '';
|
|
517
602
|
if (localFileContent.trim() === transformedRemoteContent.trim()) {
|
|
518
603
|
console.log(' No content changes detected');
|
|
519
604
|
}
|
|
@@ -526,7 +611,8 @@ async function displayDetailedDiff(operation, operationType, typeMap) {
|
|
|
526
611
|
// Show diff preview and count changes
|
|
527
612
|
colorConsole.info(' Content diff preview:');
|
|
528
613
|
let previewLines = 0;
|
|
529
|
-
const
|
|
614
|
+
const shouldLimitPreviewLines = options.limitPreviewLines !== false;
|
|
615
|
+
const maxPreviewLines = shouldLimitPreviewLines ? 10 : Number.POSITIVE_INFINITY;
|
|
530
616
|
for (const part of diff) {
|
|
531
617
|
// Count non-empty lines only for more accurate statistics
|
|
532
618
|
const lines = part.value.split('\n').filter((line) => line.trim() !== '');
|
|
@@ -551,10 +637,10 @@ async function displayDetailedDiff(operation, operationType, typeMap) {
|
|
|
551
637
|
else {
|
|
552
638
|
unchangedLines += lines.length;
|
|
553
639
|
}
|
|
554
|
-
if (previewLines >= maxPreviewLines)
|
|
640
|
+
if (shouldLimitPreviewLines && previewLines >= maxPreviewLines)
|
|
555
641
|
break;
|
|
556
642
|
}
|
|
557
|
-
if (previewLines >= maxPreviewLines && (addedLines + removedLines > previewLines)) {
|
|
643
|
+
if (shouldLimitPreviewLines && previewLines >= maxPreviewLines && (addedLines + removedLines > previewLines)) {
|
|
558
644
|
colorConsole.gray(` ... (${addedLines + removedLines - previewLines} more changes)`);
|
|
559
645
|
}
|
|
560
646
|
const summaryText = `\n š Change Summary: ${colorConsole.green(`+${addedLines} lines added`)}, ${colorConsole.red(`-${removedLines} lines removed`)}, ${unchangedLines} lines unchanged`;
|
|
@@ -570,7 +656,7 @@ async function displayDetailedDiff(operation, operationType, typeMap) {
|
|
|
570
656
|
/**
|
|
571
657
|
* Display status/preview of changes
|
|
572
658
|
*/
|
|
573
|
-
async function displayStatus(operations, isStatusOnly = false, isSingleFile = false, showDetailedPreview = false, typeMap, showDelete = false) {
|
|
659
|
+
async function displayStatus(operations, isStatusOnly = false, isSingleFile = false, showDetailedPreview = false, typeMap, showDelete = false, remoteName) {
|
|
574
660
|
if (isSingleFile) {
|
|
575
661
|
colorConsole.important('\nš LeadCMS File Status');
|
|
576
662
|
}
|
|
@@ -593,19 +679,19 @@ async function displayStatus(operations, isStatusOnly = false, isSingleFile = fa
|
|
|
593
679
|
if (isSingleFile) {
|
|
594
680
|
// Show detailed diff for each operation
|
|
595
681
|
for (const op of operations.create) {
|
|
596
|
-
await displayDetailedDiff(op, 'New file', typeMap);
|
|
682
|
+
await displayDetailedDiff(op, 'New file', typeMap, { limitPreviewLines: false });
|
|
597
683
|
}
|
|
598
684
|
for (const op of operations.update) {
|
|
599
|
-
await displayDetailedDiff(op, 'Modified', typeMap);
|
|
685
|
+
await displayDetailedDiff(op, 'Modified', typeMap, { limitPreviewLines: false });
|
|
600
686
|
}
|
|
601
687
|
for (const op of operations.rename) {
|
|
602
|
-
await displayDetailedDiff(op, `Renamed (${op.oldSlug} ā ${op.local.slug})`, typeMap);
|
|
688
|
+
await displayDetailedDiff(op, `Renamed (${op.oldSlug} ā ${op.local.slug})`, typeMap, { limitPreviewLines: false });
|
|
603
689
|
}
|
|
604
690
|
for (const op of operations.typeChange) {
|
|
605
|
-
await displayDetailedDiff(op, `Type changed (${op.oldType} ā ${op.newType})`, typeMap);
|
|
691
|
+
await displayDetailedDiff(op, `Type changed (${op.oldType} ā ${op.newType})`, typeMap, { limitPreviewLines: false });
|
|
606
692
|
}
|
|
607
693
|
for (const op of operations.conflict) {
|
|
608
|
-
await displayDetailedDiff(op, `Conflict: ${op.reason}`, typeMap);
|
|
694
|
+
await displayDetailedDiff(op, `Conflict: ${op.reason}`, typeMap, { limitPreviewLines: false });
|
|
609
695
|
}
|
|
610
696
|
return;
|
|
611
697
|
}
|
|
@@ -688,7 +774,8 @@ async function displayStatus(operations, isStatusOnly = false, isSingleFile = fa
|
|
|
688
774
|
} // Conflicts (like git's merge conflicts)
|
|
689
775
|
if (operations.conflict.length > 0) {
|
|
690
776
|
colorConsole.warn(`ā ļø Unmerged conflicts (${operations.conflict.length} files):`);
|
|
691
|
-
|
|
777
|
+
const remoteFlag = remoteName ? ` -r ${remoteName}` : '';
|
|
778
|
+
colorConsole.info(` (use "leadcms pull-content${remoteFlag}" to merge remote changes)`);
|
|
692
779
|
colorConsole.log('');
|
|
693
780
|
// Sort conflicts by locale then slug as well
|
|
694
781
|
const sortedConflicts = [...operations.conflict].sort((a, b) => {
|
|
@@ -716,10 +803,10 @@ async function displayStatus(operations, isStatusOnly = false, isSingleFile = fa
|
|
|
716
803
|
}
|
|
717
804
|
if (!isStatusOnly) {
|
|
718
805
|
colorConsole.important('š” To resolve conflicts:');
|
|
719
|
-
colorConsole.info(
|
|
806
|
+
colorConsole.info(` ⢠Run "leadcms pull-content${remoteFlag}" to pull latest changes`);
|
|
720
807
|
colorConsole.info(' ⢠Resolve conflicts in local files');
|
|
721
|
-
colorConsole.info(
|
|
722
|
-
colorConsole.warn(
|
|
808
|
+
colorConsole.info(` ⢠Run "leadcms push-content${remoteFlag}" again`);
|
|
809
|
+
colorConsole.warn(` ⢠Or use "leadcms push-content${remoteFlag} --force" to override remote changes (ā ļø data loss risk)`);
|
|
723
810
|
colorConsole.log('');
|
|
724
811
|
}
|
|
725
812
|
}
|
|
@@ -805,16 +892,22 @@ async function showDryRunOperations(operations) {
|
|
|
805
892
|
* Main function for push command
|
|
806
893
|
*/
|
|
807
894
|
async function pushMain(options = {}) {
|
|
808
|
-
const { statusOnly = false,
|
|
895
|
+
const { statusOnly = false, targetId, targetSlug, statusFilter, showDetailedPreview = false, dryRun = false, allowDelete = false, showDelete = false, remoteContext: remoteCtx } = options;
|
|
896
|
+
let force = options.force ?? false;
|
|
809
897
|
try {
|
|
898
|
+
// Configure data service for the target remote (multi-remote support)
|
|
899
|
+
if (remoteCtx) {
|
|
900
|
+
leadCMSDataService.configureForRemote(remoteCtx.url, remoteCtx.apiKey);
|
|
901
|
+
logger.verbose(`[PUSH] Using remote "${remoteCtx.name}" (${remoteCtx.url})`);
|
|
902
|
+
}
|
|
810
903
|
const isSingleFileMode = !!(targetId || targetSlug);
|
|
811
904
|
const actionDescription = statusOnly ? 'status check' : 'push';
|
|
812
905
|
const targetDescription = targetId ? `ID ${targetId}` : targetSlug ? `slug "${targetSlug}"` : 'all content';
|
|
813
906
|
logger.verbose(`[PUSH] Starting ${actionDescription} for ${targetDescription}...`);
|
|
814
907
|
// Check for API key if not in status-only mode
|
|
815
908
|
if (!statusOnly && !dryRun) {
|
|
816
|
-
const
|
|
817
|
-
if (!
|
|
909
|
+
const hasApiKey = remoteCtx ? !!remoteCtx.apiKey : !!(await import('../lib/config.js').then(m => m.getConfig())).apiKey;
|
|
910
|
+
if (!hasApiKey) {
|
|
818
911
|
console.log('\nā Cannot push changes: No API key configured (anonymous mode)');
|
|
819
912
|
console.log('\nš” To push changes, you need to authenticate:');
|
|
820
913
|
console.log(' ⢠Set LEADCMS_API_KEY in your .env file');
|
|
@@ -873,15 +966,21 @@ async function pushMain(options = {}) {
|
|
|
873
966
|
}
|
|
874
967
|
// Fetch remote content for comparison
|
|
875
968
|
const remoteContent = await fetchRemoteContent();
|
|
969
|
+
// Load per-remote metadata-map for multi-remote support
|
|
970
|
+
let metadataMap;
|
|
971
|
+
if (remoteCtx) {
|
|
972
|
+
const rc = await import('../lib/remote-context.js');
|
|
973
|
+
metadataMap = await rc.readMetadataMap(remoteCtx);
|
|
974
|
+
}
|
|
876
975
|
// Match local vs remote content with type mapping for proper content transformation
|
|
877
976
|
// In status mode, enable deletion detection if showDelete is true (for display purposes)
|
|
878
977
|
// In push mode, only detect deletions if allowDelete is true (for execution)
|
|
879
978
|
const shouldDetectDeletions = statusOnly ? (showDelete || allowDelete) : allowDelete;
|
|
880
|
-
const operations = await matchContent(filteredLocalContent, remoteContent, remoteTypeMap, shouldDetectDeletions);
|
|
979
|
+
const operations = await matchContent(filteredLocalContent, remoteContent, remoteTypeMap, shouldDetectDeletions, metadataMap);
|
|
881
980
|
// Filter operations if targeting specific content
|
|
882
|
-
const finalOperations = isSingleFileMode
|
|
883
|
-
filterContentOperations(operations, targetId, targetSlug)
|
|
884
|
-
operations;
|
|
981
|
+
const finalOperations = (isSingleFileMode || (statusFilter && statusFilter.length > 0))
|
|
982
|
+
? filterContentOperations(operations, targetId, targetSlug, statusFilter)
|
|
983
|
+
: operations;
|
|
885
984
|
// Check if we found the target content
|
|
886
985
|
if (isSingleFileMode) {
|
|
887
986
|
const totalChanges = countPushChanges(finalOperations, true);
|
|
@@ -905,7 +1004,7 @@ async function pushMain(options = {}) {
|
|
|
905
1004
|
}
|
|
906
1005
|
}
|
|
907
1006
|
// Display status
|
|
908
|
-
await displayStatus(finalOperations, statusOnly, isSingleFileMode, showDetailedPreview, remoteTypeMap, statusOnly ? showDelete : true);
|
|
1007
|
+
await displayStatus(finalOperations, statusOnly, isSingleFileMode, showDetailedPreview, remoteTypeMap, statusOnly ? showDelete : true, remoteCtx?.name);
|
|
909
1008
|
// If status only, we're done
|
|
910
1009
|
if (statusOnly) {
|
|
911
1010
|
return;
|
|
@@ -939,7 +1038,7 @@ async function pushMain(options = {}) {
|
|
|
939
1038
|
return;
|
|
940
1039
|
}
|
|
941
1040
|
// Execute the sync
|
|
942
|
-
const results = await executePush(finalOperations, { force });
|
|
1041
|
+
const results = await executePush(finalOperations, { force, remoteCtx });
|
|
943
1042
|
// Display final message based on results
|
|
944
1043
|
if (results.failed === 0) {
|
|
945
1044
|
colorConsole.success('\nš Content push completed successfully!');
|
|
@@ -964,7 +1063,7 @@ async function pushMain(options = {}) {
|
|
|
964
1063
|
* Execute the actual push operations
|
|
965
1064
|
*/
|
|
966
1065
|
async function executePush(operations, options = {}) {
|
|
967
|
-
const { force = false } = options;
|
|
1066
|
+
const { force = false, remoteCtx } = options;
|
|
968
1067
|
// Handle force updates for conflicts
|
|
969
1068
|
if (force && operations.conflict.length > 0) {
|
|
970
1069
|
console.log(`\nš Force updating ${operations.conflict.length} conflicted items...`);
|
|
@@ -976,13 +1075,13 @@ async function executePush(operations, options = {}) {
|
|
|
976
1075
|
}
|
|
977
1076
|
}
|
|
978
1077
|
// Use individual operations
|
|
979
|
-
return await executeIndividualOperations(operations, { force });
|
|
1078
|
+
return await executeIndividualOperations(operations, { force, remoteCtx });
|
|
980
1079
|
}
|
|
981
1080
|
/**
|
|
982
1081
|
* Execute operations individually (one by one)
|
|
983
1082
|
*/
|
|
984
1083
|
async function executeIndividualOperations(operations, options = {}) {
|
|
985
|
-
const { force = false } = options;
|
|
1084
|
+
const { force = false, remoteCtx } = options;
|
|
986
1085
|
let successful = 0;
|
|
987
1086
|
let failed = 0;
|
|
988
1087
|
// Create new content
|
|
@@ -992,7 +1091,7 @@ async function executeIndividualOperations(operations, options = {}) {
|
|
|
992
1091
|
try {
|
|
993
1092
|
const result = await leadCMSDataService.createContent(formatContentForAPI(op.local));
|
|
994
1093
|
if (result) {
|
|
995
|
-
await updateLocalMetadata(op.local, result);
|
|
1094
|
+
await updateLocalMetadata(op.local, result, remoteCtx);
|
|
996
1095
|
successful++;
|
|
997
1096
|
colorConsole.success(`ā
Created: ${colorConsole.highlight(`${op.local.type}/${op.local.slug}`)}`);
|
|
998
1097
|
}
|
|
@@ -1015,7 +1114,7 @@ async function executeIndividualOperations(operations, options = {}) {
|
|
|
1015
1114
|
if (op.remote?.id) {
|
|
1016
1115
|
const result = await leadCMSDataService.updateContent(op.remote.id, formatContentForAPI(op.local));
|
|
1017
1116
|
if (result) {
|
|
1018
|
-
await updateLocalMetadata(op.local, result);
|
|
1117
|
+
await updateLocalMetadata(op.local, result, remoteCtx);
|
|
1019
1118
|
successful++;
|
|
1020
1119
|
colorConsole.success(`ā
Updated: ${colorConsole.highlight(`${op.local.type}/${op.local.slug}`)}`);
|
|
1021
1120
|
}
|
|
@@ -1043,7 +1142,7 @@ async function executeIndividualOperations(operations, options = {}) {
|
|
|
1043
1142
|
if (op.remote?.id) {
|
|
1044
1143
|
const result = await leadCMSDataService.updateContent(op.remote.id, formatContentForAPI(op.local));
|
|
1045
1144
|
if (result) {
|
|
1046
|
-
await updateLocalMetadata(op.local, result);
|
|
1145
|
+
await updateLocalMetadata(op.local, result, remoteCtx);
|
|
1047
1146
|
successful++;
|
|
1048
1147
|
colorConsole.success(`ā
Renamed: ${op.oldSlug} -> ${op.local.slug}`);
|
|
1049
1148
|
}
|
|
@@ -1071,7 +1170,7 @@ async function executeIndividualOperations(operations, options = {}) {
|
|
|
1071
1170
|
if (op.remote?.id) {
|
|
1072
1171
|
const result = await leadCMSDataService.updateContent(op.remote.id, formatContentForAPI(op.local));
|
|
1073
1172
|
if (result) {
|
|
1074
|
-
await updateLocalMetadata(op.local, result);
|
|
1173
|
+
await updateLocalMetadata(op.local, result, remoteCtx);
|
|
1075
1174
|
successful++;
|
|
1076
1175
|
colorConsole.success(`ā
Type changed: ${op.local.slug} (${op.oldType} -> ${op.newType})`);
|
|
1077
1176
|
}
|
|
@@ -1117,8 +1216,10 @@ async function executeIndividualOperations(operations, options = {}) {
|
|
|
1117
1216
|
if (successful > 0) {
|
|
1118
1217
|
logger.info(`\nš Syncing latest changes from LeadCMS to local store...`);
|
|
1119
1218
|
try {
|
|
1120
|
-
const {
|
|
1121
|
-
await
|
|
1219
|
+
const { pullLeadCMSContent } = await import('./pull-leadcms-content.js');
|
|
1220
|
+
await pullLeadCMSContent({ remoteContext: remoteCtx });
|
|
1221
|
+
const { pullLeadCMSMedia } = await import('./pull-leadcms-media.js');
|
|
1222
|
+
await pullLeadCMSMedia({ remoteContext: remoteCtx });
|
|
1122
1223
|
console.log('ā
Local content store synchronized with latest changes');
|
|
1123
1224
|
}
|
|
1124
1225
|
catch (error) {
|
|
@@ -1132,11 +1233,35 @@ async function executeIndividualOperations(operations, options = {}) {
|
|
|
1132
1233
|
* Format local content for API submission
|
|
1133
1234
|
*/
|
|
1134
1235
|
/**
|
|
1135
|
-
* Update local file with metadata from LeadCMS response
|
|
1236
|
+
* Update local file with metadata from LeadCMS response.
|
|
1237
|
+
* In multi-remote mode, saves IDs and timestamps to per-remote map files.
|
|
1238
|
+
* Frontmatter is only updated for the default remote (or in single-remote mode).
|
|
1136
1239
|
*/
|
|
1137
|
-
async function updateLocalMetadata(localContent, remoteResponse) {
|
|
1240
|
+
async function updateLocalMetadata(localContent, remoteResponse, remoteCtx) {
|
|
1138
1241
|
const { filePath } = localContent;
|
|
1139
1242
|
const ext = path.extname(filePath);
|
|
1243
|
+
// Per-remote metadata updates
|
|
1244
|
+
if (remoteCtx) {
|
|
1245
|
+
try {
|
|
1246
|
+
const rc = await import('../lib/remote-context.js');
|
|
1247
|
+
const metaMap = await rc.readMetadataMap(remoteCtx);
|
|
1248
|
+
if (remoteResponse.id != null) {
|
|
1249
|
+
rc.setRemoteId(metaMap, localContent.locale, localContent.slug, remoteResponse.id);
|
|
1250
|
+
}
|
|
1251
|
+
rc.setMetadataForContent(metaMap, localContent.locale, localContent.slug, {
|
|
1252
|
+
createdAt: remoteResponse.createdAt,
|
|
1253
|
+
updatedAt: remoteResponse.updatedAt,
|
|
1254
|
+
});
|
|
1255
|
+
await rc.writeMetadataMap(remoteCtx, metaMap);
|
|
1256
|
+
}
|
|
1257
|
+
catch (error) {
|
|
1258
|
+
console.warn(`Failed to update remote map files for ${filePath}:`, error.message);
|
|
1259
|
+
}
|
|
1260
|
+
}
|
|
1261
|
+
// Only update frontmatter in single-remote mode or for the default remote
|
|
1262
|
+
if (remoteCtx && !remoteCtx.isDefault) {
|
|
1263
|
+
return;
|
|
1264
|
+
}
|
|
1140
1265
|
try {
|
|
1141
1266
|
if (ext === '.mdx') {
|
|
1142
1267
|
const fileContent = await fs.readFile(filePath, 'utf-8');
|
|
@@ -1182,6 +1307,7 @@ export { analyzeContentTypeFromFiles };
|
|
|
1182
1307
|
export { isYes };
|
|
1183
1308
|
export { normalizeFormat };
|
|
1184
1309
|
export { fetchRemoteContent };
|
|
1310
|
+
export { displayDetailedDiff };
|
|
1185
1311
|
// Re-export formatContentForAPI from utility module for testing
|
|
1186
1312
|
export { formatContentForAPI } from '../lib/content-api-formatting.js';
|
|
1187
1313
|
// Re-export the new comparison function for consistency
|
|
@@ -1191,6 +1317,10 @@ export { transformRemoteForComparison } from "../lib/content-transformation.js";
|
|
|
1191
1317
|
* Used by the unified status-all renderer.
|
|
1192
1318
|
*/
|
|
1193
1319
|
export async function getContentStatusData(options = {}) {
|
|
1320
|
+
// Configure data service for the target remote (multi-remote support)
|
|
1321
|
+
if (options.remoteContext) {
|
|
1322
|
+
leadCMSDataService.configureForRemote(options.remoteContext.url, options.remoteContext.apiKey);
|
|
1323
|
+
}
|
|
1194
1324
|
const localContent = await readLocalContent();
|
|
1195
1325
|
if (localContent.length === 0) {
|
|
1196
1326
|
return {
|
|
@@ -1208,7 +1338,13 @@ export async function getContentStatusData(options = {}) {
|
|
|
1208
1338
|
logger.verbose('[STATUS] Warning: content types response was not an array, proceeding without type map');
|
|
1209
1339
|
}
|
|
1210
1340
|
const remoteContent = await fetchRemoteContent();
|
|
1211
|
-
|
|
1341
|
+
// Load per-remote maps for multi-remote support
|
|
1342
|
+
let metadataMap;
|
|
1343
|
+
if (options.remoteContext) {
|
|
1344
|
+
const rc = await import('../lib/remote-context.js');
|
|
1345
|
+
metadataMap = await rc.readMetadataMap(options.remoteContext);
|
|
1346
|
+
}
|
|
1347
|
+
const operations = await matchContent(localContent, remoteContent, remoteTypeMap, options.showDelete || false, metadataMap);
|
|
1212
1348
|
return {
|
|
1213
1349
|
operations,
|
|
1214
1350
|
totalLocal: localContent.length,
|