Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions scripts/ci-eduwork-windows-release.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -166,8 +166,11 @@ $name $Version — Windows x64 Electron 开发版
$receipt | ConvertTo-Json -Depth 20 | Set-Content (Join-Path $publish 'release-receipt.json') -Encoding utf8NoBOM
if (-not $Development) {
Copy-Item -LiteralPath $notesPath -Destination (Join-Path $publish 'RELEASE-NOTES.md')
& node (Join-Path $CoreRoot 'scripts/github-update-manifest.mjs') (Join-Path $publish 'release-receipt.json') "ecnu/$name"
if ($LASTEXITCODE -ne 0) { throw 'GitHub update manifest generation failed' }
# Other institutions publish their own update feeds from the release receipt.
if ($name -in @('EduWork','EduWork-ECNU')) {
& node (Join-Path $CoreRoot 'scripts/github-update-manifest.mjs') (Join-Path $publish 'release-receipt.json') "ecnu/$name"
if ($LASTEXITCODE -ne 0) { throw 'GitHub update manifest generation failed' }
}
}
} catch {
$receipt.error = $_.Exception.Message
Expand Down
4 changes: 3 additions & 1 deletion scripts/ci-pinned-dsh-desktop.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,9 @@ try {
$result.asset=@{name=[IO.Path]::GetFileName($archive);bytes=(Get-Item $archive).Length;sha256=(Get-FileHash $archive -Algorithm SHA256).Hash.ToLowerInvariant()}
$result.passed=$true
$result | ConvertTo-Json -Depth 16 | Set-Content (Join-Path $publish "$platform-$(if ($Stable) {'release'} else {'alpha'})-receipt.json") -Encoding utf8NoBOM
if ($Stable -and $IsWindows) {
# The GitHub update manifest only exists for the ECNU editions; institutional
# editions publish their own update source from the receipt.
if ($Stable -and $IsWindows -and $name -in @('EduWork','EduWork-ECNU')) {
& node (Join-Path $CoreRoot 'scripts/github-update-manifest.mjs') (Join-Path $publish 'windows-release-receipt.json') "ECNU/$name"
}
} catch { $result.error=$_.Exception.Message;Write-Host $_.ScriptStackTrace;throw }
Expand Down
3 changes: 2 additions & 1 deletion scripts/lib/windows-stages.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -383,7 +383,8 @@ async function publishWindows({ workspace, coreRoot, receiptBase }) {
delete receipt.checks.asset
delete receipt.checks.portableExtractorAssets
await writeJSON(join(publish, 'release-receipt.json'), receipt)
if (!development && !desktopVersion(workspace.parameters.version).prerelease) {
// Other institutions publish their own update feeds from the release receipt.
if (!development && !desktopVersion(workspace.parameters.version).prerelease && ['EduWork', 'EduWork-ECNU'].includes(name)) {
await copyFileTo(join(editionRoot, releaseNotesFile), join(publish, 'RELEASE-NOTES.md'))
await runNode(join(coreRoot, 'scripts/github-update-manifest.mjs'), [join(publish, 'release-receipt.json'), `ecnu/${name}`])
.catch(() => { throw new Error('GitHub update manifest generation failed') })
Expand Down
10 changes: 5 additions & 5 deletions source-receipt.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"version": "0.4.1",
"sourceState": "local-development-snapshot",
"createdAt": "2026-10-10T12:18:00.131Z",
"fileSetSHA256": "acc953eec3ccb84d8011f95e5ec90fa31bf4e0b1f1f1a4a09b69fab45cf29f67",
"fileSetSHA256": "25cb95e6524ac89129f7d9bc4bb25f9effe353a8842eafb5cf033fffadd9bd2b",
"files": [
{
"path": ".gitattributes",
Expand Down Expand Up @@ -5169,11 +5169,11 @@
},
{
"path": "scripts/ci-eduwork-windows-release.ps1",
"sha256": "5577252103532f5e4d73a8e8bbba77792bb9d8d9764f82c42da089bfe1f4528f"
"sha256": "010bcc6aa378adc9f93eff86c7545d4b4af39764fb65d93b39ded2fd8d10aa9e"
},
{
"path": "scripts/ci-pinned-dsh-desktop.ps1",
"sha256": "aace3cee93fa1e65a78c67f16b7842cbc46f122754617386650927f3cad5bc1d"
"sha256": "0952ffe55c29b531280de8f61ba1a7fc1469dc27320fff6fd4f325f4fa1faccc"
},
{
"path": "scripts/close-release-test-desktop.mjs",
Expand Down Expand Up @@ -5305,7 +5305,7 @@
},
{
"path": "scripts/lib/windows-stages.mjs",
"sha256": "d54afc7c9cb82e4df0ac86eb19571288f6017115a9b04b842eb28b47739278f8"
"sha256": "ca10a3e3f5cdd0f4509abf5c633c02abded381adcbb903e29abf4f0076e7de97"
},
{
"path": "scripts/lib/zip.mjs",
Expand Down Expand Up @@ -5721,7 +5721,7 @@
},
{
"path": "tests/eduwork-windows-release.test.mjs",
"sha256": "83cf53a36a4fa58a6d5592153fca42e0ac78af910c5af5934fb482a19235d771"
"sha256": "babfc9dd028ba7faf0fbd960b68bc7db7a333fa920e13d37f5f7dfef0d268f30"
},
{
"path": "tests/fixtures/audit-stack-probe.mjs",
Expand Down
49 changes: 47 additions & 2 deletions tests/eduwork-windows-release.test.mjs
Original file line number Diff line number Diff line change
@@ -1,16 +1,61 @@
import test from 'node:test'
import assert from 'node:assert/strict'
import { validateReceipt,releasePublication,validateExtractorReceipt,validatedReleaseFiles } from '../scripts/publish-windows-release.mjs'
import {mkdtemp,writeFile,rm} from 'node:fs/promises'
import {mkdtemp,mkdir,writeFile,readFile,readdir,rm} from 'node:fs/promises'
import {tmpdir} from 'node:os'
import {join} from 'node:path'
import {basename,join,resolve,sep} from 'node:path'
import {createHash} from 'node:crypto'
import {githubUpdateManifest,githubUpdateManifestBytes} from '../scripts/github-update-manifest.mjs'
import {windowsStages} from '../scripts/lib/windows-stages.mjs'
import {fileURLToPath} from 'node:url'
const context={repository:'ECNU/EduWork',version:'0.3.0',commit:'a'.repeat(40)}
const receipt={schemaVersion:1,kind:'eduwork-windows-release',validationProfile:'ci-build-launch-and-extract-v2',passed:true,version:'0.3.0',edition:'EduWork',shell:'electron',platform:'windows-x64',coreCommit:'d'.repeat(40),editionCommit:context.commit,checks:Object.fromEntries(['sourceAndDependencies','desktopLaunch','archiveManifest','nativeRuntimes','portableExtractor'].map(key=>[key,'passed'])),asset:{name:'EduWork-0.3.0-windows-x64-electron.zip',bytes:1024,sha256:'b'.repeat(64)}}
function extractorFiles(edition,version) {return {asset:{name:`${edition}-${version}-windows-x64-setup.zip`,bytes:2048,sha256:'e'.repeat(64)},receipt:{name:`${edition}-${version}-windows-x64-setup.zip.json`,bytes:512,sha256:'f'.repeat(64)}}}
receipt.portableExtractor=extractorFiles('EduWork',receipt.version)
receipt.releaseNotes={approved:true,sha256:'c'.repeat(64)}

test('Windows publish stage keeps institution artifacts without inventing an ECNU update repository',async t=>{
const coreRoot=fileURLToPath(new URL('..',import.meta.url))
for(const recipe of ['npm','pinned-source']) for(const [name,distribution] of [['EduWork','eduwork'],['EduWork-ECNU','eduwork-chatecnu'],['ExampleCampus','example-campus']]) {
for(const [version,development] of [['0.4.3',false],['0.4.3-alpha.1',false],['0.4.3-dev.20261010.1',true]]) await t.test(`${recipe}: ${name} ${version}`,async t=>{
const root=await mkdtemp(join(tmpdir(),'eduwork-publish-stage-'))
t.after(async()=>{
const target=resolve(root)
assert.ok(target.startsWith(resolve(tmpdir())+sep) && basename(target).startsWith('eduwork-publish-stage-'))
await rm(target,{recursive:true,force:true})
})
const file=async(path,content)=>{await mkdir(join(root,path,'..'),{recursive:true});await writeFile(join(root,path),content)}
const json=async(path,value)=>file(path,JSON.stringify(value))
const asset={name:`${name}-${version}-windows-x64-electron.zip`,bytes:32,sha256:'a'.repeat(64)}
const extractor=extractorFiles(name,version)
const archive=Buffer.from('Synthetic publication input, not an application')
await file(`desktop/${asset.name}`,archive)
await json('desktop/package.json',{output:join(root,'desktop',asset.name)})
await json(recipe==='pinned-source'?'product/assembly.json':'web/assembly/assembly.json',{distribution,dshVersion:'0.2.0-rc.2',dshCommit:'b'.repeat(40)})
await json('inputs/inputs.json',{})
await json('gui/acceptance-checks.json',{asset,portableExtractorAssets:extractor})
for(const filename of [extractor.asset.name,`${extractor.asset.name}.sha256`,extractor.receipt.name]) await file(`gui/extractor-publish/${filename}`,'Synthetic extractor evidence')
await file('RELEASE-NOTES.md','Synthetic approved release notes')
const workspace={parameters:{name,version,development,releaseNotesFile:'RELEASE-NOTES.md',editionRoot:root,recipe},resolvePath:path=>join(root,path)}
const receiptBase={schemaVersion:1,kind:development?'eduwork-windows-development':'eduwork-windows-release',edition:name,version,shell:'electron',platform:'windows-x64'}
await windowsStages({coreRoot,receiptBase,recipe}).find(stage=>stage.name==='publish').run(workspace)
const files=await readdir(join(root,'publish'))
const expectedManifest=version==='0.4.3' && name!=='ExampleCampus'
assert.equal(files.includes('update-windows-amd64.json'),expectedManifest)
assert.equal(files.includes('RELEASE-NOTES.md'),!development)
assert.equal(files.includes(extractor.asset.name),!development)
assert.deepEqual(await readFile(join(root,'publish',asset.name)),archive)
const published=JSON.parse(await readFile(join(root,'publish/release-receipt.json'),'utf8'))
assert.equal(published.distribution,distribution)
assert.deepEqual(published.asset,asset)
if(expectedManifest){
const manifest=JSON.parse(await readFile(join(root,'publish/update-windows-amd64.json'),'utf8'))
assert.equal(manifest.artifacts[0].url,`https://github.com/ecnu/${name}/releases/download/v${version}/${asset.name}`)
assert.equal(manifest.artifacts[0].sha256,asset.sha256)
}
})
}
})
test('release publisher accepts the complete matching Windows receipt',()=>assert.equal(validateReceipt(receipt,context),'EduWork'))

test('release publisher rejects missing or failed packaged native runtime checks',()=>{
Expand Down
Loading