Skip to content
Open
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
82 changes: 6 additions & 76 deletions .github/workflows/pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,81 +2,11 @@ name: PR Pipeline
on:
pull_request:
branches: [master]
permissions:
contents: read
jobs:
build-deploy:
runs-on: self-hosted
env:
SCROLL_REGISTRY_PR_NAMESPACE: druid-team-experimental
SCROLL_REGISTRY_ENDPOINT: ${{ secrets.SCROLL_REGISTRY_ENDPOINT }}
SCROLL_REGISTRY_API_KEY: ${{ secrets.SCROLL_REGISTRY_API_KEY }}
SCROLL_REGISTRY_API_SECRET: ${{ secrets.SCROLL_REGISTRY_API_SECRET }}
SCROLL_REGISTRY_BUCKET: ${{ secrets.SCROLL_REGISTRY_BUCKET_STAGING }}
DRUID_CLI_VERSION: v0.1.257
steps:
- uses: actions/checkout@v3
- uses: actions/setup-go@v3
with:
go-version: ">=1.19.3"
- uses: actions/setup-node@v4
with:
node-version: "22"
cache: npm
cache-dependency-path: ui/package-lock.json
- name: Build and test Scroll UI
working-directory: ui
run: npm ci && npm test && npm run type-check && npm run build
- run: apt update && apt install -y make
- name: Build scroll tree
env:
SCROLL_REGISTRY_HOST: ${{ secrets.SCROLL_REGISTRY_HOST }}
run: |
if [ "${{ github.event.pull_request.head.repo.full_name }}" = "${{ github.repository }}" ]; then
registry_host="${SCROLL_REGISTRY_HOST#http://}"
registry_host="${registry_host#https://}"
registry_host="${registry_host%%/*}"
export DRUID_COLDSTARTER_IMAGE="${registry_host}/${SCROLL_REGISTRY_PR_NAMESPACE}/druid:stable-pr${{ github.event.pull_request.number }}"
fi
make build-tree
- name: Get registry binary
if: github.event.pull_request.head.repo.full_name == github.repository
uses: robinraju/release-downloader@v1.7
with:
repository: "highcard-dev/druid-cli"
tag: ${{ env.DRUID_CLI_VERSION }}
fileName: "druid"
token: ${{ secrets.GO_REPO_TOKEN }}
- name: Install druid
if: github.event.pull_request.head.repo.full_name == github.repository
run: |
chmod +x druid
mv druid /usr/local/bin/druid
- name: Validate all scrolls
run: ./scripts/validate_all_scrolls.sh && bash ./scripts/validate_ui_coverage.sh
- name: Test bounded push parallelism
run: bash ./scripts/tests/push-parallel.test.sh
- name: Login to registry
id: registry_login
continue-on-error: true
if: github.event.pull_request.head.repo.full_name == github.repository
run: druid login --host ${{ secrets.SCROLL_REGISTRY_HOST }} --user '${{ secrets.SCROLL_REGISTRY_USER }}' --password ${{ secrets.SCROLL_REGISTRY_PASSWORD }}
- name: Check scroll changes
id: scroll-changes
run: |
set -euo pipefail
git fetch origin "${{ github.base_ref }}" --depth=1
if git diff --quiet "origin/${{ github.base_ref }}"..HEAD -- scrolls; then
echo "changed=false" >> "$GITHUB_OUTPUT"
echo "No scroll source changes; skipping preview push."
else
echo "changed=true" >> "$GITHUB_OUTPUT"
echo "Scroll source changed; publishing preview tags."
fi
- name: Push experimental PR tags
if: github.event.pull_request.head.repo.full_name == github.repository && steps.scroll-changes.outputs.changed == 'true' && steps.registry_login.outcome == 'success'
env:
SCROLL_REGISTRY_HOST: ${{ secrets.SCROLL_REGISTRY_HOST }}
SCROLL_REGISTRY_NAMESPACE: druid-team-experimental
SCROLL_REGISTRY_RUNTIME_NAMESPACE: druid-team
SCROLL_TAG_SUFFIX: -pr${{ github.event.pull_request.number }}
SCROLL_PUSH_CATEGORIES: "0"
run: bash ./scripts/push.sh
uses: ./.github/workflows/scrolls.yml
secrets: inherit
with:
release: false
50 changes: 8 additions & 42 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,47 +2,13 @@ name: Release Changed Pipeline
on:
workflow_dispatch:
push:
branches:
- master
tags:
- v*
branches: [master]
tags: [v*]
permissions:
contents: read
jobs:
build-deploy:
runs-on: self-hosted
env:
SCROLL_REGISTRY_ENDPOINT: ${{ secrets.SCROLL_REGISTRY_ENDPOINT }}
SCROLL_REGISTRY_API_KEY: ${{ secrets.SCROLL_REGISTRY_API_KEY }}
SCROLL_REGISTRY_API_SECRET: ${{ secrets.SCROLL_REGISTRY_API_SECRET }}
SCROLL_REGISTRY_BUCKET: ${{ secrets.SCROLL_REGISTRY_BUCKET_STAGING }}
SCROLL_REGISTRY_HOST: ${{ secrets.SCROLL_REGISTRY_HOST }}
SCROLL_REGISTRY_USER: ${{ secrets.SCROLL_REGISTRY_USER }}
SCROLL_REGISTRY_PASSWORD: ${{ secrets.SCROLL_REGISTRY_PASSWORD }}
DRUID_CLI_VERSION: v0.1.257
steps:
- uses: actions/checkout@v3
- uses: actions/setup-go@v3
with:
go-version: ">=1.19.3"
- uses: actions/setup-node@v4
with:
node-version: "22"
cache: npm
cache-dependency-path: ui/package-lock.json
- name: Build and test Scroll UI
working-directory: ui
run: npm ci && npm test && npm run type-check && npm run build
- name: Get registry binary
uses: robinraju/release-downloader@v1.7
with:
repository: "highcard-dev/druid-cli"
tag: ${{ env.DRUID_CLI_VERSION }}
fileName: "druid"
token: ${{ secrets.GO_REPO_TOKEN }}
- run: chmod +x druid
- name: Install druid
run: mv druid /usr/local/bin/druid
- name: druid version
run: druid version
- run: ./scripts/validate_all_scrolls.sh && bash ./scripts/validate_ui_coverage.sh
- name: Push release scrolls
run: bash ./scripts/push.sh
uses: ./.github/workflows/scrolls.yml
secrets: inherit
with:
release: true
78 changes: 78 additions & 0 deletions .github/workflows/scrolls.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
name: Build and push Scrolls
on:
workflow_call:
inputs:
release:
description: Update release tags instead of PR preview tags.
required: true
type: boolean
permissions:
contents: read

