From d3cca5a28d8c048b4619cf6fe6e9e270375a6d44 Mon Sep 17 00:00:00 2001 From: Jonah Snider Date: Wed, 12 Aug 2026 15:04:00 -0700 Subject: [PATCH 1/6] feat: add date parsing to keep-a-changelog Closes #40 Signed-off-by: Jonah Snider --- .changeset/clean-dates-smile.md | 5 ++++ .../docs/providers/keep-a-changelog.mdx | 25 +++++++++++++++++-- .../providers/keep-a-changelog.ts | 14 +++++++++++ .../providers/markdown.ts | 7 +++++- .../tests/keep-a-changelog.test.ts | 11 ++++++++ 5 files changed, 59 insertions(+), 3 deletions(-) create mode 100644 .changeset/clean-dates-smile.md diff --git a/.changeset/clean-dates-smile.md b/.changeset/clean-dates-smile.md new file mode 100644 index 0000000..c2423f4 --- /dev/null +++ b/.changeset/clean-dates-smile.md @@ -0,0 +1,5 @@ +--- +'starlight-changelogs': minor +--- + +Adds an `extractDate` option to the Keep a Changelog provider for extracting release dates from version titles. diff --git a/docs/src/content/docs/providers/keep-a-changelog.mdx b/docs/src/content/docs/providers/keep-a-changelog.mdx index 79d815c..5d6da28 100644 --- a/docs/src/content/docs/providers/keep-a-changelog.mdx +++ b/docs/src/content/docs/providers/keep-a-changelog.mdx @@ -35,7 +35,7 @@ The Keep a Changelog provider accepts the following configuration options: ### `provider` -**Required** +**Required** **Type:** `'keep-a-changelog'` Defines the provider to use for loading the changelog file. @@ -66,7 +66,7 @@ import { ### `changelog` -**Required** +**Required** **Type:** `string` Defines the path or URL of the changelog file using the Keep a Changelog format to load. @@ -91,6 +91,27 @@ changelogsLoader([ ]) ``` +### `extractDate` + +**Type:** `({ title }: { title: string }) => Date | undefined` + +An optional function called for every version entry to extract its release date from the original version title. +The function receives the original title before release dates are stripped from version titles by default. + +For example, the following function extracts ISO 8601 dates from standard Keep a Changelog version titles: + +```ts +changelogsLoader([ + { + extractDate: ({ title }) => { + const date = title.match(/\d{4}-\d{2}-\d{2}$/)?.[0] + + return date ? new Date(`${date}T00:00:00`) : undefined + }, + }, +]) +``` + ### `title` diff --git a/packages/starlight-changelogs/providers/keep-a-changelog.ts b/packages/starlight-changelogs/providers/keep-a-changelog.ts index 2b77d5b..024aa04 100644 --- a/packages/starlight-changelogs/providers/keep-a-changelog.ts +++ b/packages/starlight-changelogs/providers/keep-a-changelog.ts @@ -13,6 +13,20 @@ export const KeepAChangelogProviderConfigSchema = ProviderBaseConfigSchema.exten * When using a URL, it should point to a raw file that contains the changelog, e.g. a GitHub raw URL. */ changelog: z.string(), + /** + * An optional function called for every version entry to extract its release date from the original version title. + */ + extractDate: z + .function({ + input: [ + z.object({ + /** The original version title found in the changelog. */ + title: z.string(), + }), + ], + output: z.union([z.date(), z.undefined()]), + }) + .optional(), /** The type of provider used to load the changelog, `keep-a-changelog` in this case. */ provider: z.literal('keep-a-changelog'), }) diff --git a/packages/starlight-changelogs/providers/markdown.ts b/packages/starlight-changelogs/providers/markdown.ts index 73ba51b..0e75a9e 100644 --- a/packages/starlight-changelogs/providers/markdown.ts +++ b/packages/starlight-changelogs/providers/markdown.ts @@ -112,7 +112,6 @@ function parseMarkdown(config: MarkdownProviderConfig, content: string) { function addEntry(version: MarkdownVersion, index: number) { const parsedVersion = parseMarkdownVersion(config, version, index) if (!parsedVersion) return - if (config.markdown.ignoredVersions?.includes(parsedVersion.title)) return entries.push(parsedVersion) } @@ -152,12 +151,16 @@ function parseMarkdownVersion( title = processedTitle } + if (config.markdown.ignoredVersions?.includes(title)) return + + const date = config.extractDate?.({ title: version.title }) const [id, slug] = slugifyVersion(config, title) return { id, body: toMarkdown({ type: 'root', children: version.nodes as RootContent[] }), base: config.base, + ...(date ? { date } : {}), index, provider: config.provider, slug, @@ -173,6 +176,8 @@ export interface MarkdownProviderConfig extends z.output Date | undefined) | undefined /** Markdown-specific configuration options for parsing the changelog. */ markdown: { /** Version titles to ignore when parsing the changelog (applied after `process` function). */ diff --git a/packages/starlight-changelogs/tests/keep-a-changelog.test.ts b/packages/starlight-changelogs/tests/keep-a-changelog.test.ts index 3155aae..4ad5c1a 100644 --- a/packages/starlight-changelogs/tests/keep-a-changelog.test.ts +++ b/packages/starlight-changelogs/tests/keep-a-changelog.test.ts @@ -16,6 +16,11 @@ describe('fs', () => { base: 'test', changelog: '../../../fixtures/keep-a-changelog/keep-a-changelog.md', enabled: true, + extractDate: ({ title }) => { + const date = /\d{4}-\d{2}-\d{2}$/.exec(title)?.[0] + + return date ? new Date(`${date}T00:00:00`) : undefined + }, pagefind: false, pageSize: 5, title: 'Test', @@ -50,6 +55,12 @@ describe('fs', () => { expect(versions[3]?.data.title).toBe('0.3.0') }) + test('extracts release dates from the original version titles', () => { + const version = store.values()[0] + + expect(version?.data.date).toEqual(new Date('2023-03-05T00:00:00')) + }) + test('loads the first version', () => { const version = store.values().at(-1) From 172f86b5d699e30af695ddbadc02d7afbfc0ea4f Mon Sep 17 00:00:00 2001 From: Jonah Snider Date: Tue, 18 Aug 2026 08:01:25 -0700 Subject: [PATCH 2/6] feat: use Keep a Changelog date syntax Signed-off-by: Jonah Snider --- .changeset/clean-dates-smile.md | 2 +- .../docs/providers/keep-a-changelog.mdx | 24 ++----------------- .../providers/keep-a-changelog.ts | 22 ++++++----------- .../providers/markdown.ts | 9 ++++--- .../tests/keep-a-changelog.test.ts | 7 +----- 5 files changed, 15 insertions(+), 49 deletions(-) diff --git a/.changeset/clean-dates-smile.md b/.changeset/clean-dates-smile.md index c2423f4..636b58f 100644 --- a/.changeset/clean-dates-smile.md +++ b/.changeset/clean-dates-smile.md @@ -2,4 +2,4 @@ 'starlight-changelogs': minor --- -Adds an `extractDate` option to the Keep a Changelog provider for extracting release dates from version titles. +Automatically extracts release dates from Keep a Changelog version headings. diff --git a/docs/src/content/docs/providers/keep-a-changelog.mdx b/docs/src/content/docs/providers/keep-a-changelog.mdx index 5d6da28..79d5b77 100644 --- a/docs/src/content/docs/providers/keep-a-changelog.mdx +++ b/docs/src/content/docs/providers/keep-a-changelog.mdx @@ -6,7 +6,8 @@ description: Learn how to display changelogs using the Keep a Changelog format w The Starlight Changelogs plugin uses Astro’s [content collections](https://docs.astro.build/en/guides/content-collections/), which are configured in the `src/content.config.ts` file. The provided `changelogsLoader()` loader can be used to load one or more changelog files from different providers, including [Keep a Changelog](https://keepachangelog.com). -[Unreleased versions](https://keepachangelog.com/en/1.1.0/#effort) are ignored and release dates are stripped from version titles by default. +[Unreleased versions](https://keepachangelog.com/en/1.1.0/#effort) are ignored. +Release dates are automatically extracted from [standard version headings](https://keepachangelog.com/en/1.1.0/) and stripped from version titles. Changelog files using the Keep a Changelog format can be loaded by using the `provider: 'keep-a-changelog'` option. @@ -91,27 +92,6 @@ changelogsLoader([ ]) ``` -### `extractDate` - -**Type:** `({ title }: { title: string }) => Date | undefined` - -An optional function called for every version entry to extract its release date from the original version title. -The function receives the original title before release dates are stripped from version titles by default. - -For example, the following function extracts ISO 8601 dates from standard Keep a Changelog version titles: - -```ts -changelogsLoader([ - { - extractDate: ({ title }) => { - const date = title.match(/\d{4}-\d{2}-\d{2}$/)?.[0] - - return date ? new Date(`${date}T00:00:00`) : undefined - }, - }, -]) -``` - ### `title` diff --git a/packages/starlight-changelogs/providers/keep-a-changelog.ts b/packages/starlight-changelogs/providers/keep-a-changelog.ts index 024aa04..75e9140 100644 --- a/packages/starlight-changelogs/providers/keep-a-changelog.ts +++ b/packages/starlight-changelogs/providers/keep-a-changelog.ts @@ -13,29 +13,21 @@ export const KeepAChangelogProviderConfigSchema = ProviderBaseConfigSchema.exten * When using a URL, it should point to a raw file that contains the changelog, e.g. a GitHub raw URL. */ changelog: z.string(), - /** - * An optional function called for every version entry to extract its release date from the original version title. - */ - extractDate: z - .function({ - input: [ - z.object({ - /** The original version title found in the changelog. */ - title: z.string(), - }), - ], - output: z.union([z.date(), z.undefined()]), - }) - .optional(), /** The type of provider used to load the changelog, `keep-a-changelog` in this case. */ provider: z.literal('keep-a-changelog'), }) const provider: MarkdownProviderConfig['provider'] = { name: 'keep-a-changelog', label: 'Keep a Changelog' } +const versionHeadingRegex = /^(?.+) - (?\d{4}-\d{2}-\d{2})$/ const markdown: MarkdownProviderConfig['markdown'] = { + getDate(title) { + const date = versionHeadingRegex.exec(title)?.groups?.['date'] + + return date ? new Date(`${date}T00:00:00`) : undefined + }, ignoredVersions: ['Unreleased'], process({ title }) { - return title.replace(/(?.*?)\s-\s?\d{4}-\d{2}-\d{2}\s*$/, '$') + return versionHeadingRegex.exec(title)?.groups?.['version'] ?? title }, versionHeadingLevel: 2, } diff --git a/packages/starlight-changelogs/providers/markdown.ts b/packages/starlight-changelogs/providers/markdown.ts index 0e75a9e..53a47ba 100644 --- a/packages/starlight-changelogs/providers/markdown.ts +++ b/packages/starlight-changelogs/providers/markdown.ts @@ -112,6 +112,7 @@ function parseMarkdown(config: MarkdownProviderConfig, content: string) { function addEntry(version: MarkdownVersion, index: number) { const parsedVersion = parseMarkdownVersion(config, version, index) if (!parsedVersion) return + if (config.markdown.ignoredVersions?.includes(parsedVersion.title)) return entries.push(parsedVersion) } @@ -151,9 +152,7 @@ function parseMarkdownVersion( title = processedTitle } - if (config.markdown.ignoredVersions?.includes(title)) return - - const date = config.extractDate?.({ title: version.title }) + const date = config.markdown.getDate?.(version.title) const [id, slug] = slugifyVersion(config, title) return { @@ -176,10 +175,10 @@ export interface MarkdownProviderConfig extends z.output Date | undefined) | undefined /** Markdown-specific configuration options for parsing the changelog. */ markdown: { + /** An optional function called to get a release date from the original version title. */ + getDate?: (title: string) => Date | undefined /** Version titles to ignore when parsing the changelog (applied after `process` function). */ ignoredVersions?: string[] /** diff --git a/packages/starlight-changelogs/tests/keep-a-changelog.test.ts b/packages/starlight-changelogs/tests/keep-a-changelog.test.ts index 4ad5c1a..f78092b 100644 --- a/packages/starlight-changelogs/tests/keep-a-changelog.test.ts +++ b/packages/starlight-changelogs/tests/keep-a-changelog.test.ts @@ -16,11 +16,6 @@ describe('fs', () => { base: 'test', changelog: '../../../fixtures/keep-a-changelog/keep-a-changelog.md', enabled: true, - extractDate: ({ title }) => { - const date = /\d{4}-\d{2}-\d{2}$/.exec(title)?.[0] - - return date ? new Date(`${date}T00:00:00`) : undefined - }, pagefind: false, pageSize: 5, title: 'Test', @@ -55,7 +50,7 @@ describe('fs', () => { expect(versions[3]?.data.title).toBe('0.3.0') }) - test('extracts release dates from the original version titles', () => { + test('extracts release dates from standard version headings', () => { const version = store.values()[0] expect(version?.data.date).toEqual(new Date('2023-03-05T00:00:00')) From 10e342ff0a40c73e486f195cd7b46ed2e5f1d321 Mon Sep 17 00:00:00 2001 From: HiDeoo <494699+HiDeoo@users.noreply.github.com> Date: Wed, 19 Aug 2026 12:08:38 +0200 Subject: [PATCH 3/6] refactor: process title and date at the same time --- .../providers/keep-a-changelog.ts | 13 ++++++----- .../providers/markdown.ts | 23 ++++++++++--------- 2 files changed, 19 insertions(+), 17 deletions(-) diff --git a/packages/starlight-changelogs/providers/keep-a-changelog.ts b/packages/starlight-changelogs/providers/keep-a-changelog.ts index 75e9140..8d5a3dc 100644 --- a/packages/starlight-changelogs/providers/keep-a-changelog.ts +++ b/packages/starlight-changelogs/providers/keep-a-changelog.ts @@ -20,14 +20,15 @@ export const KeepAChangelogProviderConfigSchema = ProviderBaseConfigSchema.exten const provider: MarkdownProviderConfig['provider'] = { name: 'keep-a-changelog', label: 'Keep a Changelog' } const versionHeadingRegex = /^(?.+) - (?\d{4}-\d{2}-\d{2})$/ const markdown: MarkdownProviderConfig['markdown'] = { - getDate(title) { - const date = versionHeadingRegex.exec(title)?.groups?.['date'] - - return date ? new Date(`${date}T00:00:00`) : undefined - }, ignoredVersions: ['Unreleased'], process({ title }) { - return versionHeadingRegex.exec(title)?.groups?.['version'] ?? title + const match = versionHeadingRegex.exec(title) + const date = match?.groups?.['date'] + + return { + title: match?.groups?.['version'] ?? title, + ...(date ? { date: new Date(`${date}T00:00:00`) } : {}), + } }, versionHeadingLevel: 2, } diff --git a/packages/starlight-changelogs/providers/markdown.ts b/packages/starlight-changelogs/providers/markdown.ts index 53a47ba..442c986 100644 --- a/packages/starlight-changelogs/providers/markdown.ts +++ b/packages/starlight-changelogs/providers/markdown.ts @@ -144,7 +144,7 @@ function parseMarkdownVersion( ): VersionDataEntry | undefined { let title = version.title - const process = config.process ?? config.markdown.process + const process = config.process if (process) { const processedTitle = process({ title }) @@ -152,14 +152,16 @@ function parseMarkdownVersion( title = processedTitle } - const date = config.markdown.getDate?.(version.title) + const processed = config.markdown.process?.({ title: version.title }) + if (!process) title = processed?.title ?? title + const [id, slug] = slugifyVersion(config, title) return { id, body: toMarkdown({ type: 'root', children: version.nodes as RootContent[] }), base: config.base, - ...(date ? { date } : {}), + ...(processed?.date ? { date: processed.date } : {}), index, provider: config.provider, slug, @@ -177,16 +179,10 @@ export interface MarkdownProviderConfig extends z.output Date | undefined /** Version titles to ignore when parsing the changelog (applied after `process` function). */ ignoredVersions?: string[] - /** - * An optional default function called if no `process` function is defined in the main provider configuration. - * - * @see ProviderBaseConfigSchema - */ - process?: z.output['process'] + /** An optional function called to process data from the original version title. */ + process?: (context: { title: string }) => MarkdownProcessResult /** The heading level used to indicate version entries in the changelog. */ versionHeadingLevel: number } @@ -196,6 +192,11 @@ export interface MarkdownProviderConfig extends z.output Date: Wed, 19 Aug 2026 12:12:03 +0200 Subject: [PATCH 4/6] docs: restore line breaks --- docs/src/content/docs/providers/keep-a-changelog.mdx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/src/content/docs/providers/keep-a-changelog.mdx b/docs/src/content/docs/providers/keep-a-changelog.mdx index 79d5b77..6df39e6 100644 --- a/docs/src/content/docs/providers/keep-a-changelog.mdx +++ b/docs/src/content/docs/providers/keep-a-changelog.mdx @@ -36,7 +36,7 @@ The Keep a Changelog provider accepts the following configuration options: ### `provider` -**Required** +**Required** **Type:** `'keep-a-changelog'` Defines the provider to use for loading the changelog file. @@ -67,7 +67,7 @@ import { ### `changelog` -**Required** +**Required** **Type:** `string` Defines the path or URL of the changelog file using the Keep a Changelog format to load. From e3cb737c277e0717bec4e7050c5eda74e1f4b7f2 Mon Sep 17 00:00:00 2001 From: HiDeoo <494699+HiDeoo@users.noreply.github.com> Date: Wed, 19 Aug 2026 12:15:19 +0200 Subject: [PATCH 5/6] docs: mention format --- docs/src/content/docs/providers/keep-a-changelog.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/content/docs/providers/keep-a-changelog.mdx b/docs/src/content/docs/providers/keep-a-changelog.mdx index 6df39e6..13456af 100644 --- a/docs/src/content/docs/providers/keep-a-changelog.mdx +++ b/docs/src/content/docs/providers/keep-a-changelog.mdx @@ -7,7 +7,7 @@ The Starlight Changelogs plugin uses Astro’s [content collections](https://doc The provided `changelogsLoader()` loader can be used to load one or more changelog files from different providers, including [Keep a Changelog](https://keepachangelog.com). [Unreleased versions](https://keepachangelog.com/en/1.1.0/#effort) are ignored. -Release dates are automatically extracted from [standard version headings](https://keepachangelog.com/en/1.1.0/) and stripped from version titles. +Release dates using the ISO 8601 `YYYY-MM-DD` format are automatically extracted from [standard version headings](https://keepachangelog.com/en/1.1.0/#confusing-dates) and stripped from version titles. Changelog files using the Keep a Changelog format can be loaded by using the `provider: 'keep-a-changelog'` option. From 0128cfdf58137ea3ab6eb0c51f73aa2ded72bec4 Mon Sep 17 00:00:00 2001 From: HiDeoo <494699+HiDeoo@users.noreply.github.com> Date: Wed, 19 Aug 2026 12:19:13 +0200 Subject: [PATCH 6/6] chore: tweak changeset --- .changeset/clean-dates-smile.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/clean-dates-smile.md b/.changeset/clean-dates-smile.md index 636b58f..1464721 100644 --- a/.changeset/clean-dates-smile.md +++ b/.changeset/clean-dates-smile.md @@ -2,4 +2,4 @@ 'starlight-changelogs': minor --- -Automatically extracts release dates from Keep a Changelog version headings. +Extracts release dates from version headings when using the Keep a Changelog provider.