@@ -13,9 +13,52 @@ import {
1313 findReleaseAs ,
1414 hasBreakingChangeBody ,
1515 hasNonTestChanges ,
16+ parseChangedFiles ,
1617 parseSemver ,
18+ run ,
1719} from "./version.ts" ;
1820
21+ const makeCliDeps = ( options : {
22+ env ?: Record < string , string | undefined > ;
23+ files ?: Record < string , string > ;
24+ commands ?: Record < string , string | Error > ;
25+ now ?: Date ;
26+ } ) => {
27+ const outputs : string [ ] = [ ] ;
28+ const logs : string [ ] = [ ] ;
29+ const calls : string [ ] = [ ] ;
30+
31+ return {
32+ deps : {
33+ cmd : ( ...command : string [ ] ) => {
34+ const key = command . join ( " " ) ;
35+ calls . push ( key ) ;
36+ const result = options . commands ?. [ key ] ;
37+ if ( result instanceof Error ) return Promise . reject ( result ) ;
38+ return Promise . resolve ( result ?? "" ) ;
39+ } ,
40+ readTextFile : ( filePath : string ) => {
41+ const result = options . files ?. [ filePath ] ;
42+ if ( result === undefined ) {
43+ return Promise . reject ( new Error ( `ENOENT: ${ filePath } ` ) ) ;
44+ }
45+ return Promise . resolve ( result ) ;
46+ } ,
47+ envGet : ( name : string ) => options . env ?. [ name ] ,
48+ appendFile : ( _filePath : string , text : string ) => {
49+ outputs . push ( text ) ;
50+ } ,
51+ log : ( message : string ) => {
52+ logs . push ( message ) ;
53+ } ,
54+ now : ( ) => options . now ?? new Date ( "2026-02-12T09:14:29Z" ) ,
55+ } ,
56+ outputs,
57+ logs,
58+ calls,
59+ } ;
60+ } ;
61+
1962describe ( "analyzeCommits" , ( ) => {
2063 it ( "returns 'none' for empty array" , ( ) => {
2164 assertEquals ( analyzeCommits ( [ ] ) , "none" ) ;
@@ -339,6 +382,15 @@ describe("hasNonTestChanges", () => {
339382 } ) ;
340383} ) ;
341384
385+ describe ( "parseChangedFiles" , ( ) => {
386+ it ( "returns unique trimmed changed paths" , ( ) => {
387+ assertEquals (
388+ parseChangedFiles ( "src/mod.ts\n\nsrc/mod.ts\n src/util.ts \n" ) ,
389+ [ "src/mod.ts" , "src/util.ts" ] ,
390+ ) ;
391+ } ) ;
392+ } ) ;
393+
342394describe ( "calculateVersion" , ( ) => {
343395 const baseOpts = {
344396 currentVersion : "1.0.0" ,
@@ -582,6 +634,30 @@ describe("calculateVersion", () => {
582634 tag : "canary" ,
583635 } ) ;
584636 } ) ;
637+
638+ it ( "skips push release when no git tags and only test files changed" , ( ) => {
639+ const result = calculateVersion ( {
640+ ...baseOpts ,
641+ currentVersion : "0.0.0" ,
642+ subjects : [ "chore: initial" ] ,
643+ changedFiles : [ "src/foo.test.ts" , ".github/scripts/version.test.ts" ] ,
644+ noGitTags : true ,
645+ eventName : "push" ,
646+ } ) ;
647+ assertEquals ( result , { skip : true } ) ;
648+ } ) ;
649+
650+ it ( "skips canary publish when no git tags and only test files changed" , ( ) => {
651+ const result = calculateVersion ( {
652+ ...baseOpts ,
653+ currentVersion : "0.0.0" ,
654+ subjects : [ "docs: update" ] ,
655+ changedFiles : [ "src/foo.test.ts" ] ,
656+ noGitTags : true ,
657+ eventName : "pull_request" ,
658+ } ) ;
659+ assertEquals ( result , { skip : true } ) ;
660+ } ) ;
585661 } ) ;
586662
587663 describe ( "edge cases" , ( ) => {
@@ -688,3 +764,131 @@ describe("calculateVersion", () => {
688764 } ) ;
689765 } ) ;
690766} ) ;
767+
768+ describe ( "run" , ( ) => {
769+ it ( "writes release outputs for the git-tag CLI path used by GitHub Actions" , async ( ) => {
770+ const cli = makeCliDeps ( {
771+ env : {
772+ GITHUB_EVENT_NAME : "push" ,
773+ GITHUB_OUTPUT : "/tmp/github-output" ,
774+ COMMIT_SHA : "override-sha-1234567" ,
775+ } ,
776+ files : {
777+ "deno.json" : JSON . stringify ( { name : "opencode-graphiti" } ) ,
778+ } ,
779+ commands : {
780+ "git describe --tags --abbrev=0 --match v*" : "v1.2.3" ,
781+ "git log v1.2.3..HEAD --format=%s" : "feat: ship cli coverage" ,
782+ "git log v1.2.3..HEAD --format=%b" : "" ,
783+ "git diff --name-only v1.2.3..HEAD" : ".github/scripts/version.ts\n" ,
784+ } ,
785+ } ) ;
786+
787+ await run ( [ ] , cli . deps ) ;
788+
789+ assertEquals ( cli . outputs , [ "version=1.3.0\n" , "tag=latest\n" ] ) ;
790+ assertEquals ( cli . logs , [
791+ "version=1.3.0" ,
792+ "tag=latest" ,
793+ "Release version: 1.3.0" ,
794+ ] ) ;
795+ assertEquals (
796+ cli . calls . includes ( "git describe --tags --abbrev=0 --match v*" ) ,
797+ true ,
798+ ) ;
799+ } ) ;
800+
801+ it ( "covers the no-tag fallback path, package discovery, args fallback, and canary output" , async ( ) => {
802+ const cli = makeCliDeps ( {
803+ env : {
804+ GITHUB_OUTPUT : "/tmp/github-output" ,
805+ } ,
806+ files : {
807+ "package.json" : JSON . stringify ( { name : "fallback-package" } ) ,
808+ } ,
809+ commands : {
810+ "git describe --tags --abbrev=0 --match v*" : new Error ( "no tags" ) ,
811+ "npm view fallback-package version" : "0.1.0" ,
812+ "git log --format=%s" : "docs: note fallback behavior" ,
813+ "git log --format=%b" : "" ,
814+ "git log --format= --name-only" : "src/mod.ts\n" ,
815+ } ,
816+ now : new Date ( "2026-02-12T09:14:29Z" ) ,
817+ } ) ;
818+
819+ await run ( [ "pull_request" , "abcdef1234567890" ] , cli . deps ) ;
820+
821+ assertEquals ( cli . outputs , [
822+ "version=0.1.1-canary.abcdef1.20260212091429\n" ,
823+ "tag=canary\n" ,
824+ ] ) ;
825+ assertEquals (
826+ cli . logs . at ( - 1 ) ,
827+ "Canary version: 0.1.1-canary.abcdef1.20260212091429" ,
828+ ) ;
829+ assertEquals ( cli . calls . includes ( "npm view fallback-package version" ) , true ) ;
830+ } ) ;
831+
832+ it ( "reads the package name from commented deno.jsonc content" , async ( ) => {
833+ const cli = makeCliDeps ( {
834+ env : {
835+ GITHUB_OUTPUT : "/tmp/github-output" ,
836+ } ,
837+ files : {
838+ "deno.jsonc" : `{
839+ // Package metadata for release automation.
840+ "name": "commented-package",
841+ /* Keep the rest of the manifest commented-friendly. */
842+ "version": "0.0.0-development"
843+ }` ,
844+ } ,
845+ commands : {
846+ "git describe --tags --abbrev=0 --match v*" : new Error ( "no tags" ) ,
847+ "npm view commented-package version" : "0.2.0" ,
848+ "git log --format=%s" : "docs: note jsonc support" ,
849+ "git log --format=%b" : "" ,
850+ "git log --format= --name-only" : ".github/scripts/version.ts\n" ,
851+ } ,
852+ now : new Date ( "2026-02-12T09:14:29Z" ) ,
853+ } ) ;
854+
855+ await run ( [ "pull_request" , "abcdef1234567890" ] , cli . deps ) ;
856+
857+ assertEquals ( cli . outputs , [
858+ "version=0.2.1-canary.abcdef1.20260212091429\n" ,
859+ "tag=canary\n" ,
860+ ] ) ;
861+ assertEquals (
862+ cli . calls . includes ( "npm view commented-package version" ) ,
863+ true ,
864+ ) ;
865+ } ) ;
866+
867+ it ( "emits skip=true when only test files changed" , async ( ) => {
868+ const cli = makeCliDeps ( {
869+ env : {
870+ GITHUB_EVENT_NAME : "push" ,
871+ GITHUB_OUTPUT : "/tmp/github-output" ,
872+ } ,
873+ files : {
874+ "deno.json" : JSON . stringify ( { name : "opencode-graphiti" } ) ,
875+ } ,
876+ commands : {
877+ "git rev-parse HEAD" : "abc123def4567890" ,
878+ "git describe --tags --abbrev=0 --match v*" : "v1.2.3" ,
879+ "git log v1.2.3..HEAD --format=%s" : "test: add cli coverage" ,
880+ "git log v1.2.3..HEAD --format=%b" : "" ,
881+ "git diff --name-only v1.2.3..HEAD" :
882+ ".github/scripts/version.test.ts\n" ,
883+ } ,
884+ } ) ;
885+
886+ await run ( [ ] , cli . deps ) ;
887+
888+ assertEquals ( cli . outputs , [ "skip=true\n" ] ) ;
889+ assertEquals ( cli . logs , [
890+ "skip=true" ,
891+ "No release-triggering commits since v1.2.3, skipping" ,
892+ ] ) ;
893+ } ) ;
894+ } ) ;
0 commit comments