jobs:
build-and-push:
runs-on: ubuntu-latest
timeout-minutes: 360
concurrency:
group: scrolls-${{ inputs.release && 'release' || format('pr-{0}', github.event.pull_request.number) }}
cancel-in-progress: false
env:
DRUID_CLI_VERSION: ${{ vars.DRUID_CLI_VERSION || 'v0.1.257' }}
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
persist-credentials: false
- uses: actions/setup-go@v5
with:
go-version: "1.24.7"
- uses: actions/setup-node@v4
with:
node-version: "22"
cache: npm
cache-dependency-path: ui/package-lock.json
- name: Build and test Scroll UI
working-directory: ui
run: npm ci && npm test && npm run type-check && npm run build
- name: Build scroll tree
if: github.event_name == 'pull_request'
run: make build-tree
- name: Download released CLI
uses: robinraju/release-downloader@v1.7
with:
repository: highcard-dev/druid-cli
tag: ${{ env.DRUID_CLI_VERSION }}
fileName: druid
token: ${{ github.token }}
- name: Install CLI
run: |
mkdir -p "$RUNNER_TEMP/scroll-cli"
chmod +x druid
mv druid "$RUNNER_TEMP/scroll-cli/druid"
echo "$RUNNER_TEMP/scroll-cli" >> "$GITHUB_PATH"
- name: Validate catalog and UI
run: ./scripts/validate_all_scrolls.sh && bash ./scripts/validate_ui_coverage.sh
- name: Test catalog publication
run: bash ./scripts/tests/push-parallel.test.sh
- name: Push registry updates
# Fork PRs validate without receiving registry credentials.
if: >-
(inputs.release && github.event_name != 'pull_request') ||
(!inputs.release && github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository)
env:
SCROLL_REGISTRY_HOST: ${{ vars.SCROLL_REGISTRY_HOST || 'artifacts.druid.gg' }}
SCROLL_REGISTRY_NAMESPACE: ${{ inputs.release && (vars.SCROLL_REGISTRY_NAMESPACE || 'druid-team') || (vars.SCROLL_PREVIEW_NAMESPACE || 'druid-team-experimental') }}
SCROLL_REGISTRY_RUNTIME_NAMESPACE: ${{ vars.SCROLL_REGISTRY_RUNTIME_NAMESPACE || vars.SCROLL_REGISTRY_NAMESPACE || 'druid-team' }}
SCROLL_REGISTRY_USER: ${{ secrets.SCROLL_REGISTRY_USER }}
SCROLL_REGISTRY_PASSWORD: ${{ secrets.SCROLL_REGISTRY_PASSWORD }}
SCROLL_PUSH_CATEGORIES: ${{ inputs.release && '1' || '0' }}
DRUID_SCROLL_RUNTIME_IMAGE: ${{ vars.DRUID_SCROLL_RUNTIME_IMAGE }}
DRUID_SCROLL_STEAMCMD_IMAGE: ${{ vars.DRUID_SCROLL_STEAMCMD_IMAGE }}
run: |
set -euo pipefail
: "${SCROLL_REGISTRY_USER:?Set the SCROLL_REGISTRY_USER secret}"
: "${SCROLL_REGISTRY_PASSWORD:?Set the SCROLL_REGISTRY_PASSWORD secret}"
if [[ "${{ inputs.release }}" != "true" ]]; then
export SCROLL_TAG_SUFFIX="-pr${{ github.event.pull_request.number }}"
fi
bash ./scripts/push.sh
54 changes: 54 additions & 0 deletions docs/registry-publication.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# Registry updates

