ci(release): mechanize the releases-wall entry — never hand-written again
Every release used to get its releases/open-brainy.json entry typed by hand
after the fact. scripts/wall-entry.mjs derives it from the CHANGELOG entry
release.sh just composed (headline = first bullet, items = every bullet,
hash stripped) and prepends it, refusing by name on a duplicate version and
validating the whole file's shape + newest-first ordering before and after
it writes.
release.sh now runs it as its own step, between the CHANGELOG update and
the release commit, and stages releases/open-brainy.json into that commit.
The product engine's rail runs this identical script against its own
releases/brainy.json, unchanged — each repo's wall file lives beside the
CHANGELOG it derives from; there is no cross-repo step.
A --check mode validates a wall file's exact key set, field types, and
newest-first ordering with no duplicates, read-only. tests/unit/release/wall-entry.test.ts
covers derivation, prepend, duplicate refusal, and --check's shape/ordering
checks over temp copies — never the real files. --check also runs green
against both releases/open-brainy.json and releases/brainy.json as they
stand today.
2026-09-02 14:15:39 -07:00
/ * *
* scripts / wall - entry . mjs — the mechanical releases - wall entry .
*
* The script ' s only real interface is its CLI ( it has no importable
* exports by design — one door , no parallel API to drift from it ) , so
* these tests spawn it exactly as scripts / release . sh does : as a child
2026-09-02 14:51:33 -07:00
* process , against a fixture CHANGELOG and a throwaway local bare repo
* standing in for git @source . soulcraft.com :soulcraftlabs / releases . git
* ( -- remote ) plus a throwaway cache directory ( -- cache - dir ) standing in
* for ~ / . c a c h e / s o u l c r a f t - r e l e a s e s — n e v e r t h e r e a l r e m o t e , n e v e r t h e
* real developer cache .
ci(release): mechanize the releases-wall entry — never hand-written again
Every release used to get its releases/open-brainy.json entry typed by hand
after the fact. scripts/wall-entry.mjs derives it from the CHANGELOG entry
release.sh just composed (headline = first bullet, items = every bullet,
hash stripped) and prepends it, refusing by name on a duplicate version and
validating the whole file's shape + newest-first ordering before and after
it writes.
release.sh now runs it as its own step, between the CHANGELOG update and
the release commit, and stages releases/open-brainy.json into that commit.
The product engine's rail runs this identical script against its own
releases/brainy.json, unchanged — each repo's wall file lives beside the
CHANGELOG it derives from; there is no cross-repo step.
A --check mode validates a wall file's exact key set, field types, and
newest-first ordering with no duplicates, read-only. tests/unit/release/wall-entry.test.ts
covers derivation, prepend, duplicate refusal, and --check's shape/ordering
checks over temp copies — never the real files. --check also runs green
against both releases/open-brainy.json and releases/brainy.json as they
stand today.
2026-09-02 14:15:39 -07:00
* /
import { describe , it , expect , beforeEach , afterEach } from 'vitest'
import { execFileSync } from 'node:child_process'
2026-09-02 14:51:33 -07:00
import { mkdtempSync , rmSync , writeFileSync , readFileSync , chmodSync } from 'node:fs'
ci(release): mechanize the releases-wall entry — never hand-written again
Every release used to get its releases/open-brainy.json entry typed by hand
after the fact. scripts/wall-entry.mjs derives it from the CHANGELOG entry
release.sh just composed (headline = first bullet, items = every bullet,
hash stripped) and prepends it, refusing by name on a duplicate version and
validating the whole file's shape + newest-first ordering before and after
it writes.
release.sh now runs it as its own step, between the CHANGELOG update and
the release commit, and stages releases/open-brainy.json into that commit.
The product engine's rail runs this identical script against its own
releases/brainy.json, unchanged — each repo's wall file lives beside the
CHANGELOG it derives from; there is no cross-repo step.
A --check mode validates a wall file's exact key set, field types, and
newest-first ordering with no duplicates, read-only. tests/unit/release/wall-entry.test.ts
covers derivation, prepend, duplicate refusal, and --check's shape/ordering
checks over temp copies — never the real files. --check also runs green
against both releases/open-brainy.json and releases/brainy.json as they
stand today.
2026-09-02 14:15:39 -07:00
import { tmpdir } from 'node:os'
import { join } from 'node:path'
const SCRIPT = join ( process . cwd ( ) , 'scripts/wall-entry.mjs' )
/** Run the script and capture the outcome without throwing on a non-zero exit. */
function run ( args : string [ ] , cwd : string ) : { status : number ; stdout : string ; stderr : string } {
try {
const stdout = execFileSync ( 'node' , [ SCRIPT , . . . args ] , { cwd , encoding : 'utf8' } )
return { status : 0 , stdout , stderr : '' }
} catch ( err : any ) {
return { status : err.status ? ? 1 , stdout : err.stdout ? ? '' , stderr : err.stderr ? ? '' }
}
}
2026-09-02 14:51:33 -07:00
function git ( args : string [ ] , cwd : string ) : string {
return execFileSync ( 'git' , [ '-C' , cwd , . . . args ] , { encoding : 'utf8' } ) . trim ( )
}
ci(release): mechanize the releases-wall entry — never hand-written again
Every release used to get its releases/open-brainy.json entry typed by hand
after the fact. scripts/wall-entry.mjs derives it from the CHANGELOG entry
release.sh just composed (headline = first bullet, items = every bullet,
hash stripped) and prepends it, refusing by name on a duplicate version and
validating the whole file's shape + newest-first ordering before and after
it writes.
release.sh now runs it as its own step, between the CHANGELOG update and
the release commit, and stages releases/open-brainy.json into that commit.
The product engine's rail runs this identical script against its own
releases/brainy.json, unchanged — each repo's wall file lives beside the
CHANGELOG it derives from; there is no cross-repo step.
A --check mode validates a wall file's exact key set, field types, and
newest-first ordering with no duplicates, read-only. tests/unit/release/wall-entry.test.ts
covers derivation, prepend, duplicate refusal, and --check's shape/ordering
checks over temp copies — never the real files. --check also runs green
against both releases/open-brainy.json and releases/brainy.json as they
stand today.
2026-09-02 14:15:39 -07:00
const CHANGELOG_HEADER = '# Changelog\n\nAll notable changes, in this fixture.\n'
/** Build a CHANGELOG.md with one entry per [version, bullets[]] pair, newest first. */
function buildChangelog ( entries : Array < { version : string ; date : string ; bullets : string [ ] } > ) : string {
const body = entries
. map (
( e ) = >
` ### [ ${ e . version } ](https://source.soulcraft.com/soulcraftlabs/open-brainy/compare/vX...v ${ e . version } ) ( ${ e . date } ) \ n \ n ` +
e . bullets . map ( ( b ) = > ` - ${ b } (abc1234) ` ) . join ( '\n' ) +
'\n' ,
)
. join ( '\n' )
return CHANGELOG_HEADER + '\n' + body
}
function wallFile ( product : string , entries : unknown [ ] ) : string {
2026-09-02 14:51:33 -07:00
return JSON . stringify ( { product , entries } , null , 2 ) + '\n'
ci(release): mechanize the releases-wall entry — never hand-written again
Every release used to get its releases/open-brainy.json entry typed by hand
after the fact. scripts/wall-entry.mjs derives it from the CHANGELOG entry
release.sh just composed (headline = first bullet, items = every bullet,
hash stripped) and prepends it, refusing by name on a duplicate version and
validating the whole file's shape + newest-first ordering before and after
it writes.
release.sh now runs it as its own step, between the CHANGELOG update and
the release commit, and stages releases/open-brainy.json into that commit.
The product engine's rail runs this identical script against its own
releases/brainy.json, unchanged — each repo's wall file lives beside the
CHANGELOG it derives from; there is no cross-repo step.
A --check mode validates a wall file's exact key set, field types, and
newest-first ordering with no duplicates, read-only. tests/unit/release/wall-entry.test.ts
covers derivation, prepend, duplicate refusal, and --check's shape/ordering
checks over temp copies — never the real files. --check also runs green
against both releases/open-brainy.json and releases/brainy.json as they
stand today.
2026-09-02 14:15:39 -07:00
}
const BASE_ENTRY = {
version : '10.4.11' ,
date : '2026-09-02' ,
headline : 'A faster open' ,
items : [ 'A faster open.' ] ,
url : 'https://source.soulcraft.com/soulcraftlabs/open-brainy/releases/tag/v10.4.11' ,
thumb : null ,
}
2026-09-02 14:51:33 -07:00
/** A throwaway bare repo standing in for the real soulcraftlabs/releases remote. */
function initBareRemote ( ) : string {
const remoteDir = mkdtempSync ( join ( tmpdir ( ) , 'wall-remote-' ) )
execFileSync ( 'git' , [ 'init' , '--bare' , '-b' , 'main' , remoteDir ] )
return remoteDir
}
/** Seed the bare remote with an initial <product>.json, via a throwaway clone. */
function seedRemote ( remoteDir : string , product : string , entries : unknown [ ] ) : void {
const seedDir = mkdtempSync ( join ( tmpdir ( ) , 'wall-seed-' ) )
execFileSync ( 'git' , [ 'clone' , remoteDir , seedDir ] , { stdio : 'ignore' } )
git ( [ 'config' , 'user.email' , 'seed@example.com' ] , seedDir )
git ( [ 'config' , 'user.name' , 'Seed' ] , seedDir )
writeFileSync ( join ( seedDir , ` ${ product } .json ` ) , wallFile ( product , entries ) )
git ( [ 'add' , ` ${ product } .json ` ] , seedDir )
git ( [ 'commit' , '-m' , 'seed' ] , seedDir )
git ( [ 'push' , 'origin' , 'main' ] , seedDir )
rmSync ( seedDir , { recursive : true , force : true } )
}
/** Read <product>.json back out of the bare remote's main tip, via a throwaway clone. */
function readRemote ( remoteDir : string , product : string ) : any {
const readDir = mkdtempSync ( join ( tmpdir ( ) , 'wall-read-' ) )
execFileSync ( 'git' , [ 'clone' , remoteDir , readDir ] , { stdio : 'ignore' } )
const data = JSON . parse ( readFileSync ( join ( readDir , ` ${ product } .json ` ) , 'utf8' ) )
rmSync ( readDir , { recursive : true , force : true } )
return data
}
/ * * R e j e c t e v e r y p u s h — s t a n d s i n f o r a n y p u s h f a i l u r e ( i n c l u d i n g a g e n u i n e
* non - fast - forward raced by a concurrent release rail ) , which this script
* treats identically : refuse loudly , name the cure , touch nothing further . * /
function makeRemoteRejectPushes ( remoteDir : string ) : void {
const hookPath = join ( remoteDir , 'hooks' , 'pre-receive' )
writeFileSync ( hookPath , '#!/bin/sh\necho "remote: simulated push rejection" >&2\nexit 1\n' )
chmodSync ( hookPath , 0 o755 )
}
ci(release): mechanize the releases-wall entry — never hand-written again
Every release used to get its releases/open-brainy.json entry typed by hand
after the fact. scripts/wall-entry.mjs derives it from the CHANGELOG entry
release.sh just composed (headline = first bullet, items = every bullet,
hash stripped) and prepends it, refusing by name on a duplicate version and
validating the whole file's shape + newest-first ordering before and after
it writes.
release.sh now runs it as its own step, between the CHANGELOG update and
the release commit, and stages releases/open-brainy.json into that commit.
The product engine's rail runs this identical script against its own
releases/brainy.json, unchanged — each repo's wall file lives beside the
CHANGELOG it derives from; there is no cross-repo step.
A --check mode validates a wall file's exact key set, field types, and
newest-first ordering with no duplicates, read-only. tests/unit/release/wall-entry.test.ts
covers derivation, prepend, duplicate refusal, and --check's shape/ordering
checks over temp copies — never the real files. --check also runs green
against both releases/open-brainy.json and releases/brainy.json as they
stand today.
2026-09-02 14:15:39 -07:00
let dir : string
2026-09-02 14:51:33 -07:00
let remoteDir : string
let cacheDir : string
ci(release): mechanize the releases-wall entry — never hand-written again
Every release used to get its releases/open-brainy.json entry typed by hand
after the fact. scripts/wall-entry.mjs derives it from the CHANGELOG entry
release.sh just composed (headline = first bullet, items = every bullet,
hash stripped) and prepends it, refusing by name on a duplicate version and
validating the whole file's shape + newest-first ordering before and after
it writes.
release.sh now runs it as its own step, between the CHANGELOG update and
the release commit, and stages releases/open-brainy.json into that commit.
The product engine's rail runs this identical script against its own
releases/brainy.json, unchanged — each repo's wall file lives beside the
CHANGELOG it derives from; there is no cross-repo step.
A --check mode validates a wall file's exact key set, field types, and
newest-first ordering with no duplicates, read-only. tests/unit/release/wall-entry.test.ts
covers derivation, prepend, duplicate refusal, and --check's shape/ordering
checks over temp copies — never the real files. --check also runs green
against both releases/open-brainy.json and releases/brainy.json as they
stand today.
2026-09-02 14:15:39 -07:00
beforeEach ( ( ) = > {
dir = mkdtempSync ( join ( tmpdir ( ) , 'wall-entry-test-' ) )
2026-09-03 10:57:03 -07:00
// wall-entry.mjs is run with this dir as its cwd, standing in for the real
// developer checkout it reads its commit identity from (process.cwd()) —
// give it a repo-local identity the same way seedRemote gives one to the
// seed clone, so the suite is deterministic on a host with no global git
// config (a bare CI box) as much as one with a developer's own.
execFileSync ( 'git' , [ 'init' , '-q' , dir ] )
git ( [ 'config' , 'user.name' , 'Wall Entry Test' ] , dir )
git ( [ 'config' , 'user.email' , 'wall-entry-test@example.com' ] , dir )
2026-09-02 14:51:33 -07:00
remoteDir = initBareRemote ( )
cacheDir = join ( mkdtempSync ( join ( tmpdir ( ) , 'wall-cache-' ) ) , 'soulcraft-releases' )
ci(release): mechanize the releases-wall entry — never hand-written again
Every release used to get its releases/open-brainy.json entry typed by hand
after the fact. scripts/wall-entry.mjs derives it from the CHANGELOG entry
release.sh just composed (headline = first bullet, items = every bullet,
hash stripped) and prepends it, refusing by name on a duplicate version and
validating the whole file's shape + newest-first ordering before and after
it writes.
release.sh now runs it as its own step, between the CHANGELOG update and
the release commit, and stages releases/open-brainy.json into that commit.
The product engine's rail runs this identical script against its own
releases/brainy.json, unchanged — each repo's wall file lives beside the
CHANGELOG it derives from; there is no cross-repo step.
A --check mode validates a wall file's exact key set, field types, and
newest-first ordering with no duplicates, read-only. tests/unit/release/wall-entry.test.ts
covers derivation, prepend, duplicate refusal, and --check's shape/ordering
checks over temp copies — never the real files. --check also runs green
against both releases/open-brainy.json and releases/brainy.json as they
stand today.
2026-09-02 14:15:39 -07:00
} )
afterEach ( ( ) = > {
rmSync ( dir , { recursive : true , force : true } )
2026-09-02 14:51:33 -07:00
rmSync ( remoteDir , { recursive : true , force : true } )
rmSync ( cacheDir , { recursive : true , force : true } )
ci(release): mechanize the releases-wall entry — never hand-written again
Every release used to get its releases/open-brainy.json entry typed by hand
after the fact. scripts/wall-entry.mjs derives it from the CHANGELOG entry
release.sh just composed (headline = first bullet, items = every bullet,
hash stripped) and prepends it, refusing by name on a duplicate version and
validating the whole file's shape + newest-first ordering before and after
it writes.
release.sh now runs it as its own step, between the CHANGELOG update and
the release commit, and stages releases/open-brainy.json into that commit.
The product engine's rail runs this identical script against its own
releases/brainy.json, unchanged — each repo's wall file lives beside the
CHANGELOG it derives from; there is no cross-repo step.
A --check mode validates a wall file's exact key set, field types, and
newest-first ordering with no duplicates, read-only. tests/unit/release/wall-entry.test.ts
covers derivation, prepend, duplicate refusal, and --check's shape/ordering
checks over temp copies — never the real files. --check also runs green
against both releases/open-brainy.json and releases/brainy.json as they
stand today.
2026-09-02 14:15:39 -07:00
} )
2026-09-02 14:51:33 -07:00
describe ( 'wall-entry.mjs — generate + publish' , ( ) = > {
it ( 'derives headline from the first bullet and items from every bullet, hashes stripped, and pushes it to the remote' , ( ) = > {
seedRemote ( remoteDir , 'open-brainy' , [ BASE_ENTRY ] )
ci(release): mechanize the releases-wall entry — never hand-written again
Every release used to get its releases/open-brainy.json entry typed by hand
after the fact. scripts/wall-entry.mjs derives it from the CHANGELOG entry
release.sh just composed (headline = first bullet, items = every bullet,
hash stripped) and prepends it, refusing by name on a duplicate version and
validating the whole file's shape + newest-first ordering before and after
it writes.
release.sh now runs it as its own step, between the CHANGELOG update and
the release commit, and stages releases/open-brainy.json into that commit.
The product engine's rail runs this identical script against its own
releases/brainy.json, unchanged — each repo's wall file lives beside the
CHANGELOG it derives from; there is no cross-repo step.
A --check mode validates a wall file's exact key set, field types, and
newest-first ordering with no duplicates, read-only. tests/unit/release/wall-entry.test.ts
covers derivation, prepend, duplicate refusal, and --check's shape/ordering
checks over temp copies — never the real files. --check also runs green
against both releases/open-brainy.json and releases/brainy.json as they
stand today.
2026-09-02 14:15:39 -07:00
writeFileSync (
join ( dir , 'CHANGELOG.md' ) ,
buildChangelog ( [ { version : '10.4.12' , date : '2026-09-03' , bullets : [ 'fix(wall): mechanize the entry' , 'test(wall): pin the shape' ] } ] ) ,
)
const result = run (
2026-09-02 14:51:33 -07:00
[ '--product' , 'open-brainy' , '--version' , '10.4.12' , '--date' , '2026-09-03' , '--from-changelog' , 'CHANGELOG.md' , '--remote' , remoteDir , '--cache-dir' , cacheDir ] ,
ci(release): mechanize the releases-wall entry — never hand-written again
Every release used to get its releases/open-brainy.json entry typed by hand
after the fact. scripts/wall-entry.mjs derives it from the CHANGELOG entry
release.sh just composed (headline = first bullet, items = every bullet,
hash stripped) and prepends it, refusing by name on a duplicate version and
validating the whole file's shape + newest-first ordering before and after
it writes.
release.sh now runs it as its own step, between the CHANGELOG update and
the release commit, and stages releases/open-brainy.json into that commit.
The product engine's rail runs this identical script against its own
releases/brainy.json, unchanged — each repo's wall file lives beside the
CHANGELOG it derives from; there is no cross-repo step.
A --check mode validates a wall file's exact key set, field types, and
newest-first ordering with no duplicates, read-only. tests/unit/release/wall-entry.test.ts
covers derivation, prepend, duplicate refusal, and --check's shape/ordering
checks over temp copies — never the real files. --check also runs green
against both releases/open-brainy.json and releases/brainy.json as they
stand today.
2026-09-02 14:15:39 -07:00
dir ,
)
expect ( result . status ) . toBe ( 0 )
2026-09-02 14:51:33 -07:00
expect ( result . stdout ) . toMatch ( /wrote v10\.4\.12.*pushed/i )
ci(release): mechanize the releases-wall entry — never hand-written again
Every release used to get its releases/open-brainy.json entry typed by hand
after the fact. scripts/wall-entry.mjs derives it from the CHANGELOG entry
release.sh just composed (headline = first bullet, items = every bullet,
hash stripped) and prepends it, refusing by name on a duplicate version and
validating the whole file's shape + newest-first ordering before and after
it writes.
release.sh now runs it as its own step, between the CHANGELOG update and
the release commit, and stages releases/open-brainy.json into that commit.
The product engine's rail runs this identical script against its own
releases/brainy.json, unchanged — each repo's wall file lives beside the
CHANGELOG it derives from; there is no cross-repo step.
A --check mode validates a wall file's exact key set, field types, and
newest-first ordering with no duplicates, read-only. tests/unit/release/wall-entry.test.ts
covers derivation, prepend, duplicate refusal, and --check's shape/ordering
checks over temp copies — never the real files. --check also runs green
against both releases/open-brainy.json and releases/brainy.json as they
stand today.
2026-09-02 14:15:39 -07:00
2026-09-02 14:51:33 -07:00
const wall = readRemote ( remoteDir , 'open-brainy' )
ci(release): mechanize the releases-wall entry — never hand-written again
Every release used to get its releases/open-brainy.json entry typed by hand
after the fact. scripts/wall-entry.mjs derives it from the CHANGELOG entry
release.sh just composed (headline = first bullet, items = every bullet,
hash stripped) and prepends it, refusing by name on a duplicate version and
validating the whole file's shape + newest-first ordering before and after
it writes.
release.sh now runs it as its own step, between the CHANGELOG update and
the release commit, and stages releases/open-brainy.json into that commit.
The product engine's rail runs this identical script against its own
releases/brainy.json, unchanged — each repo's wall file lives beside the
CHANGELOG it derives from; there is no cross-repo step.
A --check mode validates a wall file's exact key set, field types, and
newest-first ordering with no duplicates, read-only. tests/unit/release/wall-entry.test.ts
covers derivation, prepend, duplicate refusal, and --check's shape/ordering
checks over temp copies — never the real files. --check also runs green
against both releases/open-brainy.json and releases/brainy.json as they
stand today.
2026-09-02 14:15:39 -07:00
expect ( wall . entries ) . toHaveLength ( 2 )
expect ( wall . entries [ 0 ] ) . toEqual ( {
version : '10.4.12' ,
date : '2026-09-03' ,
headline : 'fix(wall): mechanize the entry' ,
items : [ 'fix(wall): mechanize the entry' , 'test(wall): pin the shape' ] ,
url : 'https://source.soulcraft.com/soulcraftlabs/open-brainy/releases/tag/v10.4.12' ,
thumb : null ,
} )
// the older entry stays put, still second
expect ( wall . entries [ 1 ] . version ) . toBe ( '10.4.11' )
} )
it ( 'prepends newest-first — the new entry lands at index 0 ahead of every existing one' , ( ) = > {
2026-09-02 14:51:33 -07:00
seedRemote ( remoteDir , 'open-brainy' , [ BASE_ENTRY , { . . . BASE_ENTRY , version : '10.4.10' } ] )
writeFileSync ( join ( dir , 'CHANGELOG.md' ) , buildChangelog ( [ { version : '10.5.0' , date : '2026-09-03' , bullets : [ 'feat: ten five' ] } ] ) )
ci(release): mechanize the releases-wall entry — never hand-written again
Every release used to get its releases/open-brainy.json entry typed by hand
after the fact. scripts/wall-entry.mjs derives it from the CHANGELOG entry
release.sh just composed (headline = first bullet, items = every bullet,
hash stripped) and prepends it, refusing by name on a duplicate version and
validating the whole file's shape + newest-first ordering before and after
it writes.
release.sh now runs it as its own step, between the CHANGELOG update and
the release commit, and stages releases/open-brainy.json into that commit.
The product engine's rail runs this identical script against its own
releases/brainy.json, unchanged — each repo's wall file lives beside the
CHANGELOG it derives from; there is no cross-repo step.
A --check mode validates a wall file's exact key set, field types, and
newest-first ordering with no duplicates, read-only. tests/unit/release/wall-entry.test.ts
covers derivation, prepend, duplicate refusal, and --check's shape/ordering
checks over temp copies — never the real files. --check also runs green
against both releases/open-brainy.json and releases/brainy.json as they
stand today.
2026-09-02 14:15:39 -07:00
2026-09-02 14:51:33 -07:00
run ( [ '--product' , 'open-brainy' , '--version' , '10.5.0' , '--date' , '2026-09-03' , '--from-changelog' , 'CHANGELOG.md' , '--remote' , remoteDir , '--cache-dir' , cacheDir ] , dir )
ci(release): mechanize the releases-wall entry — never hand-written again
Every release used to get its releases/open-brainy.json entry typed by hand
after the fact. scripts/wall-entry.mjs derives it from the CHANGELOG entry
release.sh just composed (headline = first bullet, items = every bullet,
hash stripped) and prepends it, refusing by name on a duplicate version and
validating the whole file's shape + newest-first ordering before and after
it writes.
release.sh now runs it as its own step, between the CHANGELOG update and
the release commit, and stages releases/open-brainy.json into that commit.
The product engine's rail runs this identical script against its own
releases/brainy.json, unchanged — each repo's wall file lives beside the
CHANGELOG it derives from; there is no cross-repo step.
A --check mode validates a wall file's exact key set, field types, and
newest-first ordering with no duplicates, read-only. tests/unit/release/wall-entry.test.ts
covers derivation, prepend, duplicate refusal, and --check's shape/ordering
checks over temp copies — never the real files. --check also runs green
against both releases/open-brainy.json and releases/brainy.json as they
stand today.
2026-09-02 14:15:39 -07:00
2026-09-02 14:51:33 -07:00
const wall = readRemote ( remoteDir , 'open-brainy' )
ci(release): mechanize the releases-wall entry — never hand-written again
Every release used to get its releases/open-brainy.json entry typed by hand
after the fact. scripts/wall-entry.mjs derives it from the CHANGELOG entry
release.sh just composed (headline = first bullet, items = every bullet,
hash stripped) and prepends it, refusing by name on a duplicate version and
validating the whole file's shape + newest-first ordering before and after
it writes.
release.sh now runs it as its own step, between the CHANGELOG update and
the release commit, and stages releases/open-brainy.json into that commit.
The product engine's rail runs this identical script against its own
releases/brainy.json, unchanged — each repo's wall file lives beside the
CHANGELOG it derives from; there is no cross-repo step.
A --check mode validates a wall file's exact key set, field types, and
newest-first ordering with no duplicates, read-only. tests/unit/release/wall-entry.test.ts
covers derivation, prepend, duplicate refusal, and --check's shape/ordering
checks over temp copies — never the real files. --check also runs green
against both releases/open-brainy.json and releases/brainy.json as they
stand today.
2026-09-02 14:15:39 -07:00
expect ( wall . entries . map ( ( e : any ) = > e . version ) ) . toEqual ( [ '10.5.0' , '10.4.11' , '10.4.10' ] )
} )
2026-09-02 14:51:33 -07:00
it ( 'replaces an entry with the same version instead of duplicating it — idempotent re-runs' , ( ) = > {
seedRemote ( remoteDir , 'open-brainy' , [
{ . . . BASE_ENTRY , headline : 'stale headline, pre-fix' } ,
{ . . . BASE_ENTRY , version : '10.4.10' } ,
] )
writeFileSync ( join ( dir , 'CHANGELOG.md' ) , buildChangelog ( [ { version : '10.4.11' , date : '2026-09-02' , bullets : [ 'fix: the corrected headline' ] } ] ) )
const result = run (
[ '--product' , 'open-brainy' , '--version' , '10.4.11' , '--date' , '2026-09-02' , '--from-changelog' , 'CHANGELOG.md' , '--remote' , remoteDir , '--cache-dir' , cacheDir ] ,
dir ,
)
expect ( result . status ) . toBe ( 0 )
expect ( result . stdout ) . toMatch ( /replaced v10\.4\.11/i )
const wall = readRemote ( remoteDir , 'open-brainy' )
expect ( wall . entries ) . toHaveLength ( 2 ) // not 3 — replaced, not duplicated
expect ( wall . entries [ 0 ] . version ) . toBe ( '10.4.11' )
expect ( wall . entries [ 0 ] . headline ) . toBe ( 'fix: the corrected headline' )
expect ( wall . entries [ 1 ] . version ) . toBe ( '10.4.10' )
} )
it ( 'a re-run with byte-identical content commits nothing and still succeeds' , ( ) = > {
// headline always equals items[0] for a derived entry, so this fixture
// (unlike BASE_ENTRY, whose headline/items intentionally diverge for the
// shape-only tests below) has to keep the two in lockstep to ever roundtrip.
const stableEntry = { . . . BASE_ENTRY , headline : 'A faster open.' , items : [ 'A faster open.' ] }
seedRemote ( remoteDir , 'open-brainy' , [ stableEntry ] )
writeFileSync ( join ( dir , 'CHANGELOG.md' ) , buildChangelog ( [ { version : '10.4.11' , date : '2026-09-02' , bullets : [ 'A faster open.' ] } ] ) )
const before = readRemote ( remoteDir , 'open-brainy' )
const result = run (
[ '--product' , 'open-brainy' , '--version' , '10.4.11' , '--date' , '2026-09-02' , '--from-changelog' , 'CHANGELOG.md' , '--remote' , remoteDir , '--cache-dir' , cacheDir ] ,
dir ,
)
expect ( result . status ) . toBe ( 0 )
expect ( result . stdout ) . toMatch ( /nothing to commit/i )
expect ( readRemote ( remoteDir , 'open-brainy' ) ) . toEqual ( before )
} )
2026-09-02 14:59:49 -07:00
it ( 'derives the public package-page permalink for the product engine (private repo, never null)' , ( ) = > {
seedRemote ( remoteDir , 'brainy' , [ { . . . BASE_ENTRY , version : '11.0.5' , url : 'https://source.soulcraft.com/soulcraft/-/packages/npm/@soulcraft%2Fbrainy/11.0.5' } ] )
ci(release): mechanize the releases-wall entry — never hand-written again
Every release used to get its releases/open-brainy.json entry typed by hand
after the fact. scripts/wall-entry.mjs derives it from the CHANGELOG entry
release.sh just composed (headline = first bullet, items = every bullet,
hash stripped) and prepends it, refusing by name on a duplicate version and
validating the whole file's shape + newest-first ordering before and after
it writes.
release.sh now runs it as its own step, between the CHANGELOG update and
the release commit, and stages releases/open-brainy.json into that commit.
The product engine's rail runs this identical script against its own
releases/brainy.json, unchanged — each repo's wall file lives beside the
CHANGELOG it derives from; there is no cross-repo step.
A --check mode validates a wall file's exact key set, field types, and
newest-first ordering with no duplicates, read-only. tests/unit/release/wall-entry.test.ts
covers derivation, prepend, duplicate refusal, and --check's shape/ordering
checks over temp copies — never the real files. --check also runs green
against both releases/open-brainy.json and releases/brainy.json as they
stand today.
2026-09-02 14:15:39 -07:00
writeFileSync ( join ( dir , 'CHANGELOG.md' ) , buildChangelog ( [ { version : '11.0.6' , date : '2026-09-03' , bullets : [ 'fix: a native-only fix' ] } ] ) )
2026-09-02 14:51:33 -07:00
const result = run (
[ '--product' , 'brainy' , '--version' , '11.0.6' , '--date' , '2026-09-03' , '--from-changelog' , 'CHANGELOG.md' , '--remote' , remoteDir , '--cache-dir' , cacheDir ] ,
dir ,
)
expect ( result . status ) . toBe ( 0 )
ci(release): mechanize the releases-wall entry — never hand-written again
Every release used to get its releases/open-brainy.json entry typed by hand
after the fact. scripts/wall-entry.mjs derives it from the CHANGELOG entry
release.sh just composed (headline = first bullet, items = every bullet,
hash stripped) and prepends it, refusing by name on a duplicate version and
validating the whole file's shape + newest-first ordering before and after
it writes.
release.sh now runs it as its own step, between the CHANGELOG update and
the release commit, and stages releases/open-brainy.json into that commit.
The product engine's rail runs this identical script against its own
releases/brainy.json, unchanged — each repo's wall file lives beside the
CHANGELOG it derives from; there is no cross-repo step.
A --check mode validates a wall file's exact key set, field types, and
newest-first ordering with no duplicates, read-only. tests/unit/release/wall-entry.test.ts
covers derivation, prepend, duplicate refusal, and --check's shape/ordering
checks over temp copies — never the real files. --check also runs green
against both releases/open-brainy.json and releases/brainy.json as they
stand today.
2026-09-02 14:15:39 -07:00
2026-09-02 14:51:33 -07:00
const wall = readRemote ( remoteDir , 'brainy' )
2026-09-02 14:59:49 -07:00
expect ( wall . entries [ 0 ] . url ) . toBe ( 'https://source.soulcraft.com/soulcraft/-/packages/npm/@soulcraft%2Fbrainy/11.0.6' )
ci(release): mechanize the releases-wall entry — never hand-written again
Every release used to get its releases/open-brainy.json entry typed by hand
after the fact. scripts/wall-entry.mjs derives it from the CHANGELOG entry
release.sh just composed (headline = first bullet, items = every bullet,
hash stripped) and prepends it, refusing by name on a duplicate version and
validating the whole file's shape + newest-first ordering before and after
it writes.
release.sh now runs it as its own step, between the CHANGELOG update and
the release commit, and stages releases/open-brainy.json into that commit.
The product engine's rail runs this identical script against its own
releases/brainy.json, unchanged — each repo's wall file lives beside the
CHANGELOG it derives from; there is no cross-repo step.
A --check mode validates a wall file's exact key set, field types, and
newest-first ordering with no duplicates, read-only. tests/unit/release/wall-entry.test.ts
covers derivation, prepend, duplicate refusal, and --check's shape/ordering
checks over temp copies — never the real files. --check also runs green
against both releases/open-brainy.json and releases/brainy.json as they
stand today.
2026-09-02 14:15:39 -07:00
expect ( wall . entries [ 0 ] . thumb ) . toBeNull ( )
} )
2026-09-02 14:59:49 -07:00
it ( 'refuses a product with no permalink pattern, naming the cure' , ( ) = > {
seedRemote ( remoteDir , 'open-brainy' , [ BASE_ENTRY ] )
writeFileSync ( join ( dir , 'CHANGELOG.md' ) , buildChangelog ( [ { version : '1.0.0' , date : '2026-09-03' , bullets : [ 'feat: first' ] } ] ) )
const result = run ( [ '--product' , 'mystery' , '--version' , '1.0.0' , '--date' , '2026-09-03' , '--from-changelog' , 'CHANGELOG.md' , '--remote' , remoteDir , '--cache-dir' , cacheDir ] , dir )
expect ( result . status ) . not . toBe ( 0 )
expect ( result . stderr ) . toMatch ( /no permalink pattern for product "mystery"/ )
expect ( result . stderr ) . toMatch ( /never carry url: null/ )
} )
2026-09-02 14:51:33 -07:00
it ( 'refuses when the CHANGELOG has no entry yet for the target version, and touches no remote' , ( ) = > {
seedRemote ( remoteDir , 'open-brainy' , [ ] )
ci(release): mechanize the releases-wall entry — never hand-written again
Every release used to get its releases/open-brainy.json entry typed by hand
after the fact. scripts/wall-entry.mjs derives it from the CHANGELOG entry
release.sh just composed (headline = first bullet, items = every bullet,
hash stripped) and prepends it, refusing by name on a duplicate version and
validating the whole file's shape + newest-first ordering before and after
it writes.
release.sh now runs it as its own step, between the CHANGELOG update and
the release commit, and stages releases/open-brainy.json into that commit.
The product engine's rail runs this identical script against its own
releases/brainy.json, unchanged — each repo's wall file lives beside the
CHANGELOG it derives from; there is no cross-repo step.
A --check mode validates a wall file's exact key set, field types, and
newest-first ordering with no duplicates, read-only. tests/unit/release/wall-entry.test.ts
covers derivation, prepend, duplicate refusal, and --check's shape/ordering
checks over temp copies — never the real files. --check also runs green
against both releases/open-brainy.json and releases/brainy.json as they
stand today.
2026-09-02 14:15:39 -07:00
writeFileSync ( join ( dir , 'CHANGELOG.md' ) , buildChangelog ( [ { version : '10.4.11' , date : '2026-09-02' , bullets : [ 'fix: whatever' ] } ] ) )
2026-09-02 14:51:33 -07:00
const beforeSha = git ( [ 'rev-parse' , 'main' ] , remoteDir )
ci(release): mechanize the releases-wall entry — never hand-written again
Every release used to get its releases/open-brainy.json entry typed by hand
after the fact. scripts/wall-entry.mjs derives it from the CHANGELOG entry
release.sh just composed (headline = first bullet, items = every bullet,
hash stripped) and prepends it, refusing by name on a duplicate version and
validating the whole file's shape + newest-first ordering before and after
it writes.
release.sh now runs it as its own step, between the CHANGELOG update and
the release commit, and stages releases/open-brainy.json into that commit.
The product engine's rail runs this identical script against its own
releases/brainy.json, unchanged — each repo's wall file lives beside the
CHANGELOG it derives from; there is no cross-repo step.
A --check mode validates a wall file's exact key set, field types, and
newest-first ordering with no duplicates, read-only. tests/unit/release/wall-entry.test.ts
covers derivation, prepend, duplicate refusal, and --check's shape/ordering
checks over temp copies — never the real files. --check also runs green
against both releases/open-brainy.json and releases/brainy.json as they
stand today.
2026-09-02 14:15:39 -07:00
const result = run (
2026-09-02 14:51:33 -07:00
[ '--product' , 'open-brainy' , '--version' , '99.0.0' , '--date' , '2026-09-02' , '--from-changelog' , 'CHANGELOG.md' , '--remote' , remoteDir , '--cache-dir' , cacheDir ] ,
ci(release): mechanize the releases-wall entry — never hand-written again
Every release used to get its releases/open-brainy.json entry typed by hand
after the fact. scripts/wall-entry.mjs derives it from the CHANGELOG entry
release.sh just composed (headline = first bullet, items = every bullet,
hash stripped) and prepends it, refusing by name on a duplicate version and
validating the whole file's shape + newest-first ordering before and after
it writes.
release.sh now runs it as its own step, between the CHANGELOG update and
the release commit, and stages releases/open-brainy.json into that commit.
The product engine's rail runs this identical script against its own
releases/brainy.json, unchanged — each repo's wall file lives beside the
CHANGELOG it derives from; there is no cross-repo step.
A --check mode validates a wall file's exact key set, field types, and
newest-first ordering with no duplicates, read-only. tests/unit/release/wall-entry.test.ts
covers derivation, prepend, duplicate refusal, and --check's shape/ordering
checks over temp copies — never the real files. --check also runs green
against both releases/open-brainy.json and releases/brainy.json as they
stand today.
2026-09-02 14:15:39 -07:00
dir ,
)
expect ( result . status ) . toBe ( 1 )
2026-09-02 14:51:33 -07:00
expect ( result . stderr ) . toMatch ( /no CHANGELOG entry yet/i )
expect ( git ( [ 'rev-parse' , 'main' ] , remoteDir ) ) . toBe ( beforeSha )
ci(release): mechanize the releases-wall entry — never hand-written again
Every release used to get its releases/open-brainy.json entry typed by hand
after the fact. scripts/wall-entry.mjs derives it from the CHANGELOG entry
release.sh just composed (headline = first bullet, items = every bullet,
hash stripped) and prepends it, refusing by name on a duplicate version and
validating the whole file's shape + newest-first ordering before and after
it writes.
release.sh now runs it as its own step, between the CHANGELOG update and
the release commit, and stages releases/open-brainy.json into that commit.
The product engine's rail runs this identical script against its own
releases/brainy.json, unchanged — each repo's wall file lives beside the
CHANGELOG it derives from; there is no cross-repo step.
A --check mode validates a wall file's exact key set, field types, and
newest-first ordering with no duplicates, read-only. tests/unit/release/wall-entry.test.ts
covers derivation, prepend, duplicate refusal, and --check's shape/ordering
checks over temp copies — never the real files. --check also runs green
against both releases/open-brainy.json and releases/brainy.json as they
stand today.
2026-09-02 14:15:39 -07:00
} )
2026-09-02 14:51:33 -07:00
it ( 'refuses by naming the cure when the remote cannot be cloned' , ( ) = > {
writeFileSync ( join ( dir , 'CHANGELOG.md' ) , buildChangelog ( [ { version : '10.4.12' , date : '2026-09-03' , bullets : [ 'fix: whatever' ] } ] ) )
const noSuchRemote = join ( tmpdir ( ) , 'wall-remote-does-not-exist-' + Date . now ( ) )
ci(release): mechanize the releases-wall entry — never hand-written again
Every release used to get its releases/open-brainy.json entry typed by hand
after the fact. scripts/wall-entry.mjs derives it from the CHANGELOG entry
release.sh just composed (headline = first bullet, items = every bullet,
hash stripped) and prepends it, refusing by name on a duplicate version and
validating the whole file's shape + newest-first ordering before and after
it writes.
release.sh now runs it as its own step, between the CHANGELOG update and
the release commit, and stages releases/open-brainy.json into that commit.
The product engine's rail runs this identical script against its own
releases/brainy.json, unchanged — each repo's wall file lives beside the
CHANGELOG it derives from; there is no cross-repo step.
A --check mode validates a wall file's exact key set, field types, and
newest-first ordering with no duplicates, read-only. tests/unit/release/wall-entry.test.ts
covers derivation, prepend, duplicate refusal, and --check's shape/ordering
checks over temp copies — never the real files. --check also runs green
against both releases/open-brainy.json and releases/brainy.json as they
stand today.
2026-09-02 14:15:39 -07:00
const result = run (
2026-09-02 14:51:33 -07:00
[ '--product' , 'open-brainy' , '--version' , '10.4.12' , '--date' , '2026-09-03' , '--from-changelog' , 'CHANGELOG.md' , '--remote' , noSuchRemote , '--cache-dir' , cacheDir ] ,
ci(release): mechanize the releases-wall entry — never hand-written again
Every release used to get its releases/open-brainy.json entry typed by hand
after the fact. scripts/wall-entry.mjs derives it from the CHANGELOG entry
release.sh just composed (headline = first bullet, items = every bullet,
hash stripped) and prepends it, refusing by name on a duplicate version and
validating the whole file's shape + newest-first ordering before and after
it writes.
release.sh now runs it as its own step, between the CHANGELOG update and
the release commit, and stages releases/open-brainy.json into that commit.
The product engine's rail runs this identical script against its own
releases/brainy.json, unchanged — each repo's wall file lives beside the
CHANGELOG it derives from; there is no cross-repo step.
A --check mode validates a wall file's exact key set, field types, and
newest-first ordering with no duplicates, read-only. tests/unit/release/wall-entry.test.ts
covers derivation, prepend, duplicate refusal, and --check's shape/ordering
checks over temp copies — never the real files. --check also runs green
against both releases/open-brainy.json and releases/brainy.json as they
stand today.
2026-09-02 14:15:39 -07:00
dir ,
)
expect ( result . status ) . toBe ( 1 )
2026-09-02 14:51:33 -07:00
expect ( result . stderr ) . toMatch ( /cannot clone/i )
expect ( result . stderr ) . toMatch ( /cure:/i )
} )
it ( 'refuses by naming the cure, and touches no remote, when the fetched wall fails shape validation' , ( ) = > {
const seedDir = mkdtempSync ( join ( tmpdir ( ) , 'wall-seed-broken-' ) )
execFileSync ( 'git' , [ 'clone' , remoteDir , seedDir ] , { stdio : 'ignore' } )
git ( [ 'config' , 'user.email' , 'seed@example.com' ] , seedDir )
git ( [ 'config' , 'user.name' , 'Seed' ] , seedDir )
writeFileSync (
join ( seedDir , 'open-brainy.json' ) ,
JSON . stringify ( { product : 'open-brainy' , entries : [ { version : '10.4.11' , date : '2026-09-02' , items : [ 'x' ] , url : null } ] } , null , 2 ) ,
)
git ( [ 'add' , 'open-brainy.json' ] , seedDir )
git ( [ 'commit' , '-m' , 'seed broken' ] , seedDir )
git ( [ 'push' , 'origin' , 'main' ] , seedDir )
rmSync ( seedDir , { recursive : true , force : true } )
const beforeSha = git ( [ 'rev-parse' , 'main' ] , remoteDir )
writeFileSync ( join ( dir , 'CHANGELOG.md' ) , buildChangelog ( [ { version : '10.4.12' , date : '2026-09-03' , bullets : [ 'fix: whatever' ] } ] ) )
const result = run (
[ '--product' , 'open-brainy' , '--version' , '10.4.12' , '--date' , '2026-09-03' , '--from-changelog' , 'CHANGELOG.md' , '--remote' , remoteDir , '--cache-dir' , cacheDir ] ,
dir ,
)
expect ( result . status ) . toBe ( 1 )
expect ( result . stderr ) . toMatch ( /fails shape validation/i )
expect ( result . stderr ) . toMatch ( /missing key\(s\) headline/i )
expect ( git ( [ 'rev-parse' , 'main' ] , remoteDir ) ) . toBe ( beforeSha )
} )
it ( 'refuses by naming the cure when the remote rejects the push (stands in for a raced non-fast-forward)' , ( ) = > {
seedRemote ( remoteDir , 'open-brainy' , [ BASE_ENTRY ] )
makeRemoteRejectPushes ( remoteDir )
writeFileSync ( join ( dir , 'CHANGELOG.md' ) , buildChangelog ( [ { version : '10.4.12' , date : '2026-09-03' , bullets : [ 'fix: whatever' ] } ] ) )
const result = run (
[ '--product' , 'open-brainy' , '--version' , '10.4.12' , '--date' , '2026-09-03' , '--from-changelog' , 'CHANGELOG.md' , '--remote' , remoteDir , '--cache-dir' , cacheDir ] ,
dir ,
)
expect ( result . status ) . toBe ( 1 )
expect ( result . stderr ) . toMatch ( /push to .* failed/i )
expect ( result . stderr ) . toMatch ( /cure:/i )
ci(release): mechanize the releases-wall entry — never hand-written again
Every release used to get its releases/open-brainy.json entry typed by hand
after the fact. scripts/wall-entry.mjs derives it from the CHANGELOG entry
release.sh just composed (headline = first bullet, items = every bullet,
hash stripped) and prepends it, refusing by name on a duplicate version and
validating the whole file's shape + newest-first ordering before and after
it writes.
release.sh now runs it as its own step, between the CHANGELOG update and
the release commit, and stages releases/open-brainy.json into that commit.
The product engine's rail runs this identical script against its own
releases/brainy.json, unchanged — each repo's wall file lives beside the
CHANGELOG it derives from; there is no cross-repo step.
A --check mode validates a wall file's exact key set, field types, and
newest-first ordering with no duplicates, read-only. tests/unit/release/wall-entry.test.ts
covers derivation, prepend, duplicate refusal, and --check's shape/ordering
checks over temp copies — never the real files. --check also runs green
against both releases/open-brainy.json and releases/brainy.json as they
stand today.
2026-09-02 14:15:39 -07:00
} )
2026-09-02 14:51:33 -07:00
it ( 'refuses a cross-product write when the file\'s "product" field does not match --product' , ( ) = > {
seedRemote ( remoteDir , 'open-brainy' , [ BASE_ENTRY ] )
const seedDir = mkdtempSync ( join ( tmpdir ( ) , 'wall-seed-mismatch-' ) )
execFileSync ( 'git' , [ 'clone' , remoteDir , seedDir ] , { stdio : 'ignore' } )
git ( [ 'config' , 'user.email' , 'seed@example.com' ] , seedDir )
git ( [ 'config' , 'user.name' , 'Seed' ] , seedDir )
const corrupted = JSON . parse ( readFileSync ( join ( seedDir , 'open-brainy.json' ) , 'utf8' ) )
corrupted . product = 'brainy'
writeFileSync ( join ( seedDir , 'open-brainy.json' ) , JSON . stringify ( corrupted , null , 2 ) + '\n' )
git ( [ 'add' , 'open-brainy.json' ] , seedDir )
git ( [ 'commit' , '-m' , 'corrupt product field' ] , seedDir )
git ( [ 'push' , 'origin' , 'main' ] , seedDir )
rmSync ( seedDir , { recursive : true , force : true } )
ci(release): mechanize the releases-wall entry — never hand-written again
Every release used to get its releases/open-brainy.json entry typed by hand
after the fact. scripts/wall-entry.mjs derives it from the CHANGELOG entry
release.sh just composed (headline = first bullet, items = every bullet,
hash stripped) and prepends it, refusing by name on a duplicate version and
validating the whole file's shape + newest-first ordering before and after
it writes.
release.sh now runs it as its own step, between the CHANGELOG update and
the release commit, and stages releases/open-brainy.json into that commit.
The product engine's rail runs this identical script against its own
releases/brainy.json, unchanged — each repo's wall file lives beside the
CHANGELOG it derives from; there is no cross-repo step.
A --check mode validates a wall file's exact key set, field types, and
newest-first ordering with no duplicates, read-only. tests/unit/release/wall-entry.test.ts
covers derivation, prepend, duplicate refusal, and --check's shape/ordering
checks over temp copies — never the real files. --check also runs green
against both releases/open-brainy.json and releases/brainy.json as they
stand today.
2026-09-02 14:15:39 -07:00
writeFileSync ( join ( dir , 'CHANGELOG.md' ) , buildChangelog ( [ { version : '1.0.0' , date : '2026-09-03' , bullets : [ 'fix: wrong repo' ] } ] ) )
const result = run (
2026-09-02 14:51:33 -07:00
[ '--product' , 'open-brainy' , '--version' , '1.0.0' , '--date' , '2026-09-03' , '--from-changelog' , 'CHANGELOG.md' , '--remote' , remoteDir , '--cache-dir' , cacheDir ] ,
ci(release): mechanize the releases-wall entry — never hand-written again
Every release used to get its releases/open-brainy.json entry typed by hand
after the fact. scripts/wall-entry.mjs derives it from the CHANGELOG entry
release.sh just composed (headline = first bullet, items = every bullet,
hash stripped) and prepends it, refusing by name on a duplicate version and
validating the whole file's shape + newest-first ordering before and after
it writes.
release.sh now runs it as its own step, between the CHANGELOG update and
the release commit, and stages releases/open-brainy.json into that commit.
The product engine's rail runs this identical script against its own
releases/brainy.json, unchanged — each repo's wall file lives beside the
CHANGELOG it derives from; there is no cross-repo step.
A --check mode validates a wall file's exact key set, field types, and
newest-first ordering with no duplicates, read-only. tests/unit/release/wall-entry.test.ts
covers derivation, prepend, duplicate refusal, and --check's shape/ordering
checks over temp copies — never the real files. --check also runs green
against both releases/open-brainy.json and releases/brainy.json as they
stand today.
2026-09-02 14:15:39 -07:00
dir ,
)
expect ( result . status ) . toBe ( 1 )
2026-09-02 14:51:33 -07:00
expect ( result . stderr ) . toMatch ( /product "brainy".*--product "open-brainy"/i )
} )
} )
describe ( 'wall-entry.mjs — --dry-run' , ( ) = > {
it ( 'prints the entry and the target path, and touches neither the cache dir nor the remote' , ( ) = > {
seedRemote ( remoteDir , 'open-brainy' , [ BASE_ENTRY ] )
writeFileSync ( join ( dir , 'CHANGELOG.md' ) , buildChangelog ( [ { version : '10.4.12' , date : '2026-09-03' , bullets : [ 'fix: a dry run' ] } ] ) )
const beforeSha = git ( [ 'rev-parse' , 'main' ] , remoteDir )
const result = run (
[ '--dry-run' , '--product' , 'open-brainy' , '--version' , '10.4.12' , '--date' , '2026-09-03' , '--from-changelog' , 'CHANGELOG.md' , '--remote' , remoteDir , '--cache-dir' , cacheDir ] ,
dir ,
)
expect ( result . status ) . toBe ( 0 )
expect ( result . stdout ) . toMatch ( /would write to/i )
expect ( result . stdout ) . toMatch ( /"version": "10\.4\.12"/ )
expect ( git ( [ 'rev-parse' , 'main' ] , remoteDir ) ) . toBe ( beforeSha )
ci(release): mechanize the releases-wall entry — never hand-written again
Every release used to get its releases/open-brainy.json entry typed by hand
after the fact. scripts/wall-entry.mjs derives it from the CHANGELOG entry
release.sh just composed (headline = first bullet, items = every bullet,
hash stripped) and prepends it, refusing by name on a duplicate version and
validating the whole file's shape + newest-first ordering before and after
it writes.
release.sh now runs it as its own step, between the CHANGELOG update and
the release commit, and stages releases/open-brainy.json into that commit.
The product engine's rail runs this identical script against its own
releases/brainy.json, unchanged — each repo's wall file lives beside the
CHANGELOG it derives from; there is no cross-repo step.
A --check mode validates a wall file's exact key set, field types, and
newest-first ordering with no duplicates, read-only. tests/unit/release/wall-entry.test.ts
covers derivation, prepend, duplicate refusal, and --check's shape/ordering
checks over temp copies — never the real files. --check also runs green
against both releases/open-brainy.json and releases/brainy.json as they
stand today.
2026-09-02 14:15:39 -07:00
} )
} )
describe ( 'wall-entry.mjs — --check' , ( ) = > {
it ( 'passes a well-formed, newest-first file with no duplicates' , ( ) = > {
writeFileSync ( join ( dir , 'wall.json' ) , wallFile ( 'open-brainy' , [ BASE_ENTRY , { . . . BASE_ENTRY , version : '10.4.10' } ] ) )
const result = run ( [ '--check' , '--file' , 'wall.json' ] , dir )
expect ( result . status ) . toBe ( 0 )
expect ( result . stdout ) . toMatch ( /OK/ )
} )
2026-09-02 14:51:33 -07:00
it ( 'passes a file where "thumb" is entirely absent (optional per the HQ contract)' , ( ) = > {
const { thumb , . . . noThumb } = BASE_ENTRY as any
writeFileSync ( join ( dir , 'wall.json' ) , wallFile ( 'open-brainy' , [ noThumb ] ) )
const result = run ( [ '--check' , '--file' , 'wall.json' ] , dir )
expect ( result . status ) . toBe ( 0 )
} )
ci(release): mechanize the releases-wall entry — never hand-written again
Every release used to get its releases/open-brainy.json entry typed by hand
after the fact. scripts/wall-entry.mjs derives it from the CHANGELOG entry
release.sh just composed (headline = first bullet, items = every bullet,
hash stripped) and prepends it, refusing by name on a duplicate version and
validating the whole file's shape + newest-first ordering before and after
it writes.
release.sh now runs it as its own step, between the CHANGELOG update and
the release commit, and stages releases/open-brainy.json into that commit.
The product engine's rail runs this identical script against its own
releases/brainy.json, unchanged — each repo's wall file lives beside the
CHANGELOG it derives from; there is no cross-repo step.
A --check mode validates a wall file's exact key set, field types, and
newest-first ordering with no duplicates, read-only. tests/unit/release/wall-entry.test.ts
covers derivation, prepend, duplicate refusal, and --check's shape/ordering
checks over temp copies — never the real files. --check also runs green
against both releases/open-brainy.json and releases/brainy.json as they
stand today.
2026-09-02 14:15:39 -07:00
it ( 'catches a missing entry key' , ( ) = > {
2026-09-02 14:51:33 -07:00
const broken = { version : '1.0.0' , date : '2026-09-03' , headline : 'h' , items : [ 'i' ] } // no "url"
ci(release): mechanize the releases-wall entry — never hand-written again
Every release used to get its releases/open-brainy.json entry typed by hand
after the fact. scripts/wall-entry.mjs derives it from the CHANGELOG entry
release.sh just composed (headline = first bullet, items = every bullet,
hash stripped) and prepends it, refusing by name on a duplicate version and
validating the whole file's shape + newest-first ordering before and after
it writes.
release.sh now runs it as its own step, between the CHANGELOG update and
the release commit, and stages releases/open-brainy.json into that commit.
The product engine's rail runs this identical script against its own
releases/brainy.json, unchanged — each repo's wall file lives beside the
CHANGELOG it derives from; there is no cross-repo step.
A --check mode validates a wall file's exact key set, field types, and
newest-first ordering with no duplicates, read-only. tests/unit/release/wall-entry.test.ts
covers derivation, prepend, duplicate refusal, and --check's shape/ordering
checks over temp copies — never the real files. --check also runs green
against both releases/open-brainy.json and releases/brainy.json as they
stand today.
2026-09-02 14:15:39 -07:00
writeFileSync ( join ( dir , 'wall.json' ) , wallFile ( 'open-brainy' , [ broken ] ) )
const result = run ( [ '--check' , '--file' , 'wall.json' ] , dir )
expect ( result . status ) . toBe ( 1 )
2026-09-02 14:51:33 -07:00
expect ( result . stderr ) . toMatch ( /missing key\(s\) url/ )
ci(release): mechanize the releases-wall entry — never hand-written again
Every release used to get its releases/open-brainy.json entry typed by hand
after the fact. scripts/wall-entry.mjs derives it from the CHANGELOG entry
release.sh just composed (headline = first bullet, items = every bullet,
hash stripped) and prepends it, refusing by name on a duplicate version and
validating the whole file's shape + newest-first ordering before and after
it writes.
release.sh now runs it as its own step, between the CHANGELOG update and
the release commit, and stages releases/open-brainy.json into that commit.
The product engine's rail runs this identical script against its own
releases/brainy.json, unchanged — each repo's wall file lives beside the
CHANGELOG it derives from; there is no cross-repo step.
A --check mode validates a wall file's exact key set, field types, and
newest-first ordering with no duplicates, read-only. tests/unit/release/wall-entry.test.ts
covers derivation, prepend, duplicate refusal, and --check's shape/ordering
checks over temp copies — never the real files. --check also runs green
against both releases/open-brainy.json and releases/brainy.json as they
stand today.
2026-09-02 14:15:39 -07:00
} )
2026-09-02 14:51:33 -07:00
it ( 'catches an unexpected top-level key (e.g. the retired "history" field)' , ( ) = > {
ci(release): mechanize the releases-wall entry — never hand-written again
Every release used to get its releases/open-brainy.json entry typed by hand
after the fact. scripts/wall-entry.mjs derives it from the CHANGELOG entry
release.sh just composed (headline = first bullet, items = every bullet,
hash stripped) and prepends it, refusing by name on a duplicate version and
validating the whole file's shape + newest-first ordering before and after
it writes.
release.sh now runs it as its own step, between the CHANGELOG update and
the release commit, and stages releases/open-brainy.json into that commit.
The product engine's rail runs this identical script against its own
releases/brainy.json, unchanged — each repo's wall file lives beside the
CHANGELOG it derives from; there is no cross-repo step.
A --check mode validates a wall file's exact key set, field types, and
newest-first ordering with no duplicates, read-only. tests/unit/release/wall-entry.test.ts
covers derivation, prepend, duplicate refusal, and --check's shape/ordering
checks over temp copies — never the real files. --check also runs green
against both releases/open-brainy.json and releases/brainy.json as they
stand today.
2026-09-02 14:15:39 -07:00
const raw = JSON . parse ( wallFile ( 'open-brainy' , [ BASE_ENTRY ] ) )
2026-09-02 14:51:33 -07:00
raw . history = 'retired field'
ci(release): mechanize the releases-wall entry — never hand-written again
Every release used to get its releases/open-brainy.json entry typed by hand
after the fact. scripts/wall-entry.mjs derives it from the CHANGELOG entry
release.sh just composed (headline = first bullet, items = every bullet,
hash stripped) and prepends it, refusing by name on a duplicate version and
validating the whole file's shape + newest-first ordering before and after
it writes.
release.sh now runs it as its own step, between the CHANGELOG update and
the release commit, and stages releases/open-brainy.json into that commit.
The product engine's rail runs this identical script against its own
releases/brainy.json, unchanged — each repo's wall file lives beside the
CHANGELOG it derives from; there is no cross-repo step.
A --check mode validates a wall file's exact key set, field types, and
newest-first ordering with no duplicates, read-only. tests/unit/release/wall-entry.test.ts
covers derivation, prepend, duplicate refusal, and --check's shape/ordering
checks over temp copies — never the real files. --check also runs green
against both releases/open-brainy.json and releases/brainy.json as they
stand today.
2026-09-02 14:15:39 -07:00
writeFileSync ( join ( dir , 'wall.json' ) , JSON . stringify ( raw ) )
const result = run ( [ '--check' , '--file' , 'wall.json' ] , dir )
expect ( result . status ) . toBe ( 1 )
2026-09-02 14:51:33 -07:00
expect ( result . stderr ) . toMatch ( /unexpected key\(s\) history/ )
ci(release): mechanize the releases-wall entry — never hand-written again
Every release used to get its releases/open-brainy.json entry typed by hand
after the fact. scripts/wall-entry.mjs derives it from the CHANGELOG entry
release.sh just composed (headline = first bullet, items = every bullet,
hash stripped) and prepends it, refusing by name on a duplicate version and
validating the whole file's shape + newest-first ordering before and after
it writes.
release.sh now runs it as its own step, between the CHANGELOG update and
the release commit, and stages releases/open-brainy.json into that commit.
The product engine's rail runs this identical script against its own
releases/brainy.json, unchanged — each repo's wall file lives beside the
CHANGELOG it derives from; there is no cross-repo step.
A --check mode validates a wall file's exact key set, field types, and
newest-first ordering with no duplicates, read-only. tests/unit/release/wall-entry.test.ts
covers derivation, prepend, duplicate refusal, and --check's shape/ordering
checks over temp copies — never the real files. --check also runs green
against both releases/open-brainy.json and releases/brainy.json as they
stand today.
2026-09-02 14:15:39 -07:00
} )
it ( 'catches entries that are not newest-first' , ( ) = > {
writeFileSync ( join ( dir , 'wall.json' ) , wallFile ( 'open-brainy' , [ { . . . BASE_ENTRY , version : '10.4.10' } , BASE_ENTRY ] ) )
const result = run ( [ '--check' , '--file' , 'wall.json' ] , dir )
expect ( result . status ) . toBe ( 1 )
expect ( result . stderr ) . toMatch ( /not newest-first/ )
} )
it ( 'catches a duplicate version even with identical entries' , ( ) = > {
writeFileSync ( join ( dir , 'wall.json' ) , wallFile ( 'open-brainy' , [ BASE_ENTRY , { . . . BASE_ENTRY } ] ) )
const result = run ( [ '--check' , '--file' , 'wall.json' ] , dir )
expect ( result . status ) . toBe ( 1 )
expect ( result . stderr ) . toMatch ( /duplicate version 10\.4\.11/ )
} )
it ( 'catches an empty items array' , ( ) = > {
writeFileSync ( join ( dir , 'wall.json' ) , wallFile ( 'open-brainy' , [ { . . . BASE_ENTRY , items : [ ] } ] ) )
const result = run ( [ '--check' , '--file' , 'wall.json' ] , dir )
expect ( result . status ) . toBe ( 1 )
expect ( result . stderr ) . toMatch ( /"items" must be a non-empty array/ )
} )
it ( 'catches a malformed date' , ( ) = > {
writeFileSync ( join ( dir , 'wall.json' ) , wallFile ( 'open-brainy' , [ { . . . BASE_ENTRY , date : '09/03/2026' } ] ) )
const result = run ( [ '--check' , '--file' , 'wall.json' ] , dir )
expect ( result . status ) . toBe ( 1 )
expect ( result . stderr ) . toMatch ( /"date" must be a YYYY-MM-DD string/ )
} )
} )