2026-09-10 18:27:41 -07:00
import test from "node:test" ;
import assert from "node:assert/strict" ;
import fs from "node:fs/promises" ;
import os from "node:os" ;
import path from "node:path" ;
2026-09-15 15:23:24 -07:00
import { promisify } from "node:util" ;
import { execFile as callback } from "node:child_process" ;
import { planPinUpgrades , applyPinUpgrades , type UpgradeEffects } from "../src/capability-language/pin-upgrades.js" ;
2026-09-10 18:27:41 -07:00
const execFile = promisify ( callback );
2026-09-15 15:23:24 -07:00
test (
"real jj snapshots and immutable Git publication propagate a changed interface into the root" ,
{ skip : ! process . env . QX_CHECK_GENERATOR },
async ( context ) => {
const workbench = await fs . mkdtemp ( path . join ( os . tmpdir (), "qx-real-upgrade-" ));
context . after (() => fs . rm ( workbench , { recursive : true , force : true }));
// Exercise the actual effects with local bare remotes, without external writes.
const environment = {
GIT_CONFIG_COUNT : "1" ,
GIT_CONFIG_KEY_0 : `url.file:// ${ workbench } /remotes/.insteadOf` ,
GIT_CONFIG_VALUE_0 : "https://upgrade.test/" ,
QUIXOS_JJ_NO_CHECKPOINT : "1" ,
QUIXOS_CHECK_GENERATOR : process.env.QX_CHECK_GENERATOR ! ,
};
const previous = Object . fromEntries ( Object . keys ( environment ). map (( key ) => [ key , process . env [ key ]]));
Object . assign ( process . env , environment );
context . after (() => {
for ( const [ key , value ] of Object . entries ( previous ))
if ( value === undefined ) delete process . env [ key ];
else process . env [ key ] = value ;
});
const run = async ( cwd : string , command : string , args : string []) =>
( await execFile ( command , args , { cwd })). stdout . trim ();
await fs . mkdir ( path . join ( workbench , "remotes" ));
const nodes = [];
for ( const [ kind , directory , remote ] of [
[ "interface" , "resources/Named" , "named.git" ],
[ "workspace" , "root" , "workspace.git" ],
] as const ) {
const root = path . join ( workbench , directory );
await fs . mkdir ( root , { recursive : true });
await run ( workbench , "git" , [ "init" , "--bare" , path . join ( workbench , "remotes" , remote )]);
await run ( root , "jj" , [ "git" , "init" , "--colocate" ]);
await run ( root , "git" , [ "remote" , "add" , "origin" , `https://upgrade.test/ ${ remote } ` ]);
await fs . writeFile ( path . join ( root , ".gitignore" ), ".quixos/\n" );
const child = nodes [ 0 ];
await fs . writeFile (
path . join ( root , "quixos.lock" ),
`quixos-lock version 1 { quixos source { repository "https://upgrade.test/quixos.git"; commit " ${ "a" . repeat ( 40 ) } "; } ${ child ? `interface Named source { repository " ${ child . source . repository } "; commit " ${ child . source . commit } "; }` : "" } }` ,
);
await fs . writeFile (
path . join ( root , ` ${ kind } .qx` ),
kind === "interface"
? 'interface Named id "interface:named" revision "interface:named@1" {}'
: `workspace W id "workspace:w" revision "workspace:w@1" commit " ${ "a" . repeat ( 40 ) } " { import interface Named; atom A id "atom:a"; }` ,
);
await run ( root , "jj" , [ "describe" , "-m" , "Initial source" ]);
const commit = await run ( root , "jj" , [ "log" , "--no-graph" , "-r" , "@" , "-T" , "commit_id" ]);
await run ( root , "git" , [ "push" , "origin" , ` ${ commit } :refs/tags/quixos-reachability/ ${ commit } ` ]);
nodes . push ({ kind , directory , source : { repository : `https://upgrade.test/ ${ remote } ` , commit } });
}
await fs . mkdir ( path . join ( workbench , ".quixos" ));
await fs . writeFile ( path . join ( workbench , ".quixos/resource-graph.json" ), JSON . stringify ({ resources : [ nodes [ 0 ]] }));
await fs . appendFile ( path . join ( workbench , nodes [ 0 ]. directory , "interface.qx" ), "\n// incremental author edit\n" );
const plan = await planPinUpgrades ( workbench , { nodes , bootstrap : true });
const result = await applyPinUpgrades ( plan );
assert . equal ( result . activated , false );
const graph = JSON . parse ( await fs . readFile ( path . join ( workbench , ".quixos/resource-graph.json" ), "utf8" ));
const commit = graph . resources [ 0 ]. source . commit ;
assert . notEqual ( commit , nodes [ 0 ]. source . commit );
assert . match ( await fs . readFile ( path . join ( workbench , "root/quixos.lock" ), "utf8" ), new RegExp ( commit ));
assert . match (
await run ( workbench , "git" , [ "--git-dir" , path . join ( workbench , "remotes/named.git" ), "show-ref" ]),
new RegExp ( commit ),
);
},
);
2026-09-13 22:07:18 -07:00
2026-09-22 13:51:32 -07:00
for ( const dependencyKind of [ "interface" , "build" ] as const )
test ( ` ${ dependencyKind } pin upgrades publish children before parent locks and resume without republishing completed nodes` , async ( context ) => {
const workbench = await fs . mkdtemp ( path . join ( os . tmpdir (), "qx-upgrade-test-" ));
context . after (() => fs . rm ( workbench , { recursive : true , force : true }));
const from = "a" . repeat ( 40 ),
to = "b" . repeat ( 40 ),
framework = "c" . repeat ( 40 );
const sources = [
{
kind : "workspace" as const ,
directory : "root" ,
source : { repository : "https://example.test/workspace.git" , commit : from },
},
{
kind : dependencyKind ,
directory : "resources/Named" ,
source : { repository : "https://example.test/named.git" , commit : from },
},
];
const lock = `quixos-lock version 1 { quixos source { repository "https://example.test/quixos.git"; commit " ${ framework } "; }` ;
for ( const node of sources ) {
const directory = path . join ( workbench , node . directory );
await fs . mkdir ( directory , { recursive : true });
await execFile ( "git" , [ "-C" , directory , "init" ]);
await execFile ( "git" , [ "-C" , directory , "remote" , "add" , "origin" , node . source . repository ]);
await fs . writeFile ( path . join ( directory , ".gitignore" ), ".quixos/\n" );
await fs . writeFile (
path . join ( directory , "quixos.lock" ),
lock +
( node . kind === "workspace"
? ` ${ dependencyKind } Named source { repository " ${ sources [ 1 ]. source . repository } "; commit " ${ from } "; }`
: "" ) +
" }" ,
);
}
2026-09-15 15:23:24 -07:00
await fs . writeFile (
2026-09-22 13:51:32 -07:00
path . join (
workbench ,
dependencyKind === "interface" ? "resources/Named/interface.qx" : "resources/Named/types.ts" ,
),
dependencyKind === "interface"
? 'interface Named id "interface:named" revision "interface:named@1" { value name id "member:name" : string { get id "op:get"; } }'
: "export type Name = string;" ,
2026-09-15 15:23:24 -07:00
);
2026-09-22 13:51:32 -07:00
await fs . writeFile (
path . join ( workbench , "root/workspace.qx" ),
`workspace W id "workspace:w" revision "workspace:w@1" commit " ${ from } " { ${ dependencyKind === "interface" ? "import interface Named;" : "" } atom A id "atom:a"; }` ,
);
const snapshotMap = path . join ( workbench , "snapshots.json" );
await fs . writeFile (
snapshotMap ,
JSON . stringify ({
resources : [
{
kind : dependencyKind ,
repository : sources [ 1 ]. source . repository ,
commit : to ,
directory : path.join ( workbench , "resources/Named" ),
},
],
}),
);
const oldMap = process . env . QUIXOS_SNAPSHOT_MAP ;
process . env . QUIXOS_SNAPSHOT_MAP = snapshotMap ;
context . after (() => {
if ( oldMap === undefined ) delete process . env . QUIXOS_SNAPSHOT_MAP ;
else process . env . QUIXOS_SNAPSHOT_MAP = oldMap ;
});
const plan = await planPinUpgrades ( workbench , { nodes : sources });
await fs . mkdir ( path . join ( workbench , ".quixos" ), { recursive : true });
await fs . writeFile (
path . join ( workbench , ".quixos/resource-graph.json" ),
JSON . stringify ({ resources : [{ ... sources [ 1 ], source : { resolver : "git" , ... sources [ 1 ]. source } }] }),
);
assert . deepEqual (
plan . nodes . map (( node ) => node . directory ),
[ "resources/Named" , "root" ],
);
let fail = true ;
const published : string [] = [];
const effects : UpgradeEffects = {
check : async ( node ) => {
if ( node . kind === "workspace" && fail ) throw new Error ( "refactor required" );
},
snapshot : async () => to ,
publish : async ( root ) => {
published . push ( path . relative ( workbench , root ));
},
};
await assert . rejects (() => applyPinUpgrades ( plan , undefined , effects ), /refactor required/ );
assert . deepEqual ( published , [ "resources/Named" ]);
assert . match ( await fs . readFile ( path . join ( workbench , "root/quixos.lock" ), "utf8" ), new RegExp ( to ));
const id = ( await fs . readdir ( path . join ( workbench , ".quixos/upgrades" )))
. find (( name ) => name . endsWith ( ".json" )) !
. slice ( 0 , - 5 );
fail = false ;
await fs . appendFile ( path . join ( workbench , "root/workspace.qx" ), "\n// explicit refactor\n" );
await assert . rejects (() => applyPinUpgrades ( plan , id , effects ), /accept-edits/ );
const result = await applyPinUpgrades ( plan , id , effects , { acceptEdits : true });
assert . equal ( result . activated , false );
assert . deepEqual ( published , [ "resources/Named" , "root" ]);
const graph = JSON . parse ( await fs . readFile ( path . join ( workbench , ".quixos/resource-graph.json" ), "utf8" ));
assert . equal ( graph . resources . length , 1 );
assert . equal ( graph . resources [ 0 ]. source . commit , to );
const next = await planPinUpgrades ( workbench , {
nodes : [ sources [ 0 ], { ... sources [ 1 ], source : graph.resources [ 0 ]. source }],
});
assert . deepEqual ( next . nodes . find (( node ) => node . kind === "workspace" ) ! . dependencies , [ "resources/Named" ]);
2026-09-15 15:23:24 -07:00
});