2026-05-30 01:22:32 +00:00
import { spawnSync } from "node:child_process" ;
2026-07-10 03:50:37 +00:00
import { mkdir , mkdtemp , readdir , readFile , rm , writeFile } from "node:fs/promises" ;
2026-05-30 01:22:32 +00:00
import { tmpdir } from "node:os" ;
import path from "node:path" ;
2026-06-25 10:05:43 +00:00
import { pathToFileURL } from "node:url" ;
2026-07-10 03:50:37 +00:00
import { afterEach , describe , expect , it } from "vitest" ;
2026-05-30 01:22:32 +00:00
const repoRoot = process . cwd ( ) ;
2026-07-10 03:50:37 +00:00
const tempWorkspaces = new Set < string > ( ) ;
afterEach ( async ( ) = > {
await Promise . all ( [ . . . tempWorkspaces ] . map ( ( workspace ) = > rm ( workspace , { recursive : true , force : true } ) ) ) ;
tempWorkspaces . clear ( ) ;
} ) ;
2026-05-30 01:22:32 +00:00
describe ( "development scripts" , ( ) = > {
2026-07-14 08:18:05 +00:00
it . each ( [
{ message : "feat: add side chats" , expectedStatus : 0 } ,
{ message : "Merge pull request #123 from owner/feature" , expectedStatus : 0 } ,
{ message : 'Revert "feat: add side chats"' , expectedStatus : 1 } ,
{ message : "v5.1.0" , expectedStatus : 1 } ,
{ message : "fixup! feat: add side chats" , expectedStatus : 1 } ,
{ message : "squash! feat: add side chats" , expectedStatus : 1 } ,
] ) ( "enforces the commit message policy for $message" , ( { message , expectedStatus } ) = > {
expect ( runCommitlint ( message ) ) . toBe ( expectedStatus ) ;
2026-07-14 06:53:19 +00:00
} ) ;
2026-05-31 11:26:11 +00:00
it ( "fails style builds when CSS files are missing from the style order file" , async ( ) = > {
2026-06-19 23:18:19 +00:00
const cwd = await styleOrderFixture ( ) ;
2026-05-30 02:00:08 +00:00
2026-06-19 23:18:19 +00:00
const result = runNodeScript ( "scripts/build-styles.mjs" , [ ] , cwd ) ;
2026-05-30 02:00:08 +00:00
expect ( result . status ) . toBe ( 1 ) ;
2026-05-31 11:26:11 +00:00
expect ( result . stderr ) . toContain ( "CSS files missing from src/styles/order.json: 10-unlisted.css" ) ;
2026-05-30 02:00:08 +00:00
} ) ;
2026-05-30 01:22:32 +00:00
it ( "passes the expected codex generate-ts arguments" , async ( ) = > {
const cwd = await tempWorkspace ( ) ;
2026-06-25 10:05:43 +00:00
const calls : { command : string ; args : string [ ] ; cwd : string } [ ] = [ ] ;
const { generateAppServerTypes } = await import ( pathToFileURL ( path . join ( repoRoot , "scripts" , "generate-app-server-types.mjs" ) ) . href ) ;
2026-07-14 12:15:21 +00:00
await writeAppServerCompatibility ( cwd , "0.144.4" , [ "app-server" , "generate-ts" , "--experimental" , "--test-setting" ] ) ;
2026-06-25 10:05:43 +00:00
await generateAppServerTypes ( {
cwd ,
2026-07-14 12:15:21 +00:00
readCodexVersion : ( ) = > "0.144.4" ,
2026-06-25 10:05:43 +00:00
async runCommand ( command : string , args : string [ ] , options : { cwd : string } ) {
calls . push ( { command , args , cwd : options.cwd } ) ;
2026-07-10 03:46:38 +00:00
const outIndex = args . indexOf ( "--out" ) ;
const outputDir = args [ outIndex + 1 ] ;
if ( ! outputDir ) throw new Error ( "Missing generated output directory" ) ;
await mkdir ( path . join ( options . cwd , outputDir , "v2" ) , { recursive : true } ) ;
2026-06-25 10:05:43 +00:00
await writeFile (
2026-07-10 03:46:38 +00:00
path . join ( options . cwd , outputDir , "v2" , "Example.ts" ) ,
2026-06-25 10:05:43 +00:00
"// GENERATED CODE! DO NOT MODIFY BY HAND!\nexport type Example = string | null | null;\n" ,
) ;
} ,
2026-05-30 01:22:32 +00:00
} ) ;
2026-07-10 03:46:38 +00:00
expect ( calls ) . toHaveLength ( 1 ) ;
expect ( calls [ 0 ] ) . toMatchObject ( { command : "codex" , cwd } ) ;
2026-07-14 12:15:21 +00:00
expect ( calls [ 0 ] ? . args . slice ( 0 , 5 ) ) . toEqual ( [ "app-server" , "generate-ts" , "--experimental" , "--test-setting" , "--out" ] ) ;
expect ( calls [ 0 ] ? . args [ 5 ] ? . replaceAll ( "\\" , "/" ) ) . toMatch ( /^src\/generated\/\.app-server-/ ) ;
2026-05-30 01:22:32 +00:00
await expect ( readFile ( path . join ( cwd , "src" , "generated" , "app-server" , "v2" , "Example.ts" ) , "utf8" ) ) . resolves . toContain (
"export type Example = string | null;" ,
) ;
} ) ;
2026-07-10 03:46:38 +00:00
it ( "preserves generated bindings when generation fails" , async ( ) = > {
const cwd = await tempWorkspace ( ) ;
const generatedDir = path . join ( cwd , "src" , "generated" , "app-server" ) ;
await mkdir ( generatedDir , { recursive : true } ) ;
await writeFile ( path . join ( generatedDir , "Existing.ts" ) , "export type Existing = true;\n" ) ;
2026-07-14 12:15:21 +00:00
await writeAppServerCompatibility ( cwd , "0.144.4" ) ;
2026-07-10 03:46:38 +00:00
const { generateAppServerTypes } = await import ( pathToFileURL ( path . join ( repoRoot , "scripts" , "generate-app-server-types.mjs" ) ) . href ) ;
await expect (
generateAppServerTypes ( {
cwd ,
2026-07-14 12:15:21 +00:00
readCodexVersion : ( ) = > "0.144.4" ,
2026-07-10 03:46:38 +00:00
runCommand : async ( ) = > {
throw new Error ( "generation failed" ) ;
} ,
} ) ,
) . rejects . toThrow ( "generation failed" ) ;
await expect ( readFile ( path . join ( generatedDir , "Existing.ts" ) , "utf8" ) ) . resolves . toBe ( "export type Existing = true;\n" ) ;
await expect ( readdir ( path . join ( cwd , "src" , "generated" ) ) ) . resolves . toEqual ( [ "app-server" ] ) ;
} ) ;
2026-07-14 12:15:21 +00:00
it ( "checks normalized generated bindings without replacing the tracked tree" , async ( ) = > {
const cwd = await tempWorkspace ( ) ;
const generatedDir = path . join ( cwd , "src" , "generated" , "app-server" ) ;
await mkdir ( path . join ( generatedDir , "v2" ) , { recursive : true } ) ;
await writeFile (
path . join ( generatedDir , "v2" , "Example.ts" ) ,
"// GENERATED CODE! DO NOT MODIFY BY HAND!\n// This file was mechanically normalized after generation by scripts/generate-app-server-types.mjs.\nexport type Example = string | null;\n" ,
) ;
await writeAppServerCompatibility ( cwd , "0.144.4" ) ;
const { generateAppServerTypes } = await import ( pathToFileURL ( path . join ( repoRoot , "scripts" , "generate-app-server-types.mjs" ) ) . href ) ;
const generate = async ( _command : string , args : string [ ] , options : { cwd : string } ) = > {
const outputDir = args [ args . indexOf ( "--out" ) + 1 ] ;
if ( ! outputDir ) throw new Error ( "Missing generated output directory" ) ;
await mkdir ( path . join ( options . cwd , outputDir , "v2" ) , { recursive : true } ) ;
await writeFile (
path . join ( options . cwd , outputDir , "v2" , "Example.ts" ) ,
"// GENERATED CODE! DO NOT MODIFY BY HAND!\nexport type Example = string | null | null;\n" ,
) ;
} ;
await expect (
generateAppServerTypes ( { cwd , check : true , readCodexVersion : ( ) = > "0.144.4" , runCommand : generate } ) ,
) . resolves . toBeUndefined ( ) ;
const trackedSource = await readFile ( path . join ( generatedDir , "v2" , "Example.ts" ) , "utf8" ) ;
await expect (
generateAppServerTypes ( {
cwd ,
check : true ,
readCodexVersion : ( ) = > "0.144.4" ,
async runCommand ( command : string , args : string [ ] , options : { cwd : string } ) {
await generate ( command , args , options ) ;
const outputDir = args [ args . indexOf ( "--out" ) + 1 ] ;
if ( ! outputDir ) throw new Error ( "Missing generated output directory" ) ;
await writeFile ( path . join ( options . cwd , outputDir , "v2" , "Extra.ts" ) , "export type Extra = true;\n" ) ;
} ,
} ) ,
) . rejects . toThrow ( "generated app-server bindings are out of date:\n added: v2/Extra.ts" ) ;
await expect ( readFile ( path . join ( generatedDir , "v2" , "Example.ts" ) , "utf8" ) ) . resolves . toBe ( trackedSource ) ;
await expect ( readdir ( path . join ( cwd , "src" , "generated" ) ) ) . resolves . toEqual ( [ "app-server" ] ) ;
} ) ;
it ( "refuses to generate bindings with a different Codex CLI patch" , async ( ) = > {
const cwd = await tempWorkspace ( ) ;
await writeAppServerCompatibility ( cwd , "0.144.4" ) ;
const { generateAppServerTypes } = await import ( pathToFileURL ( path . join ( repoRoot , "scripts" , "generate-app-server-types.mjs" ) ) . href ) ;
await expect ( generateAppServerTypes ( { cwd , readCodexVersion : ( ) = > "0.144.5" , runCommand : async ( ) = > undefined } ) ) . rejects . toThrow (
"Codex CLI 0.144.4 is required to generate app-server bindings; found 0.144.5." ,
) ;
} ) ;
2026-07-07 13:06:19 +00:00
it ( "reads app-server compatibility policy from the declared baseline in API baseline checks" , async ( ) = > {
2026-06-12 09:21:17 +00:00
const cwd = await tempWorkspace ( ) ;
2026-07-07 13:06:19 +00:00
await mkdir ( path . join ( cwd , "src" , "app-server" ) , { recursive : true } ) ;
2026-06-26 02:39:55 +00:00
const { createApiBaselineReport } = await import ( pathToFileURL ( path . join ( repoRoot , "scripts" , "api-baseline.mjs" ) ) . href ) ;
2026-06-12 09:21:17 +00:00
await writeJson ( path . join ( cwd , "package.json" ) , {
version : "1.0.0" ,
devDependencies : {
obsidian : "~1.12.3" ,
} ,
} ) ;
await writeJson ( path . join ( cwd , "package-lock.json" ) , {
packages : {
"node_modules/obsidian" : {
version : "1.12.3" ,
} ,
} ,
} ) ;
await writeJson ( path . join ( cwd , "manifest.json" ) , {
minAppVersion : "1.12.0" ,
} ) ;
await writeJson ( path . join ( cwd , "versions.json" ) , {
"1.0.0" : "1.12.0" ,
} ) ;
await writeFile (
path . join ( cwd , "README.md" ) ,
[
"## Compatibility" ,
"" ,
"| Key | Version | Notes |" ,
"| --- | --- | --- |" ,
"| `codex.testedCliVersion` | `0.139.0` | Tested CLI. |" ,
2026-06-19 11:11:51 +00:00
"| `manifest.minAppVersion` | `1.12.0` | Minimum app version. |" ,
"| `obsidian` API types | `1.12.3` | Compile-time API package. |" ,
2026-06-12 09:21:17 +00:00
] . join ( "\n" ) ,
) ;
2026-07-07 13:06:19 +00:00
await mkdir ( path . join ( cwd , "src" , "app-server" , "connection" ) , { recursive : true } ) ;
await writeJson ( path . join ( cwd , "src" , "app-server" , "connection" , "compatibility.json" ) , {
codexAppServer : {
2026-07-14 12:15:21 +00:00
testedCliVersion : "0.139.0" ,
2026-07-07 13:06:19 +00:00
typeGeneration : {
2026-07-14 12:15:21 +00:00
arguments : [ "app-server" , "generate-ts" , "--experimental" ] ,
2026-07-07 13:06:19 +00:00
} ,
initialize : {
capabilities : {
experimentalApi : true ,
requestAttestation : false ,
} ,
} ,
} ,
} ) ;
2026-06-12 09:21:17 +00:00
2026-06-26 02:39:55 +00:00
const report = await createApiBaselineReport ( {
cwd ,
readCodexVersion : ( ) = > "0.139.0" ,
2026-06-12 09:21:17 +00:00
} ) ;
2026-07-14 12:15:21 +00:00
expect ( report . codex . recordedTestedCliVersion ) . toBe ( "0.139.0" ) ;
expect ( report . codex . generationArguments ) . toEqual ( [ "app-server" , "generate-ts" , "--experimental" ] ) ;
expect ( report . codex . readmeMatchesRecordedVersion ) . toBe ( true ) ;
expect ( report . codex . localCliMatchesRecordedVersion ) . toBe ( true ) ;
2026-06-12 09:21:17 +00:00
expect ( report . codex . initializeExperimentalApi ) . toBe ( true ) ;
expect ( report . codex . initializeRequestAttestationDisabled ) . toBe ( true ) ;
expect ( report . failures ) . toEqual ( [ ] ) ;
2026-07-10 03:48:36 +00:00
2026-07-14 12:15:21 +00:00
const missingLocalCliReport = await createApiBaselineReport ( {
2026-07-10 03:48:36 +00:00
cwd ,
readCodexVersion : ( ) = > null ,
} ) ;
2026-07-14 12:15:21 +00:00
expect ( missingLocalCliReport . failures ) . toContain ( "local codex --version could not be read." ) ;
} ) ;
it ( "rejects the obsolete recorded-only API baseline mode" , ( ) = > {
const result = runNodeScript ( "scripts/api-baseline.mjs" , [ "--recorded-only" ] , repoRoot ) ;
expect ( result . status ) . toBe ( 1 ) ;
expect ( result . stderr ) . toContain ( "Usage: node scripts/api-baseline.mjs [--json]" ) ;
} ) ;
it ( "reports app-server provenance drift between the recorded, README, and local CLI versions" , async ( ) = > {
const cwd = await apiBaselineFixture ( { recordedCodexVersion : "0.144.4" , readmeCodexVersion : "0.144.0" } ) ;
const { createApiBaselineReport } = await import ( pathToFileURL ( path . join ( repoRoot , "scripts" , "api-baseline.mjs" ) ) . href ) ;
const report = await createApiBaselineReport ( { cwd , readCodexVersion : ( ) = > "0.144.5" } ) ;
expect ( report . failures ) . toContain ( "README Codex CLI 0.144.0 does not match recorded tested CLI 0.144.4." ) ;
expect ( report . failures ) . toContain ( "local Codex CLI 0.144.5 does not match recorded tested CLI 0.144.4." ) ;
} ) ;
it ( "checks generated app-server bindings in CI and release paths" , async ( ) = > {
const [ checkWorkflow , releaseWorkflow , releasePreflight ] = await Promise . all ( [
readFile ( path . join ( repoRoot , ".github" , "workflows" , "check.yml" ) , "utf8" ) ,
readFile ( path . join ( repoRoot , ".github" , "workflows" , "release.yml" ) , "utf8" ) ,
readFile ( path . join ( repoRoot , "scripts" , "release" , "preflight.mjs" ) , "utf8" ) ,
] ) ;
for ( const workflow of [ checkWorkflow , releaseWorkflow ] ) {
expect ( workflow ) . toContain ( "npm install --global" ) ;
expect ( workflow ) . toContain ( "node scripts/app-server-compatibility.mjs --tested-cli-version" ) ;
expect ( workflow ) . toContain ( "npm run generate:app-server-types:check" ) ;
}
expect ( releasePreflight ) . toContain ( 'run("npm", ["run", "generate:app-server-types:check"]);' ) ;
2026-06-12 09:21:17 +00:00
} ) ;
2026-06-26 02:45:25 +00:00
it ( "reports representative CSS usage policy failures" , async ( ) = > {
2026-06-20 10:15:58 +00:00
const cwd = await cssUsageFixture ( {
"src/styles/10-component.css" : [
".codex-panel__used { display: block; }" ,
".codex-panel__test-only { display: block; }" ,
".codex-panel__unused { display: block; }" ,
] . join ( "\n" ) ,
2026-06-24 06:54:51 +00:00
"src/component.ts" : [
2026-06-26 02:45:25 +00:00
'export const className = "codex-panel__used";' ,
2026-06-24 07:09:29 +00:00
"export const dynamicClassName = `codex-panel__task-step--$" + "{status}`;" ,
2026-06-24 06:54:51 +00:00
] . join ( "\n" ) ,
2026-06-26 02:45:25 +00:00
"tests/component.test.ts" : 'expect("codex-panel__test-only").toBeTruthy();\n' ,
2026-06-24 06:37:18 +00:00
} ) ;
2026-06-27 13:31:54 +00:00
const result = runNodeScript ( "scripts/check-css-usage.mjs" , [ ] , cwd ) ;
2026-06-24 06:37:18 +00:00
2026-06-24 06:54:51 +00:00
expect ( result . status ) . toBe ( 1 ) ;
expect ( result . stdout ) . toBe ( "" ) ;
expect ( result . stderr ) . toContain ( "CSS usage check failed." ) ;
expect ( result . stderr ) . toContain ( "Dynamic CSS class prefixes are not allowed:" ) ;
expect ( result . stderr ) . toContain ( "codex-panel__task-step--" ) ;
2026-06-26 02:45:25 +00:00
expect ( result . stderr ) . toContain ( "codex-panel__test-only" ) ;
expect ( result . stderr ) . toContain ( "codex-panel__unused" ) ;
2026-06-24 06:37:18 +00:00
} ) ;
2026-07-15 09:01:00 +00:00
it ( "reports unused panel custom properties" , async ( ) = > {
const cwd = await cssUsageFixture ( {
"src/styles/10-component.css" : [
".codex-panel__used {" ,
" --codex-panel-used-size: 1px;" ,
" --codex-panel-unused-size: 2px;" ,
" width: var(--codex-panel-used-size);" ,
" /*" ,
" --codex-panel-commented-size: 3px;" ,
" width: var(--codex-panel-unused-size);" ,
" */" ,
"}" ,
] . join ( "\n" ) ,
"src/component.ts" : 'export const className = "codex-panel__used";\n' ,
} ) ;
const result = runNodeScript ( "scripts/check-css-usage.mjs" , [ ] , cwd ) ;
expect ( result . status ) . toBe ( 1 ) ;
expect ( result . stderr ) . toContain ( "Unused CSS custom property candidates:" ) ;
expect ( result . stderr ) . toContain ( "--codex-panel-unused-size" ) ;
expect ( result . stderr ) . not . toContain ( "--codex-panel-used-size\n" ) ;
expect ( result . stderr ) . not . toContain ( "--codex-panel-commented-size" ) ;
} ) ;
it ( "does not count a longer source class name as usage" , async ( ) = > {
const cwd = await cssUsageFixture ( {
"src/styles/10-component.css" : [ ".codex-panel__item { display: block; }" , ".codex-panel__item-detail { display: block; }" ] . join ( "\n" ) ,
"src/component.ts" : 'export const className = "codex-panel__item-detail";\n' ,
} ) ;
const result = runNodeScript ( "scripts/check-css-usage.mjs" , [ ] , cwd ) ;
expect ( result . status ) . toBe ( 1 ) ;
expect ( result . stderr ) . toContain ( "Unused CSS class candidates:" ) ;
expect ( result . stderr ) . toContain ( " codex-panel__item\n" ) ;
} ) ;
2026-07-14 06:53:19 +00:00
it ( "prepares release notes from conventional commits since the previous tag" , async ( ) = > {
const cwd = await tempWorkspace ( ) ;
await writeJson ( path . join ( cwd , "package.json" ) , { version : "2.3.2" } ) ;
await writeJson ( path . join ( cwd , "package-lock.json" ) , {
version : "2.3.2" ,
packages : { "" : { version : "2.3.2" } } ,
} ) ;
await writeJson ( path . join ( cwd , "manifest.json" ) , { version : "2.3.2" , minAppVersion : "1.12.0" } ) ;
await writeJson ( path . join ( cwd , "versions.json" ) , { "2.3.2" : "1.12.0" } ) ;
runGit ( [ "init" ] , cwd ) ;
runGit ( [ "config" , "user.name" , "Codex Panel Tests" ] , cwd ) ;
runGit ( [ "config" , "user.email" , "tests@example.com" ] , cwd ) ;
runGit ( [ "add" , "." ] , cwd ) ;
runGit ( [ "commit" , "-m" , "chore: establish release baseline" ] , cwd ) ;
runGit ( [ "tag" , "2.3.2" ] , cwd ) ;
await writeFile ( path . join ( cwd , "change.txt" ) , "side chats\n" ) ;
runGit ( [ "add" , "change.txt" ] , cwd ) ;
runGit ( [ "commit" , "-m" , "feat(chat): add side chat support" ] , cwd ) ;
const result = runNodeScript ( "scripts/release/prepare.mjs" , [ "2.4.0" ] , cwd ) ;
expect ( result . status ) . toBe ( 0 ) ;
await expect ( readFile ( path . join ( cwd , ".github" , "release-notes" , "2.4.0.md" ) , "utf8" ) ) . resolves . toBe (
"## Changes\n\n- Add side chat support.\n" ,
) ;
await expect ( readJson ( path . join ( cwd , "package.json" ) ) ) . resolves . toMatchObject ( { version : "2.4.0" } ) ;
} ) ;
2026-05-30 01:22:32 +00:00
it ( "fails release prepare before changing version files when release notes already exist" , async ( ) = > {
const cwd = await tempWorkspace ( ) ;
await mkdir ( path . join ( cwd , ".github" , "release-notes" ) , { recursive : true } ) ;
const packageJson = { version : "2.3.2" } ;
const packageLockJson = { version : "2.3.2" , packages : { "" : { version : "2.3.2" } } } ;
const manifestJson = { version : "2.3.2" , minAppVersion : "1.12.0" } ;
const versionsJson = { "2.3.2" : "1.12.0" } ;
await writeJson ( path . join ( cwd , "package.json" ) , packageJson ) ;
await writeJson ( path . join ( cwd , "package-lock.json" ) , packageLockJson ) ;
await writeJson ( path . join ( cwd , "manifest.json" ) , manifestJson ) ;
await writeJson ( path . join ( cwd , "versions.json" ) , versionsJson ) ;
await writeFile ( path . join ( cwd , ".github" , "release-notes" , "2.3.3.md" ) , "## Changes\n\n- Existing\n" ) ;
const result = runNodeScript ( "scripts/release/prepare.mjs" , [ "2.3.3" ] , cwd ) ;
expect ( result . status ) . toBe ( 1 ) ;
expect ( result . stderr ) . toContain ( ".github/release-notes/2.3.3.md already exists" ) ;
await expect ( readJson ( path . join ( cwd , "package.json" ) ) ) . resolves . toEqual ( packageJson ) ;
await expect ( readJson ( path . join ( cwd , "package-lock.json" ) ) ) . resolves . toEqual ( packageLockJson ) ;
await expect ( readJson ( path . join ( cwd , "manifest.json" ) ) ) . resolves . toEqual ( manifestJson ) ;
await expect ( readJson ( path . join ( cwd , "versions.json" ) ) ) . resolves . toEqual ( versionsJson ) ;
} ) ;
} ) ;
async function tempWorkspace ( ) : Promise < string > {
2026-07-10 03:50:37 +00:00
const workspace = await mkdtemp ( path . join ( tmpdir ( ) , "codex-panel-scripts-" ) ) ;
tempWorkspaces . add ( workspace ) ;
return workspace ;
2026-05-30 01:22:32 +00:00
}
2026-06-19 23:18:19 +00:00
async function styleOrderFixture ( ) : Promise < string > {
const cwd = await tempWorkspace ( ) ;
await mkdir ( path . join ( cwd , "src" , "styles" ) , { recursive : true } ) ;
await writeJson ( path . join ( cwd , "src" , "styles" , "order.json" ) , [ "00-tokens.css" ] ) ;
await writeFile ( path . join ( cwd , "src" , "styles" , "00-tokens.css" ) , ".codex-panel { color: var(--text-normal); }\n" ) ;
await writeFile ( path . join ( cwd , "src" , "styles" , "10-unlisted.css" ) , ".codex-panel__extra { display: block; }\n" ) ;
return cwd ;
}
2026-07-14 12:15:21 +00:00
async function apiBaselineFixture ( options : { recordedCodexVersion : string ; readmeCodexVersion : string } ) : Promise < string > {
const cwd = await tempWorkspace ( ) ;
await writeJson ( path . join ( cwd , "package.json" ) , { version : "1.0.0" , devDependencies : { obsidian : "~1.12.3" } } ) ;
await writeJson ( path . join ( cwd , "package-lock.json" ) , {
packages : { "node_modules/obsidian" : { version : "1.12.3" } } ,
} ) ;
await writeJson ( path . join ( cwd , "manifest.json" ) , { minAppVersion : "1.12.0" } ) ;
await writeJson ( path . join ( cwd , "versions.json" ) , { "1.0.0" : "1.12.0" } ) ;
await writeFile (
path . join ( cwd , "README.md" ) ,
[
"## Compatibility" ,
"" ,
"| Key | Version | Notes |" ,
"| --- | --- | --- |" ,
` | \` codex.testedCliVersion \` | \` ${ options . readmeCodexVersion } \` | Tested CLI. | ` ,
"| `manifest.minAppVersion` | `1.12.0` | Minimum app version. |" ,
"| `obsidian` API types | `1.12.3` | Compile-time API package. |" ,
] . join ( "\n" ) ,
) ;
await writeAppServerCompatibility ( cwd , options . recordedCodexVersion ) ;
return cwd ;
}
async function writeAppServerCompatibility (
cwd : string ,
testedCliVersion : string ,
generationArguments = [ "app-server" , "generate-ts" , "--experimental" ] ,
) : Promise < void > {
await mkdir ( path . join ( cwd , "src" , "app-server" , "connection" ) , { recursive : true } ) ;
await writeJson ( path . join ( cwd , "src" , "app-server" , "connection" , "compatibility.json" ) , {
codexAppServer : {
testedCliVersion ,
typeGeneration : {
arguments : generationArguments ,
} ,
initialize : {
capabilities : {
experimentalApi : true ,
requestAttestation : false ,
} ,
} ,
} ,
} ) ;
}
2026-06-20 10:15:58 +00:00
async function cssUsageFixture ( files : Record < string , string > ) : Promise < string > {
const cwd = await tempWorkspace ( ) ;
await mkdir ( path . join ( cwd , "src" , "styles" ) , { recursive : true } ) ;
await mkdir ( path . join ( cwd , "tests" ) , { recursive : true } ) ;
await writeJson ( path . join ( cwd , "src" , "styles" , "order.json" ) , [ "10-component.css" ] ) ;
for ( const [ file , source ] of Object . entries ( files ) ) {
await mkdir ( path . dirname ( path . join ( cwd , file ) ) , { recursive : true } ) ;
await writeFile ( path . join ( cwd , file ) , source ) ;
}
return cwd ;
}
2026-05-30 01:22:32 +00:00
function runNodeScript ( script : string , args : string [ ] = [ ] , cwd = repoRoot , env : NodeJS.ProcessEnv = { } ) {
return spawnSync ( process . execPath , [ path . join ( repoRoot , script ) , . . . args ] , {
cwd ,
encoding : "utf8" ,
env : { . . . process . env , . . . env } ,
shell : false ,
} ) ;
}
2026-07-14 06:53:19 +00:00
function runCommitlint ( message : string ) : number | null {
return spawnSync ( process . execPath , [ path . join ( repoRoot , "node_modules" , "@commitlint" , "cli" , "cli.js" ) ] , {
cwd : repoRoot ,
encoding : "utf8" ,
input : ` ${ message } \ n ` ,
shell : false ,
} ) . status ;
}
function runGit ( args : string [ ] , cwd : string ) : void {
const result = spawnSync ( "git" , args , { cwd , encoding : "utf8" , shell : false } ) ;
if ( result . status !== 0 ) throw new Error ( ` git ${ args . join ( " " ) } failed: ${ result . stderr } ` ) ;
}
2026-05-30 01:22:32 +00:00
async function writeJson ( file : string , value : unknown ) : Promise < void > {
await writeFile ( file , ` ${ JSON . stringify ( value , null , 2 ) } \ n ` ) ;
}
async function readJson ( file : string ) : Promise < unknown > {
return JSON . parse ( await readFile ( file , "utf8" ) ) ;
}