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
13 changes: 9 additions & 4 deletions .github/workflows/build-and-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,18 @@ jobs:
- name: Install Dependencies
run: npm install

- name: Build release layout
run: npm run build:release

- name: Set artifact name
- name: Set release metadata
run: |
safe_ref="${GITHUB_REF_NAME//\//-}"
echo "PLUGIN_ARTIFACT=neuroglancer-plugin-${safe_ref}.zip" >> "$GITHUB_ENV"
echo "OUROBOROS_PLUGIN_RELEASE_TAG=${GITHUB_REF_NAME}" >> "$GITHUB_ENV"
echo "OUROBOROS_PLUGIN_ARTIFACT=neuroglancer-plugin-${safe_ref}.zip" >> "$GITHUB_ENV"

- name: Build release layout
run: npm run build:release

- name: Validate release layout
run: npm run validate:release

- name: Archive Release
uses: thedoctor0/zip-release@0.7.5
Expand Down
24 changes: 22 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,25 @@ To test in Ouroboros, run Ouroboros in development mode as well, and use the `Te

### Release artifact

Tagged releases publish `neuroglancer-plugin-<tag>.zip`. The archive root is the
same layout that Ouroboros expects inside its plugin folder:
The current production pin for Ouroboros package builds is:

- tag: `v1.0.0`
- release asset: `release.zip`

That asset predates the named-artifact release workflow, but its archive root is
still the same layout that Ouroboros expects inside its plugin folder:

- `package.json`
- `index.html`
- `icon.svg`
- `compose.yml`
- frontend assets and backend runtime files

The legacy `v1.0.0` asset does not include `plugin-release.json`; Ouroboros
production package metadata should therefore record the pinned release tag and
asset name directly.

New tagged releases publish `neuroglancer-plugin-<tag>.zip` with:

- `package.json`
- `index.html`
Expand All @@ -34,6 +51,9 @@ same layout that Ouroboros expects inside its plugin folder:
- frontend assets and runtime files
- `plugin-release.json`

The `plugin-release.json` metadata records the release tag, artifact name,
commit, and packaged plugin version.

For production package preinstalls, unpack the archive to
`extra-resources/preinstalled-plugins/neuroglancer-plugin/` before building the
Ouroboros app package. On first launch, Ouroboros copies that folder into the
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"index": "./index.html",
"dockerCompose": "./compose.yml",
"private": true,
"version": "0.0.0",
"version": "1.0.0",
"type": "module",
"main": "dist/main.js",
"files": [
Expand All @@ -17,6 +17,7 @@
"dev": "concurrently \"npm run dev-frontend\" \"npm run dev-backend\"",
"build": "tsc -b && vite build",
"build:release": "npm run build && node scripts/write-release-manifest.mjs",
"validate:release": "node scripts/validate-release-manifest.mjs",
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"preview": "vite preview"
},
Expand Down
50 changes: 50 additions & 0 deletions scripts/validate-release-manifest.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { readFile } from 'node:fs/promises'
import { existsSync } from 'node:fs'
import { join } from 'node:path'

const root = process.cwd()
const dist = join(root, 'dist')
const packageJson = JSON.parse(await readFile(join(root, 'package.json'), 'utf8'))
const releaseTag = process.env.OUROBOROS_PLUGIN_RELEASE_TAG ?? process.env.GITHUB_REF_NAME ?? null
const releaseVersion = process.env.OUROBOROS_PLUGIN_RELEASE_VERSION ?? versionFromReleaseTag(releaseTag) ?? packageJson.version
const artifactName = process.env.OUROBOROS_PLUGIN_ARTIFACT ?? artifactNameForRelease(releaseTag)

assert(existsSync(join(dist, 'package.json')), 'release layout is missing package.json')
assert(existsSync(join(dist, packageJson.index)), `release layout is missing ${packageJson.index}`)
assert(existsSync(join(dist, packageJson.icon)), `release layout is missing ${packageJson.icon}`)
assert(
existsSync(join(dist, packageJson.dockerCompose)),
`release layout is missing ${packageJson.dockerCompose}`
)

const manifest = JSON.parse(await readFile(join(dist, 'plugin-release.json'), 'utf8'))

assert(manifest.name === packageJson.name, 'manifest name mismatch')
assert(manifest.pluginName === packageJson.pluginName, 'manifest pluginName mismatch')
assert(manifest.version === releaseVersion, 'manifest release version mismatch')
assert(manifest.packageVersion === packageJson.version, 'manifest package version mismatch')
assert(manifest.releaseTag === releaseTag, 'manifest release tag mismatch')
assert(manifest.artifactName === artifactName, 'manifest artifact name mismatch')
assert(manifest.index === packageJson.index, 'manifest index mismatch')
assert(manifest.icon === packageJson.icon, 'manifest icon mismatch')
assert(manifest.dockerCompose === packageJson.dockerCompose, 'manifest compose path mismatch')

console.log(`Validated Neuroglancer release layout for ${releaseTag ?? packageJson.version}.`)

function artifactNameForRelease(tag) {
const suffix = tag ? safeFilePart(tag) : packageJson.version
return `${packageJson.name}-${suffix}.zip`
}

function versionFromReleaseTag(tag) {
if (!tag?.startsWith('v')) return null
return tag.slice(1)
}

function safeFilePart(value) {
return value.replaceAll('/', '-')
}

function assert(condition, message) {
if (!condition) throw new Error(message)
}
23 changes: 21 additions & 2 deletions scripts/write-release-manifest.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,37 @@ import { join } from 'node:path'
const root = process.cwd()
const dist = join(root, 'dist')
const packageJson = JSON.parse(await readFile(join(root, 'package.json'), 'utf8'))
const releaseTag = process.env.OUROBOROS_PLUGIN_RELEASE_TAG ?? process.env.GITHUB_REF_NAME ?? null
const releaseVersion = process.env.OUROBOROS_PLUGIN_RELEASE_VERSION ?? versionFromReleaseTag(releaseTag) ?? packageJson.version
const artifactName = process.env.OUROBOROS_PLUGIN_ARTIFACT ?? artifactNameForRelease(releaseTag)

const manifest = {
name: packageJson.name,
pluginName: packageJson.pluginName,
version: packageJson.version,
version: releaseVersion,
packageVersion: packageJson.version,
index: packageJson.index,
icon: packageJson.icon,
dockerCompose: packageJson.dockerCompose,
artifactName: `${packageJson.name}-${packageJson.version}.zip`,
releaseTag,
artifactName,
commit: process.env.GITHUB_SHA ?? null,
ref: process.env.GITHUB_REF_NAME ?? null
}

await mkdir(dist, { recursive: true })
await writeFile(join(dist, 'plugin-release.json'), `${JSON.stringify(manifest, null, 2)}\n`)

function artifactNameForRelease(tag) {
const suffix = tag ? safeFilePart(tag) : packageJson.version
return `${packageJson.name}-${suffix}.zip`
}

function versionFromReleaseTag(tag) {
if (!tag?.startsWith('v')) return null
return tag.slice(1)
}

function safeFilePart(value) {
return value.replaceAll('/', '-')
}
Loading