Skip to content
Merged
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
25 changes: 23 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,11 @@ name: Release
# release notes from CHANGELOG.md,
# 5. publishes the npm thin-installer (shim + per-platform packages).
#
# Before triggering: bump package.json. CHANGELOG.md entries can live under
# `## [Unreleased]` — step 2 takes care of moving them. Set the NPM_TOKEN secret.
# Before triggering: bump package.json on the default branch, and dispatch this
# workflow from that branch — a guard step rejects anything else, because step 2
# pushes its commit to whichever branch triggered the run. CHANGELOG.md entries
# can live under `## [Unreleased]`; step 2 moves them. Never pre-create a
# `## [<version>]` block. Set the NPM_TOKEN secret.
on:
workflow_dispatch: {}

Expand All @@ -24,6 +27,24 @@ jobs:
release:
runs-on: ubuntu-latest
steps:
- name: Refuse to release from a non-default branch
# The promote step below pushes the CHANGELOG back to the branch
# this workflow was dispatched on. Dispatched from a short-lived
# `release/*` branch, that commit lands there and disappears with
# the branch — which is exactly how v1.16.0 shipped while `main`
# kept a stale CHANGELOG and package.json (#296). Releases come
# from the default branch or they do not happen.
env:
REF_NAME: ${{ github.ref_name }}
DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
run: |
set -euo pipefail
if [ "$REF_NAME" != "$DEFAULT_BRANCH" ]; then
echo "::error::Release was dispatched from '$REF_NAME'. Run it from '$DEFAULT_BRANCH' so the CHANGELOG promotion is pushed to a branch that survives."
exit 1
fi
echo "Releasing from '$REF_NAME' (default branch)."

- uses: actions/checkout@v6
with:
# Default checkout is detached at a SHA; we need an actual branch
Expand Down
28 changes: 19 additions & 9 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,15 @@ and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

### Fixes