CI builds the Scroll UI, validates the catalog, logs into the registry and runs
`scripts/push.sh`. That shared script is the explicit catalog used by both CI
and local seeding. Release runs update the existing repositories and tags,
with category metadata pushed before Scroll artifacts.

There are no Druid account credentials, Core/Auth API calls, publication
snapshots, catalog registration calls or visibility changes. These repositories
are already public and registered; CI only updates their OCI artifacts.

## Configuration

Only two GitHub Actions secrets are required:

- `SCROLL_REGISTRY_USER`
- `SCROLL_REGISTRY_PASSWORD`

Optional repository variables override the existing defaults:

| Variable | Default |
| --- | --- |
| `SCROLL_REGISTRY_HOST` | `artifacts.druid.gg` |
| `SCROLL_REGISTRY_NAMESPACE` | `druid-team` |
| `SCROLL_PREVIEW_NAMESPACE` | `druid-team-experimental` |
| `SCROLL_REGISTRY_RUNTIME_NAMESPACE` | Release namespace |
| `DRUID_CLI_VERSION` | `v0.1.257` |

`DRUID_CLI_VERSION` selects the released CLI and default runtime image tag.
`DRUID_SCROLL_RUNTIME_IMAGE` and `DRUID_SCROLL_STEAMCMD_IMAGE` can override the
runtime image references independently. CI downloads the CLI release binary;
it does not check out or build CLI source.

Same-repository PRs push preview tags suffixed with `-pr<number>` into the
preview namespace without changing release tags or category metadata. Fork
PRs only build and validate. Runtime image references stay in the release
namespace. Both reusable-workflow callers inherit the repository secrets.

## Verification and dependencies

Run `bash scripts/tests/push-parallel.test.sh` and
`bash scripts/validate_all_scrolls.sh` locally. Use `SCROLL_PUSH_DRY_RUN=1 bash
scripts/push.sh` to inspect the catalog without registry writes. The push script
retains its serial default, bounded parallel option and category barrier.

The existing Go tree generator, Scroll validator and UI staging helper remain:
they generate or validate actual artifact content. The CI-only Go API publisher
and workflow parser are removed.

