dotmd-cli 0.69.0 → 0.70.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 +144 -964
- package/bin/dotmd.mjs +238 -195
- package/dotmd.config.example.mjs +5 -8
- package/package.json +6 -10
- package/src/agent-context.mjs +132 -0
- package/src/atomic-mutation.mjs +1505 -0
- package/src/baton.mjs +109 -114
- package/src/bulk-tag.mjs +7 -7
- package/src/commands.mjs +326 -12
- package/src/completions.mjs +38 -98
- package/src/config.mjs +18 -3
- package/src/diff.mjs +7 -3
- package/src/doctor.mjs +12 -5
- package/src/export.mjs +154 -25
- package/src/fix-refs.mjs +2 -0
- package/src/frontmatter-fix.mjs +2 -0
- package/src/frontmatter.mjs +3 -2
- package/src/git.mjs +531 -14
- package/src/graph.mjs +53 -25
- package/src/guard.mjs +163 -60
- package/src/hud.mjs +65 -76
- package/src/index-file.mjs +28 -16
- package/src/index.mjs +17 -12
- package/src/init.mjs +1 -1
- package/src/journal.mjs +145 -12
- package/src/lifecycle.mjs +554 -282
- package/src/lint.mjs +3 -3
- package/src/managed-path.mjs +192 -0
- package/src/migrate-prompts.mjs +2 -0
- package/src/migrate-template.mjs +2 -0
- package/src/migrate.mjs +7 -1
- package/src/new.mjs +135 -54
- package/src/output-identity.mjs +106 -0
- package/src/pickup-card.mjs +24 -10
- package/src/pickup.mjs +457 -0
- package/src/prompts.mjs +134 -75
- package/src/query.mjs +22 -10
- package/src/reference-planner.mjs +292 -0
- package/src/rename.mjs +65 -73
- package/src/render.mjs +17 -8
- package/src/runlist.mjs +109 -71
- package/src/section.mjs +2 -1
- package/src/ship.mjs +39 -20
- package/src/stats.mjs +1 -1
- package/src/status-metadata.mjs +87 -0
- package/src/statuses.mjs +11 -26
- package/src/summary.mjs +14 -3
- package/src/update.mjs +38 -10
- package/src/use.mjs +4 -1
- package/src/util.mjs +1 -0
- package/src/validate.mjs +14 -6
- package/src/watch.mjs +6 -1
- package/src/notion.mjs +0 -528
package/src/doctor.mjs
CHANGED
|
@@ -4,7 +4,7 @@ import { fixBrokenRefs } from './fix-refs.mjs';
|
|
|
4
4
|
import { runLint } from './lint.mjs';
|
|
5
5
|
import { runTouch } from './lifecycle.mjs';
|
|
6
6
|
import { buildIndex, collectDocFiles } from './index.mjs';
|
|
7
|
-
import {
|
|
7
|
+
import { writeRenderedIndex } from './index-file.mjs';
|
|
8
8
|
import { renderCheck, renderManualFixes } from './render.mjs';
|
|
9
9
|
import { bold, dim, green, yellow } from './color.mjs';
|
|
10
10
|
import { checkClaudeCommands, removeGeneratedSlashCommands } from './claude-commands.mjs';
|
|
@@ -66,13 +66,20 @@ export function runDoctor(argv, config, opts = {}) {
|
|
|
66
66
|
return;
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
-
const { dryRun } = opts;
|
|
69
|
+
const { dryRun, testHooks } = opts;
|
|
70
70
|
// 0.37.0 (F4): the mode banner makes it impossible to mistake a preview run
|
|
71
71
|
// for a real one — and tells the user the exact flag that flips it.
|
|
72
72
|
const modeNote = dryRun
|
|
73
73
|
? dim('[preview — run with --apply to write]')
|
|
74
74
|
: dim('[applying changes]');
|
|
75
75
|
process.stdout.write(bold('dotmd doctor') + ' ' + modeNote + '\n\n');
|
|
76
|
+
if (dryRun) {
|
|
77
|
+
const skippedHooks = ['validate', 'transformDoc', 'formatSnapshot', 'renderCheck']
|
|
78
|
+
.filter(name => typeof config.hooks?.[name] === 'function');
|
|
79
|
+
if (skippedHooks.length > 0) {
|
|
80
|
+
process.stdout.write(dim(`[preview] Custom ${skippedHooks.join(', ')} hook${skippedHooks.length === 1 ? '' : 's'} skipped; diagnostics and rendering below use built-in behavior only.\n\n`));
|
|
81
|
+
}
|
|
82
|
+
}
|
|
76
83
|
|
|
77
84
|
// Step 1: Fix broken references
|
|
78
85
|
process.stdout.write(bold('1. Fixing broken references...') + '\n');
|
|
@@ -95,8 +102,7 @@ export function runDoctor(argv, config, opts = {}) {
|
|
|
95
102
|
} else if (dryRun) {
|
|
96
103
|
process.stdout.write('[dry-run] Would regenerate index.\n');
|
|
97
104
|
} else {
|
|
98
|
-
|
|
99
|
-
writeIndex(renderIndexFile(index, config), config);
|
|
105
|
+
writeRenderedIndex(() => buildIndex(config, { fast: true }), config, { testHooks });
|
|
100
106
|
process.stdout.write('Index updated.\n');
|
|
101
107
|
}
|
|
102
108
|
|
|
@@ -126,7 +132,8 @@ export function runDoctor(argv, config, opts = {}) {
|
|
|
126
132
|
}
|
|
127
133
|
|
|
128
134
|
// Step 6: Show remaining check
|
|
129
|
-
|
|
135
|
+
const issueLabel = dryRun ? '6. Remaining issues in current tree (preview fixes above were not applied):' : '6. Remaining issues:';
|
|
136
|
+
process.stdout.write('\n' + bold(issueLabel) + '\n');
|
|
130
137
|
const freshIndex = buildIndex(config);
|
|
131
138
|
process.stdout.write(renderCheck(freshIndex, config));
|
|
132
139
|
const manual = renderManualFixes(freshIndex);
|
package/src/export.mjs
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
1
|
+
import { existsSync, lstatSync, mkdirSync, readFileSync, realpathSync, statSync, writeFileSync } from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { extractFrontmatter } from './frontmatter.mjs';
|
|
4
4
|
import { buildIndex } from './index.mjs';
|
|
5
5
|
import { buildGraph } from './graph.mjs';
|
|
6
|
-
import { resolveDocPath, toRepoPath, capitalize, die } from './util.mjs';
|
|
6
|
+
import { resolveDocPath, resolveRefPath, toRepoPath, capitalize, die } from './util.mjs';
|
|
7
|
+
import { allocateOutputIdentities } from './output-identity.mjs';
|
|
7
8
|
|
|
8
9
|
export function runExport(argv, config, opts = {}) {
|
|
9
10
|
const positional = [];
|
|
@@ -99,10 +100,11 @@ export function runExport(argv, config, opts = {}) {
|
|
|
99
100
|
}
|
|
100
101
|
} else if (format === 'html') {
|
|
101
102
|
const outDir = output ?? 'dotmd-export';
|
|
103
|
+
const identities = allocateOutputIdentities(index.docs);
|
|
104
|
+
exportHtml(docsWithBody, config, outDir, identities, { dryRun });
|
|
102
105
|
if (dryRun) {
|
|
103
106
|
process.stdout.write(`${prefix}Would export ${docs.length} docs as HTML to ${outDir}/\n`);
|
|
104
107
|
} else {
|
|
105
|
-
exportHtml(docsWithBody, config, outDir);
|
|
106
108
|
process.stdout.write(`Exported ${docs.length} docs to ${outDir}/\n`);
|
|
107
109
|
}
|
|
108
110
|
}
|
|
@@ -235,22 +237,30 @@ ul.toc li { padding: 0.3rem 0; }
|
|
|
235
237
|
ul.toc .status-group { font-weight: 600; margin-top: 1rem; }
|
|
236
238
|
`.trim();
|
|
237
239
|
|
|
238
|
-
function exportHtml(docs, config, outDir) {
|
|
239
|
-
|
|
240
|
+
function exportHtml(docs, config, outDir, identities, { dryRun = false } = {}) {
|
|
241
|
+
const emittedPaths = new Set(docs.map(doc => doc.path));
|
|
242
|
+
const pages = [{ htmlPath: 'index.html', content: buildIndexPage(docs, config, identities) }];
|
|
243
|
+
for (const doc of docs) {
|
|
244
|
+
const identity = identities.get(doc.path);
|
|
245
|
+
if (!identity) throw new Error(`Missing output identity for ${doc.path}`);
|
|
246
|
+
pages.push({
|
|
247
|
+
htmlPath: identity.htmlPath,
|
|
248
|
+
content: buildDocPage(doc, identity, identities, emittedPaths, config),
|
|
249
|
+
});
|
|
250
|
+
}
|
|
240
251
|
|
|
241
|
-
|
|
242
|
-
const
|
|
243
|
-
|
|
252
|
+
const guard = createOutputGuard(outDir);
|
|
253
|
+
for (const page of pages) guard.validate(page.htmlPath);
|
|
254
|
+
if (dryRun) return;
|
|
244
255
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
writeFileSync(path.join(outDir, slug + '.html'), html, 'utf8');
|
|
256
|
+
for (const page of pages) {
|
|
257
|
+
guard.createParent(page.htmlPath);
|
|
258
|
+
guard.validate(page.htmlPath);
|
|
259
|
+
writeFileSync(path.join(guard.outputRoot, ...page.htmlPath.split('/')), page.content, 'utf8');
|
|
250
260
|
}
|
|
251
261
|
}
|
|
252
262
|
|
|
253
|
-
function buildIndexPage(docs, config) {
|
|
263
|
+
function buildIndexPage(docs, config, identities) {
|
|
254
264
|
const today = new Date().toISOString().slice(0, 10);
|
|
255
265
|
const byStatus = {};
|
|
256
266
|
for (const d of docs) {
|
|
@@ -265,8 +275,8 @@ function buildIndexPage(docs, config) {
|
|
|
265
275
|
if (!group?.length) continue;
|
|
266
276
|
toc += `<li class="status-group">${capitalize(status)} (${group.length})</li>\n`;
|
|
267
277
|
for (const doc of group) {
|
|
268
|
-
const
|
|
269
|
-
toc += `<li><a href="${
|
|
278
|
+
const identity = identities.get(doc.path);
|
|
279
|
+
toc += `<li><a href="${escHtml(identity.htmlUrl)}">${escHtml(doc.title)}</a></li>\n`;
|
|
270
280
|
}
|
|
271
281
|
}
|
|
272
282
|
|
|
@@ -285,12 +295,12 @@ ${toc}</ul>
|
|
|
285
295
|
`;
|
|
286
296
|
}
|
|
287
297
|
|
|
288
|
-
function buildDocPage(doc) {
|
|
289
|
-
const
|
|
290
|
-
const badgeClass = `badge-${
|
|
298
|
+
function buildDocPage(doc, identity, identities, emittedPaths, config) {
|
|
299
|
+
const status = doc.status ?? 'unknown';
|
|
300
|
+
const badgeClass = `badge-${status.replace(/[^a-z0-9_-]/gi, '-')}`;
|
|
291
301
|
|
|
292
302
|
let meta = `<table class="meta">`;
|
|
293
|
-
meta += `<tr><td>Status</td><td><span class="badge ${badgeClass}">${
|
|
303
|
+
meta += `<tr><td>Status</td><td><span class="badge ${badgeClass}">${escHtml(status)}</span></td></tr>`;
|
|
294
304
|
if (doc.updated) meta += `<tr><td>Updated</td><td>${escHtml(doc.updated)}</td></tr>`;
|
|
295
305
|
if (doc.modules?.length) meta += `<tr><td>Module</td><td>${escHtml(doc.modules.join(', '))}</td></tr>`;
|
|
296
306
|
if (doc.surfaces?.length) meta += `<tr><td>Surface</td><td>${escHtml(doc.surfaces.join(', '))}</td></tr>`;
|
|
@@ -298,7 +308,8 @@ function buildDocPage(doc) {
|
|
|
298
308
|
meta += `<tr><td>Path</td><td><code>${escHtml(doc.path)}</code></td></tr>`;
|
|
299
309
|
meta += `</table>`;
|
|
300
310
|
|
|
301
|
-
const bodyHtml = mdToHtml(doc.body);
|
|
311
|
+
const bodyHtml = mdToHtml(doc.body, href => resolveHtmlLink(href, doc, identity, identities, emittedPaths, config));
|
|
312
|
+
const indexHref = relativeHtmlUrl(identity.htmlPath, 'index.html');
|
|
302
313
|
|
|
303
314
|
return `<!DOCTYPE html>
|
|
304
315
|
<html lang="en"><head>
|
|
@@ -307,7 +318,7 @@ function buildDocPage(doc) {
|
|
|
307
318
|
<title>${escHtml(doc.title)}</title>
|
|
308
319
|
<style>${CSS}</style>
|
|
309
320
|
</head><body>
|
|
310
|
-
<nav><a href="
|
|
321
|
+
<nav><a href="${escHtml(indexHref)}">← Index</a></nav>
|
|
311
322
|
<article>
|
|
312
323
|
<h1>${escHtml(doc.title)}</h1>
|
|
313
324
|
${meta}
|
|
@@ -317,7 +328,7 @@ ${bodyHtml}
|
|
|
317
328
|
`;
|
|
318
329
|
}
|
|
319
330
|
|
|
320
|
-
function mdToHtml(body) {
|
|
331
|
+
function mdToHtml(body, resolveLink = href => href) {
|
|
321
332
|
if (!body?.trim()) return '';
|
|
322
333
|
let html = escHtml(body);
|
|
323
334
|
|
|
@@ -340,7 +351,10 @@ function mdToHtml(body) {
|
|
|
340
351
|
html = html.replace(/(?<!<code>)`([^`]+)`/g, '<code>$1</code>');
|
|
341
352
|
|
|
342
353
|
// Links
|
|
343
|
-
html = html.replace(/\[([^\]]+)\]\(([^)]+)\)/g,
|
|
354
|
+
html = html.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (_, label, escapedHref) => {
|
|
355
|
+
const href = resolveLink(unescapeHtml(escapedHref));
|
|
356
|
+
return href == null ? label : `<a href="${escHtml(href)}">${label}</a>`;
|
|
357
|
+
});
|
|
344
358
|
|
|
345
359
|
// Blockquotes
|
|
346
360
|
html = html.replace(/^> (.+)$/gm, '<blockquote>$1</blockquote>');
|
|
@@ -363,5 +377,120 @@ function mdToHtml(body) {
|
|
|
363
377
|
}
|
|
364
378
|
|
|
365
379
|
function escHtml(text) {
|
|
366
|
-
return text.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
|
380
|
+
return String(text).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
function unescapeHtml(text) {
|
|
384
|
+
return text.replace(/"/g, '"').replace(/>/g, '>').replace(/</g, '<').replace(/&/g, '&');
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
function encodeRelativeUrl(value) {
|
|
388
|
+
return value.split('/').map(segment => segment === '..' || segment === '.' ? segment : encodeURIComponent(segment)).join('/');
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
function relativeHtmlUrl(from, to) {
|
|
392
|
+
const relative = path.posix.relative(path.posix.dirname(from), to) || path.posix.basename(to);
|
|
393
|
+
return encodeRelativeUrl(relative);
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
function resolveHtmlLink(href, doc, identity, identities, emittedPaths, config) {
|
|
397
|
+
if (!href || href.startsWith('#') || href.startsWith('/') || href.startsWith('//') || /^[a-z][a-z0-9+.-]*:/i.test(href)) {
|
|
398
|
+
return href;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
const hashAt = href.indexOf('#');
|
|
402
|
+
const targetPart = hashAt === -1 ? href : href.slice(0, hashAt);
|
|
403
|
+
const fragment = hashAt === -1 ? '' : href.slice(hashAt);
|
|
404
|
+
if (!targetPart.toLowerCase().endsWith('.md')) return href;
|
|
405
|
+
|
|
406
|
+
const docDir = path.dirname(path.join(config.repoRoot, doc.path));
|
|
407
|
+
const resolved = resolveRefPath(targetPart, docDir, config.repoRoot);
|
|
408
|
+
const targetPath = resolved ? toRepoPath(resolved, config.repoRoot) : null;
|
|
409
|
+
if (!targetPath || !emittedPaths.has(targetPath)) return null;
|
|
410
|
+
return relativeHtmlUrl(identity.htmlPath, identities.get(targetPath).htmlPath) + fragment;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
function nearestExistingAncestor(value) {
|
|
414
|
+
let current = value;
|
|
415
|
+
while (true) {
|
|
416
|
+
try {
|
|
417
|
+
lstatSync(current);
|
|
418
|
+
return current;
|
|
419
|
+
} catch (err) {
|
|
420
|
+
if (err.code !== 'ENOENT') throw err;
|
|
421
|
+
}
|
|
422
|
+
const parent = path.dirname(current);
|
|
423
|
+
if (parent === current) throw new Error(`Cannot resolve output ancestor: ${value}`);
|
|
424
|
+
current = parent;
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
function createOutputGuard(outDir) {
|
|
429
|
+
const outputRoot = path.resolve(outDir);
|
|
430
|
+
const initialAncestor = nearestExistingAncestor(outputRoot);
|
|
431
|
+
const initialAncestorPhysical = realpathSync(initialAncestor);
|
|
432
|
+
const outputRootPhysical = existsSync(outputRoot)
|
|
433
|
+
? realpathSync(outputRoot)
|
|
434
|
+
: path.resolve(initialAncestorPhysical, path.relative(initialAncestor, outputRoot));
|
|
435
|
+
|
|
436
|
+
if (existsSync(outputRoot) && !statSync(outputRoot).isDirectory()) {
|
|
437
|
+
throw new Error(`HTML output root is not a directory: ${outputRoot}`);
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
function validate(htmlPath) {
|
|
441
|
+
const destination = path.resolve(outputRoot, ...htmlPath.split('/'));
|
|
442
|
+
if (destination === outputRoot || !destination.startsWith(outputRoot + path.sep)) {
|
|
443
|
+
throw new Error(`HTML output escapes destination: ${htmlPath}`);
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
const currentAncestor = nearestExistingAncestor(outputRoot);
|
|
447
|
+
const currentPhysical = realpathSync(currentAncestor);
|
|
448
|
+
const currentRootPhysical = existsSync(outputRoot)
|
|
449
|
+
? realpathSync(outputRoot)
|
|
450
|
+
: path.resolve(currentPhysical, path.relative(currentAncestor, outputRoot));
|
|
451
|
+
if (currentRootPhysical !== outputRootPhysical) {
|
|
452
|
+
throw new Error(`HTML output ancestry changed during export: ${outputRoot}`);
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
let current = outputRoot;
|
|
456
|
+
const components = htmlPath.split('/');
|
|
457
|
+
for (let i = 0; i < components.length; i++) {
|
|
458
|
+
const component = components[i];
|
|
459
|
+
current = path.join(current, component);
|
|
460
|
+
let entry;
|
|
461
|
+
try { entry = lstatSync(current); } catch (err) {
|
|
462
|
+
if (err.code === 'ENOENT') break;
|
|
463
|
+
throw err;
|
|
464
|
+
}
|
|
465
|
+
if (entry.isSymbolicLink()) {
|
|
466
|
+
throw new Error(`HTML output path contains a descendant symlink: ${current}`);
|
|
467
|
+
}
|
|
468
|
+
const isDestination = i === components.length - 1;
|
|
469
|
+
if ((!isDestination && !entry.isDirectory()) || (isDestination && entry.isDirectory())) {
|
|
470
|
+
throw new Error(`HTML output path has an incompatible existing entry: ${current}`);
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
function createParent(htmlPath) {
|
|
476
|
+
if (!existsSync(outputRoot)) {
|
|
477
|
+
const ancestor = nearestExistingAncestor(outputRoot);
|
|
478
|
+
let current = ancestor;
|
|
479
|
+
for (const component of path.relative(ancestor, outputRoot).split(path.sep).filter(Boolean)) {
|
|
480
|
+
validate(htmlPath);
|
|
481
|
+
current = path.join(current, component);
|
|
482
|
+
if (!existsSync(current)) mkdirSync(current);
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
let current = outputRoot;
|
|
486
|
+
const components = htmlPath.split('/').slice(0, -1);
|
|
487
|
+
for (const component of components) {
|
|
488
|
+
validate(htmlPath);
|
|
489
|
+
current = path.join(current, component);
|
|
490
|
+
if (!existsSync(current)) mkdirSync(current);
|
|
491
|
+
if (!statSync(current).isDirectory()) throw new Error(`HTML output parent is not a directory: ${current}`);
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
return { outputRoot, validate, createParent };
|
|
367
496
|
}
|
package/src/fix-refs.mjs
CHANGED
|
@@ -4,6 +4,7 @@ import { extractFrontmatter, replaceFrontmatter } from './frontmatter.mjs';
|
|
|
4
4
|
import { toRepoPath, escapeRegex, warn } from './util.mjs';
|
|
5
5
|
import { buildIndex, collectDocFiles } from './index.mjs';
|
|
6
6
|
import { green, dim, yellow } from './color.mjs';
|
|
7
|
+
import { authorizeManagedSweep } from './managed-path.mjs';
|
|
7
8
|
|
|
8
9
|
export function runFixRefs(argv, config, opts = {}) {
|
|
9
10
|
const { dryRun } = opts;
|
|
@@ -21,6 +22,7 @@ export function fixBrokenRefs(config, opts = {}) {
|
|
|
21
22
|
const { dryRun, quiet } = opts;
|
|
22
23
|
const index = buildIndex(config);
|
|
23
24
|
const allFiles = collectDocFiles(config);
|
|
25
|
+
authorizeManagedSweep(allFiles, config, { kind: 'Reference fix source' });
|
|
24
26
|
|
|
25
27
|
// Build a map of basename → absolute path for all docs
|
|
26
28
|
const basenameMap = new Map();
|
package/src/frontmatter-fix.mjs
CHANGED
|
@@ -3,6 +3,7 @@ import { extractFrontmatter, parseSimpleFrontmatter } from './frontmatter.mjs';
|
|
|
3
3
|
import { asString, toRepoPath, escapeRegex, warn } from './util.mjs';
|
|
4
4
|
import { collectDocFiles } from './index.mjs';
|
|
5
5
|
import { bold, green, dim } from './color.mjs';
|
|
6
|
+
import { authorizeManagedSweep } from './managed-path.mjs';
|
|
6
7
|
|
|
7
8
|
// Caps must stay in lockstep with the warnings emitted by validatePlanShape in
|
|
8
9
|
// src/validate.mjs — that's where the user first sees these numbers. Targets
|
|
@@ -16,6 +17,7 @@ const FIELDS = [
|
|
|
16
17
|
export function runFrontmatterFix(config, opts = {}) {
|
|
17
18
|
const { dryRun, out = process.stdout } = opts;
|
|
18
19
|
const allFiles = collectDocFiles(config);
|
|
20
|
+
authorizeManagedSweep(allFiles, config, { kind: 'Frontmatter fix source' });
|
|
19
21
|
const results = [];
|
|
20
22
|
|
|
21
23
|
for (const filePath of allFiles) {
|
package/src/frontmatter.mjs
CHANGED
|
@@ -13,17 +13,18 @@ export function normalizeEol(text) {
|
|
|
13
13
|
export function extractFrontmatter(raw) {
|
|
14
14
|
const text = normalizeEol(raw);
|
|
15
15
|
if (!text.startsWith('---\n')) {
|
|
16
|
-
return { frontmatter: '', body: text };
|
|
16
|
+
return { frontmatter: '', body: text, bodyLineOffset: 0 };
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
const endMarker = text.indexOf('\n---\n', 4);
|
|
20
20
|
if (endMarker === -1) {
|
|
21
|
-
return { frontmatter: '', body: text };
|
|
21
|
+
return { frontmatter: '', body: text, bodyLineOffset: 0 };
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
return {
|
|
25
25
|
frontmatter: text.slice(4, endMarker),
|
|
26
26
|
body: text.slice(endMarker + 5),
|
|
27
|
+
bodyLineOffset: text.slice(0, endMarker + 5).split('\n').length - 1,
|
|
27
28
|
};
|
|
28
29
|
}
|
|
29
30
|
|