@se-studio/contentful-cms 1.0.15 → 1.0.17
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/CHANGELOG.md +12 -0
- package/README.md +2 -0
- package/dist/bin/cms-edit.js +4 -0
- package/dist/bin/cms-edit.js.map +1 -1
- package/dist/commands/backup.d.ts +3 -0
- package/dist/commands/backup.d.ts.map +1 -0
- package/dist/commands/backup.js +143 -0
- package/dist/commands/backup.js.map +1 -0
- package/dist/commands/diff.d.ts.map +1 -1
- package/dist/commands/diff.js +43 -3
- package/dist/commands/diff.js.map +1 -1
- package/dist/commands/restore.d.ts +4 -0
- package/dist/commands/restore.d.ts.map +1 -0
- package/dist/commands/restore.js +146 -0
- package/dist/commands/restore.js.map +1 -0
- package/dist/commands/rtf.d.ts.map +1 -1
- package/dist/commands/rtf.js +107 -2
- package/dist/commands/rtf.js.map +1 -1
- package/dist/commands/skill.js +3 -3
- package/dist/commands/skill.js.map +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -1
- package/dist/index.js.map +1 -1
- package/dist/rtf/fromMarkdown.d.ts +6 -1
- package/dist/rtf/fromMarkdown.d.ts.map +1 -1
- package/dist/rtf/fromMarkdown.js +7 -0
- package/dist/rtf/fromMarkdown.js.map +1 -1
- package/dist/rtf/replacePlainText.d.ts +66 -0
- package/dist/rtf/replacePlainText.d.ts.map +1 -0
- package/dist/rtf/replacePlainText.js +455 -0
- package/dist/rtf/replacePlainText.js.map +1 -0
- package/dist/session/backup.d.ts +34 -0
- package/dist/session/backup.d.ts.map +1 -0
- package/dist/session/backup.js +64 -0
- package/dist/session/backup.js.map +1 -0
- package/package.json +3 -3
- package/skills/cms-guidelines/README.md +3 -3
- package/skills/core/SKILL.md +14 -0
|
@@ -0,0 +1,455 @@
|
|
|
1
|
+
import { BLOCKS } from '@contentful/rich-text-types';
|
|
2
|
+
import { markdownToRtfInlines } from './fromMarkdown.js';
|
|
3
|
+
import { isRtfDocument } from './utils.js';
|
|
4
|
+
export class RichTextReplaceError extends Error {
|
|
5
|
+
constructor(message) {
|
|
6
|
+
super(message);
|
|
7
|
+
this.name = 'RichTextReplaceError';
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
function plainToInlines(plain) {
|
|
11
|
+
const node = { nodeType: 'text', value: plain, marks: [], data: {} };
|
|
12
|
+
return [node];
|
|
13
|
+
}
|
|
14
|
+
function resolveReplacementInlines(options) {
|
|
15
|
+
const { replaceMarkdown, replacePlain } = options;
|
|
16
|
+
if (replacePlain !== undefined && replaceMarkdown !== undefined) {
|
|
17
|
+
throw new RichTextReplaceError('Provide either replacePlain or replaceMarkdown, not both');
|
|
18
|
+
}
|
|
19
|
+
if (replacePlain !== undefined) {
|
|
20
|
+
if (replacePlain.includes('\n') || replacePlain.includes('\r')) {
|
|
21
|
+
throw new RichTextReplaceError('replacePlain must not contain line breaks (inline only)');
|
|
22
|
+
}
|
|
23
|
+
return plainToInlines(replacePlain);
|
|
24
|
+
}
|
|
25
|
+
if (replaceMarkdown === undefined || replaceMarkdown === '') {
|
|
26
|
+
throw new RichTextReplaceError('Provide replacePlain or replaceMarkdown');
|
|
27
|
+
}
|
|
28
|
+
if (replaceMarkdown.includes('\n') || replaceMarkdown.includes('\r')) {
|
|
29
|
+
throw new RichTextReplaceError('replaceMarkdown must not contain line breaks (inline only)');
|
|
30
|
+
}
|
|
31
|
+
return markdownToRtfInlines(replaceMarkdown);
|
|
32
|
+
}
|
|
33
|
+
// ---------------------------------------------------------------------------
|
|
34
|
+
// Quidget span protection (FR-1)
|
|
35
|
+
// ---------------------------------------------------------------------------
|
|
36
|
+
/**
|
|
37
|
+
* Returns the [start, end) ranges of all top-level `{*...*}` quidget spans in `str`.
|
|
38
|
+
* Only non-nested spans (no inner braces) are matched. Used to prevent search matches
|
|
39
|
+
* from landing inside an already-inserted quidget token.
|
|
40
|
+
*/
|
|
41
|
+
function getQuidgetSpans(str) {
|
|
42
|
+
const spans = [];
|
|
43
|
+
const re = /\{\*[^{}]*\*\}/g;
|
|
44
|
+
for (;;) {
|
|
45
|
+
const m = re.exec(str);
|
|
46
|
+
if (m === null)
|
|
47
|
+
break;
|
|
48
|
+
spans.push([m.index, m.index + m[0].length]);
|
|
49
|
+
}
|
|
50
|
+
return spans;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Returns true if the range [matchStart, matchEnd) is strictly contained inside any protected
|
|
54
|
+
* span — i.e. it starts AFTER the span start. This allows searching for a full quidget token
|
|
55
|
+
* itself (matchStart === spanStart is not protected) while blocking matches for shorter strings
|
|
56
|
+
* that land inside an existing quidget.
|
|
57
|
+
*/
|
|
58
|
+
function isInsideAnySpan(matchStart, matchEnd, spans) {
|
|
59
|
+
for (const [s, e] of spans) {
|
|
60
|
+
if (matchStart > s && matchEnd <= e)
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
// ---------------------------------------------------------------------------
|
|
66
|
+
// Section filtering helpers
|
|
67
|
+
// ---------------------------------------------------------------------------
|
|
68
|
+
const HEADING_LEVEL_MAP = {
|
|
69
|
+
[BLOCKS.HEADING_1]: 1,
|
|
70
|
+
[BLOCKS.HEADING_2]: 2,
|
|
71
|
+
[BLOCKS.HEADING_3]: 3,
|
|
72
|
+
[BLOCKS.HEADING_4]: 4,
|
|
73
|
+
[BLOCKS.HEADING_5]: 5,
|
|
74
|
+
[BLOCKS.HEADING_6]: 6,
|
|
75
|
+
};
|
|
76
|
+
function isHeadingBlock(block) {
|
|
77
|
+
return block.nodeType in HEADING_LEVEL_MAP;
|
|
78
|
+
}
|
|
79
|
+
function getHeadingLevel(block) {
|
|
80
|
+
return HEADING_LEVEL_MAP[block.nodeType] ?? 0;
|
|
81
|
+
}
|
|
82
|
+
function extractBlockPlainText(block) {
|
|
83
|
+
const parts = [];
|
|
84
|
+
walkTextNodes(block.content, (_p, _i, t) => parts.push(t.value));
|
|
85
|
+
return parts.join('');
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Returns a shallow Document view containing only the blocks within the named heading section.
|
|
89
|
+
* The section starts immediately after the matched heading and ends before the next heading of
|
|
90
|
+
* equal or higher level (i.e. lower or equal heading number).
|
|
91
|
+
*
|
|
92
|
+
* When multiple headings share the same text, `nth` (1-indexed, default 1) selects which to use.
|
|
93
|
+
* Throws {@link RichTextReplaceError} if the heading is not found or `nth` exceeds the count.
|
|
94
|
+
*/
|
|
95
|
+
function resolveSectionDoc(clone, section, nth = 1) {
|
|
96
|
+
const blocks = clone.content;
|
|
97
|
+
let matchCount = 0;
|
|
98
|
+
for (let i = 0; i < blocks.length; i++) {
|
|
99
|
+
const block = blocks[i];
|
|
100
|
+
if (!block || !isHeadingBlock(block))
|
|
101
|
+
continue;
|
|
102
|
+
if (extractBlockPlainText(block) !== section)
|
|
103
|
+
continue;
|
|
104
|
+
matchCount++;
|
|
105
|
+
if (matchCount < nth)
|
|
106
|
+
continue;
|
|
107
|
+
const level = getHeadingLevel(block);
|
|
108
|
+
let end = blocks.length;
|
|
109
|
+
for (let j = i + 1; j < blocks.length; j++) {
|
|
110
|
+
const b = blocks[j];
|
|
111
|
+
if (b && isHeadingBlock(b) && getHeadingLevel(b) <= level) {
|
|
112
|
+
end = j;
|
|
113
|
+
break;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
// Shallow doc wrapping the same block objects — mutations via applyAllReplacements affect clone
|
|
117
|
+
return {
|
|
118
|
+
nodeType: BLOCKS.DOCUMENT,
|
|
119
|
+
data: {},
|
|
120
|
+
content: blocks.slice(i + 1, end),
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
if (matchCount === 0) {
|
|
124
|
+
throw new RichTextReplaceError(`No heading matching section "${section}" found in document`);
|
|
125
|
+
}
|
|
126
|
+
throw new RichTextReplaceError(`Section "${section}" has ${String(matchCount)} occurrence(s) but --section-nth ${String(nth)} was requested`);
|
|
127
|
+
}
|
|
128
|
+
// ---------------------------------------------------------------------------
|
|
129
|
+
// Public API
|
|
130
|
+
// ---------------------------------------------------------------------------
|
|
131
|
+
/**
|
|
132
|
+
* Replaces plain-text occurrences in a Contentful rich-text document.
|
|
133
|
+
*
|
|
134
|
+
* **Matching:** By default, search runs only inside individual `text` node `value` strings.
|
|
135
|
+
* Use `ignoreMarks: true` to match across adjacent sibling text nodes in the same block
|
|
136
|
+
* (e.g. `**Annual fee: **$95` split by bold).
|
|
137
|
+
*
|
|
138
|
+
* **Quidget protection:** Matches that fall inside an existing `{*...*}` span are always
|
|
139
|
+
* skipped — this prevents nesting a new quidget inside an already-inserted one.
|
|
140
|
+
*
|
|
141
|
+
* **Modes:** `all` replaces every non-overlapping occurrence. `exactlyOne` requires exactly
|
|
142
|
+
* one occurrence in the entire document or throws {@link RichTextReplaceError}.
|
|
143
|
+
*
|
|
144
|
+
* @returns A deep-cloned document with replacements applied (input is not mutated).
|
|
145
|
+
*/
|
|
146
|
+
export function replaceRtfPlainText(document, options) {
|
|
147
|
+
if (!isRtfDocument(document)) {
|
|
148
|
+
throw new RichTextReplaceError('Value is not a rich text document');
|
|
149
|
+
}
|
|
150
|
+
const { find, mode, ignoreMarks, section, sectionNth } = options;
|
|
151
|
+
if (find === '') {
|
|
152
|
+
throw new RichTextReplaceError('find must be a non-empty string');
|
|
153
|
+
}
|
|
154
|
+
const replacement = resolveReplacementInlines(options);
|
|
155
|
+
const clone = structuredClone(document);
|
|
156
|
+
const targetDoc = section !== undefined ? resolveSectionDoc(clone, section, sectionNth) : clone;
|
|
157
|
+
const total = ignoreMarks
|
|
158
|
+
? countOccurrencesIgnoreMarks(targetDoc, find, options.tableCol)
|
|
159
|
+
: countOccurrencesInDocument(targetDoc, find, options.tableCol);
|
|
160
|
+
if (mode === 'exactlyOne' && total !== 1) {
|
|
161
|
+
throw new RichTextReplaceError(total === 0
|
|
162
|
+
? `No occurrence of find string in document`
|
|
163
|
+
: `Expected exactly one occurrence of find string, found ${String(total)}`);
|
|
164
|
+
}
|
|
165
|
+
if (total === 0) {
|
|
166
|
+
return { document: clone, matchCount: 0 };
|
|
167
|
+
}
|
|
168
|
+
if (ignoreMarks) {
|
|
169
|
+
applyAllReplacementsIgnoreMarks(targetDoc, find, replacement, options.tableCol);
|
|
170
|
+
}
|
|
171
|
+
else {
|
|
172
|
+
applyAllReplacements(targetDoc, find, replacement, options.tableCol);
|
|
173
|
+
}
|
|
174
|
+
return { document: clone, matchCount: total };
|
|
175
|
+
}
|
|
176
|
+
// ---------------------------------------------------------------------------
|
|
177
|
+
// Block / inline root collection (FR-4: tableCol filtering)
|
|
178
|
+
// ---------------------------------------------------------------------------
|
|
179
|
+
function collectInlineRoots(doc, tableCol) {
|
|
180
|
+
const roots = [];
|
|
181
|
+
for (const block of doc.content) {
|
|
182
|
+
collectInlineRootsFromBlock(block, roots, tableCol);
|
|
183
|
+
}
|
|
184
|
+
return roots;
|
|
185
|
+
}
|
|
186
|
+
function collectInlineRootsFromBlock(block, roots, tableCol) {
|
|
187
|
+
switch (block.nodeType) {
|
|
188
|
+
case BLOCKS.PARAGRAPH:
|
|
189
|
+
case BLOCKS.HEADING_1:
|
|
190
|
+
case BLOCKS.HEADING_2:
|
|
191
|
+
case BLOCKS.HEADING_3:
|
|
192
|
+
case BLOCKS.HEADING_4:
|
|
193
|
+
case BLOCKS.HEADING_5:
|
|
194
|
+
case BLOCKS.HEADING_6:
|
|
195
|
+
roots.push(block.content);
|
|
196
|
+
break;
|
|
197
|
+
case BLOCKS.QUOTE:
|
|
198
|
+
for (const b of block.content) {
|
|
199
|
+
collectInlineRootsFromBlock(b, roots, tableCol);
|
|
200
|
+
}
|
|
201
|
+
break;
|
|
202
|
+
case BLOCKS.UL_LIST:
|
|
203
|
+
case BLOCKS.OL_LIST:
|
|
204
|
+
for (const item of block.content) {
|
|
205
|
+
collectInlineRootsFromBlock(item, roots, tableCol);
|
|
206
|
+
}
|
|
207
|
+
break;
|
|
208
|
+
case BLOCKS.LIST_ITEM:
|
|
209
|
+
for (const b of block.content) {
|
|
210
|
+
collectInlineRootsFromBlock(b, roots, tableCol);
|
|
211
|
+
}
|
|
212
|
+
break;
|
|
213
|
+
case BLOCKS.TABLE:
|
|
214
|
+
for (const row of block.content) {
|
|
215
|
+
if (row.nodeType !== BLOCKS.TABLE_ROW)
|
|
216
|
+
continue;
|
|
217
|
+
const cells = row.content;
|
|
218
|
+
for (let colIdx = 0; colIdx < cells.length; colIdx++) {
|
|
219
|
+
if (tableCol !== undefined && colIdx + 1 !== tableCol)
|
|
220
|
+
continue;
|
|
221
|
+
const cell = cells[colIdx];
|
|
222
|
+
if (!cell)
|
|
223
|
+
continue;
|
|
224
|
+
for (const inner of cell.content) {
|
|
225
|
+
collectInlineRootsFromBlock(inner, roots, tableCol);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
break;
|
|
230
|
+
default:
|
|
231
|
+
break;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
// ---------------------------------------------------------------------------
|
|
235
|
+
// Text node walking
|
|
236
|
+
// ---------------------------------------------------------------------------
|
|
237
|
+
export function walkTextNodes(inlines, fn) {
|
|
238
|
+
for (let i = 0; i < inlines.length; i++) {
|
|
239
|
+
const n = inlines[i];
|
|
240
|
+
if (!n)
|
|
241
|
+
continue;
|
|
242
|
+
if (n.nodeType === 'text') {
|
|
243
|
+
fn(inlines, i, n);
|
|
244
|
+
}
|
|
245
|
+
else {
|
|
246
|
+
const inl = n;
|
|
247
|
+
if ('content' in inl && Array.isArray(inl.content)) {
|
|
248
|
+
walkTextNodes(inl.content, fn);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
// ---------------------------------------------------------------------------
|
|
254
|
+
// Per-text-node count & replace (default mode, FR-1 quidget protection)
|
|
255
|
+
// ---------------------------------------------------------------------------
|
|
256
|
+
function countOccurrencesInDocument(doc, find, tableCol) {
|
|
257
|
+
let n = 0;
|
|
258
|
+
for (const root of collectInlineRoots(doc, tableCol)) {
|
|
259
|
+
walkTextNodes(root, (_parent, _index, text) => {
|
|
260
|
+
n += countNonOverlappingInString(text.value, find);
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
return n;
|
|
264
|
+
}
|
|
265
|
+
function countNonOverlappingInString(haystack, needle) {
|
|
266
|
+
const spans = getQuidgetSpans(haystack);
|
|
267
|
+
let count = 0;
|
|
268
|
+
let pos = 0;
|
|
269
|
+
while (pos <= haystack.length - needle.length) {
|
|
270
|
+
const i = haystack.indexOf(needle, pos);
|
|
271
|
+
if (i === -1)
|
|
272
|
+
break;
|
|
273
|
+
if (isInsideAnySpan(i, i + needle.length, spans)) {
|
|
274
|
+
// Advance past the end of the span that contains this match
|
|
275
|
+
const containing = spans.find(([s, e]) => i > s && i + needle.length <= e);
|
|
276
|
+
pos = containing ? containing[1] : i + needle.length;
|
|
277
|
+
continue;
|
|
278
|
+
}
|
|
279
|
+
count++;
|
|
280
|
+
pos = i + needle.length;
|
|
281
|
+
}
|
|
282
|
+
return count;
|
|
283
|
+
}
|
|
284
|
+
function applyAllReplacements(doc, find, replacement, tableCol) {
|
|
285
|
+
const byParent = new Map();
|
|
286
|
+
for (const root of collectInlineRoots(doc, tableCol)) {
|
|
287
|
+
walkTextNodes(root, (parent, index, text) => {
|
|
288
|
+
if (countNonOverlappingInString(text.value, find) === 0)
|
|
289
|
+
return;
|
|
290
|
+
let list = byParent.get(parent);
|
|
291
|
+
if (!list) {
|
|
292
|
+
list = [];
|
|
293
|
+
byParent.set(parent, list);
|
|
294
|
+
}
|
|
295
|
+
list.push(index);
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
for (const [parent, indices] of byParent) {
|
|
299
|
+
const uniqueDesc = [...new Set(indices)].sort((a, b) => b - a);
|
|
300
|
+
for (const idx of uniqueDesc) {
|
|
301
|
+
replaceAllInTextNodeAt(parent, idx, find, replacement);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
function replaceAllInTextNodeAt(parent, index, find, replacement) {
|
|
306
|
+
const node = parent[index];
|
|
307
|
+
const v = node.value;
|
|
308
|
+
const spans = getQuidgetSpans(v);
|
|
309
|
+
const matches = [];
|
|
310
|
+
let pos = 0;
|
|
311
|
+
while (pos <= v.length - find.length) {
|
|
312
|
+
const i = v.indexOf(find, pos);
|
|
313
|
+
if (i === -1)
|
|
314
|
+
break;
|
|
315
|
+
if (isInsideAnySpan(i, i + find.length, spans)) {
|
|
316
|
+
const containing = spans.find(([s, e]) => i > s && i + find.length <= e);
|
|
317
|
+
pos = containing ? containing[1] : i + find.length;
|
|
318
|
+
continue;
|
|
319
|
+
}
|
|
320
|
+
matches.push(i);
|
|
321
|
+
pos = i + find.length;
|
|
322
|
+
}
|
|
323
|
+
if (matches.length === 0)
|
|
324
|
+
return;
|
|
325
|
+
const out = [];
|
|
326
|
+
let cursor = 0;
|
|
327
|
+
for (const m of matches) {
|
|
328
|
+
if (m > cursor) {
|
|
329
|
+
out.push({ ...node, value: v.slice(cursor, m) });
|
|
330
|
+
}
|
|
331
|
+
out.push(...replacement.map(cloneInlineTree));
|
|
332
|
+
cursor = m + find.length;
|
|
333
|
+
}
|
|
334
|
+
if (cursor < v.length) {
|
|
335
|
+
out.push({ ...node, value: v.slice(cursor) });
|
|
336
|
+
}
|
|
337
|
+
parent.splice(index, 1, ...out);
|
|
338
|
+
}
|
|
339
|
+
/** Groups consecutive direct-child text nodes in `inlines` into runs (inline nodes break runs). */
|
|
340
|
+
function collectDirectTextRuns(inlines) {
|
|
341
|
+
const runs = [];
|
|
342
|
+
let current = [];
|
|
343
|
+
let offset = 0;
|
|
344
|
+
const flush = () => {
|
|
345
|
+
if (current.length > 0) {
|
|
346
|
+
runs.push({
|
|
347
|
+
parent: inlines,
|
|
348
|
+
textNodes: current,
|
|
349
|
+
virtualStr: current.map((e) => e.node.value).join(''),
|
|
350
|
+
});
|
|
351
|
+
current = [];
|
|
352
|
+
offset = 0;
|
|
353
|
+
}
|
|
354
|
+
};
|
|
355
|
+
for (let i = 0; i < inlines.length; i++) {
|
|
356
|
+
const n = inlines[i];
|
|
357
|
+
if (!n)
|
|
358
|
+
continue;
|
|
359
|
+
if (n.nodeType === 'text') {
|
|
360
|
+
const start = offset;
|
|
361
|
+
offset += n.value.length;
|
|
362
|
+
current.push({ nodeIndex: i, node: n, startOffset: start, endOffset: offset });
|
|
363
|
+
}
|
|
364
|
+
else {
|
|
365
|
+
flush();
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
flush();
|
|
369
|
+
return runs;
|
|
370
|
+
}
|
|
371
|
+
function countOccurrencesIgnoreMarks(doc, find, tableCol) {
|
|
372
|
+
let n = 0;
|
|
373
|
+
for (const root of collectInlineRoots(doc, tableCol)) {
|
|
374
|
+
for (const run of collectDirectTextRuns(root)) {
|
|
375
|
+
n += countNonOverlappingInString(run.virtualStr, find);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
return n;
|
|
379
|
+
}
|
|
380
|
+
function applyAllReplacementsIgnoreMarks(doc, find, replacement, tableCol) {
|
|
381
|
+
for (const root of collectInlineRoots(doc, tableCol)) {
|
|
382
|
+
// Collect runs fresh each pass (splices invalidate indices)
|
|
383
|
+
// Process one run at a time; within a run process matches in reverse order.
|
|
384
|
+
const runs = collectDirectTextRuns(root);
|
|
385
|
+
// Reverse so that splices at later positions don't shift earlier ones.
|
|
386
|
+
for (let ri = runs.length - 1; ri >= 0; ri--) {
|
|
387
|
+
const run = runs[ri];
|
|
388
|
+
if (!run)
|
|
389
|
+
continue;
|
|
390
|
+
const { virtualStr, textNodes, parent } = run;
|
|
391
|
+
const spans = getQuidgetSpans(virtualStr);
|
|
392
|
+
const matches = [];
|
|
393
|
+
let pos = 0;
|
|
394
|
+
while (pos <= virtualStr.length - find.length) {
|
|
395
|
+
const i = virtualStr.indexOf(find, pos);
|
|
396
|
+
if (i === -1)
|
|
397
|
+
break;
|
|
398
|
+
if (isInsideAnySpan(i, i + find.length, spans)) {
|
|
399
|
+
const containing = spans.find(([s, e]) => i > s && i + find.length <= e);
|
|
400
|
+
pos = containing ? containing[1] : i + find.length;
|
|
401
|
+
continue;
|
|
402
|
+
}
|
|
403
|
+
matches.push(i);
|
|
404
|
+
pos = i + find.length;
|
|
405
|
+
}
|
|
406
|
+
if (matches.length === 0)
|
|
407
|
+
continue;
|
|
408
|
+
// Apply in reverse so splices don't corrupt earlier match positions.
|
|
409
|
+
for (let mi = matches.length - 1; mi >= 0; mi--) {
|
|
410
|
+
const matchStart = matches[mi];
|
|
411
|
+
if (matchStart === undefined)
|
|
412
|
+
continue;
|
|
413
|
+
const matchEnd = matchStart + find.length;
|
|
414
|
+
const involved = textNodes.filter((tn) => tn.startOffset < matchEnd && tn.endOffset > matchStart);
|
|
415
|
+
if (involved.length === 0)
|
|
416
|
+
continue;
|
|
417
|
+
const first = involved[0];
|
|
418
|
+
const last = involved[involved.length - 1];
|
|
419
|
+
if (!first || !last)
|
|
420
|
+
continue;
|
|
421
|
+
const out = [];
|
|
422
|
+
const prefixLen = matchStart - first.startOffset;
|
|
423
|
+
if (prefixLen > 0) {
|
|
424
|
+
out.push({ ...first.node, value: first.node.value.slice(0, prefixLen) });
|
|
425
|
+
}
|
|
426
|
+
out.push(...replacement.map(cloneInlineTree));
|
|
427
|
+
const suffixStart = matchEnd - last.startOffset;
|
|
428
|
+
if (suffixStart < last.node.value.length) {
|
|
429
|
+
out.push({ ...last.node, value: last.node.value.slice(suffixStart) });
|
|
430
|
+
}
|
|
431
|
+
parent.splice(first.nodeIndex, last.nodeIndex - first.nodeIndex + 1, ...out);
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
// ---------------------------------------------------------------------------
|
|
437
|
+
// Clone helpers
|
|
438
|
+
// ---------------------------------------------------------------------------
|
|
439
|
+
function cloneInlineTree(n) {
|
|
440
|
+
if (n.nodeType === 'text') {
|
|
441
|
+
return {
|
|
442
|
+
...n,
|
|
443
|
+
marks: n.marks.map((m) => ({ ...m })),
|
|
444
|
+
};
|
|
445
|
+
}
|
|
446
|
+
const inl = n;
|
|
447
|
+
if (Array.isArray(inl.content)) {
|
|
448
|
+
return {
|
|
449
|
+
...inl,
|
|
450
|
+
content: inl.content.map(cloneInlineTree),
|
|
451
|
+
};
|
|
452
|
+
}
|
|
453
|
+
return { ...inl };
|
|
454
|
+
}
|
|
455
|
+
//# sourceMappingURL=replacePlainText.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"replacePlainText.js","sourceRoot":"","sources":["../../src/rtf/replacePlainText.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { SerializedEntry } from './state.js';
|
|
2
|
+
export declare const BACKUPS_DIR: string;
|
|
3
|
+
export interface BackupMeta {
|
|
4
|
+
version: 1;
|
|
5
|
+
createdAt: string;
|
|
6
|
+
label: string;
|
|
7
|
+
spaceKey: string;
|
|
8
|
+
spaceId: string;
|
|
9
|
+
environment: string;
|
|
10
|
+
defaultLocale: string;
|
|
11
|
+
rootEntryId: string;
|
|
12
|
+
rootSlug: string | null;
|
|
13
|
+
entryCount: number;
|
|
14
|
+
}
|
|
15
|
+
export interface BackupFile {
|
|
16
|
+
meta: BackupMeta;
|
|
17
|
+
entries: Record<string, SerializedEntry>;
|
|
18
|
+
}
|
|
19
|
+
export declare function generateBackupFilename(spaceKey: string, rootEntryId: string): string;
|
|
20
|
+
/**
|
|
21
|
+
* Resolves a backup name/path to an absolute file path.
|
|
22
|
+
* - Bare filename (no path separators) → look in BACKUPS_DIR
|
|
23
|
+
* - Relative or absolute path → resolve from cwd
|
|
24
|
+
*/
|
|
25
|
+
export declare function resolveBackupPath(nameOrPath: string): string;
|
|
26
|
+
export declare function writeBackup(backup: BackupFile, outputPath: string): void;
|
|
27
|
+
export declare function readBackup(resolvedPath: string): BackupFile;
|
|
28
|
+
export interface BackupListEntry {
|
|
29
|
+
filename: string;
|
|
30
|
+
filePath: string;
|
|
31
|
+
meta: BackupMeta;
|
|
32
|
+
}
|
|
33
|
+
export declare function listBackups(): BackupListEntry[];
|
|
34
|
+
//# sourceMappingURL=backup.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"backup.d.ts","sourceRoot":"","sources":["../../src/session/backup.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAElD,eAAO,MAAM,WAAW,QAAwD,CAAC;AAEjF,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,CAAC,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,UAAU,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;CAC1C;AAED,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,MAAM,CAOpF;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAK5D;AAED,wBAAgB,WAAW,CAAC,MAAM,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI,CAGxE;AAED,wBAAgB,UAAU,CAAC,YAAY,EAAE,MAAM,GAAG,UAAU,CAiB3D;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,UAAU,CAAC;CAClB;AAED,wBAAgB,WAAW,IAAI,eAAe,EAAE,CAiB/C"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import * as fs from 'node:fs';
|
|
2
|
+
import * as os from 'node:os';
|
|
3
|
+
import * as path from 'node:path';
|
|
4
|
+
export const BACKUPS_DIR = path.join(os.homedir(), '.contentful-cms', 'backups');
|
|
5
|
+
export function generateBackupFilename(spaceKey, rootEntryId) {
|
|
6
|
+
const now = new Date();
|
|
7
|
+
const pad = (n, len = 2) => String(n).padStart(len, '0');
|
|
8
|
+
const timestamp = `${now.getFullYear()}${pad(now.getMonth() + 1)}${pad(now.getDate())}` +
|
|
9
|
+
`-${pad(now.getHours())}${pad(now.getMinutes())}${pad(now.getSeconds())}`;
|
|
10
|
+
return `${spaceKey}-${rootEntryId}-${timestamp}.json`;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Resolves a backup name/path to an absolute file path.
|
|
14
|
+
* - Bare filename (no path separators) → look in BACKUPS_DIR
|
|
15
|
+
* - Relative or absolute path → resolve from cwd
|
|
16
|
+
*/
|
|
17
|
+
export function resolveBackupPath(nameOrPath) {
|
|
18
|
+
if (!nameOrPath.includes(path.sep) && !nameOrPath.includes('/')) {
|
|
19
|
+
return path.join(BACKUPS_DIR, nameOrPath);
|
|
20
|
+
}
|
|
21
|
+
return path.resolve(nameOrPath);
|
|
22
|
+
}
|
|
23
|
+
export function writeBackup(backup, outputPath) {
|
|
24
|
+
fs.mkdirSync(path.dirname(outputPath), { recursive: true });
|
|
25
|
+
fs.writeFileSync(outputPath, JSON.stringify(backup, null, 2), 'utf-8');
|
|
26
|
+
}
|
|
27
|
+
export function readBackup(resolvedPath) {
|
|
28
|
+
if (!fs.existsSync(resolvedPath)) {
|
|
29
|
+
throw new Error(`Backup not found: ${resolvedPath}\nRun "cms-edit backup list" to see available backups.`);
|
|
30
|
+
}
|
|
31
|
+
let parsed;
|
|
32
|
+
try {
|
|
33
|
+
parsed = JSON.parse(fs.readFileSync(resolvedPath, 'utf-8'));
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
throw new Error(`Failed to parse backup file: ${resolvedPath}`);
|
|
37
|
+
}
|
|
38
|
+
const backup = parsed;
|
|
39
|
+
if (!backup?.meta || backup.meta.version !== 1 || typeof backup.entries !== 'object') {
|
|
40
|
+
throw new Error(`Invalid backup file format: ${resolvedPath}`);
|
|
41
|
+
}
|
|
42
|
+
return backup;
|
|
43
|
+
}
|
|
44
|
+
export function listBackups() {
|
|
45
|
+
if (!fs.existsSync(BACKUPS_DIR))
|
|
46
|
+
return [];
|
|
47
|
+
const files = fs.readdirSync(BACKUPS_DIR).filter((f) => f.endsWith('.json'));
|
|
48
|
+
const results = [];
|
|
49
|
+
for (const filename of files) {
|
|
50
|
+
const filePath = path.join(BACKUPS_DIR, filename);
|
|
51
|
+
try {
|
|
52
|
+
const parsed = JSON.parse(fs.readFileSync(filePath, 'utf-8'));
|
|
53
|
+
if (parsed?.meta?.version === 1) {
|
|
54
|
+
results.push({ filename, filePath, meta: parsed.meta });
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
// skip corrupt files silently — caller may warn
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
// Sort newest-first by createdAt (ISO strings sort lexicographically)
|
|
62
|
+
return results.sort((a, b) => b.meta.createdAt.localeCompare(a.meta.createdAt));
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=backup.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"backup.js","sourceRoot":"","sources":["../../src/session/backup.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@se-studio/contentful-cms",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.17",
|
|
4
4
|
"description": "CLI tool for AI agents to read and edit Contentful draft content without publish permissions",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"picocolors": "^1.1.1"
|
|
34
34
|
},
|
|
35
35
|
"devDependencies": {
|
|
36
|
-
"@biomejs/biome": "^2.4.
|
|
36
|
+
"@biomejs/biome": "^2.4.8",
|
|
37
37
|
"@types/node": "^22.19.15",
|
|
38
38
|
"typescript": "^5.9.3",
|
|
39
39
|
"vitest": "^4.1.0"
|
|
@@ -56,6 +56,6 @@
|
|
|
56
56
|
"type-check": "tsc --noEmit",
|
|
57
57
|
"lint": "biome lint .",
|
|
58
58
|
"clean": "rm -rf dist .turbo *.tsbuildinfo",
|
|
59
|
-
"test": "vitest run
|
|
59
|
+
"test": "vitest run"
|
|
60
60
|
}
|
|
61
61
|
}
|
|
@@ -18,7 +18,7 @@ Guidelines are short markdown fragments — one per CMS type — that describe h
|
|
|
18
18
|
pnpm --filter <appName> generate-showcase
|
|
19
19
|
```
|
|
20
20
|
This writes `src/generated/showcase-examples.json`.
|
|
21
|
-
3. **Curate showcase mocks** — run the [curate-showcase-mocks skill](.
|
|
21
|
+
3. **Curate showcase mocks** — run the [curate-showcase-mocks skill](.agents/skills/se-marketing-sites/curate-showcase-mocks/SKILL.md) to select the best examples into `showcase-mocks.json`. This makes the showcase (and screenshots) show realistic content.
|
|
22
22
|
4. **Generate field list** — parse TypeScript types to produce structured field metadata:
|
|
23
23
|
```bash
|
|
24
24
|
# From the app directory:
|
|
@@ -27,7 +27,7 @@ Guidelines are short markdown fragments — one per CMS type — that describe h
|
|
|
27
27
|
# Or from repo root:
|
|
28
28
|
pnpm --filter <appName> exec cms-generate-field-list --app-dir .
|
|
29
29
|
```
|
|
30
|
-
5. **Generate guidelines** — run the [Generate CMS guidelines skill](.
|
|
30
|
+
5. **Generate guidelines** — run the [Generate CMS guidelines skill](.agents/skills/contentful-cms/generate-cms-guidelines/SKILL.md).
|
|
31
31
|
|
|
32
32
|
Steps 1–3 only need to be repeated when content or component code changes significantly. Step 4 only needs to be repeated when TypeScript types change. Step 5 can be re-run at any time.
|
|
33
33
|
|
|
@@ -35,7 +35,7 @@ Steps 1–3 only need to be repeated when content or component code changes sign
|
|
|
35
35
|
|
|
36
36
|
## How to run guideline generation
|
|
37
37
|
|
|
38
|
-
Use the Cursor skill at [`.
|
|
38
|
+
Use the Cursor skill at [`.agents/skills/contentful-cms/generate-cms-guidelines/SKILL.md`](.agents/skills/contentful-cms/generate-cms-guidelines/SKILL.md). It supports three modes:
|
|
39
39
|
|
|
40
40
|
| Mode | When to use |
|
|
41
41
|
|---|---|
|
package/skills/core/SKILL.md
CHANGED
|
@@ -121,6 +121,20 @@ cms-edit rtf @c1 body --file path/to/file.md
|
|
|
121
121
|
cms-edit rtf @c1 body - < path/to/file.md
|
|
122
122
|
```
|
|
123
123
|
|
|
124
|
+
**Surgical rich text replace** (compliance / quidget tokens — does not re-import the whole field):
|
|
125
|
+
|
|
126
|
+
- Matching is **per Contentful text node** only. If the author split a phrase with bold (e.g. `**Annual fee: **$95`), the string `Annual fee: $95` will **not** match as one piece; use a shorter `--find` or full `rtf … --file`.
|
|
127
|
+
- Quidget strings contain `*`, which Markdown would treat as italic — use **`--replace-plain`**, not `--replace`.
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
# Replace every "$95" in this field with the quidget (e.g. multiple table cells)
|
|
131
|
+
cms-edit rtf replace @c2 body --find '$95' --replace-plain '{*credit_card_id*:*5048345*,*field*:*annual_fees*,*api*:*cc*}' --mode all
|
|
132
|
+
|
|
133
|
+
# Exactly one occurrence required (fails if 0 or 2+)
|
|
134
|
+
cms-edit rtf replace @c2 body --find '75,000 miles after spending $4,000 in the first 3 months' \
|
|
135
|
+
--replace-plain '{*credit_card_id*:*5048345*,*field*:*bonus_miles*,*api*:*cc*}' --mode exactlyOne
|
|
136
|
+
```
|
|
137
|
+
|
|
124
138
|
### Step 5: Review changes
|
|
125
139
|
|
|
126
140
|
```bash
|