diff --git a/.github/workflows/ci/package.json b/.github/workflows/ci/package.json
index 3fdbbd6bf45..2eaed7537ea 100644
--- a/.github/workflows/ci/package.json
+++ b/.github/workflows/ci/package.json
@@ -1,5 +1,6 @@
{
- "dependencies": {
+ "devDependencies": {
+ "@types/node": "^24.10.2",
"typescript": "^5.9.3"
}
}
diff --git a/.github/workflows/ci/tsconfig.json b/.github/workflows/ci/tsconfig.json
index c68e78721ac..dca5bb226ee 100644
--- a/.github/workflows/ci/tsconfig.json
+++ b/.github/workflows/ci/tsconfig.json
@@ -6,6 +6,7 @@
"checkJs": true,
"esModuleInterop": true,
"module": "ESNext",
+ "types": ["node"],
"skipLibCheck": true
}
}
diff --git a/.github/workflows/sync-github-releases.yml b/.github/workflows/sync-github-releases.yml
new file mode 100644
index 00000000000..101751a3d55
--- /dev/null
+++ b/.github/workflows/sync-github-releases.yml
@@ -0,0 +1,47 @@
+name: Publish new Release
+
+on:
+ push:
+ branches:
+ - main
+ paths:
+ - 'packages/vike/CHANGELOG.md'
+ workflow_dispatch:
+
+permissions:
+ contents: read
+
+jobs:
+ publish_package:
+ runs-on: ubuntu-latest
+ permissions:
+ contents: write
+ id-token: write
+ environment: release
+ steps:
+ - uses: actions/checkout@v6
+ - name: Install Bun
+ uses: oven-sh/setup-bun@v2
+ with:
+ version: 1.2.19
+ - name: Install pnpm
+ uses: pnpm/action-setup@v5
+ with:
+ version: 10
+ - name: Install Node
+ uses: actions/setup-node@v6
+ with:
+ node-version: 24
+ registry-url: 'https://registry.npmjs.org'
+ - name: Install modules
+ run: pnpm install --filter ./packages/vike
+ - name: Create NPM release
+ working-directory: ./packages/vike
+ run: npm publish --access public
+ env:
+ NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
+ - name: Create GitHub release
+ run: bun ./.github/workflows/sync-github-releases/sync-releases.ts
+ env:
+ GITHUB_DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
+ GITHUB_TOKEN: ${{ github.token }}
diff --git a/.github/workflows/sync-github-releases/fixtures/changelog-telefunc.md b/.github/workflows/sync-github-releases/fixtures/changelog-telefunc.md
new file mode 100644
index 00000000000..b3353d68f0e
--- /dev/null
+++ b/.github/workflows/sync-github-releases/fixtures/changelog-telefunc.md
@@ -0,0 +1,31 @@
+## [0.1.2](https://github.com/brillout/telefunc/compare/v0.1.1...v0.1.2) (2022-01-25)
+
+
+### Bug Fixes
+
+* improve TelefunctionError type ([41a572a](https://github.com/brillout/telefunc/commit/41a572a))
+* make error handling consistent between remote call and SSR call ([10edb6a](https://github.com/brillout/telefunc/commit/10edb6a))
+
+
+
+## [0.1.1](https://github.com/brillout/telefunc/compare/0.1.0...0.1.1) (2022-01-24)
+
+
+### Bug Fixes
+
+* enable isomorphic imports by refactoring source code file structure and adopting new TS/ESM/CJS strategy ([d0c182d](https://github.com/brillout/telefunc/commit/d0c182d))
+* warn user upon incorrect usage of isomorphic imports ([72700ca](https://github.com/brillout/telefunc/commit/72700ca))
+
+
+
+# Telefunc (new 2021/2022 version) `0.1.0`
+
+Initial release.
+
+# Telefunc (old 2020 version) `0.0.26`
+
+See [github.com/brillout/telefunc-old](https://github.com/brillout/telefunc-old).
+
+# Wildcard API `v0.5.3`
+
+See [github.com/brillout/wildcard-api](https://github.com/brillout/wildcard-api).
diff --git a/.github/workflows/sync-github-releases/fixtures/changelog-vike-react.md b/.github/workflows/sync-github-releases/fixtures/changelog-vike-react.md
new file mode 100644
index 00000000000..4a9e68d8a51
--- /dev/null
+++ b/.github/workflows/sync-github-releases/fixtures/changelog-vike-react.md
@@ -0,0 +1,39 @@
+## [0.1.6](https://github.com/vikejs/vike-react/compare/v0.1.5...v0.1.6) (2023-06-24)
+
+### Bug Fixes
+
+- fix 'vike-react' type ([9fe6825](https://github.com/vikejs/vike-react/commit/9fe6825))
+- keep concrete types of vike-react config ([07a811f](https://github.com/vikejs/vike-react/commit/07a811f))
+- simplify type `Config` ([2fe1dcc](https://github.com/vikejs/vike-react/commit/2fe1dcc))
+
+## [0.1.5](https://github.com/vikejs/vike-react/compare/v0.1.4...v0.1.5) (2023-05-26)
+
+### Bug Fixes
+
+- use latest vps version ([f35836e](https://github.com/vikejs/vike-react/commit/f35836e))
+
+## [0.1.4](https://github.com/vikejs/vike-react/compare/v0.1.3...v0.1.4) (2023-05-26)
+
+### Bug Fixes
+
+- use latest vps commit ([d08e03d](https://github.com/vikejs/vike-react/commit/d08e03d))
+
+## [0.1.3](https://github.com/vikejs/vike-react/compare/v0.1.2...v0.1.3) (2023-05-24)
+
+### Bug Fixes
+
+- fix type pointer ([c7841f2](https://github.com/vikejs/vike-react/commit/c7841f2))
+
+## [0.1.2](https://github.com/vikejs/vike-react/compare/v0.1.1...v0.1.2) (2023-05-24)
+
+### Bug Fixes
+
+- fix ESM import paths - 2 ([5c881e5](https://github.com/vikejs/vike-react/commit/5c881e5))
+
+## [0.1.1](https://github.com/vikejs/vike-react/compare/v0.1.0...v0.1.1) (2023-05-24)
+
+### Bug Fixes
+
+- fix ESM import paths ([60c4423](https://github.com/vikejs/vike-react/commit/60c4423))
+
+## 0.1.0 (2023-05-24)
diff --git a/.github/workflows/sync-github-releases/fixtures/changelog-vike-solid.md b/.github/workflows/sync-github-releases/fixtures/changelog-vike-solid.md
new file mode 100644
index 00000000000..d160428d7e3
--- /dev/null
+++ b/.github/workflows/sync-github-releases/fixtures/changelog-vike-solid.md
@@ -0,0 +1,58 @@
+## [0.7.2](https://github.com/vikejs/vike-solid/compare/v0.7.1...v0.7.2) (2024-08-17)
+
+
+### Bug Fixes
+
+* enable useConfig() after hydration ([#179](https://github.com/vikejs/vike-solid/issues/179)) ([0d28905](https://github.com/vikejs/vike-solid/commit/0d28905))
+
+
+
+## [0.7.1](https://github.com/vikejs/vike-solid/compare/v0.7.0...v0.7.1) (2024-08-12)
+
+
+### Bug Fixes
+
+* don't unnecessarily pass useConfig() values to client-side ([613bce3](https://github.com/vikejs/vike-solid/commit/613bce3))
+
+
+### Features
+
+* new [components `
` and ``](https://vike.dev/useConfig#config-head) ([#104](https://github.com/vikejs/vike-solid/issues/104)) ([ed1c070](https://github.com/vikejs/vike-solid/commit/ed1c070))
+
+
+
+# [0.7.0](https://github.com/vikejs/vike-solid/compare/v0.6.2...v0.7.0) (2024-08-06)
+
+
+### Bug Fixes
+
+* avoid the default of title/lang setting to override Head setting ([1f91f16](https://github.com/vikejs/vike-solid/commit/1f91f16))
+* export Vike config only at /config ([d66c678](https://github.com/vikejs/vike-solid/commit/d66c678))
+
+
+### Features
+
+* [useConfig()](https://vike.dev/useConfig) ([88496ed](https://github.com/vikejs/vike-solid/commit/88496ed))
+* new hook [`onAfterRenderClient`](https://vike.dev/onAfterRenderClient) ([149f555](https://github.com/vikejs/vike-solid/commit/149f555))
+
+
+### BREAKING CHANGES
+
+* component `` removed: use `clientOnly()` helper instead https://vike.dev/clientOnly
+* Update to `vike@0.4.191` or above.
+
+
+
+## [0.6.2](https://github.com/vikejs/vike-solid/compare/v0.6.1...v0.6.2) (2024-06-25)
+
+
+### Features
+
+* Add streaming support for Web Stream ([69ac3cd](https://github.com/vikejs/vike-solid/commit/69ac3cd))
+
+
+
+## [0.6.1](https://github.com/vikejs/vike-solid/compare/v0.6.0...v0.6.1) (2024-06-22)
+
+
+**For previous versions, see [MIGRATION.md](https://github.com/vikejs/vike-solid/blob/main/MIGRATION.md).**
diff --git a/.github/workflows/sync-github-releases/fixtures/changelog-vike-vue.md b/.github/workflows/sync-github-releases/fixtures/changelog-vike-vue.md
new file mode 100644
index 00000000000..892c7d1ca7d
--- /dev/null
+++ b/.github/workflows/sync-github-releases/fixtures/changelog-vike-vue.md
@@ -0,0 +1,33 @@
+## [0.2.3](https://github.com/vikejs/vike-vue/compare/v0.2.2...v0.2.3) (2023-09-08)
+
+### Bug Fixes
+
+- Fix `__name is not defined` ([11417ca](https://github.com/vikejs/vike-vue/commit/11417ca))
+
+## [0.2.2](https://github.com/vikejs/vike-vue/compare/v0.2.1...v0.2.2) (2023-09-05)
+
+### Bug Fixes
+
+- Fix 'Error: Cannot find module' ([6b35d81](https://github.com/vikejs/vike-vue/commit/6b35d81))
+
+## [0.2.1](https://github.com/vikejs/vike-vue/compare/v0.2.0...v0.2.1) (2023-08-29)
+
+- Fix peer dependency on `vite-plugin-ssr`.
+
+## [0.2.0](https://github.com/vikejs/vike-vue/compare/v0.1.1...v0.2.0) (2023-08-28)
+
+### Bug Fixes
+
+- Actually support `config.meta.ssr = false` ([3ee182a](https://github.com/vikejs/vike-vue/commit/3ee182a))
+
+### Features
+
+- Add `Head` config option ([75cd700](https://github.com/vikejs/vike-vue/commit/75cd700))
+
+## [0.1.1](https://github.com/vikejs/vike-vue/compare/v0.1.0...v0.1.1) (2023-08-20)
+
+- Add README content to [npm package](https://www.npmjs.com/package/vike-vue)
+
+## 0.1.0 (2023-08-20)
+
+- Initial version
diff --git a/.github/workflows/sync-github-releases/fixtures/changelog-vike.md b/.github/workflows/sync-github-releases/fixtures/changelog-vike.md
new file mode 100644
index 00000000000..56d01aea278
--- /dev/null
+++ b/.github/workflows/sync-github-releases/fixtures/changelog-vike.md
@@ -0,0 +1,59 @@
+# [0.1.0-beta.10](https://github.com/brillout/vite-plugin-ssr/compare/v0.1.0-beta.8...v0.1.0-beta.10) (2021-03-05)
+
+
+### Bug Fixes
+
+* convert windows path to posix for micromatch ([cc9c405](https://github.com/brillout/vite-plugin-ssr/commit/cc9c405)), closes [#4](https://github.com/brillout/vite-plugin-ssr/issues/4)
+* don't try to inject dynamic import polyfill ([fdffd37](https://github.com/brillout/vite-plugin-ssr/commit/fdffd37))
+* **boilerplates:** remove duplicated file ([4421aa6](https://github.com/brillout/vite-plugin-ssr/commit/4421aa6))
+
+
+### Features
+
+* allow render hook to return an object instead of HTML ([a649eaf](https://github.com/brillout/vite-plugin-ssr/commit/a649eaf))
+* support _500.page.js and improve error handling ([d492b9c](https://github.com/brillout/vite-plugin-ssr/commit/d492b9c))
+* support `html` tag composition ([9a57006](https://github.com/brillout/vite-plugin-ssr/commit/9a57006))
+
+
+
+# [0.1.0-beta.9](https://github.com/brillout/vite-plugin-ssr/compare/v0.1.0-beta.8...v0.1.0-beta.9) (2021-03-01)
+
+### Bug Fixes
+
+* Fix released build
+
+
+
+# [0.1.0-beta.8](https://github.com/brillout/vite-plugin-ssr/compare/v0.1.0-beta.7...v0.1.0-beta.8) (2021-03-01)
+
+
+### Bug Fixes
+
+* re-export default ([cd43e6e](https://github.com/brillout/vite-plugin-ssr/commit/cd43e6e))
+* use `in` operator only on objects ([819dfe1](https://github.com/brillout/vite-plugin-ssr/commit/819dfe1))
+
+
+### Features
+
+* remove html.sanitize and make sanitized automatic ([95d145c](https://github.com/brillout/vite-plugin-ssr/commit/95d145c))
+
+
+### BREAKING CHANGES
+
+* Removed `html.sanitize()`; simply directly insert the
+string and vite-plugin-ssr will automatically sanitize it
+
+
+
+# [0.1.0-beta.7](https://github.com/brillout/vite-plugin-ssr/compare/v0.1.0-beta.6...v0.1.0-beta.7) (2021-02-28)
+
+
+### Features
+
+* Pass `Page` to addContextProps. ([2512ee3](https://github.com/brillout/vite-plugin-ssr/commit/2512ee3))
+
+
+
+# [0.1.0-beta.6](https://github.com/brillout/vite-plugin-ssr/tree/963afbafa5697d7745b6803bf1475b4aad7559c2) (2021-02-22)
+
+Initial public release
diff --git a/.github/workflows/sync-github-releases/package.json b/.github/workflows/sync-github-releases/package.json
new file mode 100644
index 00000000000..09faea87c5d
--- /dev/null
+++ b/.github/workflows/sync-github-releases/package.json
@@ -0,0 +1,10 @@
+{
+ "scripts": {
+ "run": "bun ./sync-releases.ts ",
+ "try": "bun ./sync-releases.ts --dry-run"
+ },
+ "devDependencies": {
+ "@types/node": "^24.10.2",
+ "typescript": "^5.9.3"
+ }
+}
diff --git a/.github/workflows/sync-github-releases/sync-releases.spec.ts b/.github/workflows/sync-github-releases/sync-releases.spec.ts
new file mode 100644
index 00000000000..f71d55da3a9
--- /dev/null
+++ b/.github/workflows/sync-github-releases/sync-releases.spec.ts
@@ -0,0 +1,157 @@
+import { readFileSync } from 'node:fs'
+import path from 'node:path'
+import { describe, expect, it } from 'vitest'
+import { getDefaultBranch, getReleasePlan, getReleaseSections, getRepository } from './sync-releases'
+
+function readFixture(name: string): string {
+ return readFileSync(path.join(__dirname, 'fixtures', name), 'utf8')
+}
+
+describe('getReleaseSections()', () => {
+ it('maps changelog headings to version tags', () => {
+ const changelog = `## [1.0.1](https://example.org) (2026-03-01)
+
+### Features
+
+* Added release automation.
+
+## [1.0.0](https://example.org) (2026-02-28)
+
+### Bug Fixes
+
+* Fixed old release notes.
+`
+
+ expect(getReleaseSections(changelog)).toEqual({
+ 'v1.0.1': '### Features\n\n* Added release automation.',
+ 'v1.0.0': '### Bug Fixes\n\n* Fixed old release notes.',
+ })
+ })
+
+ it('parses oldest Vike entries (single # headings, pre-release versions)', () => {
+ const sections = getReleaseSections(readFixture('changelog-vike.md'))
+ expect(Object.keys(sections)).toEqual([
+ 'v0.1.0-beta.10',
+ 'v0.1.0-beta.9',
+ 'v0.1.0-beta.8',
+ 'v0.1.0-beta.7',
+ 'v0.1.0-beta.6',
+ ])
+ expect(sections['v0.1.0-beta.10']).toContain('### Features')
+ expect(sections['v0.1.0-beta.8']).toContain('### BREAKING CHANGES')
+ expect(sections['v0.1.0-beta.6']).toContain('Initial public release')
+ })
+
+ it('parses oldest Telefunc entries (trailing non-versioned sections)', () => {
+ const sections = getReleaseSections(readFixture('changelog-telefunc.md'))
+ expect(Object.keys(sections)).toEqual(['v0.1.2', 'v0.1.1'])
+ expect(sections['v0.1.2']).toContain('improve TelefunctionError type')
+ expect(sections['v0.1.1']).toContain('isomorphic imports')
+ })
+
+ it('parses oldest vike-vue entries (no-link headings, dash bullets)', () => {
+ const sections = getReleaseSections(readFixture('changelog-vike-vue.md'))
+ expect(Object.keys(sections)).toEqual(['v0.2.3', 'v0.2.2', 'v0.2.1', 'v0.2.0', 'v0.1.1'])
+ expect(sections['v0.2.1']).toContain('Fix peer dependency')
+ expect(sections['v0.2.0']).toContain('Add `Head` config option')
+ })
+
+ it('parses oldest vike-solid entries (single # headings, mixed formats)', () => {
+ const sections = getReleaseSections(readFixture('changelog-vike-solid.md'))
+ expect(Object.keys(sections)).toEqual(['v0.7.2', 'v0.7.1', 'v0.7.0', 'v0.6.2', 'v0.6.1'])
+ expect(sections['v0.7.0']).toContain('### BREAKING CHANGES')
+ expect(sections['v0.6.1']).toContain('MIGRATION.md')
+ })
+
+ it('parses oldest vike-react entries (no-link initial version)', () => {
+ const sections = getReleaseSections(readFixture('changelog-vike-react.md'))
+ expect(Object.keys(sections)).toEqual(['v0.1.6', 'v0.1.5', 'v0.1.4', 'v0.1.3', 'v0.1.2', 'v0.1.1'])
+ expect(sections['v0.1.6']).toContain("fix 'vike-react' type")
+ expect(sections['v0.1.1']).toContain('fix ESM import paths')
+ })
+})
+
+describe('getReleasePlan()', () => {
+ it('creates the current release when missing and updates stale notes', () => {
+ const plan = getReleasePlan({
+ defaultBranch: 'main',
+ versionTag: 'v1.0.1',
+ sections: {
+ 'v1.0.1': 'New release notes',
+ 'v1.0.0': 'Updated old notes',
+ 'v0.9.0': 'Existing notes',
+ },
+ releases: [
+ { id: 1, tag_name: 'v1.0.0', body: 'Outdated old notes' },
+ { id: 2, tag_name: 'v0.9.0', body: 'Existing notes' },
+ ],
+ })
+
+ expect(plan).toEqual({
+ releaseToCreate: {
+ tag_name: 'v1.0.1',
+ target_commitish: 'main',
+ name: 'v1.0.1',
+ body: 'New release notes',
+ },
+ releasesToUpdate: [{ release_id: 1, tag_name: 'v1.0.0', body: 'Updated old notes' }],
+ })
+ })
+
+ it('updates the current release instead of creating a duplicate', () => {
+ const plan = getReleasePlan({
+ defaultBranch: 'main',
+ versionTag: 'v1.0.1',
+ sections: {
+ 'v1.0.1': 'Fresh release notes',
+ },
+ releases: [{ id: 3, tag_name: 'v1.0.1', body: 'Stale release notes' }],
+ })
+
+ expect(plan).toEqual({
+ releaseToCreate: null,
+ releasesToUpdate: [{ release_id: 3, tag_name: 'v1.0.1', body: 'Fresh release notes' }],
+ })
+ })
+
+ it('throws when the current version is missing from the changelog', () => {
+ expect(() =>
+ getReleasePlan({
+ defaultBranch: 'main',
+ versionTag: 'v1.0.1',
+ sections: {},
+ releases: [],
+ }),
+ ).toThrow('Missing changelog entry for v1.0.1')
+ })
+})
+
+describe('local fallbacks', () => {
+ it('falls back to the production repository when run locally', () => {
+ const previous = process.env.GITHUB_REPOSITORY
+ try {
+ delete process.env.GITHUB_REPOSITORY
+ expect(getRepository()).toEqual({ owner: 'vikejs', repo: 'vike' })
+ } finally {
+ if (previous === undefined) {
+ delete process.env.GITHUB_REPOSITORY
+ } else {
+ process.env.GITHUB_REPOSITORY = previous
+ }
+ }
+ })
+
+ it('falls back to the main branch when run locally', () => {
+ const previous = process.env.GITHUB_DEFAULT_BRANCH
+ try {
+ delete process.env.GITHUB_DEFAULT_BRANCH
+ expect(getDefaultBranch()).toBe('main')
+ } finally {
+ if (previous === undefined) {
+ delete process.env.GITHUB_DEFAULT_BRANCH
+ } else {
+ process.env.GITHUB_DEFAULT_BRANCH = previous
+ }
+ }
+ })
+})
diff --git a/.github/workflows/sync-github-releases/sync-releases.ts b/.github/workflows/sync-github-releases/sync-releases.ts
new file mode 100644
index 00000000000..2e61bcfbcd8
--- /dev/null
+++ b/.github/workflows/sync-github-releases/sync-releases.ts
@@ -0,0 +1,242 @@
+// Keeps GitHub releases aligned with `CHANGELOG.md`.
+// => It derives release notes from `CHANGELOG.md`, creates the current release if needed (and any missing releases), and updates existing releases whose published notes are outdated (e.g. if CHANGELOG.md was manually edited).
+
+export { getReleasePlan }
+export { getReleaseSections }
+export { getDefaultBranch }
+export { getRepository }
+
+import assert from 'node:assert'
+import { readFile } from 'node:fs/promises'
+import { createRequire } from 'node:module'
+import path from 'node:path'
+import { execSync } from 'node:child_process'
+import { setTimeout } from 'node:timers/promises'
+import { fileURLToPath } from 'node:url'
+const require = createRequire(import.meta.url)
+const { version } = require('../../../packages/vike/package.json') as { version: string }
+
+type Release = {
+ id: number
+ tag_name: string
+ body: string | null
+}
+type ReleaseSections = Record
+type ReleaseCreateInput = {
+ tag_name: string
+ target_commitish: string
+ name: string
+ body: string
+}
+type ReleaseUpdateInput = {
+ release_id: number
+ tag_name: string
+ body: string
+}
+
+async function main(): Promise {
+ // Local testing:
+ // GITHUB_TOKEN= bun ./.github/workflows/sync-github-releases/sync-releases.ts
+ // Dry-run (no GitHub token needed):
+ // bun ./.github/workflows/sync-github-releases/sync-releases.ts --dry-run
+ const dryRun = process.argv.includes('--dry-run')
+ const { owner, repo } = getRepository()
+ const defaultBranch = getDefaultBranch()
+ const versionTag = `v${version}`
+ const changelog = await readRepositoryFile('packages/vike/CHANGELOG.md')
+ const sections = getReleaseSections(changelog)
+
+ if (dryRun) {
+ console.log(`Dry-run mode — no GitHub API calls will be made.`)
+ console.log(`Repository: ${owner}/${repo}`)
+ console.log(`Version tag: ${versionTag}`)
+ console.log(`Changelog sections found: ${Object.keys(sections).join(', ')}`)
+ assert(sections[versionTag], `Missing changelog entry for ${versionTag}`)
+ console.log(`\nRelease notes for ${versionTag}:\n${sections[versionTag]}`)
+ return
+ }
+
+ const token = getGithubToken()
+
+ // https://docs.github.com/en/rest/releases/releases#list-releases
+ const releases = await githubRequest(`/repos/${owner}/${repo}/releases?per_page=100`, {
+ token,
+ })
+
+ if (releases.length === 0) {
+ // Publish releases that are in CHANGELOG but not published
+ // Create release from oldest to newest, so that the release list
+ // is sorted by creation date in the same order as the changelog sections
+ const allTagReleasesToCreate = Object.keys(sections).reverse()
+ for (const tagName of allTagReleasesToCreate) {
+ await githubRequest(`/repos/${owner}/${repo}/releases`, {
+ token,
+ method: 'POST',
+ body: {
+ name: tagName,
+ tag_name: tagName,
+ target_commitish: defaultBranch,
+ body: sections[tagName],
+ },
+ })
+ console.log(`Created release ${tagName}`)
+ // Avoid hitting GitHub abuse rate limits
+ await setTimeout(500)
+ }
+ } else {
+ const { releaseToCreate, releasesToUpdate } = getReleasePlan({ defaultBranch, releases, sections, versionTag })
+
+ if (releaseToCreate) {
+ // https://docs.github.com/en/rest/releases/releases#create-a-release
+ await githubRequest(`/repos/${owner}/${repo}/releases`, {
+ token,
+ method: 'POST',
+ body: releaseToCreate,
+ })
+ console.log(`Created release ${versionTag}`)
+ }
+
+ for (const release of releasesToUpdate) {
+ // https://docs.github.com/en/rest/releases/releases#update-a-release
+ await githubRequest(`/repos/${owner}/${repo}/releases/${release.release_id}`, {
+ token,
+ method: 'PATCH',
+ body: { body: release.body },
+ })
+ console.log(`Updated release ${release.tag_name}`)
+ // Avoid hitting GitHub abuse rate limits
+ await setTimeout(500)
+ }
+ }
+}
+
+function getReleaseSections(changelog: string): ReleaseSections {
+ const sections: ReleaseSections = {}
+ // Matches changelog headings: `## [0.4.257](...)` or `# [0.1.0-beta.6](...)`
+ const regex = /^##? \[(\d+\.\d+\.\d+[^\]]*)\]/gm
+ const matches: { version: string; index: number }[] = []
+
+ let match: RegExpExecArray | null
+ while ((match = regex.exec(changelog)) !== null) {
+ matches.push({ version: match[1], index: match.index })
+ }
+
+ matches.forEach((match, index) => {
+ const start = changelog.indexOf('\n', match.index)
+ const end = matches[index + 1]?.index ?? changelog.length
+ const notes = changelog.slice(start, end).trim()
+ sections[`v${match.version}`] = notes
+ })
+
+ return sections
+}
+
+function getReleasePlan({
+ defaultBranch,
+ releases,
+ sections,
+ versionTag,
+}: {
+ defaultBranch: string
+ releases: Release[]
+ sections: ReleaseSections
+ versionTag: string
+}): {
+ releaseToCreate: ReleaseCreateInput | null
+ releasesToUpdate: ReleaseUpdateInput[]
+} {
+ const currentBody = sections[versionTag]
+ assert(currentBody, `Missing changelog entry for ${versionTag}`)
+
+ const releaseToCreate = releases.some((release) => release.tag_name === versionTag)
+ ? null
+ : {
+ tag_name: versionTag,
+ target_commitish: defaultBranch,
+ name: versionTag,
+ body: currentBody,
+ }
+
+ const releasesToUpdate = releases.flatMap((release) => {
+ const body = sections[release.tag_name]
+ if (!body || body === release.body) return []
+ return [{ release_id: release.id, tag_name: release.tag_name, body }]
+ })
+
+ return { releaseToCreate, releasesToUpdate }
+}
+
+function getRepository(): { owner: string; repo: string } {
+ const repository = process.env.GITHUB_REPOSITORY ?? getRepositoryFromGit()
+ const [owner, repo] = repository.split('/')
+ assert(owner && repo, `Invalid GITHUB_REPOSITORY value: ${repository}`)
+ return { owner, repo }
+}
+
+function getRepositoryFromGit(): string {
+ const url = execSync('git remote get-url origin', { encoding: 'utf8' }).trim()
+ // Handles both https://github.com/owner/repo.git and git@github.com:owner/repo.git
+ const match = url.match(/github\.com[:/](.+?)(?:\.git)?$/)
+ assert(match, `Cannot parse GitHub repository from git remote: ${url}`)
+ return match[1]
+}
+
+function getGithubToken(): string {
+ const token = process.env.GITHUB_TOKEN
+ if (!token) {
+ console.error(
+ [
+ 'GITHUB_TOKEN is not set, run:',
+ ' GITHUB_TOKEN= pnpm run run',
+ 'Or dry-run (no token needed):',
+ ' pnpm run try',
+ ].join('\n'),
+ )
+ process.exit(1)
+ }
+ return token
+}
+
+function getDefaultBranch(): string {
+ return process.env.GITHUB_DEFAULT_BRANCH ?? 'main'
+}
+
+async function readRepositoryFile(relativePath: string): Promise {
+ return readFile(path.join(getRepositoryRoot(), relativePath), 'utf8')
+}
+
+function getRepositoryRoot(): string {
+ return path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../..')
+}
+
+async function githubRequest(
+ pathname: string,
+ { body, method = 'GET', token }: { body?: unknown; method?: 'GET' | 'PATCH' | 'POST'; token: string },
+): Promise {
+ const apiUrl = process.env.GITHUB_API_URL ?? 'https://api.github.com'
+ const response = await fetch(new URL(pathname, apiUrl), {
+ method,
+ headers: {
+ Accept: 'application/vnd.github+json',
+ Authorization: `Bearer ${token}`,
+ 'Content-Type': 'application/json',
+ 'User-Agent': 'vike-sync-github-releases-workflow',
+ 'X-GitHub-Api-Version': '2022-11-28',
+ },
+ body: body ? JSON.stringify(body) : undefined,
+ })
+
+ if (!response.ok) {
+ const errorBody = await response.text()
+ throw new Error(
+ `GitHub API request failed (${method} ${pathname}): ${response.status} ${response.statusText}\n${errorBody}`,
+ )
+ }
+
+ if (response.status === 204) return undefined as T
+ return (await response.json()) as T
+}
+
+if (process.argv[1] === fileURLToPath(import.meta.url)) {
+ void main()
+}
diff --git a/.github/workflows/sync-github-releases/tsconfig.json b/.github/workflows/sync-github-releases/tsconfig.json
new file mode 100644
index 00000000000..dca5bb226ee
--- /dev/null
+++ b/.github/workflows/sync-github-releases/tsconfig.json
@@ -0,0 +1,12 @@
+{
+ "compilerOptions": {
+ "strict": true,
+ "moduleResolution": "bundler",
+ "noImplicitAny": true,
+ "checkJs": true,
+ "esModuleInterop": true,
+ "module": "ESNext",
+ "types": ["node"],
+ "skipLibCheck": true
+ }
+}
diff --git a/README.md b/README.md
index 663be729356..2d4a914c884 100644
--- a/README.md
+++ b/README.md
@@ -5,7 +5,7 @@
## Links
:eyes: What is Vike: [vike.dev](https://vike.dev)
-:clipboard: Version history & breaking changes: [CHANGELOG.md](/CHANGELOG.md)
+:clipboard: Version history & breaking changes: [CHANGELOG.md](/packages/vike/CHANGELOG.md)
:question: Get help: [vike.dev > FAQ > How can I reach out for help?](https://vike.dev/faq#how-can-i-reach-out-for-help)
:green_heart: Contribute: [CONTRIBUTING.md](/CONTRIBUTING.md)
diff --git a/docs/pages/migration/+Page.mdx b/docs/pages/migration/+Page.mdx
index 94ac9efadfd..6392bba97e8 100644
--- a/docs/pages/migration/+Page.mdx
+++ b/docs/pages/migration/+Page.mdx
@@ -1,6 +1,6 @@
import { Link } from '@brillout/docpress'
-For non-breaking version updates, see [CHANGELOG.md](https://github.com/vikejs/vike/blob/main/CHANGELOG.md) instead.
+For non-breaking version updates, see [CHANGELOG.md](https://github.com/vikejs/vike/blob/main/packages/vike/CHANGELOG.md) instead.
List of breaking version updates:
-
diff --git a/docs/pages/releases/+Page.mdx b/docs/pages/releases/+Page.mdx
index 06541478988..418d52d7af8 100644
--- a/docs/pages/releases/+Page.mdx
+++ b/docs/pages/releases/+Page.mdx
@@ -8,7 +8,7 @@ import { Link } from '@brillout/docpress'
## See also
-- [`vike` > `CHANGELOG.md`](https://github.com/vikejs/vike/blob/main/CHANGELOG.md)
+- [`vike` > `CHANGELOG.md`](https://github.com/vikejs/vike/blob/main/packages/vike/CHANGELOG.md)
- [`vike-react` > `CHANGELOG.md`](https://github.com/vikejs/vike-react/blob/main/packages/vike-react/CHANGELOG.md)
- [`vike-vue` > `CHANGELOG.md`](https://github.com/vikejs/vike-vue/blob/main/packages/vike-vue/CHANGELOG.md)
- [`vike-solid` > `CHANGELOG.md`](https://github.com/vikejs/vike-solid/blob/main/CHANGELOG.md)
diff --git a/docs/pages/releases/2024-06/+Page.mdx b/docs/pages/releases/2024-06/+Page.mdx
index 2aa3bffac12..5c70dac42e6 100644
--- a/docs/pages/releases/2024-06/+Page.mdx
+++ b/docs/pages/releases/2024-06/+Page.mdx
@@ -39,7 +39,7 @@ What we have been working on in June 2024.
**See also**
-[`vike` > `CHANGELOG.md`](https://github.com/vikejs/vike/blob/main/CHANGELOG.md)
+[`vike` > `CHANGELOG.md`](https://github.com/vikejs/vike/blob/main/packages/vike/CHANGELOG.md)
[`vike-react` > `CHANGELOG.md`](https://github.com/vikejs/vike-react/blob/main/packages/vike-react/CHANGELOG.md)
[`vike-vue` > `CHANGELOG.md`](https://github.com/vikejs/vike-vue/blob/main/packages/vike-vue/CHANGELOG.md)
[`vike-solid` > `CHANGELOG.md`](https://github.com/vikejs/vike-solid/blob/main/CHANGELOG.md)
@@ -276,7 +276,7 @@ Major features we're currently working on:
## See also
-- [`vike` > `CHANGELOG.md`](https://github.com/vikejs/vike/blob/main/CHANGELOG.md)
+- [`vike` > `CHANGELOG.md`](https://github.com/vikejs/vike/blob/main/packages/vike/CHANGELOG.md)
- [`vike-react` > `CHANGELOG.md`](https://github.com/vikejs/vike-react/blob/main/packages/vike-react/CHANGELOG.md)
- [`vike-vue` > `CHANGELOG.md`](https://github.com/vikejs/vike-vue/blob/main/packages/vike-vue/CHANGELOG.md)
- [`vike-solid` > `CHANGELOG.md`](https://github.com/vikejs/vike-solid/blob/main/CHANGELOG.md)
diff --git a/docs/pages/releases/2024-07/+Page.mdx b/docs/pages/releases/2024-07/+Page.mdx
index 151b19c507f..39ed717909c 100644
--- a/docs/pages/releases/2024-07/+Page.mdx
+++ b/docs/pages/releases/2024-07/+Page.mdx
@@ -28,7 +28,7 @@ What we have been working on in July 2024.
**See also**
-[`vike` > `CHANGELOG.md`](https://github.com/vikejs/vike/blob/main/CHANGELOG.md)
+[`vike` > `CHANGELOG.md`](https://github.com/vikejs/vike/blob/main/packages/vike/CHANGELOG.md)
[`vike-react` > `CHANGELOG.md`](https://github.com/vikejs/vike-react/blob/main/packages/vike-react/CHANGELOG.md)
[`vike-vue` > `CHANGELOG.md`](https://github.com/vikejs/vike-vue/blob/main/packages/vike-vue/CHANGELOG.md)
[`vike-solid` > `CHANGELOG.md`](https://github.com/vikejs/vike-solid/blob/main/CHANGELOG.md)
@@ -170,7 +170,7 @@ We've been talking with the community to gather feedback for the upcoming new ma
## See also
-- [`vike` > `CHANGELOG.md`](https://github.com/vikejs/vike/blob/main/CHANGELOG.md)
+- [`vike` > `CHANGELOG.md`](https://github.com/vikejs/vike/blob/main/packages/vike/CHANGELOG.md)
- [`vike-react` > `CHANGELOG.md`](https://github.com/vikejs/vike-react/blob/main/packages/vike-react/CHANGELOG.md)
- [`vike-vue` > `CHANGELOG.md`](https://github.com/vikejs/vike-vue/blob/main/packages/vike-vue/CHANGELOG.md)
- [`vike-solid` > `CHANGELOG.md`](https://github.com/vikejs/vike-solid/blob/main/CHANGELOG.md)
diff --git a/docs/pages/releases/2024-08/+Page.mdx b/docs/pages/releases/2024-08/+Page.mdx
index 93215ac35e1..a00c651f451 100644
--- a/docs/pages/releases/2024-08/+Page.mdx
+++ b/docs/pages/releases/2024-08/+Page.mdx
@@ -21,7 +21,7 @@ import { BlogHeader } from '../../blog/BlogHeader'
**See also**
-[`vike` > `CHANGELOG.md`](https://github.com/vikejs/vike/blob/main/CHANGELOG.md)
+[`vike` > `CHANGELOG.md`](https://github.com/vikejs/vike/blob/main/packages/vike/CHANGELOG.md)
[`vike-react` > `CHANGELOG.md`](https://github.com/vikejs/vike-react/blob/main/packages/vike-react/CHANGELOG.md)
[`vike-vue` > `CHANGELOG.md`](https://github.com/vikejs/vike-vue/blob/main/packages/vike-vue/CHANGELOG.md)
[`vike-solid` > `CHANGELOG.md`](https://github.com/vikejs/vike-solid/blob/main/CHANGELOG.md)
@@ -71,7 +71,7 @@ Together with our upcoming structural improvements to [`vike-node`](https://gith
## See also
-- [`vike` > `CHANGELOG.md`](https://github.com/vikejs/vike/blob/main/CHANGELOG.md)
+- [`vike` > `CHANGELOG.md`](https://github.com/vikejs/vike/blob/main/packages/vike/CHANGELOG.md)
- [`vike-react` > `CHANGELOG.md`](https://github.com/vikejs/vike-react/blob/main/packages/vike-react/CHANGELOG.md)
- [`vike-vue` > `CHANGELOG.md`](https://github.com/vikejs/vike-vue/blob/main/packages/vike-vue/CHANGELOG.md)
- [`vike-solid` > `CHANGELOG.md`](https://github.com/vikejs/vike-solid/blob/main/CHANGELOG.md)
diff --git a/docs/pages/versioning/+Page.mdx b/docs/pages/versioning/+Page.mdx
index 994a32d17bb..dfbc0b8af70 100644
--- a/docs/pages/versioning/+Page.mdx
+++ b/docs/pages/versioning/+Page.mdx
@@ -3,11 +3,11 @@ Vike version updates that keep the leading digit (e.g. `0.4.x` => `0.4.y` or `1.
## `MINOR BREAKING CHANGES`
-[`MINOR BREAKING CHANGES`](https://github.com/vikejs/vike/blob/main/CHANGELOG.md#minor-breaking-changes) can be introduced in any version update, even patch updates (e.g. `1.2.3` => `1.2.4`).
+[`MINOR BREAKING CHANGES`](https://github.com/vikejs/vike/blob/main/packages/vike/CHANGELOG.md#minor-breaking-changes) can be introduced in any version update, even patch updates (e.g. `1.2.3` => `1.2.4`).
They are expected to affect only very few users — you're unlikely to be one of them. So we recommend that you ignore `MINOR BREAKING CHANGES` unless updating breaks your app.
-> If updating Vike breaks your app, check the [`CHANGELOG.md`](https://github.com/vikejs/vike/blob/main/CHANGELOG.md) for any `MINOR BREAKING CHANGES`. If you experience a breaking change that isn't listed then reach out — we'll treat it as a regression and fix it.
+> If updating Vike breaks your app, check the [`CHANGELOG.md`](https://github.com/vikejs/vike/blob/main/packages/vike/CHANGELOG.md) for any `MINOR BREAKING CHANGES`. If you experience a breaking change that isn't listed then reach out — we'll treat it as a regression and fix it.
> Being able to introduce minor breaking changes in any version update allows us to *significantly* speed up Vike’s development.
>
diff --git a/CHANGELOG.md b/packages/vike/CHANGELOG.md
similarity index 100%
rename from CHANGELOG.md
rename to packages/vike/CHANGELOG.md
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 85b8e53c2ea..bcfc2558dfd 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -31,7 +31,19 @@ importers:
version: 4.1.4(@edge-runtime/vm@3.2.0)(@types/node@24.10.2)(vite@8.0.7(@types/node@24.10.2)(esbuild@0.27.3)(jiti@2.6.1)(terser@5.38.1)(tsx@4.20.6))
.github/workflows/ci:
- dependencies:
+ devDependencies:
+ '@types/node':
+ specifier: ^24.10.2
+ version: 24.10.2
+ typescript:
+ specifier: ^5.9.3
+ version: 5.9.3
+
+ .github/workflows/sync-github-releases:
+ devDependencies:
+ '@types/node':
+ specifier: ^24.10.2
+ version: 24.10.2
typescript:
specifier: ^5.9.3
version: 5.9.3
diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml
index 6352536a565..a033b902d15 100644
--- a/pnpm-workspace.yaml
+++ b/pnpm-workspace.yaml
@@ -6,4 +6,4 @@ packages:
- 'test/'
- 'test/*'
- 'test-deprecated-design/*'
- - '.github/workflows/ci'
+ - '.github/workflows/*'
diff --git a/vitest.config.ts b/vitest.config.ts
index 546c6668c07..63c20f344af 100644
--- a/vitest.config.ts
+++ b/vitest.config.ts
@@ -9,7 +9,12 @@ export default defineConfig({
projects: [
{
test: {
- include: ['packages/**/*.spec.ts'],
+ include: ['packages/**/*.spec.ts', '.github/**/*.spec.ts'],
+ exclude: [
+ '**/node_modules/**',
+ './packages/vike/src/node/vite/shared/resolveVikeConfigInternal/crawlPlusFilePaths/test-file-structure/**',
+ '.github/workflows/ci/prepare.spec.ts',
+ ],
name: 'unit',
env,
},