- Release notes published to GitHub now carry every section of the changelog. A section whose heading was more than one word — such as the list of new features — could be dropped from the published notes without any warning, which is how v1.16.0 shipped with its entire feature list missing. Those entries have been restored to the changelog. (#296)
- Each version heading in the changelog now links to this project's own release instead of a different project's, where the tag does not exist. (#296)
- Publishing a release now refuses to run from anywhere but the project's main branch, so the changelog update it makes can no longer be left behind on a temporary branch and lost. (#296)


## [1.16.0] - 2026-09-03

### New Features

- Module-level variables are now part of the graph, so shared state can be traced to the procedures that read and write it. (#251)
Expand Down Expand Up @@ -193,12 +202,13 @@ and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
### Fixed

- Closed ArkTS test database handles before removing temporary directories, preventing Windows `EBUSY`/`EPERM` teardown failures.
[1.7.3]: https://github.com/colbymchenry/codegraph/releases/tag/v1.7.3
[1.8.0]: https://github.com/colbymchenry/codegraph/releases/tag/v1.8.0
[1.9.0]: https://github.com/colbymchenry/codegraph/releases/tag/v1.9.0
[1.10.0]: https://github.com/colbymchenry/codegraph/releases/tag/v1.10.0
[1.11.0]: https://github.com/colbymchenry/codegraph/releases/tag/v1.11.0
[1.12.0]: https://github.com/colbymchenry/codegraph/releases/tag/v1.12.0
[1.13.0]: https://github.com/colbymchenry/codegraph/releases/tag/v1.13.0
[1.14.0]: https://github.com/colbymchenry/codegraph/releases/tag/v1.14.0
[1.15.0]: https://github.com/colbymchenry/codegraph/releases/tag/v1.15.0
[1.7.3]: https://github.com/ardelperal/codegraph-vba/releases/tag/v1.7.3
[1.8.0]: https://github.com/ardelperal/codegraph-vba/releases/tag/v1.8.0
[1.9.0]: https://github.com/ardelperal/codegraph-vba/releases/tag/v1.9.0
[1.10.0]: https://github.com/ardelperal/codegraph-vba/releases/tag/v1.10.0
[1.11.0]: https://github.com/ardelperal/codegraph-vba/releases/tag/v1.11.0
[1.12.0]: https://github.com/ardelperal/codegraph-vba/releases/tag/v1.12.0
[1.13.0]: https://github.com/ardelperal/codegraph-vba/releases/tag/v1.13.0
[1.14.0]: https://github.com/ardelperal/codegraph-vba/releases/tag/v1.14.0
[1.15.0]: https://github.com/ardelperal/codegraph-vba/releases/tag/v1.15.0
[1.16.0]: https://github.com/ardelperal/codegraph-vba/releases/tag/v1.16.0
209 changes: 206 additions & 3 deletions __tests__/prepare-release.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import * as path from 'node:path';
import * as os from 'node:os';

const SCRIPT = path.resolve(__dirname, '..', 'scripts', 'prepare-release.mjs');
const EXTRACTOR = path.resolve(__dirname, '..', 'scripts', 'extract-release-notes.mjs');

function run(cwd: string, ...args: string[]) {
const out = execFileSync('node', [SCRIPT, ...args], { cwd, encoding: 'utf8' });
Expand All @@ -32,6 +33,12 @@ Some intro.

`;

/** Read back the `## [1.2.3]` block of the CHANGELOG the script just wrote. */
function result123(cwd: string) {
const result = fs.readFileSync(path.join(cwd, 'CHANGELOG.md'), 'utf8');
return result.split('## [1.2.3]')[1].split('## [1.2.2]')[0];
}

describe('prepare-release.mjs', () => {
let dir: string;
afterEach(() => {
Expand Down Expand Up @@ -171,7 +178,7 @@ describe('prepare-release.mjs', () => {
run(dir);
const result = fs.readFileSync(path.join(dir, 'CHANGELOG.md'), 'utf8');
expect(result).toContain(
'[1.2.3]: https://github.com/colbymchenry/codegraph/releases/tag/v1.2.3',
'[1.2.3]: https://github.com/ardelperal/codegraph-vba/releases/tag/v1.2.3',
);
});

Expand All @@ -182,12 +189,12 @@ describe('prepare-release.mjs', () => {
run(dir);
const result = fs.readFileSync(path.join(dir, 'CHANGELOG.md'), 'utf8');
expect(result).toContain(
'[1.2.3]: https://github.com/colbymchenry/codegraph/releases/tag/v1.2.3',
'[1.2.3]: https://github.com/ardelperal/codegraph-vba/releases/tag/v1.2.3',
);
});

it('does not double-add an existing link reference', () => {
const ref = '[1.2.3]: https://github.com/colbymchenry/codegraph/releases/tag/v1.2.3';
const ref = '[1.2.3]: https://github.com/ardelperal/codegraph-vba/releases/tag/v1.2.3';
dir = setup(
HEADER +
`## [Unreleased]\n\n### Added\n- x\n\n## [1.2.2] - 2026-01-01\n\n${ref}\n`,
Expand All @@ -199,6 +206,202 @@ describe('prepare-release.mjs', () => {
});
});

// Regression suite for #296. Every fixture above uses the old
// single-word Keep-a-Changelog vocabulary, which is why a `\w+`-only
// sub-section pattern shipped green while the real CHANGELOG had
// already moved to `### New Features` / `### Breaking Changes`.
describe("multi-word sub-section headings (the repo's real vocabulary)", () => {
it('carries every New Features entry into [version] when merging (Case B)', () => {
dir = setup(
HEADER +
`## [1.2.3] - 2026-02-02

## [Unreleased]

### New Features
- Feature one
- Feature two

### Fixes
- A fix

## [1.2.2] - 2026-01-01
`,
);
const out = run(dir);
expect(out).toContain('merged 3');

const v123 = result123(dir);
expect(v123).toContain('### New Features');
expect(v123).toContain('- Feature one');
expect(v123).toContain('- Feature two');
expect(v123).toContain('### Fixes');
expect(v123).toContain('- A fix');
});

it('reproduces the v1.16.0 shape: New Features first, above an empty pre-created block', () => {
// The exact input that lost 16 bullets: a pre-created empty
// [version] block, and `### New Features` as the FIRST heading in
// [Unreleased] — so it landed in `leading`, which Case B ignored.
dir = setup(
HEADER +
`## [1.2.3] - 2026-02-02

## [Unreleased]

### New Features
- Headline feature

### Changed
- A change

### Fixes
- A fix

## [1.2.2] - 2026-01-01
`,
);
run(dir);

const v123 = result123(dir);
expect(v123).toContain('- Headline feature');
expect(v123).toContain('- A change');
expect(v123).toContain('- A fix');
});

it('merges into a matching multi-word heading instead of duplicating it', () => {
dir = setup(
HEADER +
`## [Unreleased]

### New Features
- Late feature

## [1.2.3] - 2026-02-02

### New Features
- Early feature

## [1.2.2] - 2026-01-01
`,
);
run(dir);

const v123 = result123(dir);
expect(v123).toContain('- Early feature');
expect(v123).toContain('- Late feature');
expect(v123.split('### New Features').length - 1).toBe(1);
});

it('still merges the single-word headings the older entries use', () => {
// Must stay silent: the fix widens the pattern, it must not change
// how the legacy vocabulary behaves.
dir = setup(
HEADER +
`## [Unreleased]

### Added
- New thing

## [1.2.3] - 2026-02-02

### Added
- Old thing

## [1.2.2] - 2026-01-01
`,
);
run(dir);

const v123 = result123(dir);
expect(v123).toContain('- Old thing');
expect(v123).toContain('- New thing');
expect(v123.split('### Added').length - 1).toBe(1);
});

it('carries over entries written before any heading rather than dropping them', () => {
// Defence in depth: whatever the heading vocabulary becomes, a
// bullet with no recognised heading above it must survive.
dir = setup(
HEADER +
`## [Unreleased]

- Heading-less entry

### Fixes
- A fix

## [1.2.3] - 2026-02-02

### Fixes
- Prior fix

## [1.2.2] - 2026-01-01
`,
);
const out = run(dir);
expect(out).toContain('merged 2');

const v123 = result123(dir);
expect(v123).toContain('- Heading-less entry');
expect(v123).toContain('- Prior fix');
expect(v123).toContain('- A fix');
});

it('leaves [Unreleased] empty so the next release cannot republish these entries', () => {
// The second half of #296: entries left behind in [Unreleased]
// after a release get published a second time by the next one.
dir = setup(
HEADER +
`## [Unreleased]

### New Features
- Shipped once

## [1.2.3] - 2026-02-02

### Fixes
- Prior
`,
);
run(dir);

const result = fs.readFileSync(path.join(dir, 'CHANGELOG.md'), 'utf8');
const unrel = result.split('## [Unreleased]')[1].split('## [1.2.3]')[0];
expect(unrel).not.toContain('- Shipped once');
expect(unrel.trim()).toBe('');
});

it('the published notes carry the New Features section end to end', () => {
// Closes the loop through the script the workflow actually pipes
// into `gh release create --notes-file`.
dir = setup(
HEADER +
`## [1.2.3] - 2026-02-02

## [Unreleased]

### New Features
- Headline feature

### Fixes
- A fix

## [1.2.2] - 2026-01-01
`,
);
run(dir);

const notes = execFileSync('node', [EXTRACTOR, '1.2.3'], {
cwd: dir,
encoding: 'utf8',
});
expect(notes).toContain('### New Features');
expect(notes).toContain('- Headline feature');
expect(notes).toContain('- A fix');
});
});

describe('extractor integration', () => {
it('the resulting [version] block is what extract-release-notes.mjs would surface', () => {
// Run prepare, then extract — confirm the output contains all the
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@aroman22/codegraph-vba",
"version": "1.15.0",
"version": "1.16.0",
"description": "Supercharge AI coding agents with semantic code intelligence — surgical context, fewer tool calls, faster answers. 100% local.",
"main": "dist/index.js",
"types": "dist/index.d.ts",
Expand Down
Loading