From 8ca6967859d74901d6af1a86b2b15afcd82c3b61 Mon Sep 17 00:00:00 2001 From: Sameer Chore Date: Thu, 24 Sep 2026 02:08:44 +0530 Subject: [PATCH 1/6] changed to show proper error /error falback to locate the exact error file in blog post date --- scripts/data/blog.mjs | 56 ++++++++++++++++++++++++++-- tests/data/blog.test.mjs | 79 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 131 insertions(+), 4 deletions(-) create mode 100644 tests/data/blog.test.mjs diff --git a/scripts/data/blog.mjs b/scripts/data/blog.mjs index 9c90fd4a..5e362824 100644 --- a/scripts/data/blog.mjs +++ b/scripts/data/blog.mjs @@ -11,6 +11,55 @@ const OUTPUT = join(ROOT, 'generated', 'blog.json'); const titleFromBody = body => body.match(/^#\s+(.+)$/m)?.[1].trim() ?? null; +const daysInMonth = (year, month) => { + if (month === 2) { + return year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0) ? 29 : 28; + } + + return [31, 0, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month - 1] ?? 0; +}; + +const normalizeDate = (value, file, source) => { + const rawDate = source + .match(/^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/)?.[1] + ?.match(/^date:\s*(.*?)\s*$/m)?.[1]; + const calendar = rawDate?.match(/^['"]?(\d{4})-(\d{2})-(\d{2})/); + + if ( + calendar && + (Number(calendar[2]) < 1 || + Number(calendar[2]) > 12 || + Number(calendar[3]) < 1 || + Number(calendar[3]) > + daysInMonth(Number(calendar[1]), Number(calendar[2]))) + ) { + const display = JSON.stringify(rawDate.trim()); + throw new Error( + `Invalid blog date in ${join('pages', 'blog', 'posts', file)}: ${display} ` + + '(expected an ISO 8601 date or timestamp)' + ); + } + + const date = + value instanceof Date + ? value + : typeof value === 'string' && value.trim() + ? new Date(value) + : null; + + if (!date || Number.isNaN(date.getTime())) { + const displayValue = rawDate?.trim() || value; + const display = + displayValue === undefined ? '' : JSON.stringify(displayValue); + throw new Error( + `Invalid blog date in ${join('pages', 'blog', 'posts', file)}: ${display} ` + + '(expected an ISO 8601 date or timestamp)' + ); + } + + return date.toISOString(); +}; + const readPosts = async () => { const entries = await readdir(POSTS_DIR); const files = entries.filter(name => name.endsWith('.md')); @@ -18,15 +67,14 @@ const readPosts = async () => { const posts = await Promise.all( files.map(async file => { const slug = file.replace(/\.md$/, ''); - const { data, content } = matter( - await readFile(join(POSTS_DIR, file), 'utf8') - ); + const source = await readFile(join(POSTS_DIR, file), 'utf8'); + const { data, content } = matter(source); return { slug, title: titleFromBody(content) ?? slug, authors: data.authors?.split(',').map(s => s.trim()), - date: new Date(data.date).toISOString(), + date: normalizeDate(data.date, file, source), category: data.category ?? null, image: data.image ?? null, ...(data.description && { description: data.description }), diff --git a/tests/data/blog.test.mjs b/tests/data/blog.test.mjs new file mode 100644 index 00000000..6ad649b6 --- /dev/null +++ b/tests/data/blog.test.mjs @@ -0,0 +1,79 @@ +import { strict as assert } from 'node:assert'; +import { execFile } from 'node:child_process'; +import { + copyFile, + mkdtemp, + mkdir, + readFile, + rm, + writeFile, +} from 'node:fs/promises'; +import { promisify } from 'node:util'; +import { fileURLToPath } from 'node:url'; +import { join } from 'node:path'; +import { test } from 'node:test'; + +const execFileAsync = promisify(execFile); +const blogScript = fileURLToPath( + new URL('../../scripts/data/blog.mjs', import.meta.url) +); + +const createFixture = async content => { + const root = await mkdtemp(join('tests', 'data', '.blog-')); + await mkdir(join(root, 'scripts', 'data'), { recursive: true }); + await mkdir(join(root, 'pages', 'blog', 'posts'), { recursive: true }); + await mkdir(join(root, 'generated'), { recursive: true }); + await copyFile(blogScript, join(root, 'scripts', 'data', 'blog.mjs')); + await writeFile(join(root, 'pages', 'blog', 'posts', 'example.md'), content); + return root; +}; + +const runFixture = root => + execFileAsync(process.execPath, ['scripts/data/blog.mjs'], { cwd: root }); + +test('generates blog data for valid dates', async t => { + const root = await createFixture("---\ndate: '2026-09-22'\n---\n# Test\n"); + t.after(() => rm(root, { recursive: true, force: true })); + + await runFixture(root); + const posts = JSON.parse( + await readFile(join(root, 'generated', 'blog.json'), 'utf8') + ); + + assert.equal(posts[0].date, '2026-09-22T00:00:00.000Z'); +}); + +test('reports the file for an invalid blog date', async t => { + const root = await createFixture('---\ndate: not-a-date\n---\n# Test\n'); + t.after(() => rm(root, { recursive: true, force: true })); + + await assert.rejects(runFixture(root), error => { + assert.match(error.stderr, /Invalid blog date/); + assert.match(error.stderr, /pages[\\/]blog[\\/]posts[\\/]example\.md/); + assert.match(error.stderr, /not-a-date/); + return true; + }); +}); + +test('rejects nonexistent calendar dates', async t => { + const root = await createFixture('---\ndate: 2026-02-30\n---\n# Test\n'); + t.after(() => rm(root, { recursive: true, force: true })); + + await assert.rejects(runFixture(root), error => { + assert.match(error.stderr, /Invalid blog date/); + assert.match(error.stderr, /2026-02-30/); + return true; + }); +}); + +test('reports the file for a missing blog date', async t => { + const root = await createFixture('---\ntitle: Test\n---\n# Test\n'); + t.after(() => rm(root, { recursive: true, force: true })); + + await assert.rejects(runFixture(root), error => { + assert.match(error.stderr, /Invalid blog date/); + assert.match(error.stderr, /example\.md/); + assert.match(error.stderr, //); + return true; + }); +}); From 5756658efc2ea630dc17880e0606ea42518141d1 Mon Sep 17 00:00:00 2001 From: Sameer Chore Date: Thu, 24 Sep 2026 02:16:35 +0530 Subject: [PATCH 2/6] Added some test over blog post invalid pattern datess to handle error and to not show the wrong date to blog by its own --- scripts/data/blog.mjs | 9 +++++++-- tests/data/blog.test.mjs | 26 ++++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/scripts/data/blog.mjs b/scripts/data/blog.mjs index 5e362824..2df2fd8b 100644 --- a/scripts/data/blog.mjs +++ b/scripts/data/blog.mjs @@ -23,7 +23,9 @@ const normalizeDate = (value, file, source) => { const rawDate = source .match(/^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/)?.[1] ?.match(/^date:\s*(.*?)\s*$/m)?.[1]; - const calendar = rawDate?.match(/^['"]?(\d{4})-(\d{2})-(\d{2})/); + const calendar = rawDate?.match( + /^['"]?(\d{4})-(\d{1,2})-(\d{1,2})(?:(?:[Tt]|[ \t]+)(\d{1,2}):(\d{2})(?::(\d{2}))?)?/ + ); if ( calendar && @@ -31,7 +33,10 @@ const normalizeDate = (value, file, source) => { Number(calendar[2]) > 12 || Number(calendar[3]) < 1 || Number(calendar[3]) > - daysInMonth(Number(calendar[1]), Number(calendar[2]))) + daysInMonth(Number(calendar[1]), Number(calendar[2])) || + (calendar[4] !== undefined && Number(calendar[4]) > 23) || + (calendar[5] !== undefined && Number(calendar[5]) > 59) || + (calendar[6] !== undefined && Number(calendar[6]) > 59)) ) { const display = JSON.stringify(rawDate.trim()); throw new Error( diff --git a/tests/data/blog.test.mjs b/tests/data/blog.test.mjs index 6ad649b6..ab95797b 100644 --- a/tests/data/blog.test.mjs +++ b/tests/data/blog.test.mjs @@ -66,6 +66,32 @@ test('rejects nonexistent calendar dates', async t => { }); }); +test('rejects one-digit dates that roll over', async t => { + const root = await createFixture( + '---\ndate: 2024-2-30 10:00:00\n---\n# Test\n' + ); + t.after(() => rm(root, { recursive: true, force: true })); + + await assert.rejects(runFixture(root), error => { + assert.match(error.stderr, /Invalid blog date/); + assert.match(error.stderr, /2024-2-30/); + return true; + }); +}); + +test('rejects out-of-range time components', async t => { + const root = await createFixture( + '---\ndate: 2024-01-01 25:60:60\n---\n# Test\n' + ); + t.after(() => rm(root, { recursive: true, force: true })); + + await assert.rejects(runFixture(root), error => { + assert.match(error.stderr, /Invalid blog date/); + assert.match(error.stderr, /25:60:60/); + return true; + }); +}); + test('reports the file for a missing blog date', async t => { const root = await createFixture('---\ntitle: Test\n---\n# Test\n'); t.after(() => rm(root, { recursive: true, force: true })); From 907bf4716fe0cbc7d657cdb1b1fa0c5a068e36f4 Mon Sep 17 00:00:00 2001 From: Sameer Chore Date: Thu, 24 Sep 2026 19:40:20 +0530 Subject: [PATCH 3/6] Added some test over diffrent date pattern causing error Signed-off-by: Sameer Chore --- scripts/data/blog.mjs | 20 ++++++++++++-------- tests/data/blog.test.mjs | 2 +- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/scripts/data/blog.mjs b/scripts/data/blog.mjs index 2df2fd8b..af377511 100644 --- a/scripts/data/blog.mjs +++ b/scripts/data/blog.mjs @@ -11,6 +11,16 @@ const OUTPUT = join(ROOT, 'generated', 'blog.json'); const titleFromBody = body => body.match(/^#\s+(.+)$/m)?.[1].trim() ?? null; +class InvalidBlogDateError extends Error { + constructor(file, display) { + super( + `Invalid blog date in ${join('pages', 'blog', 'posts', file)}: ${display} ` + + '(expected an ISO 8601 date or timestamp)' + ); + this.name = 'InvalidBlogDateError'; + } +} + const daysInMonth = (year, month) => { if (month === 2) { return year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0) ? 29 : 28; @@ -39,10 +49,7 @@ const normalizeDate = (value, file, source) => { (calendar[6] !== undefined && Number(calendar[6]) > 59)) ) { const display = JSON.stringify(rawDate.trim()); - throw new Error( - `Invalid blog date in ${join('pages', 'blog', 'posts', file)}: ${display} ` + - '(expected an ISO 8601 date or timestamp)' - ); + throw new InvalidBlogDateError(file, display); } const date = @@ -56,10 +63,7 @@ const normalizeDate = (value, file, source) => { const displayValue = rawDate?.trim() || value; const display = displayValue === undefined ? '' : JSON.stringify(displayValue); - throw new Error( - `Invalid blog date in ${join('pages', 'blog', 'posts', file)}: ${display} ` + - '(expected an ISO 8601 date or timestamp)' - ); + throw new InvalidBlogDateError(file, display); } return date.toISOString(); diff --git a/tests/data/blog.test.mjs b/tests/data/blog.test.mjs index ab95797b..aa2cf0ed 100644 --- a/tests/data/blog.test.mjs +++ b/tests/data/blog.test.mjs @@ -48,7 +48,7 @@ test('reports the file for an invalid blog date', async t => { t.after(() => rm(root, { recursive: true, force: true })); await assert.rejects(runFixture(root), error => { - assert.match(error.stderr, /Invalid blog date/); + assert.match(error.stderr, /InvalidBlogDateError: Invalid blog date/); assert.match(error.stderr, /pages[\\/]blog[\\/]posts[\\/]example\.md/); assert.match(error.stderr, /not-a-date/); return true; From 10e985de3ce6f6eeeeb5f59f5da955e38c848ddf Mon Sep 17 00:00:00 2001 From: Sameer Chore Date: Fri, 25 Sep 2026 04:19:49 +0530 Subject: [PATCH 4/6] fix: allow valid ISO end-of-day blog dates --- scripts/data/blog.mjs | 6 +++++- tests/data/blog.test.mjs | 22 ++++++++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/scripts/data/blog.mjs b/scripts/data/blog.mjs index af377511..81149a18 100644 --- a/scripts/data/blog.mjs +++ b/scripts/data/blog.mjs @@ -44,7 +44,11 @@ const normalizeDate = (value, file, source) => { Number(calendar[3]) < 1 || Number(calendar[3]) > daysInMonth(Number(calendar[1]), Number(calendar[2])) || - (calendar[4] !== undefined && Number(calendar[4]) > 23) || + (calendar[4] !== undefined && + (Number(calendar[4]) > 24 || + (Number(calendar[4]) === 24 && + (Number(calendar[5]) !== 0 || + (calendar[6] !== undefined && Number(calendar[6]) !== 0))))) || (calendar[5] !== undefined && Number(calendar[5]) > 59) || (calendar[6] !== undefined && Number(calendar[6]) > 59)) ) { diff --git a/tests/data/blog.test.mjs b/tests/data/blog.test.mjs index aa2cf0ed..2da66102 100644 --- a/tests/data/blog.test.mjs +++ b/tests/data/blog.test.mjs @@ -92,6 +92,28 @@ test('rejects out-of-range time components', async t => { }); }); +test('accepts ISO end-of-day timestamps', async t => { + for (const value of ['2026-09-22T24:00:00Z', '2026-09-22 24:00']) { + const root = await createFixture(`---\ndate: '${value}'\n---\n# Test\n`); + t.after(() => rm(root, { recursive: true, force: true })); + + await runFixture(root); + } +}); + +test('rejects nonzero end-of-day time components', async t => { + const root = await createFixture( + "---\ndate: '2026-09-22T24:00:01Z'\n---\n# Test\n" + ); + t.after(() => rm(root, { recursive: true, force: true })); + + await assert.rejects(runFixture(root), error => { + assert.match(error.stderr, /Invalid blog date/); + assert.match(error.stderr, /24:00:01/); + return true; + }); +}); + test('reports the file for a missing blog date', async t => { const root = await createFixture('---\ntitle: Test\n---\n# Test\n'); t.after(() => rm(root, { recursive: true, force: true })); From 7da7888284b36ca68fe177e097c13787d6feb7f8 Mon Sep 17 00:00:00 2001 From: Sameer Chore Date: Fri, 25 Sep 2026 20:11:54 +0530 Subject: [PATCH 5/6] fix: normalize offsetless blog timestamps as UTC --- scripts/data/blog.mjs | 8 +++++++- tests/data/blog.test.mjs | 28 ++++++++++++++++++++++++++-- 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/scripts/data/blog.mjs b/scripts/data/blog.mjs index 81149a18..7370f66f 100644 --- a/scripts/data/blog.mjs +++ b/scripts/data/blog.mjs @@ -60,7 +60,13 @@ const normalizeDate = (value, file, source) => { value instanceof Date ? value : typeof value === 'string' && value.trim() - ? new Date(value) + ? new Date( + /^\d{4}-\d{1,2}-\d{1,2}(?:[Tt]|[ \t]+)\d{1,2}:\d{2}(?::\d{2}(?:\.\d+)?)?$/.test( + value.trim() + ) + ? `${value.trim().replace(/[ \t]+/, 'T')}Z` + : value + ) : null; if (!date || Number.isNaN(date.getTime())) { diff --git a/tests/data/blog.test.mjs b/tests/data/blog.test.mjs index 2da66102..4e8ab856 100644 --- a/tests/data/blog.test.mjs +++ b/tests/data/blog.test.mjs @@ -28,8 +28,11 @@ const createFixture = async content => { return root; }; -const runFixture = root => - execFileAsync(process.execPath, ['scripts/data/blog.mjs'], { cwd: root }); +const runFixture = (root, timeZone = 'UTC') => + execFileAsync(process.execPath, ['scripts/data/blog.mjs'], { + cwd: root, + env: { ...process.env, TZ: timeZone }, + }); test('generates blog data for valid dates', async t => { const root = await createFixture("---\ndate: '2026-09-22'\n---\n# Test\n"); @@ -43,6 +46,27 @@ test('generates blog data for valid dates', async t => { assert.equal(posts[0].date, '2026-09-22T00:00:00.000Z'); }); +test('normalizes offset-less quoted date-times as UTC', async t => { + const root = await createFixture( + "---\ndate: '2026-09-22 10:00'\n---\n# Test\n" + ); + t.after(() => rm(root, { recursive: true, force: true })); + + const dates = []; + for (const timeZone of ['UTC', 'America/Los_Angeles']) { + await runFixture(root, timeZone); + const posts = JSON.parse( + await readFile(join(root, 'generated', 'blog.json'), 'utf8') + ); + dates.push(posts[0].date); + } + + assert.deepEqual(dates, [ + '2026-09-22T10:00:00.000Z', + '2026-09-22T10:00:00.000Z', + ]); +}); + test('reports the file for an invalid blog date', async t => { const root = await createFixture('---\ndate: not-a-date\n---\n# Test\n'); t.after(() => rm(root, { recursive: true, force: true })); From d72f4b56db53680f45c01419ca1fae8e858202d9 Mon Sep 17 00:00:00 2001 From: Sameer Chore Date: Sun, 4 Oct 2026 04:40:22 +0530 Subject: [PATCH 6/6] refactor: simplify blog date validation and error reporting --- scripts/data/blog.mjs | 73 ++++--------------------------- tests/data/blog.test.mjs | 93 +++------------------------------------- 2 files changed, 14 insertions(+), 152 deletions(-) diff --git a/scripts/data/blog.mjs b/scripts/data/blog.mjs index 7370f66f..824faec1 100644 --- a/scripts/data/blog.mjs +++ b/scripts/data/blog.mjs @@ -11,69 +11,13 @@ const OUTPUT = join(ROOT, 'generated', 'blog.json'); const titleFromBody = body => body.match(/^#\s+(.+)$/m)?.[1].trim() ?? null; -class InvalidBlogDateError extends Error { - constructor(file, display) { - super( - `Invalid blog date in ${join('pages', 'blog', 'posts', file)}: ${display} ` + - '(expected an ISO 8601 date or timestamp)' - ); - this.name = 'InvalidBlogDateError'; - } -} - -const daysInMonth = (year, month) => { - if (month === 2) { - return year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0) ? 29 : 28; - } - - return [31, 0, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month - 1] ?? 0; -}; - -const normalizeDate = (value, file, source) => { - const rawDate = source - .match(/^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/)?.[1] - ?.match(/^date:\s*(.*?)\s*$/m)?.[1]; - const calendar = rawDate?.match( - /^['"]?(\d{4})-(\d{1,2})-(\d{1,2})(?:(?:[Tt]|[ \t]+)(\d{1,2}):(\d{2})(?::(\d{2}))?)?/ - ); - - if ( - calendar && - (Number(calendar[2]) < 1 || - Number(calendar[2]) > 12 || - Number(calendar[3]) < 1 || - Number(calendar[3]) > - daysInMonth(Number(calendar[1]), Number(calendar[2])) || - (calendar[4] !== undefined && - (Number(calendar[4]) > 24 || - (Number(calendar[4]) === 24 && - (Number(calendar[5]) !== 0 || - (calendar[6] !== undefined && Number(calendar[6]) !== 0))))) || - (calendar[5] !== undefined && Number(calendar[5]) > 59) || - (calendar[6] !== undefined && Number(calendar[6]) > 59)) - ) { - const display = JSON.stringify(rawDate.trim()); - throw new InvalidBlogDateError(file, display); - } - - const date = - value instanceof Date - ? value - : typeof value === 'string' && value.trim() - ? new Date( - /^\d{4}-\d{1,2}-\d{1,2}(?:[Tt]|[ \t]+)\d{1,2}:\d{2}(?::\d{2}(?:\.\d+)?)?$/.test( - value.trim() - ) - ? `${value.trim().replace(/[ \t]+/, 'T')}Z` - : value - ) - : null; +const parseDate = (value, file) => { + const date = value ? new Date(value) : null; if (!date || Number.isNaN(date.getTime())) { - const displayValue = rawDate?.trim() || value; - const display = - displayValue === undefined ? '' : JSON.stringify(displayValue); - throw new InvalidBlogDateError(file, display); + throw new Error( + `Invalid or missing blog date in pages/blog/posts/${file}: ${JSON.stringify(value ?? '')}` + ); } return date.toISOString(); @@ -86,14 +30,15 @@ const readPosts = async () => { const posts = await Promise.all( files.map(async file => { const slug = file.replace(/\.md$/, ''); - const source = await readFile(join(POSTS_DIR, file), 'utf8'); - const { data, content } = matter(source); + const { data, content } = matter( + await readFile(join(POSTS_DIR, file), 'utf8') + ); return { slug, title: titleFromBody(content) ?? slug, authors: data.authors?.split(',').map(s => s.trim()), - date: normalizeDate(data.date, file, source), + date: parseDate(data.date, file), category: data.category ?? null, image: data.image ?? null, ...(data.description && { description: data.description }), diff --git a/tests/data/blog.test.mjs b/tests/data/blog.test.mjs index 4e8ab856..bc5a0bf8 100644 --- a/tests/data/blog.test.mjs +++ b/tests/data/blog.test.mjs @@ -28,11 +28,8 @@ const createFixture = async content => { return root; }; -const runFixture = (root, timeZone = 'UTC') => - execFileAsync(process.execPath, ['scripts/data/blog.mjs'], { - cwd: root, - env: { ...process.env, TZ: timeZone }, - }); +const runFixture = root => + execFileAsync(process.execPath, ['scripts/data/blog.mjs'], { cwd: root }); test('generates blog data for valid dates', async t => { const root = await createFixture("---\ndate: '2026-09-22'\n---\n# Test\n"); @@ -46,105 +43,25 @@ test('generates blog data for valid dates', async t => { assert.equal(posts[0].date, '2026-09-22T00:00:00.000Z'); }); -test('normalizes offset-less quoted date-times as UTC', async t => { - const root = await createFixture( - "---\ndate: '2026-09-22 10:00'\n---\n# Test\n" - ); - t.after(() => rm(root, { recursive: true, force: true })); - - const dates = []; - for (const timeZone of ['UTC', 'America/Los_Angeles']) { - await runFixture(root, timeZone); - const posts = JSON.parse( - await readFile(join(root, 'generated', 'blog.json'), 'utf8') - ); - dates.push(posts[0].date); - } - - assert.deepEqual(dates, [ - '2026-09-22T10:00:00.000Z', - '2026-09-22T10:00:00.000Z', - ]); -}); - test('reports the file for an invalid blog date', async t => { const root = await createFixture('---\ndate: not-a-date\n---\n# Test\n'); t.after(() => rm(root, { recursive: true, force: true })); await assert.rejects(runFixture(root), error => { - assert.match(error.stderr, /InvalidBlogDateError: Invalid blog date/); + assert.match(error.stderr, /Invalid or missing blog date/); assert.match(error.stderr, /pages[\\/]blog[\\/]posts[\\/]example\.md/); assert.match(error.stderr, /not-a-date/); return true; }); }); -test('rejects nonexistent calendar dates', async t => { - const root = await createFixture('---\ndate: 2026-02-30\n---\n# Test\n'); - t.after(() => rm(root, { recursive: true, force: true })); - - await assert.rejects(runFixture(root), error => { - assert.match(error.stderr, /Invalid blog date/); - assert.match(error.stderr, /2026-02-30/); - return true; - }); -}); - -test('rejects one-digit dates that roll over', async t => { - const root = await createFixture( - '---\ndate: 2024-2-30 10:00:00\n---\n# Test\n' - ); - t.after(() => rm(root, { recursive: true, force: true })); - - await assert.rejects(runFixture(root), error => { - assert.match(error.stderr, /Invalid blog date/); - assert.match(error.stderr, /2024-2-30/); - return true; - }); -}); - -test('rejects out-of-range time components', async t => { - const root = await createFixture( - '---\ndate: 2024-01-01 25:60:60\n---\n# Test\n' - ); - t.after(() => rm(root, { recursive: true, force: true })); - - await assert.rejects(runFixture(root), error => { - assert.match(error.stderr, /Invalid blog date/); - assert.match(error.stderr, /25:60:60/); - return true; - }); -}); - -test('accepts ISO end-of-day timestamps', async t => { - for (const value of ['2026-09-22T24:00:00Z', '2026-09-22 24:00']) { - const root = await createFixture(`---\ndate: '${value}'\n---\n# Test\n`); - t.after(() => rm(root, { recursive: true, force: true })); - - await runFixture(root); - } -}); - -test('rejects nonzero end-of-day time components', async t => { - const root = await createFixture( - "---\ndate: '2026-09-22T24:00:01Z'\n---\n# Test\n" - ); - t.after(() => rm(root, { recursive: true, force: true })); - - await assert.rejects(runFixture(root), error => { - assert.match(error.stderr, /Invalid blog date/); - assert.match(error.stderr, /24:00:01/); - return true; - }); -}); - test('reports the file for a missing blog date', async t => { const root = await createFixture('---\ntitle: Test\n---\n# Test\n'); t.after(() => rm(root, { recursive: true, force: true })); await assert.rejects(runFixture(root), error => { - assert.match(error.stderr, /Invalid blog date/); - assert.match(error.stderr, /example\.md/); + assert.match(error.stderr, /Invalid or missing blog date/); + assert.match(error.stderr, /pages[\\/]blog[\\/]posts[\\/]example\.md/); assert.match(error.stderr, //); return true; });