diff --git a/.github/workflows/build-and-release.yml b/.github/workflows/build-and-release.yml index abcc595..a1fdb60 100644 --- a/.github/workflows/build-and-release.yml +++ b/.github/workflows/build-and-release.yml @@ -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 diff --git a/README.md b/README.md index be3ba61..956be25 100644 --- a/README.md +++ b/README.md @@ -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-.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-.zip` with: - `package.json` - `index.html` @@ -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 diff --git a/package.json b/package.json index 8b94198..9a68872 100644 --- a/package.json +++ b/package.json @@ -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": [ @@ -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" }, diff --git a/scripts/validate-release-manifest.mjs b/scripts/validate-release-manifest.mjs new file mode 100644 index 0000000..20ad371 --- /dev/null +++ b/scripts/validate-release-manifest.mjs @@ -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) +} diff --git a/scripts/write-release-manifest.mjs b/scripts/write-release-manifest.mjs index 7a00704..161dc00 100644 --- a/scripts/write-release-manifest.mjs +++ b/scripts/write-release-manifest.mjs @@ -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('/', '-') +}