The Scroll UI still needs published Druid UI packages exporting
`ensureFileService`, `addAndRunCommand` and `stopCommand`, and a matching lockfile.
Local unpublished packages do not prove that a clean CI build will pass.
Registry updates themselves do not depend on Core publication APIs or a Druid
account.
16 changes: 14 additions & 2 deletions scripts/push.sh
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ SCROLL_PUSH_CATEGORIES="${SCROLL_PUSH_CATEGORIES:-1}"
SCROLL_PUSH_ARTIFACTS="${SCROLL_PUSH_ARTIFACTS:-1}"
SCROLL_PUSH_JOBS="${SCROLL_PUSH_JOBS:-1}"
SCROLL_PUSH_UI="${SCROLL_PUSH_UI:-1}"

if [[ ! "$SCROLL_PUSH_JOBS" =~ ^[1-9][0-9]*$ ]]; then
echo "SCROLL_PUSH_JOBS must be a positive integer (got: $SCROLL_PUSH_JOBS)" >&2
exit 2
Expand Down Expand Up @@ -78,6 +77,15 @@ login_if_configured() {
fi
}

rewrite_registry_reference() {
local value="$1"
if [[ "$value" == "$runtime_image" || "$value" == "$steamcmd_image" ]]; then
printf '%s' "$value"
return
fi
printf '%s' "${value//artifacts.druid.gg\/druid-team/$registry_prefix}"
}

run() {
local -a command=("$@")
local index
Expand All @@ -101,7 +109,11 @@ run() {
for index in "${!command[@]}"; do
command[$index]="${command[$index]//artifacts.druid.gg\/druid-team\/druid:v0.1.257-steamcmd/$steamcmd_image}"
command[$index]="${command[$index]//artifacts.druid.gg\/druid-team\/druid:v0.1.257/$runtime_image}"
command[$index]="${command[$index]//artifacts.druid.gg\/druid-team/$registry_prefix}"
if [[ "${command[2]:-}" == "category" ]]; then
command[$index]="${command[$index]//artifacts.druid.gg\/druid-team/$registry_prefix}"
else
command[$index]="$(rewrite_registry_reference "${command[$index]}")"
fi
done
if [[ "${command[0]:-}" == "druid" ]]; then
command[0]="$DRUID_BIN"
Expand Down
16 changes: 16 additions & 0 deletions scripts/stage-scroll-ui/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,22 @@ func stage(source, destination, bundle string) error {
if err := cp.Copy(source, destination); err != nil {
return fmt.Errorf("copy Scroll: %w", err)
}
// Version directories inherit family presentation. Keep it inside the staged
// release so the lifecycle never depends on mutable category tags afterward.
if _, err := os.Stat(filepath.Join(destination, ".meta")); os.IsNotExist(err) {
for parent := filepath.Dir(source); parent != "." && parent != filepath.Dir(parent); parent = filepath.Dir(parent) {
metadata := filepath.Join(parent, ".meta")
if info, err := os.Stat(metadata); err == nil && info.IsDir() {
if err := cp.Copy(metadata, filepath.Join(destination, ".meta")); err != nil {
return fmt.Errorf("copy presentation: %w", err)
}
break
}
if filepath.Base(parent) == "scrolls" {
break
}
}
}

scrollPath := filepath.Join(destination, "scroll.yaml")
scrollBytes, err := os.ReadFile(scrollPath)
Expand Down
10 changes: 10 additions & 0 deletions scripts/stage-scroll-ui/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,13 @@ func TestStageAddsPrivateUIAndMinecraftManifest(t *testing.T) {
if err := os.MkdirAll(source, 0755); err != nil {
t.Fatal(err)
}
metadata := filepath.Join(filepath.Dir(source), ".meta")
if err := os.MkdirAll(metadata, 0755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(metadata, "en-US.md"), []byte("---\nname: Paper\n---\nPresentation\n"), 0644); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(source, "scroll.yaml"), []byte("name: test\ndesc: test\nversion: 0.0.1\napp_version: 1.21.7\ncommands: {}\n"), 0644); err != nil {
t.Fatal(err)
}
Expand All @@ -40,6 +47,9 @@ func TestStageAddsPrivateUIAndMinecraftManifest(t *testing.T) {
if err := stage(source, destination, bundle); err != nil {
t.Fatal(err)
}
if _, err := os.Stat(filepath.Join(destination, ".meta", "en-US.md")); err != nil {
t.Fatal("family presentation missing from release", err)
}

stagedYAML, err := os.ReadFile(filepath.Join(destination, "scroll.yaml"))
if err != nil {
Expand Down
Loading
Loading