Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions tools/loop-sync/dist/sync.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,13 @@ export interface SyncOptions {
help?: boolean;
json?: boolean;
}
/**
* Extract frontmatter from markdown files
*/
export declare function extractFrontmatter(content: string): {
frontmatter: Record<string, string>;
body: string;
};
/**
* Main sync function
*/
Expand Down
13 changes: 7 additions & 6 deletions tools/loop-sync/dist/sync.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,15 +36,16 @@ async function readFileContent(filePath) {
/**
* Extract frontmatter from markdown files
*/
function extractFrontmatter(content) {
export function extractFrontmatter(content) {
const frontmatter = {};
let body = content;
if (content.startsWith('---')) {
const endIndex = content.indexOf('---', 3);
if (endIndex !== -1) {
if (content.startsWith('---\n') || content.startsWith('---\r\n')) {
const endIndex = content.indexOf('\n---', 3);
if (endIndex !== -1 && (endIndex + 4 === content.length || content[endIndex + 4] === '\n' || content[endIndex + 4] === '\r')) {
const fmContent = content.slice(3, endIndex);
body = content.slice(endIndex + 3);
for (const line of fmContent.split('\n')) {
const closingEnd = content.indexOf('\n', endIndex + 4);
body = closingEnd === -1 ? content.slice(endIndex + 1) : content.slice(closingEnd + 1);
for (const line of fmContent.split(/\r?\n/)) {
const colonIndex = line.indexOf(':');
if (colonIndex !== -1) {
const key = line.slice(0, colonIndex).trim();
Expand Down
7 changes: 4 additions & 3 deletions tools/loop-sync/src/sync.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,15 +63,16 @@ async function readFileContent(filePath: string): Promise<string | null> {
/**
* Extract frontmatter from markdown files
*/
function extractFrontmatter(content: string): { frontmatter: Record<string, string>; body: string } {
export function extractFrontmatter(content: string): { frontmatter: Record<string, string>; body: string } {
const frontmatter: Record<string, string> = {};
let body = content;

if (content.startsWith('---\n') || content.startsWith('---\r\n')) {
const endIndex = content.indexOf('\n---', 3);
if (endIndex !== -1) {
if (endIndex !== -1 && (endIndex + 4 === content.length || content[endIndex + 4] === '\n' || content[endIndex + 4] === '\r')) {
const fmContent = content.slice(3, endIndex);
body = content.slice(endIndex + 4);
const closingEnd = content.indexOf('\n', endIndex + 4);
body = closingEnd === -1 ? content.slice(endIndex + 1) : content.slice(closingEnd + 1);

for (const line of fmContent.split(/\r?\n/)) {
const colonIndex = line.indexOf(':');
Expand Down
32 changes: 31 additions & 1 deletion tools/loop-sync/test/sync.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import path from 'node:path';
import { mkdir, writeFile, rm } from 'node:fs/promises';
import { execFile } from 'node:child_process';
import { promisify } from 'node:util';
import { runSync, formatReport } from '../dist/sync.js';
import { runSync, formatReport, extractFrontmatter } from '../dist/sync.js';

const testDir = path.join(process.cwd(), '.test-tmp');
const exec = promisify(execFile);
Expand Down Expand Up @@ -118,6 +118,36 @@ describe('formatReport', () => {
});
});

describe('extractFrontmatter', () => {
test('parses LF frontmatter', () => {
const { frontmatter, body } = extractFrontmatter(
'---\nkey: value\n---\nbody text\n',
);
assert.deepEqual(frontmatter, { key: 'value' });
assert.equal(body, 'body text\n');
});

test('parses CRLF frontmatter without trailing carriage returns', () => {
const { frontmatter, body } = extractFrontmatter(
'---\r\nkey: value\r\nother: thing\r\n---\r\nbody text\r\n',
);
assert.deepEqual(frontmatter, { key: 'value', other: 'thing' });
assert.ok(!Object.values(frontmatter).some((v) => v.endsWith('\r')));
assert.ok(!Object.keys(frontmatter).some((k) => k.endsWith('\r')));
assert.equal(body, 'body text\r\n');
});

test('rejects opening fence without newline (---hello)', () => {
const { frontmatter } = extractFrontmatter('---hello\nkey: value\n---\n');
assert.deepEqual(frontmatter, {});
});

test('rejects closing fence without preceding newline', () => {
const { frontmatter } = extractFrontmatter('---\nkey: value\n---oops\n');
assert.deepEqual(frontmatter, {});
});
});

describe('cli', () => {
beforeEach(setupTestDir);
afterEach(cleanupTestDir);
Expand Down