Skip to content

Publish

Publish #83

Workflow file for this run

name: Publish
on:
workflow_dispatch:
inputs:
package:
description: 'Package to release'
required: true
type: choice
options:
- eclipsa
- create-eclipsa
- image
- content
- motion
- markdown
- native-swiftui
- native-android-compose
- native-gtk4
release_type:
description: 'Version bump type'
required: true
type: choice
options:
- patch
- minor
- major
- alpha-patch
- alpha-minor
- alpha-major
- beta-patch
- beta-minor
- beta-major
permissions:
id-token: write
contents: write
jobs:
prepare:
runs-on: ubuntu-latest
concurrency:
group: publish-${{ inputs.package }}
cancel-in-progress: false
permissions:
contents: write
id-token: write
env:
GH_TOKEN: ${{ github.token }}
PACKAGE_KEY: ${{ inputs.package }}
INPUT_RELEASE_TYPE: ${{ inputs.release_type }}
outputs:
build_command: ${{ steps.package.outputs.build_command }}
npm_tag: ${{ steps.metadata.outputs.npmTag }}
package_json_path: ${{ steps.package.outputs.package_json_path }}
publish_dir: ${{ steps.package.outputs.publish_dir }}
publish_package_json_path: ${{ steps.package.outputs.publish_package_json_path }}
native_host_matrix: ${{ steps.package.outputs.native_host_matrix }}
tag: ${{ steps.metadata.outputs.tag }}
version: ${{ steps.metadata.outputs.version }}
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup Bun
uses: oven-sh/setup-bun@v2
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: '24'
registry-url: 'https://registry.npmjs.org'
- name: Verify trusted publishing runtime
shell: bash
run: |
node -e "
const parse = (version) => version.replace(/^v/, '').split('.').map(Number);
const gte = (left, right) => {
for (let i = 0; i < Math.max(left.length, right.length); i += 1) {
const a = left[i] ?? 0;
const b = right[i] ?? 0;
if (a > b) return true;
if (a < b) return false;
}
return true;
};
const nodeVersion = process.versions.node;
const npmVersion = require('child_process').execFileSync('npm', ['--version'], { encoding: 'utf8' }).trim();
if (!gte(parse(nodeVersion), parse('22.14.0'))) {
throw new Error(\`Trusted publishing requires Node 22.14.0+, got \${nodeVersion}\`);
}
if (!gte(parse(npmVersion), parse('11.5.1'))) {
throw new Error(\`Trusted publishing requires npm 11.5.1+, got \${npmVersion}\`);
}
"
- name: Install dependencies
run: bun install --frozen-lockfile
- name: Resolve package paths
id: package
shell: bash
run: |
case "$PACKAGE_KEY" in
eclipsa)
package_dir="packages/eclipsa"
;;
create-eclipsa)
package_dir="packages/create-eclipsa"
;;
image)
package_dir="packages/image"
;;
content)
package_dir="packages/content"
;;
motion)
package_dir="packages/motion"
;;
markdown)
package_dir="packages/markdown"
;;
native-swiftui)
package_dir="packages/native-swiftui"
;;
native-android-compose)
package_dir="packages/native-android-compose"
;;
native-gtk4)
package_dir="packages/native-gtk4"
;;
*)
echo "Unsupported package: $PACKAGE_KEY"
exit 1
;;
esac
package_json_path="$package_dir/package.json"
publish_dir="$package_dir/dist"
publish_package_json_path="$publish_dir/package.json"
build_command="bun run --filter ./$package_dir build"
native_host_matrix="$(node -e "
const packageKey = process.argv[1];
const matrices = {
'native-swiftui': [
{
package: 'native-swiftui',
host: 'macos-14',
artifact: 'darwin-arm64',
package_dir: 'packages/native-swiftui',
build: 'swift build --package-path ./macos-swiftui --product EclipsaNativeMacOS',
output: 'artifacts/darwin-arm64/EclipsaNativeMacOS',
source: 'macos-swiftui/.build/debug/EclipsaNativeMacOS',
},
{
package: 'native-swiftui',
host: 'macos-13',
artifact: 'darwin-x64',
package_dir: 'packages/native-swiftui',
build: 'swift build --package-path ./macos-swiftui --product EclipsaNativeMacOS',
output: 'artifacts/darwin-x64/EclipsaNativeMacOS',
source: 'macos-swiftui/.build/debug/EclipsaNativeMacOS',
},
],
'native-android-compose': [
{
package: 'native-android-compose',
host: 'ubuntu-latest',
artifact: 'android-universal',
package_dir: 'packages/native-android-compose',
build: './android-compose/gradlew --project-dir ./android-compose :app:assembleDebug',
output: 'artifacts/android-universal/app-debug.apk',
source: 'android-compose/app/build/outputs/apk/debug/app-debug.apk',
},
],
'native-gtk4': [
{
package: 'native-gtk4',
host: 'ubuntu-latest',
artifact: 'gtk4-placeholder',
package_dir: 'packages/native-gtk4',
build: 'mkdir -p artifacts/gtk4-placeholder && touch artifacts/gtk4-placeholder/placeholder.json',
output: 'artifacts/gtk4-placeholder/placeholder.json',
source: 'artifacts/gtk4-placeholder/placeholder.json',
},
],
};
process.stdout.write(JSON.stringify(matrices[packageKey] ?? []));
" "$PACKAGE_KEY")"
if [ ! -r "$package_json_path" ]; then
echo "package.json not found or not readable: $package_json_path"
exit 1
fi
echo "package_dir=$package_dir" >> "$GITHUB_OUTPUT"
echo "package_json_path=$package_json_path" >> "$GITHUB_OUTPUT"
echo "publish_dir=$publish_dir" >> "$GITHUB_OUTPUT"
echo "publish_package_json_path=$publish_package_json_path" >> "$GITHUB_OUTPUT"
echo "build_command=$build_command" >> "$GITHUB_OUTPUT"
echo "native_host_matrix=$native_host_matrix" >> "$GITHUB_OUTPUT"
- name: Resolve release metadata
id: metadata
shell: bash
run: |
metadata_json="$(bun scripts/release/resolve-release-metadata.ts \
--package-json '${{ steps.package.outputs.package_json_path }}' \
--package-key "$PACKAGE_KEY" \
--release-type "$INPUT_RELEASE_TYPE")"
node -e "
const data = JSON.parse(process.argv[1]);
for (const [key, value] of Object.entries(data)) {
process.stdout.write(\`\${key}=\${value}\n\`);
}
" "$metadata_json" >> "$GITHUB_OUTPUT"
- name: Ensure release refs do not exist already
shell: bash
run: |
if git rev-parse -q --verify "refs/tags/${{ steps.metadata.outputs.tag }}" >/dev/null; then
echo "Tag already exists: ${{ steps.metadata.outputs.tag }}"
exit 1
fi
if gh release view "${{ steps.metadata.outputs.tag }}" >/dev/null 2>&1; then
echo "Release already exists: ${{ steps.metadata.outputs.tag }}"
exit 1
fi
build_optimizer_bindings:
if: ${{ inputs.package == 'eclipsa' }}
needs: prepare
runs-on: ${{ matrix.settings.host }}
strategy:
fail-fast: false
matrix:
settings:
- host: macos-latest
target: x86_64-apple-darwin
build: bun ./scripts/run-napi.ts build --platform --release --manifest-path ../eclipsa/compiler/rust/Cargo.toml --package-json-path ./package.json --target-dir ../eclipsa/compiler/rust/target --output-dir ./generated --js-package-name @eclipsa/optimizer --target x86_64-apple-darwin
- host: macos-latest
target: aarch64-apple-darwin
build: bun ./scripts/run-napi.ts build --platform --release --manifest-path ../eclipsa/compiler/rust/Cargo.toml --package-json-path ./package.json --target-dir ../eclipsa/compiler/rust/target --output-dir ./generated --js-package-name @eclipsa/optimizer --target aarch64-apple-darwin
- host: windows-latest
target: x86_64-pc-windows-msvc
build: bun ./scripts/run-napi.ts build --platform --release --manifest-path ../eclipsa/compiler/rust/Cargo.toml --package-json-path ./package.json --target-dir ../eclipsa/compiler/rust/target --output-dir ./generated --js-package-name @eclipsa/optimizer --target x86_64-pc-windows-msvc
- host: windows-latest
target: aarch64-pc-windows-msvc
build: bun ./scripts/run-napi.ts build --platform --release --manifest-path ../eclipsa/compiler/rust/Cargo.toml --package-json-path ./package.json --target-dir ../eclipsa/compiler/rust/target --output-dir ./generated --js-package-name @eclipsa/optimizer --target aarch64-pc-windows-msvc
- host: ubuntu-latest
target: x86_64-unknown-linux-gnu
build: bun ./scripts/run-napi.ts build --platform --release --manifest-path ../eclipsa/compiler/rust/Cargo.toml --package-json-path ./package.json --target-dir ../eclipsa/compiler/rust/target --output-dir ./generated --js-package-name @eclipsa/optimizer --target x86_64-unknown-linux-gnu --use-napi-cross
- host: ubuntu-latest
target: aarch64-unknown-linux-gnu
build: bun ./scripts/run-napi.ts build --platform --release --manifest-path ../eclipsa/compiler/rust/Cargo.toml --package-json-path ./package.json --target-dir ../eclipsa/compiler/rust/target --output-dir ./generated --js-package-name @eclipsa/optimizer --target aarch64-unknown-linux-gnu --use-napi-cross
- host: ubuntu-latest
target: x86_64-unknown-linux-musl
build: bun ./scripts/run-napi.ts build --platform --release --manifest-path ../eclipsa/compiler/rust/Cargo.toml --package-json-path ./package.json --target-dir ../eclipsa/compiler/rust/target --output-dir ./generated --js-package-name @eclipsa/optimizer --target x86_64-unknown-linux-musl -x
- host: ubuntu-latest
target: aarch64-unknown-linux-musl
build: bun ./scripts/run-napi.ts build --platform --release --manifest-path ../eclipsa/compiler/rust/Cargo.toml --package-json-path ./package.json --target-dir ../eclipsa/compiler/rust/target --output-dir ./generated --js-package-name @eclipsa/optimizer --target aarch64-unknown-linux-musl -x
- host: ubuntu-latest
target: wasm32-wasip1-threads
build: bun ./scripts/run-napi.ts build --platform --release --manifest-path ../eclipsa/compiler/rust/Cargo.toml --package-json-path ./package.json --target-dir ../eclipsa/compiler/rust/target --output-dir ./generated --js-package-name @eclipsa/optimizer --target wasm32-wasip1-threads
env:
MACOSX_DEPLOYMENT_TARGET: '10.13'
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Bun
uses: oven-sh/setup-bun@v2
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: '24'
- name: Setup Rust
uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.settings.target }}
- name: Setup Zig
if: ${{ contains(matrix.settings.target, 'musl') }}
uses: mlugg/setup-zig@v2
with:
version: 0.14.1
- name: Install cargo-zigbuild
if: ${{ contains(matrix.settings.target, 'musl') }}
uses: taiki-e/install-action@v2
env:
GITHUB_TOKEN: ${{ github.token }}
with:
tool: cargo-zigbuild
- name: Install dependencies
run: bun install --frozen-lockfile
- name: Update package version for build
shell: bash
run: |
node -e "
const fs = require('fs');
const path = process.argv[1];
const version = process.argv[2];
const pkg = JSON.parse(fs.readFileSync(path, 'utf8'));
pkg.version = version;
fs.writeFileSync(path, JSON.stringify(pkg, null, 2) + '\n');
" 'packages/optimizer/package.json' '${{ needs.prepare.outputs.version }}'
- name: Build binding
shell: bash
working-directory: packages/optimizer
run: ${{ matrix.settings.build }}
- name: Verify WASI binding entry
if: ${{ matrix.settings.target == 'wasm32-wasip1-threads' }}
shell: bash
working-directory: packages/optimizer
run: |
node -e "const binding = require('./generated/optimizer.wasi.cjs'); if (typeof binding.compileClient !== 'function') { throw new Error('Expected compileClient export from WASI binding entry.'); }"
- name: Upload binding artifact
uses: actions/upload-artifact@v4
with:
name: bindings-${{ matrix.settings.target }}
path: packages/optimizer/generated/*
if-no-files-found: error
build_native_host_artifacts:
if: ${{ inputs.package == 'native-swiftui' || inputs.package == 'native-android-compose' || inputs.package == 'native-gtk4' }}
needs: prepare
runs-on: ${{ matrix.settings.host }}
strategy:
fail-fast: false
matrix:
settings: ${{ fromJson(needs.prepare.outputs.native_host_matrix) }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Bun
uses: oven-sh/setup-bun@v2
- name: Setup Java
if: ${{ matrix.settings.package == 'native-android-compose' }}
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: '21'
- name: Setup Android SDK
if: ${{ matrix.settings.package == 'native-android-compose' }}
uses: android-actions/setup-android@v3
- name: Install Android SDK packages
if: ${{ matrix.settings.package == 'native-android-compose' }}
shell: bash
run: |
yes | sdkmanager --licenses >/dev/null
sdkmanager "platform-tools" "platforms;android-36" "build-tools;36.0.0"
- name: Install dependencies
run: bun install --frozen-lockfile
- name: Build native host artifact
shell: bash
working-directory: ${{ matrix.settings.package_dir }}
run: ${{ matrix.settings.build }}
- name: Stage native host artifact
shell: bash
working-directory: ${{ matrix.settings.package_dir }}
run: |
mkdir -p "$(dirname '${{ matrix.settings.output }}')"
cp '${{ matrix.settings.source }}' '${{ matrix.settings.output }}'
- name: Upload native host artifact
uses: actions/upload-artifact@v4
with:
name: ${{ matrix.settings.artifact }}
path: ${{ matrix.settings.package_dir }}/${{ matrix.settings.output }}
if-no-files-found: error
publish_eclipsa:
if: ${{ inputs.package == 'eclipsa' }}
needs:
- prepare
- build_optimizer_bindings
runs-on: ubuntu-latest
permissions:
contents: write
id-token: write
env:
GH_TOKEN: ${{ github.token }}
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup Bun
uses: oven-sh/setup-bun@v2
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: '24'
registry-url: 'https://registry.npmjs.org'
- name: Setup Rust
uses: dtolnay/rust-toolchain@stable
with:
targets: wasm32-wasip1-threads
- name: Install dependencies
run: bun install --frozen-lockfile
- name: Update package versions for publish
shell: bash
run: |
node -e "
const fs = require('fs');
const version = process.argv[1];
const paths = process.argv.slice(2);
for (const path of paths) {
const pkg = JSON.parse(fs.readFileSync(path, 'utf8'));
pkg.version = version;
fs.writeFileSync(path, JSON.stringify(pkg, null, 2) + '\n');
}
" '${{ needs.prepare.outputs.version }}' 'packages/eclipsa/package.json' 'packages/optimizer/package.json'
- name: Build optimizer JS package
shell: bash
run: bun run --filter @eclipsa/optimizer build:js
- name: Build eclipsa package
if: needs.prepare.outputs.build_command != ''
shell: bash
run: ${{ needs.prepare.outputs.build_command }}
- name: Verify eclipsa dist package metadata
shell: bash
run: |
publish_package_json_path='${{ needs.prepare.outputs.publish_package_json_path }}'
if [ ! -r "$publish_package_json_path" ]; then
echo "dist package.json not found or not readable: $publish_package_json_path"
exit 1
fi
node -e "
const fs = require('fs');
const packageJsonPath = process.argv[1];
const expectedVersion = process.argv[2];
const pkg = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
if (pkg.private !== false) {
throw new Error(\`Expected private=false in \${packageJsonPath}\`);
}
if (pkg.version !== expectedVersion) {
throw new Error(\`Expected version \${expectedVersion} in \${packageJsonPath}, got \${pkg.version}\`);
}
if (!pkg.repository) {
throw new Error(\`Expected repository in \${packageJsonPath}\`);
}
if (!pkg.exports) {
throw new Error(\`Expected exports in \${packageJsonPath}\`);
}
" "$publish_package_json_path" '${{ needs.prepare.outputs.version }}'
- name: Generate root WASI loader assets
shell: bash
working-directory: packages/optimizer
run: bun run build:native -- --target wasm32-wasip1-threads
- name: Create npm package directories
shell: bash
run: bun run --filter @eclipsa/optimizer create:npm-dirs
- name: Download all binding artifacts
uses: actions/download-artifact@v4
with:
path: packages/optimizer/artifacts
pattern: bindings-*
merge-multiple: false
- name: Flatten downloaded binding artifacts
shell: bash
working-directory: packages/optimizer
run: bun ./scripts/prepare-downloaded-artifacts.ts
- name: Sync npm package versions
shell: bash
run: bun run --filter @eclipsa/optimizer version:napi
- name: Hydrate npm package artifacts
shell: bash
run: bun run --filter @eclipsa/optimizer artifacts
- name: Publish optimizer native packages
shell: bash
run: |
publish_args=(--provenance --access public)
if [[ '${{ needs.prepare.outputs.npm_tag }}' != 'latest' ]]; then
publish_args+=(--tag '${{ needs.prepare.outputs.npm_tag }}')
fi
for package_dir in packages/optimizer/npm/*; do
if [[ ! -d "$package_dir" ]]; then
continue
fi
package_json_path="$package_dir/package.json"
if [[ ! -f "$package_json_path" ]]; then
echo "Skipping package without package.json: $package_dir"
continue
fi
package_name="$(node -e "const fs = require('fs'); const pkg = JSON.parse(fs.readFileSync(process.argv[1], 'utf8')); process.stdout.write(pkg.name ?? '');" "$package_json_path")"
package_version="$(node -e "const fs = require('fs'); const pkg = JSON.parse(fs.readFileSync(process.argv[1], 'utf8')); process.stdout.write(pkg.version ?? '');" "$package_json_path")"
if [[ -z "$package_name" || -z "$package_version" ]]; then
echo "Skipping package with invalid name/version: $package_dir"
continue
fi
if npm view "${package_name}@${package_version}" version --json >/dev/null 2>&1; then
echo "Skipping already published package: ${package_name}@${package_version}"
continue
fi
npm publish "$package_dir" "${publish_args[@]}"
done
- name: Pack optimizer tarball
id: pack_optimizer
shell: bash
working-directory: packages/optimizer
run: |
bun ./scripts/sync-package-manifest.ts publish
tarball_output="$(bun pm pack --quiet --destination "$RUNNER_TEMP")"
tarball_output="$(echo "$tarball_output" | tr -d '\r\n')"
if [[ "$tarball_output" = /* ]]; then
tarball_path="$tarball_output"
else
tarball_path="$RUNNER_TEMP/$tarball_output"
fi
if [[ ! -f "$tarball_path" ]]; then
echo "Tarball was not generated at expected path: $tarball_path"
exit 1
fi
echo "path=$tarball_path" >> "$GITHUB_OUTPUT"
- name: Publish optimizer package
shell: bash
run: |
optimizer_package_json='packages/optimizer/package.json'
package_name="$(node -e "const fs = require('fs'); const pkg = JSON.parse(fs.readFileSync(process.argv[1], 'utf8')); process.stdout.write(pkg.name ?? '');" "$optimizer_package_json")"
package_version="$(node -e "const fs = require('fs'); const pkg = JSON.parse(fs.readFileSync(process.argv[1], 'utf8')); process.stdout.write(pkg.version ?? '');" "$optimizer_package_json")"
if [[ -z "$package_name" || -z "$package_version" ]]; then
echo "Optimizer package.json is missing a valid name/version"
exit 1
fi
if npm view "${package_name}@${package_version}" version --json >/dev/null 2>&1; then
echo "Skipping already published package: ${package_name}@${package_version}"
exit 0
fi
publish_args=(npm publish '${{ steps.pack_optimizer.outputs.path }}' --provenance --access public)
if [[ '${{ needs.prepare.outputs.npm_tag }}' != 'latest' ]]; then
publish_args+=(--tag '${{ needs.prepare.outputs.npm_tag }}')
fi
"${publish_args[@]}"
- name: Pack eclipsa tarball
id: pack_eclipsa
shell: bash
run: |
publish_dir='${{ needs.prepare.outputs.publish_dir }}'
tarball_output="$(cd "$publish_dir" && bun pm pack --quiet --destination "$RUNNER_TEMP")"
tarball_output="$(echo "$tarball_output" | tr -d '\r\n')"
if [[ "$tarball_output" = /* ]]; then
tarball_path="$tarball_output"
else
tarball_path="$RUNNER_TEMP/$tarball_output"
fi
if [[ ! -f "$tarball_path" ]]; then
echo "Tarball was not generated at expected path: $tarball_path"
exit 1
fi
echo "path=$tarball_path" >> "$GITHUB_OUTPUT"
- name: Publish eclipsa package
shell: bash
run: |
publish_args=(npm publish '${{ steps.pack_eclipsa.outputs.path }}' --provenance --access public)
if [[ '${{ needs.prepare.outputs.npm_tag }}' != 'latest' ]]; then
publish_args+=(--tag '${{ needs.prepare.outputs.npm_tag }}')
fi
"${publish_args[@]}"
- name: Restore development manifest
shell: bash
run: bun run --filter @eclipsa/optimizer restore:manifest
- name: Create release with generated notes
shell: bash
run: |
gh release create '${{ needs.prepare.outputs.tag }}' \
--generate-notes \
--target '${{ github.sha }}'
publish_other:
if: ${{ inputs.package == 'native-swiftui' || inputs.package == 'native-android-compose' || inputs.package == 'native-gtk4' }}
needs:
- prepare
- build_native_host_artifacts
runs-on: ubuntu-latest
permissions:
contents: write
id-token: write
env:
GH_TOKEN: ${{ github.token }}
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup Bun
uses: oven-sh/setup-bun@v2
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: '24'
registry-url: 'https://registry.npmjs.org'
- name: Install dependencies
run: bun install --frozen-lockfile
- name: Download native host artifacts
if: ${{ inputs.package == 'native-swiftui' || inputs.package == 'native-android-compose' }}
uses: actions/download-artifact@v4
with:
path: ${{ needs.prepare.outputs.publish_dir }}/../artifacts
pattern: '*'
merge-multiple: false
- name: Update package version for publish
shell: bash
run: |
node -e "
const fs = require('fs');
const path = process.argv[1];
const version = process.argv[2];
const pkg = JSON.parse(fs.readFileSync(path, 'utf8'));
pkg.version = version;
if (pkg.private === true) {
pkg.private = false;
}
fs.writeFileSync(path, JSON.stringify(pkg, null, 2) + '\n');
" '${{ needs.prepare.outputs.package_json_path }}' '${{ needs.prepare.outputs.version }}'
- name: Build package
if: needs.prepare.outputs.build_command != ''
shell: bash
run: |
if [[ '${{ inputs.package }}' == 'native-swiftui' || '${{ inputs.package }}' == 'native-android-compose' || '${{ inputs.package }}' == 'native-gtk4' ]]; then
export ECLIPSA_NATIVE_REQUIRE_HOST_ARTIFACTS=1
fi
${{ needs.prepare.outputs.build_command }}
- name: Verify dist package metadata
shell: bash
run: |
publish_package_json_path='${{ needs.prepare.outputs.publish_package_json_path }}'
if [ ! -r "$publish_package_json_path" ]; then
echo "dist package.json not found or not readable: $publish_package_json_path"
exit 1
fi
node -e "
const fs = require('fs');
const packageJsonPath = process.argv[1];
const expectedVersion = process.argv[2];
const pkg = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
if (pkg.private !== false) {
throw new Error(\`Expected private=false in \${packageJsonPath}\`);
}
if (pkg.version !== expectedVersion) {
throw new Error(\`Expected version \${expectedVersion} in \${packageJsonPath}, got \${pkg.version}\`);
}
if (!pkg.repository) {
throw new Error(\`Expected repository in \${packageJsonPath}\`);
}
if (!pkg.exports) {
throw new Error(\`Expected exports in \${packageJsonPath}\`);
}
" "$publish_package_json_path" '${{ needs.prepare.outputs.version }}'
- name: Verify native host bundle metadata
if: ${{ inputs.package == 'native-swiftui' || inputs.package == 'native-android-compose' || inputs.package == 'native-gtk4' }}
shell: bash
run: |
host_manifest='${{ needs.prepare.outputs.publish_dir }}/host/manifest.json'
if [ ! -r "$host_manifest" ]; then
echo "host manifest not found or not readable: $host_manifest"
exit 1
fi
node -e "
const fs = require('fs');
const hostManifestPath = process.argv[1];
const packageKey = process.argv[2];
const manifest = JSON.parse(fs.readFileSync(hostManifestPath, 'utf8'));
if (manifest.formatVersion !== 1) {
throw new Error(\`Expected formatVersion=1 in \${hostManifestPath}\`);
}
if (!Array.isArray(manifest.targets)) {
throw new Error(\`Expected targets array in \${hostManifestPath}\`);
}
if (packageKey !== 'native-gtk4' && manifest.targets.length === 0) {
throw new Error(\`Expected at least one bundled target in \${hostManifestPath}\`);
}
" "$host_manifest" '${{ inputs.package }}'
- name: Pack package tarball
id: pack
shell: bash
run: |
publish_dir='${{ needs.prepare.outputs.publish_dir }}'
tarball_output="$(cd "$publish_dir" && bun pm pack --quiet --destination "$RUNNER_TEMP")"
tarball_output="$(echo "$tarball_output" | tr -d '\r\n')"
if [[ "$tarball_output" = /* ]]; then
tarball_path="$tarball_output"
else
tarball_path="$RUNNER_TEMP/$tarball_output"
fi
if [[ ! -f "$tarball_path" ]]; then
echo "Tarball was not generated at expected path: $tarball_path"
exit 1
fi
echo "path=$tarball_path" >> "$GITHUB_OUTPUT"
- name: Publish package tarball to npm
shell: bash
run: |
publish_args=(npm publish '${{ steps.pack.outputs.path }}' --provenance --access public)
if [[ '${{ needs.prepare.outputs.npm_tag }}' != 'latest' ]]; then
publish_args+=(--tag '${{ needs.prepare.outputs.npm_tag }}')
fi
"${publish_args[@]}"
- name: Create release with generated notes
shell: bash
run: |
gh release create '${{ needs.prepare.outputs.tag }}' \
--generate-notes \
--target '${{ github.sha }}'
publish_other_non_native:
if: ${{ inputs.package != 'eclipsa' && inputs.package != 'native-swiftui' && inputs.package != 'native-android-compose' && inputs.package != 'native-gtk4' }}
needs: prepare
runs-on: ubuntu-latest
permissions:
contents: write
id-token: write
env:
GH_TOKEN: ${{ github.token }}
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup Bun
uses: oven-sh/setup-bun@v2
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: '24'
registry-url: 'https://registry.npmjs.org'
- name: Install dependencies
run: bun install --frozen-lockfile
- name: Update package version for publish
shell: bash
run: |
node -e "
const fs = require('fs');
const path = process.argv[1];
const version = process.argv[2];
const pkg = JSON.parse(fs.readFileSync(path, 'utf8'));
pkg.version = version;
if (pkg.private === true) {
pkg.private = false;
}
fs.writeFileSync(path, JSON.stringify(pkg, null, 2) + '\n');
" '${{ needs.prepare.outputs.package_json_path }}' '${{ needs.prepare.outputs.version }}'
- name: Build package
if: needs.prepare.outputs.build_command != ''
shell: bash
run: ${{ needs.prepare.outputs.build_command }}
- name: Verify dist package metadata
shell: bash
run: |
publish_package_json_path='${{ needs.prepare.outputs.publish_package_json_path }}'
if [ ! -r "$publish_package_json_path" ]; then
echo "dist package.json not found or not readable: $publish_package_json_path"
exit 1
fi
node -e "
const fs = require('fs');
const packageJsonPath = process.argv[1];
const expectedVersion = process.argv[2];
const pkg = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
if (pkg.private !== false) {
throw new Error(\`Expected private=false in \${packageJsonPath}\`);
}
if (pkg.version !== expectedVersion) {
throw new Error(\`Expected version \${expectedVersion} in \${packageJsonPath}, got \${pkg.version}\`);
}
if (!pkg.repository) {
throw new Error(\`Expected repository in \${packageJsonPath}\`);
}
if (!pkg.exports) {
throw new Error(\`Expected exports in \${packageJsonPath}\`);
}
" "$publish_package_json_path" '${{ needs.prepare.outputs.version }}'
- name: Pack package tarball
id: pack
shell: bash
run: |
publish_dir='${{ needs.prepare.outputs.publish_dir }}'
tarball_output="$(cd "$publish_dir" && bun pm pack --quiet --destination "$RUNNER_TEMP")"
tarball_output="$(echo "$tarball_output" | tr -d '\r\n')"
if [[ "$tarball_output" = /* ]]; then
tarball_path="$tarball_output"
else
tarball_path="$RUNNER_TEMP/$tarball_output"
fi
if [[ ! -f "$tarball_path" ]]; then
echo "Tarball was not generated at expected path: $tarball_path"
exit 1
fi
echo "path=$tarball_path" >> "$GITHUB_OUTPUT"
- name: Publish package tarball to npm
shell: bash
run: |
publish_args=(npm publish '${{ steps.pack.outputs.path }}' --provenance --access public)
if [[ '${{ needs.prepare.outputs.npm_tag }}' != 'latest' ]]; then
publish_args+=(--tag '${{ needs.prepare.outputs.npm_tag }}')
fi
"${publish_args[@]}"
- name: Create release with generated notes
shell: bash
run: |
gh release create '${{ needs.prepare.outputs.tag }}' \
--generate-notes \
--target '${{ github.sha }}'