Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6d54c1e96c | ||
|
|
c0531aa048 |
@@ -6,6 +6,11 @@
|
|||||||
# was enabled once the tree was reformatted on main (ADR-ci-002 Amendment 1 /
|
# was enabled once the tree was reformatted on main (ADR-ci-002 Amendment 1 /
|
||||||
# issue #35). The release job (static binary for D2) and the platform matrix
|
# issue #35). The release job (static binary for D2) and the platform matrix
|
||||||
# layer on later, step by step.
|
# layer on later, step by step.
|
||||||
|
#
|
||||||
|
# A separate, lightweight `manifests` job logic-tests the package-manifest
|
||||||
|
# render scripts (Scoop/Homebrew) used by publish.yaml — bash + node only, no
|
||||||
|
# toolchain — so a render regression surfaces on the breaking push rather than
|
||||||
|
# weeks later at the next manual publish dispatch (ADR-0056 Amendment 2).
|
||||||
name: ci
|
name: ci
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
@@ -46,3 +51,21 @@ jobs:
|
|||||||
run: nix develop -c cargo clippy --all-targets -- -D warnings
|
run: nix develop -c cargo clippy --all-targets -- -D warnings
|
||||||
- name: test
|
- name: test
|
||||||
run: nix develop -c cargo test --no-fail-fast
|
run: nix develop -c cargo test --no-fail-fast
|
||||||
|
|
||||||
|
# Logic test for the package-manifest render scripts. Renders with DUMMY
|
||||||
|
# inputs and validates the output — it never publishes or touches the lazyeval
|
||||||
|
# repos (that is publish.yaml's manual job). Runs on the same image but skips
|
||||||
|
# nix: it needs only bash + node, both in the base image.
|
||||||
|
#
|
||||||
|
# NOTE: the CI image has no ruby, so the script's `ruby -c` formula syntax
|
||||||
|
# check is skipped here (it degrades gracefully); the Scoop JSON is still
|
||||||
|
# validated with node and both manifests' fields are asserted. Full formula
|
||||||
|
# syntax is checked dev-side (ruby present) on every pre-commit local run.
|
||||||
|
manifests:
|
||||||
|
runs-on: ci-public
|
||||||
|
container:
|
||||||
|
image: git.lazyeval.net/oli/rdbms-playground-ci:latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: render-script tests (Scoop + Homebrew)
|
||||||
|
run: bash scripts/test-package-renders.sh
|
||||||
|
|||||||
@@ -71,9 +71,133 @@ jobs:
|
|||||||
nix develop -c cargo publish --locked
|
nix develop -c cargo publish --locked
|
||||||
echo "published rdbms-playground $VER to crates.io."
|
echo "published rdbms-playground $VER to crates.io."
|
||||||
|
|
||||||
# Future manual publication targets go here as independent, idempotent jobs,
|
# Update the lazyeval Scoop bucket (Windows). Renders the manifest from the
|
||||||
# e.g.:
|
# release's .sha256 sidecars and commits it to lazyeval/scoop-bucket. Pushes
|
||||||
# scoop-bucket: { ... update the lazyeval Scoop bucket manifest ... }
|
# with the lazyeval-ci bot token (LAZYEVAL_PKG_TOKEN), which is scoped — via
|
||||||
# homebrew-tap: { ... update the lazyeval Homebrew formula ... }
|
# the bot's org-team membership — to the lazyeval package repos only, so a
|
||||||
# winget: { ... komac submit, or a manual PR helper ... }
|
# leak cannot touch oli/rdbms-playground.
|
||||||
# No `needs:` between them — each checks the target and skips if already done.
|
scoop-bucket:
|
||||||
|
runs-on: ci-public
|
||||||
|
container:
|
||||||
|
image: git.lazyeval.net/oli/rdbms-playground-ci:latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4 # default ref (main) — current render script
|
||||||
|
|
||||||
|
- name: update the lazyeval Scoop bucket (idempotent)
|
||||||
|
shell: bash
|
||||||
|
env:
|
||||||
|
TAG: ${{ inputs.tag }}
|
||||||
|
# Passed via env, never inlined into the script, so the value stays
|
||||||
|
# masked in logs; it only materialises in the clone URL at runtime.
|
||||||
|
PKG_TOKEN: ${{ secrets.LAZYEVAL_PKG_TOKEN }}
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
VER="${TAG#v}"
|
||||||
|
echo "scoop: targeting rdbms-playground $VER ($TAG)"
|
||||||
|
|
||||||
|
base="https://git.lazyeval.net/oli/rdbms-playground/releases/download/$TAG"
|
||||||
|
fetch_hash() {
|
||||||
|
local asset="$1" line
|
||||||
|
echo "scoop: fetching $asset.sha256" >&2
|
||||||
|
line=$(curl -fsSL "$base/$asset.sha256") \
|
||||||
|
|| { echo "ERROR: cannot fetch $asset.sha256 — is $TAG released with assets?" >&2; exit 1; }
|
||||||
|
# First whitespace-delimited field is the hash. `read` is a bash
|
||||||
|
# builtin (no awk, which the slim CI image may lack).
|
||||||
|
local hash _
|
||||||
|
read -r hash _ <<<"$line"
|
||||||
|
printf '%s' "$hash"
|
||||||
|
}
|
||||||
|
h_x64=$(fetch_hash "rdbms-playground-$TAG-x86_64-pc-windows-gnu.exe")
|
||||||
|
h_arm=$(fetch_hash "rdbms-playground-$TAG-aarch64-pc-windows-gnullvm.exe")
|
||||||
|
|
||||||
|
echo "scoop: rendering manifest"
|
||||||
|
bash scripts/render-scoop-manifest.sh "$VER" "$h_x64" "$h_arm" > /tmp/rdbms-playground.json
|
||||||
|
node -e 'JSON.parse(require("fs").readFileSync("/tmp/rdbms-playground.json","utf8"))' \
|
||||||
|
|| { echo "ERROR: rendered Scoop manifest is not valid JSON" >&2; exit 1; }
|
||||||
|
|
||||||
|
work=$(mktemp -d)
|
||||||
|
echo "scoop: cloning lazyeval/scoop-bucket"
|
||||||
|
git clone --depth 1 "https://lazyeval-ci:${PKG_TOKEN}@git.lazyeval.net/lazyeval/scoop-bucket.git" "$work"
|
||||||
|
cp /tmp/rdbms-playground.json "$work/rdbms-playground.json"
|
||||||
|
|
||||||
|
cd "$work"
|
||||||
|
git config user.name "lazyeval-ci"
|
||||||
|
git config user.email "ci@lazyeval.net"
|
||||||
|
git add rdbms-playground.json
|
||||||
|
if git diff --cached --quiet; then
|
||||||
|
echo "scoop: manifest already at $VER — nothing to commit."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
git commit -m "rdbms-playground $VER"
|
||||||
|
# Push to main explicitly: a freshly-created (empty) repo clone may put
|
||||||
|
# the first commit on a differently-named local branch. Assumes the
|
||||||
|
# bucket/tap default branch is `main` (Gitea's default for new repos).
|
||||||
|
git push origin HEAD:main
|
||||||
|
echo "scoop: bucket updated to rdbms-playground $VER."
|
||||||
|
|
||||||
|
# Update the lazyeval Homebrew tap (macOS + Linux). Same shape as scoop-bucket;
|
||||||
|
# writes Formula/rdbms-playground.rb into lazyeval/homebrew-tap.
|
||||||
|
homebrew-tap:
|
||||||
|
runs-on: ci-public
|
||||||
|
container:
|
||||||
|
image: git.lazyeval.net/oli/rdbms-playground-ci:latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: update the lazyeval Homebrew tap (idempotent)
|
||||||
|
shell: bash
|
||||||
|
env:
|
||||||
|
TAG: ${{ inputs.tag }}
|
||||||
|
PKG_TOKEN: ${{ secrets.LAZYEVAL_PKG_TOKEN }}
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
VER="${TAG#v}"
|
||||||
|
echo "homebrew: targeting rdbms-playground $VER ($TAG)"
|
||||||
|
|
||||||
|
base="https://git.lazyeval.net/oli/rdbms-playground/releases/download/$TAG"
|
||||||
|
fetch_hash() {
|
||||||
|
local asset="$1" line
|
||||||
|
echo "homebrew: fetching $asset.sha256" >&2
|
||||||
|
line=$(curl -fsSL "$base/$asset.sha256") \
|
||||||
|
|| { echo "ERROR: cannot fetch $asset.sha256 — is $TAG released with assets?" >&2; exit 1; }
|
||||||
|
# First whitespace-delimited field is the hash. `read` is a bash
|
||||||
|
# builtin (no awk, which the slim CI image may lack).
|
||||||
|
local hash _
|
||||||
|
read -r hash _ <<<"$line"
|
||||||
|
printf '%s' "$hash"
|
||||||
|
}
|
||||||
|
mac_arm=$(fetch_hash "rdbms-playground-$TAG-aarch64-apple-darwin")
|
||||||
|
mac_x64=$(fetch_hash "rdbms-playground-$TAG-x86_64-apple-darwin")
|
||||||
|
lin_arm=$(fetch_hash "rdbms-playground-$TAG-aarch64-unknown-linux-musl")
|
||||||
|
lin_x64=$(fetch_hash "rdbms-playground-$TAG-x86_64-unknown-linux-musl")
|
||||||
|
|
||||||
|
echo "homebrew: rendering formula"
|
||||||
|
bash scripts/render-homebrew-formula.sh "$VER" "$mac_arm" "$mac_x64" "$lin_arm" "$lin_x64" \
|
||||||
|
> /tmp/rdbms-playground.rb
|
||||||
|
grep -q '^class RdbmsPlayground < Formula$' /tmp/rdbms-playground.rb \
|
||||||
|
|| { echo "ERROR: rendered formula looks malformed" >&2; exit 1; }
|
||||||
|
|
||||||
|
work=$(mktemp -d)
|
||||||
|
echo "homebrew: cloning lazyeval/homebrew-tap"
|
||||||
|
git clone --depth 1 "https://lazyeval-ci:${PKG_TOKEN}@git.lazyeval.net/lazyeval/homebrew-tap.git" "$work"
|
||||||
|
mkdir -p "$work/Formula"
|
||||||
|
cp /tmp/rdbms-playground.rb "$work/Formula/rdbms-playground.rb"
|
||||||
|
|
||||||
|
cd "$work"
|
||||||
|
git config user.name "lazyeval-ci"
|
||||||
|
git config user.email "ci@lazyeval.net"
|
||||||
|
git add Formula/rdbms-playground.rb
|
||||||
|
if git diff --cached --quiet; then
|
||||||
|
echo "homebrew: formula already at $VER — nothing to commit."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
git commit -m "rdbms-playground $VER"
|
||||||
|
# Push to main explicitly: a freshly-created (empty) repo clone may put
|
||||||
|
# the first commit on a differently-named local branch. Assumes the
|
||||||
|
# bucket/tap default branch is `main` (Gitea's default for new repos).
|
||||||
|
git push origin HEAD:main
|
||||||
|
echo "homebrew: tap updated to rdbms-playground $VER."
|
||||||
|
|
||||||
|
# winget remains a future sibling job here (komac on Linux CI, or a manual PR
|
||||||
|
# to microsoft/winget-pkgs). No `needs:` between jobs — each is independent and
|
||||||
|
# idempotent, so one failing or being added never breaks another.
|
||||||
|
|||||||
@@ -110,3 +110,69 @@ API pre-check + `cargo publish` as the backstop) — so future
|
|||||||
Scoop/Homebrew/winget jobs can be added alongside without breaking one
|
Scoop/Homebrew/winget jobs can be added alongside without breaking one
|
||||||
another or re-runs. The first such job's `tag`-vs-`Cargo.toml` guard
|
another or re-runs. The first such job's `tag`-vs-`Cargo.toml` guard
|
||||||
mirrors `release.yaml`.
|
mirrors `release.yaml`.
|
||||||
|
|
||||||
|
## Amendment 2 — 2026-06-19: Scoop bucket + Homebrew tap (D3 §3b/§3c)
|
||||||
|
|
||||||
|
Two more package managers wired as **sibling `publish.yaml` jobs**
|
||||||
|
(`scoop-bucket`, `homebrew-tap`), following Amendment 1's independent +
|
||||||
|
idempotent pattern. Each fetches the release's `.sha256` sidecars, renders
|
||||||
|
a manifest, and commits it into a per-manager repo.
|
||||||
|
|
||||||
|
**Repos — org-level and multi-package.** Both live under a new **`lazyeval`
|
||||||
|
Gitea organisation** (created with the `oli` account, which gives the
|
||||||
|
`git.lazyeval.net/lazyeval/...` paths): `lazyeval/scoop-bucket` and
|
||||||
|
`lazyeval/homebrew-tap`. A Scoop *bucket* and a Homebrew *tap* are by
|
||||||
|
definition **collections of manifests**, so these are reusable for future
|
||||||
|
tools, not single-package repos. Homebrew's `homebrew-` repo-name prefix is
|
||||||
|
mandatory (→ referenced as `lazyeval/tap`); Scoop's bucket name is free.
|
||||||
|
Users: `scoop bucket add lazyeval <url>` (the label is local/arbitrary;
|
||||||
|
only the URL owner is real) then `scoop install rdbms-playground`; and
|
||||||
|
`brew tap lazyeval/tap https://git.lazyeval.net/lazyeval/homebrew-tap`
|
||||||
|
(the explicit-URL form — the `user/repo` shorthand assumes GitHub) then
|
||||||
|
`brew install lazyeval/tap/rdbms-playground`.
|
||||||
|
|
||||||
|
**Credential — a scoped bot user, not an `oli` PAT.** Gitea PATs scope by
|
||||||
|
**permission category, not per-repository** (`write:repository` grants
|
||||||
|
write to *every* repo the account can reach — there is no repo picker like
|
||||||
|
GitHub fine-grained PATs). So an `oli` token would also be able to push to
|
||||||
|
`oli/rdbms-playground` itself. Instead a dedicated bot user **`lazyeval-ci`**
|
||||||
|
is a member of a `lazyeval` org team with **Write** to the package repos
|
||||||
|
only; its `write:repository` PAT is therefore effectively scoped to those
|
||||||
|
repos and **cannot touch the main project repo**. Stored as the
|
||||||
|
`LAZYEVAL_PKG_TOKEN` Actions secret on `oli/rdbms-playground` (where the
|
||||||
|
workflow runs — *not* an org secret, which wouldn't reach a user-repo
|
||||||
|
workflow; *not* on the target repos, which only receive pushes). Passed via
|
||||||
|
`env:` (never inlined), so it stays masked and only materialises in the
|
||||||
|
clone URL at runtime; pushes go to `HEAD:main` (assumes the repos default
|
||||||
|
to `main`).
|
||||||
|
|
||||||
|
**Render scripts are dependency-free bash.** The CI job container is
|
||||||
|
`node:22-bookworm-slim` — **no jq, no ruby** — so
|
||||||
|
`scripts/render-{scoop-manifest,homebrew-formula}.sh` are pure bash
|
||||||
|
(heredocs, no external deps) taking a version + the relevant hashes and
|
||||||
|
emitting the manifest on stdout. `scripts/test-package-renders.sh` is their
|
||||||
|
test (JSON validated with `node` — present in the image — plus `jq`/`ruby`
|
||||||
|
when available; field-level assertions). The job validates the rendered
|
||||||
|
Scoop JSON with `node -e JSON.parse` before committing.
|
||||||
|
|
||||||
|
**Manifest specifics.**
|
||||||
|
- *Scoop* (`rdbms-playground.json` at bucket root): `64bit` =
|
||||||
|
`x86_64-pc-windows-gnu.exe`, `arm64` =
|
||||||
|
`aarch64-pc-windows-gnullvm.exe`; each URL carries a
|
||||||
|
`#/rdbms-playground.exe` rename fragment so the `bin` shim resolves
|
||||||
|
regardless of version. Carries `checkver` (lets `scoop status` / the
|
||||||
|
community excavator see lag) but **no `autoupdate`** — our pipeline is the
|
||||||
|
updater.
|
||||||
|
- *Homebrew* (`Formula/rdbms-playground.rb`): `on_macos`/`on_linux` ×
|
||||||
|
`on_arm`/`on_intel` selecting the four bare-binary assets (macOS direct;
|
||||||
|
Linux = the static `-musl` build). **Windows absent** — Homebrew has no
|
||||||
|
Windows port. `install` drops the single staged binary under a stable
|
||||||
|
name; the `test` block runs `--version`.
|
||||||
|
|
||||||
|
**Unverified (validate on first real use):** an actual `scoop install` and
|
||||||
|
`brew install`/`brew test`; the `HEAD:main` default-branch assumption; and
|
||||||
|
whether macOS Gatekeeper accepts the **ad-hoc-signed** mac binary via
|
||||||
|
`brew` (execution should be fine — ad-hoc satisfies arm64's signing
|
||||||
|
requirement and `brew`'s curl download sets no quarantine xattr, unlike a
|
||||||
|
browser download — but this rides on the still-parked Developer-ID signing
|
||||||
|
decision). **Remaining D3:** winget (komac on Linux CI, or a manual PR).
|
||||||
|
|||||||
+1
-1
@@ -68,4 +68,4 @@ This directory contains the project's ADRs, recorded per
|
|||||||
- [ADR-0053 — Contextual `hint` command and keybinding](0053-contextual-hint-command-and-keybinding.md) — **Accepted, implemented 2026-06-15** (Phases A–D; closes **A1** + requirements **H2**). Settles the `hint` slot ADR-0003 left "ADR pending"; closes the last open piece of **A1** and tracks requirements **H2**. **Two surfaces:** an **F1 keybinding** that renders a deep hint for the *live* partial input without submitting (the primary path — a submitted `hint` command can't see the buffer it would help with, since Enter empties it), and a submitted **`hint` command** that expands on the *most recent error*. **No topic argument** (contextual only — `help <topic>` already owns explicit reference). Introduces a **tier-3 teaching layer**, deeper than the existing tier-1 (colour / error headline) and tier-2 (ambient one-liner; and the error `hint:`, which is shown **by default** since `Verbosity::Verbose` is the default — `messages short` is the opt-*out*); without it `hint` would just duplicate what's already on screen. Tier-3 content lives in the catalogue under `hint.cmd.<hint_id>` (per command form) and `hint.err.<class>` (per error/diagnostic class), each a structured `what`/`example`/`concept` block rendered via a new `note_hint*` family with `OutputStyleClass::Hint`. **Keyed per-form via a new `hint_ids: &[&str]` field on `CommandNode` mirroring `usage_ids`** (revised in Phase B): a per-*node* key proved too coarse — `add`/`drop`/`show`/`create` are each one node spanning many forms, and a live-input hint for `add 1:n relationship` must be specific to relationships; `hint_key_for_input_in_mode` reuses `usage_key_for_input_in_mode`'s form-word disambiguation, and covers the advanced-SQL forms whose `usage_ids` are empty. Not keyed off `help_id` (it is `None` on the advanced-SQL nodes purely to dedup the `help` list; that parallel gap is issue **#36**). **Clause-concept hints** (`on delete` actions, constraint slots, `with pk`, cardinality) are a recorded **deferred extension** (`hint.concept.<topic>`, issue **#37**) — per-form is the right tier-3 granularity, with position-awareness owned by tier-2 + the live `Next:` line. Runtime `translate_error` classes resolve via stored `last_error_hint_key` (`hint` command / empty-F1). (The second route — pre-submit `diagnostic.*` read live from the walker on the F1 path — is **deferred**, issue **#38**: `Diagnostic` carries no class key.) Adds `AppCommand::Hint`, a `HINT` grammar node + REGISTRY entry, the `hint_ids` field, and `last_error_hint_key`; F1 is a read-only overlay (buffer + completion memo untouched). **Content is the bulk of the work** (the mechanism is ~a day): v1 scope = ~37 command forms + 9 runtime error classes (comprehensive for those, ~57 blocks), authored **exemplars-first** (voice approved in this ADR's `/runda` review, then mass-authored in batches), enforced by a **comprehensiveness coverage test**, with graceful fall-back to tier-2 if a key is ever missing. The **pre-submit-diagnostic route + ~33 `diagnostic.*` blocks were deferred** (issue **#38**) — `Diagnostic` carries no class key, so the route needs a broad change for marginal value (tier-2 already surfaces diagnostics; many duplicate runtime classes). Forks user-chosen: two-surface model; **F1** (vs `?` / a chord); no-arg; comprehensive-for-commands-and-errors scope; exemplars-first; diagnostics deferred. OOS: per-topic `hint <topic>` (rejected — overlaps `help`); always-on tier-3 (rejected — keeps ambient terse); non-`en-US` locales + success-command teaching (deferred); clause-concept hints (issue #37); the diagnostic route (issue #38); the `help`-side advanced-SQL gap (issue #36)
|
- [ADR-0053 — Contextual `hint` command and keybinding](0053-contextual-hint-command-and-keybinding.md) — **Accepted, implemented 2026-06-15** (Phases A–D; closes **A1** + requirements **H2**). Settles the `hint` slot ADR-0003 left "ADR pending"; closes the last open piece of **A1** and tracks requirements **H2**. **Two surfaces:** an **F1 keybinding** that renders a deep hint for the *live* partial input without submitting (the primary path — a submitted `hint` command can't see the buffer it would help with, since Enter empties it), and a submitted **`hint` command** that expands on the *most recent error*. **No topic argument** (contextual only — `help <topic>` already owns explicit reference). Introduces a **tier-3 teaching layer**, deeper than the existing tier-1 (colour / error headline) and tier-2 (ambient one-liner; and the error `hint:`, which is shown **by default** since `Verbosity::Verbose` is the default — `messages short` is the opt-*out*); without it `hint` would just duplicate what's already on screen. Tier-3 content lives in the catalogue under `hint.cmd.<hint_id>` (per command form) and `hint.err.<class>` (per error/diagnostic class), each a structured `what`/`example`/`concept` block rendered via a new `note_hint*` family with `OutputStyleClass::Hint`. **Keyed per-form via a new `hint_ids: &[&str]` field on `CommandNode` mirroring `usage_ids`** (revised in Phase B): a per-*node* key proved too coarse — `add`/`drop`/`show`/`create` are each one node spanning many forms, and a live-input hint for `add 1:n relationship` must be specific to relationships; `hint_key_for_input_in_mode` reuses `usage_key_for_input_in_mode`'s form-word disambiguation, and covers the advanced-SQL forms whose `usage_ids` are empty. Not keyed off `help_id` (it is `None` on the advanced-SQL nodes purely to dedup the `help` list; that parallel gap is issue **#36**). **Clause-concept hints** (`on delete` actions, constraint slots, `with pk`, cardinality) are a recorded **deferred extension** (`hint.concept.<topic>`, issue **#37**) — per-form is the right tier-3 granularity, with position-awareness owned by tier-2 + the live `Next:` line. Runtime `translate_error` classes resolve via stored `last_error_hint_key` (`hint` command / empty-F1). (The second route — pre-submit `diagnostic.*` read live from the walker on the F1 path — is **deferred**, issue **#38**: `Diagnostic` carries no class key.) Adds `AppCommand::Hint`, a `HINT` grammar node + REGISTRY entry, the `hint_ids` field, and `last_error_hint_key`; F1 is a read-only overlay (buffer + completion memo untouched). **Content is the bulk of the work** (the mechanism is ~a day): v1 scope = ~37 command forms + 9 runtime error classes (comprehensive for those, ~57 blocks), authored **exemplars-first** (voice approved in this ADR's `/runda` review, then mass-authored in batches), enforced by a **comprehensiveness coverage test**, with graceful fall-back to tier-2 if a key is ever missing. The **pre-submit-diagnostic route + ~33 `diagnostic.*` blocks were deferred** (issue **#38**) — `Diagnostic` carries no class key, so the route needs a broad change for marginal value (tier-2 already surfaces diagnostics; many duplicate runtime classes). Forks user-chosen: two-surface model; **F1** (vs `?` / a chord); no-arg; comprehensive-for-commands-and-errors scope; exemplars-first; diagnostics deferred. OOS: per-topic `hint <topic>` (rejected — overlaps `help`); always-on tier-3 (rejected — keeps ambient terse); non-`en-US` locales + success-command teaching (deferred); clause-concept hints (issue #37); the diagnostic route (issue #38); the `help`-side advanced-SQL gap (issue #36)
|
||||||
- [ADR-0054 — Release versioning policy + version surfaces (`--version` / `version`)](0054-release-versioning-and-version-surfaces.md) — **Accepted + implemented 2026-06-16** (plan: `docs/plans/20260616-public-availability.md`, step 1 on the road to public availability; no prior issue/`requirements.md` item — an untracked gap). Fixes the **tag↔crate-version decoupling**: `Cargo.toml` built `0.1.0` while `release.yaml` named assets from the git tag, so a binary could report a version different from the asset it shipped in. **Decision:** `Cargo.toml` `version` is the **single source of truth** (read via `env!("CARGO_PKG_VERSION")`, no tag-injection); two surfaces report it through one `cli::version_text()` → catalog `cli.version_line` — a **`--version` / `-V`** CLI flag (mirrors `--help`, prints+exits in `main.rs`) and an in-app **`version`** command (REGISTRY node `app::VERSION`, `AppCommand::Version`, emits via `note_system`); and a **release-CI version guard** (`release.yaml` `test` job reads the `[package]` version from `Cargo.toml` and **fails the release** unless the `v*` tag equals `v<version>`; the guard's parse was later switched from `cargo metadata | node` to a `grep` on Cargo.toml after the former broke on the flake devShell's stdout banner). Release ritual: bump `Cargo.toml` → commit → tag → push. New keys `cli.version_line` + `help.app.version` + `parse.usage.version` + `hint.cmd.version.{what,example}` (the new REGISTRY command pulls in the comprehensiveness coverage gate). Rejected: tag-as-source (makes Cargo.toml lie). Deferred: git-hash/build-date enrichment (behind the same `version_text()` seam); UI placement beyond the command. Tested test-first: CLI parse (`--version`/`-V`/default-off), `version_text()` carries `CARGO_PKG_VERSION`, the in-app command parses + emits. Also corrected a stale `release.yaml` header comment ("macOS is deferred" → built by the dispatched `release-macos.yaml`).
|
- [ADR-0054 — Release versioning policy + version surfaces (`--version` / `version`)](0054-release-versioning-and-version-surfaces.md) — **Accepted + implemented 2026-06-16** (plan: `docs/plans/20260616-public-availability.md`, step 1 on the road to public availability; no prior issue/`requirements.md` item — an untracked gap). Fixes the **tag↔crate-version decoupling**: `Cargo.toml` built `0.1.0` while `release.yaml` named assets from the git tag, so a binary could report a version different from the asset it shipped in. **Decision:** `Cargo.toml` `version` is the **single source of truth** (read via `env!("CARGO_PKG_VERSION")`, no tag-injection); two surfaces report it through one `cli::version_text()` → catalog `cli.version_line` — a **`--version` / `-V`** CLI flag (mirrors `--help`, prints+exits in `main.rs`) and an in-app **`version`** command (REGISTRY node `app::VERSION`, `AppCommand::Version`, emits via `note_system`); and a **release-CI version guard** (`release.yaml` `test` job reads the `[package]` version from `Cargo.toml` and **fails the release** unless the `v*` tag equals `v<version>`; the guard's parse was later switched from `cargo metadata | node` to a `grep` on Cargo.toml after the former broke on the flake devShell's stdout banner). Release ritual: bump `Cargo.toml` → commit → tag → push. New keys `cli.version_line` + `help.app.version` + `parse.usage.version` + `hint.cmd.version.{what,example}` (the new REGISTRY command pulls in the comprehensiveness coverage gate). Rejected: tag-as-source (makes Cargo.toml lie). Deferred: git-hash/build-date enrichment (behind the same `version_text()` seam); UI placement beyond the command. Tested test-first: CLI parse (`--version`/`-V`/default-off), `version_text()` carries `CARGO_PKG_VERSION`, the in-app command parses + emits. Also corrected a stale `release.yaml` header comment ("macOS is deferred" → built by the dispatched `release-macos.yaml`).
|
||||||
- [ADR-0055 — `curl | sh` install script (`scripts/install.sh`)](0055-curl-sh-install-script.md) — **Accepted + implemented 2026-06-17** (plan: `docs/plans/20260616-public-availability.md`, step 2; tracked by plan + ADR, no Gitea issue — user decision). A one-line installer (`curl -fsSL <gitea-raw>/scripts/install.sh | sh`) so beginners don't hand-pick an asset + `chmod +x`. **POSIX `sh`** (shellcheck-clean), detects `uname` OS/arch → target triple (**Linux → the fully-static `*-musl`** build, macOS → `*-apple-darwin`; `amd64`/`arm64` aliased; **Windows rejected** → Scoop/winget/releases page), resolves the version from the **`releases/latest`** API (or `RDBMS_VERSION` to pin), downloads the asset **and its `.sha256` and verifies it** (mismatch aborts), installs to `~/.local/bin` (`RDBMS_INSTALL_DIR` override) with a PATH hint. Testing seams: `RDBMS_OS`/`RDBMS_ARCH` + `--print-target`. macOS note: `curl` downloads aren't Gatekeeper-quarantined so the ad-hoc binary runs as-is (Developer-ID + notarization is the postponed signing task). **Verified end-to-end against the live public `v0.1.0`** (all platform mappings, pinned + latest, checksum incl. tamper-rejection, install + run). Rejected: website-domain hosting (extra moving part; Gitea raw is simplest); deferred: uploading the script as a release asset, and a **shellcheck CI gate** (shellcheck isn't in the flake — touches ADR-ci-002). **Amendment 1 (2026-06-17):** added a Windows **`scripts/install.ps1`** (`irm | iex`; maps host CPU → our `*-windows-gnu`/`-gnullvm` `.exe`, SHA-256-verifies, installs to `%LOCALAPPDATA%\Programs\…` + user PATH) — user chose both a one-liner *and* Scoop/winget; **written but untested from this env** (no PowerShell — validate on Windows).
|
- [ADR-0055 — `curl | sh` install script (`scripts/install.sh`)](0055-curl-sh-install-script.md) — **Accepted + implemented 2026-06-17** (plan: `docs/plans/20260616-public-availability.md`, step 2; tracked by plan + ADR, no Gitea issue — user decision). A one-line installer (`curl -fsSL <gitea-raw>/scripts/install.sh | sh`) so beginners don't hand-pick an asset + `chmod +x`. **POSIX `sh`** (shellcheck-clean), detects `uname` OS/arch → target triple (**Linux → the fully-static `*-musl`** build, macOS → `*-apple-darwin`; `amd64`/`arm64` aliased; **Windows rejected** → Scoop/winget/releases page), resolves the version from the **`releases/latest`** API (or `RDBMS_VERSION` to pin), downloads the asset **and its `.sha256` and verifies it** (mismatch aborts), installs to `~/.local/bin` (`RDBMS_INSTALL_DIR` override) with a PATH hint. Testing seams: `RDBMS_OS`/`RDBMS_ARCH` + `--print-target`. macOS note: `curl` downloads aren't Gatekeeper-quarantined so the ad-hoc binary runs as-is (Developer-ID + notarization is the postponed signing task). **Verified end-to-end against the live public `v0.1.0`** (all platform mappings, pinned + latest, checksum incl. tamper-rejection, install + run). Rejected: website-domain hosting (extra moving part; Gitea raw is simplest); deferred: uploading the script as a release asset, and a **shellcheck CI gate** (shellcheck isn't in the flake — touches ADR-ci-002). **Amendment 1 (2026-06-17):** added a Windows **`scripts/install.ps1`** (`irm | iex`; maps host CPU → our `*-windows-gnu`/`-gnullvm` `.exe`, SHA-256-verifies, installs to `%LOCALAPPDATA%\Programs\…` + user PATH) — user chose both a one-liner *and* Scoop/winget; **written but untested from this env** (no PowerShell — validate on Windows).
|
||||||
- [ADR-0056 — crates.io publish-readiness + `cargo binstall` metadata (D3)](0056-crates-io-and-cargo-binstall.md) — **Prepared 2026-06-17** (plan step 3a; tracked by plan + ADR). Makes the crate **ready to publish** to crates.io (user decision) and adds `cargo-binstall` metadata; the actual `cargo publish` is a **gated, irreversible maintainer step**. Manifest: drops `publish = false`; adds `homepage` (relplay.org), `keywords`, `categories`, and an `exclude` (`/website`,`/docs`,`/.gitea`,`/.codegraph`) trimming the crate from 585 files/8.3 MiB → **353/913 KiB compressed** (code-only). Authors **`README.md`** (engine-neutral, simple/advanced-mode wording; install via curl|sh/binstall/source/prebuilt) and **`LICENSE-MIT`** (© Lazy Evaluation Ltd — *confirm holder*); the canonical **`LICENSE-APACHE`** is deferred to the maintainer (don't ship retyped legal text) — the SPDX `license` field already satisfies crates.io. **binstall** (syntax verified vs cargo-binstall SUPPORT.md): `pkg-fmt = "bin"` (bare binaries), `pkg-url` spelled `v{ version }` (the placeholder omits the `v`), plus per-target **`overrides`** mapping the common host triples to the assets we ship — `*-linux-gnu` → the static `*-linux-musl` build, `*-pc-windows-msvc` → `*-gnu`/`-gnullvm` `.exe` (macOS matches directly; the docs promise no automatic fallback). **Ordering:** publish at a **new tagged version whose release exists**, after the release — **not `0.1.0`** (diverges from the already-released 0.1.0 binaries that predate `--version`). Verified: `cargo publish --dry-run` packages + verify-builds; `cargo metadata` confirms the binstall block + 4 overrides. **Unverified:** a real `cargo binstall` run (not a dep; nothing on crates.io yet) — validate at first publish. Rejected: cargo-dist (GitHub-centric). Maintainer follow-ups: confirm © holder, add canonical `LICENSE-APACHE`, real binstall validation. **Amendment 1 (2026-06-18):** `0.2.0` **published live** (crates.io; `cargo install` + `cargo binstall` verified — the unverified-overrides caveat is resolved), via a new **manual `workflow_dispatch`** workflow `.gitea/workflows/publish.yaml` (mirrors `release-macos.yaml`; `tag` input; `cargo publish` with a crate-scoped `CARGO_REGISTRY_TOKEN` secret). Publish stays **manual** by decision — irreversible (keeps the token off every tag push), the split release (tag Linux/Windows + dispatched macOS) makes a human the "all assets up" gate, and crates.io has no Gitea-Actions trusted-publishing path. Each registry is its **own idempotent job** (crates.io job no-ops if the version exists) so Scoop/Homebrew/winget can be added as sibling jobs without interfering.
|
- [ADR-0056 — crates.io publish-readiness + `cargo binstall` metadata (D3)](0056-crates-io-and-cargo-binstall.md) — **Prepared 2026-06-17** (plan step 3a; tracked by plan + ADR). Makes the crate **ready to publish** to crates.io (user decision) and adds `cargo-binstall` metadata; the actual `cargo publish` is a **gated, irreversible maintainer step**. Manifest: drops `publish = false`; adds `homepage` (relplay.org), `keywords`, `categories`, and an `exclude` (`/website`,`/docs`,`/.gitea`,`/.codegraph`) trimming the crate from 585 files/8.3 MiB → **353/913 KiB compressed** (code-only). Authors **`README.md`** (engine-neutral, simple/advanced-mode wording; install via curl|sh/binstall/source/prebuilt) and **`LICENSE-MIT`** (© Lazy Evaluation Ltd — *confirm holder*); the canonical **`LICENSE-APACHE`** is deferred to the maintainer (don't ship retyped legal text) — the SPDX `license` field already satisfies crates.io. **binstall** (syntax verified vs cargo-binstall SUPPORT.md): `pkg-fmt = "bin"` (bare binaries), `pkg-url` spelled `v{ version }` (the placeholder omits the `v`), plus per-target **`overrides`** mapping the common host triples to the assets we ship — `*-linux-gnu` → the static `*-linux-musl` build, `*-pc-windows-msvc` → `*-gnu`/`-gnullvm` `.exe` (macOS matches directly; the docs promise no automatic fallback). **Ordering:** publish at a **new tagged version whose release exists**, after the release — **not `0.1.0`** (diverges from the already-released 0.1.0 binaries that predate `--version`). Verified: `cargo publish --dry-run` packages + verify-builds; `cargo metadata` confirms the binstall block + 4 overrides. **Unverified:** a real `cargo binstall` run (not a dep; nothing on crates.io yet) — validate at first publish. Rejected: cargo-dist (GitHub-centric). Maintainer follow-ups: confirm © holder, add canonical `LICENSE-APACHE`, real binstall validation. **Amendment 1 (2026-06-18):** `0.2.0` **published live** (crates.io; `cargo install` + `cargo binstall` verified — the unverified-overrides caveat is resolved), via a new **manual `workflow_dispatch`** workflow `.gitea/workflows/publish.yaml` (mirrors `release-macos.yaml`; `tag` input; `cargo publish` with a crate-scoped `CARGO_REGISTRY_TOKEN` secret). Publish stays **manual** by decision — irreversible (keeps the token off every tag push), the split release (tag Linux/Windows + dispatched macOS) makes a human the "all assets up" gate, and crates.io has no Gitea-Actions trusted-publishing path. Each registry is its **own idempotent job** (crates.io job no-ops if the version exists) so Scoop/Homebrew/winget can be added as sibling jobs without interfering. **Amendment 2 (2026-06-19):** **Scoop + Homebrew wired** (D3 §3b/§3c) as sibling `publish.yaml` jobs (`scoop-bucket`, `homebrew-tap`) that render manifests from the release `.sha256` sidecars and push to **org-level, multi-package** repos `lazyeval/scoop-bucket` + `lazyeval/homebrew-tap`. Credential: a scoped bot user **`lazyeval-ci`** (Gitea PATs scope by permission-category, not per-repo, so an `oli` token would over-reach to the main repo) on a `lazyeval` org team with Write to the package repos only; its PAT is the `LAZYEVAL_PKG_TOKEN` secret on `oli/rdbms-playground`. Render scripts (`scripts/render-{scoop-manifest,homebrew-formula}.sh`) are **dependency-free bash** (CI image `node:22-slim` has no jq/ruby), tested by `scripts/test-package-renders.sh`. Scoop: `#/`-rename fragment + `checkver`, no `autoupdate`. Homebrew: `on_macos`/`on_linux`×arch bare-binary formula, no Windows. **Unverified:** real `scoop`/`brew install`, the `HEAD:main` branch assumption, macOS Gatekeeper-via-brew (ad-hoc sign). **Remaining D3:** winget.
|
||||||
|
|||||||
+10
-5
@@ -85,11 +85,16 @@ since ADR-0027.)
|
|||||||
No target requires anything the user must install. ADR-ci-003.)*
|
No target requires anything the user must install. ADR-ci-003.)*
|
||||||
- [ ] **D3** Released via prebuilt binaries plus Homebrew, Scoop,
|
- [ ] **D3** Released via prebuilt binaries plus Homebrew, Scoop,
|
||||||
`winget`, and `cargo binstall`.
|
`winget`, and `cargo binstall`.
|
||||||
*(Prebuilt binaries + checksums now published to Gitea releases
|
*(Prebuilt binaries + checksums on Gitea releases (D1); **`cargo
|
||||||
(D1); the package-manager manifests (Homebrew / Scoop / winget /
|
binstall` + crates.io live** (ADR-0056); **Scoop + Homebrew wired**
|
||||||
`cargo binstall`) remain to do. The asset naming
|
(ADR-0056 Amendment 2) — `publish.yaml` `scoop-bucket` /
|
||||||
`rdbms-playground-<tag>-<target>` is already binstall-friendly.
|
`homebrew-tap` jobs render dependency-free manifests from the release
|
||||||
Tracked under ADR-ci-003 "Deferred".)*
|
`.sha256` sidecars and push them, via the scoped `lazyeval-ci` bot
|
||||||
|
token, to `lazyeval/scoop-bucket` and `lazyeval/homebrew-tap`;
|
||||||
|
rendering covered by `scripts/test-package-renders.sh`, end-to-end
|
||||||
|
install still to be user-verified. **Remaining: winget** (komac on
|
||||||
|
Linux CI, or a manual PR). Asset naming
|
||||||
|
`rdbms-playground-<tag>-<target>` is binstall-friendly.)*
|
||||||
|
|
||||||
## TUI shell
|
## TUI shell
|
||||||
|
|
||||||
|
|||||||
+15
-5
@@ -24,9 +24,11 @@
|
|||||||
%LOCALAPPDATA%\Programs\rdbms-playground.
|
%LOCALAPPDATA%\Programs\rdbms-playground.
|
||||||
|
|
||||||
.NOTES
|
.NOTES
|
||||||
Written but NOT tested on Windows from this environment (no PowerShell
|
Verified end-to-end on ARM64 Windows 11 under both Windows PowerShell 5.1
|
||||||
here) — validate on a real Windows host. The verified sibling is
|
and PowerShell 7.6, against the live v0.2.0 release. The x86_64 branch is
|
||||||
install.sh (Linux/macOS).
|
symmetric (env-based arch detection + a confirmed matching release asset)
|
||||||
|
but has not been run directly. The sibling installer is install.sh
|
||||||
|
(Linux/macOS).
|
||||||
#>
|
#>
|
||||||
[CmdletBinding()]
|
[CmdletBinding()]
|
||||||
param(
|
param(
|
||||||
@@ -98,14 +100,22 @@ try {
|
|||||||
Move-Item -Path $exe -Destination $dest -Force
|
Move-Item -Path $exe -Destination $dest -Force
|
||||||
Write-Host "installed $Bin $Version -> $dest"
|
Write-Host "installed $Bin $Version -> $dest"
|
||||||
|
|
||||||
# Add the install dir to the user PATH (persistent) if it's not there.
|
# Persist the install dir on the user PATH (for future shells) if missing.
|
||||||
$userPath = [Environment]::GetEnvironmentVariable('Path', 'User')
|
$userPath = [Environment]::GetEnvironmentVariable('Path', 'User')
|
||||||
if (-not $userPath) { $userPath = '' }
|
if (-not $userPath) { $userPath = '' }
|
||||||
if (($userPath -split ';') -notcontains $InstallDir) {
|
if (($userPath -split ';') -notcontains $InstallDir) {
|
||||||
$newPath = if ($userPath) { "$userPath;$InstallDir" } else { $InstallDir }
|
$newPath = if ($userPath) { "$userPath;$InstallDir" } else { $InstallDir }
|
||||||
[Environment]::SetEnvironmentVariable('Path', $newPath, 'User')
|
[Environment]::SetEnvironmentVariable('Path', $newPath, 'User')
|
||||||
Write-Host "added $InstallDir to your user PATH — restart your shell to pick it up"
|
|
||||||
}
|
}
|
||||||
|
# Also update THIS session's PATH so the command works immediately. The
|
||||||
|
# persisted change only reaches newly-started processes; an already-running
|
||||||
|
# shell (and, depending on how the terminal inherited its environment, even
|
||||||
|
# a freshly-opened one) won't see it until the next sign-out/in.
|
||||||
|
if (($env:Path -split ';') -notcontains $InstallDir) {
|
||||||
|
$env:Path = "$env:Path;$InstallDir"
|
||||||
|
}
|
||||||
|
Write-Host "added $InstallDir to your PATH — '$Bin' works in this window now."
|
||||||
|
Write-Host "for shells already open elsewhere, sign out and back in (or open a fresh one)."
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
Remove-Item -Path $tmp -Recurse -Force -ErrorAction SilentlyContinue
|
Remove-Item -Path $tmp -Recurse -Force -ErrorAction SilentlyContinue
|
||||||
|
|||||||
Executable
+86
@@ -0,0 +1,86 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
#
|
||||||
|
# Render the Homebrew formula for rdbms-playground to stdout.
|
||||||
|
#
|
||||||
|
# Pure function of its inputs — NO network, NO jq/ruby — so it runs unchanged in
|
||||||
|
# the CI job container (node:22-bookworm-slim: bash + coreutils only). Given a
|
||||||
|
# version and the four macOS/Linux asset SHA-256 hashes it prints a complete
|
||||||
|
# formula. The publish.yaml `homebrew-tap` job fetches the hashes from the
|
||||||
|
# release .sha256 sidecars and commits the result into lazyeval/homebrew-tap as
|
||||||
|
# Formula/rdbms-playground.rb.
|
||||||
|
#
|
||||||
|
# The release assets are bare binaries (no archive), so Homebrew stages the
|
||||||
|
# single downloaded file in the build dir and `install` drops it under a stable
|
||||||
|
# name. Windows is intentionally absent — Homebrew has no Windows port (Scoop /
|
||||||
|
# winget cover Windows).
|
||||||
|
#
|
||||||
|
# Usage: render-homebrew-formula.sh <version> <mac-arm> <mac-intel> <linux-arm> <linux-intel>
|
||||||
|
# <version> version, with or without a leading 'v'
|
||||||
|
# <mac-arm> sha256 of aarch64-apple-darwin
|
||||||
|
# <mac-intel> sha256 of x86_64-apple-darwin
|
||||||
|
# <linux-arm> sha256 of aarch64-unknown-linux-musl
|
||||||
|
# <linux-intel> sha256 of x86_64-unknown-linux-musl
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
if [ "$#" -ne 5 ]; then
|
||||||
|
echo "usage: $0 <version> <mac-arm> <mac-intel> <linux-arm> <linux-intel>" >&2
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
version=${1#v}
|
||||||
|
mac_arm=$2
|
||||||
|
mac_intel=$3
|
||||||
|
linux_arm=$4
|
||||||
|
linux_intel=$5
|
||||||
|
|
||||||
|
base="https://git.lazyeval.net/oli/rdbms-playground/releases/download/v$version"
|
||||||
|
|
||||||
|
# Ruby interpolations (#{version}, #{bin}) must survive verbatim into the
|
||||||
|
# formula; they contain no '$', so this unquoted heredoc leaves them untouched
|
||||||
|
# and only expands the shell variables below.
|
||||||
|
cat <<EOF
|
||||||
|
# typed: false
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# rdbms-playground — installs the prebuilt release binary for the host
|
||||||
|
# platform. Regenerated for each release by scripts/render-homebrew-formula.sh;
|
||||||
|
# do not edit by hand.
|
||||||
|
class RdbmsPlayground < Formula
|
||||||
|
desc "Cross-platform TUI playground for learning relational databases"
|
||||||
|
homepage "https://relplay.org"
|
||||||
|
version "$version"
|
||||||
|
license any_of: ["MIT", "Apache-2.0"]
|
||||||
|
|
||||||
|
on_macos do
|
||||||
|
on_arm do
|
||||||
|
url "$base/rdbms-playground-v$version-aarch64-apple-darwin"
|
||||||
|
sha256 "$mac_arm"
|
||||||
|
end
|
||||||
|
on_intel do
|
||||||
|
url "$base/rdbms-playground-v$version-x86_64-apple-darwin"
|
||||||
|
sha256 "$mac_intel"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
on_linux do
|
||||||
|
on_arm do
|
||||||
|
url "$base/rdbms-playground-v$version-aarch64-unknown-linux-musl"
|
||||||
|
sha256 "$linux_arm"
|
||||||
|
end
|
||||||
|
on_intel do
|
||||||
|
url "$base/rdbms-playground-v$version-x86_64-unknown-linux-musl"
|
||||||
|
sha256 "$linux_intel"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def install
|
||||||
|
# The release asset is a single bare binary; Homebrew stages it in the
|
||||||
|
# build dir under its (versioned) basename. Install it as a stable name.
|
||||||
|
bin.install Dir["*"].first => "rdbms-playground"
|
||||||
|
end
|
||||||
|
|
||||||
|
test do
|
||||||
|
assert_match "rdbms-playground #{version}", shell_output("#{bin}/rdbms-playground --version")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
EOF
|
||||||
Executable
+66
@@ -0,0 +1,66 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
#
|
||||||
|
# Render the Scoop manifest for rdbms-playground to stdout.
|
||||||
|
#
|
||||||
|
# Pure function of its inputs — NO network, NO jq/ruby — so it runs unchanged in
|
||||||
|
# the CI job container (node:22-bookworm-slim: bash + coreutils only). Given a
|
||||||
|
# version and the two Windows asset SHA-256 hashes it prints a complete,
|
||||||
|
# schema-valid Scoop manifest. The publish.yaml `scoop-bucket` job fetches the
|
||||||
|
# hashes from the release's .sha256 sidecars and commits the result into the
|
||||||
|
# lazyeval/scoop-bucket repository as rdbms-playground.json.
|
||||||
|
#
|
||||||
|
# Manifest updates are CI-driven (this script, per release), so the manifest
|
||||||
|
# carries `checkver` (so `scoop status` / the community excavator can see when
|
||||||
|
# the bucket lags upstream) but deliberately NO `autoupdate` — our pipeline is
|
||||||
|
# the updater, not Scoop's maintainer tooling.
|
||||||
|
#
|
||||||
|
# Usage: render-scoop-manifest.sh <version> <hash-x64> <hash-arm64>
|
||||||
|
# <version> version, with or without a leading 'v' (e.g. 0.2.0 or v0.2.0)
|
||||||
|
# <hash-x64> sha256 of the x86_64-pc-windows-gnu.exe asset
|
||||||
|
# <hash-arm64> sha256 of the aarch64-pc-windows-gnullvm.exe asset
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
if [ "$#" -ne 3 ]; then
|
||||||
|
echo "usage: $0 <version> <hash-x64> <hash-arm64>" >&2
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Accept either 0.2.0 or v0.2.0; the manifest 'version' field is bare.
|
||||||
|
version=${1#v}
|
||||||
|
hash_x64=$2
|
||||||
|
hash_arm64=$3
|
||||||
|
|
||||||
|
repo="https://git.lazyeval.net/oli/rdbms-playground"
|
||||||
|
base="$repo/releases/download/v$version"
|
||||||
|
|
||||||
|
# The `#/rdbms-playground.exe` fragment tells Scoop to save the versioned asset
|
||||||
|
# under a stable filename, so the `bin` shim resolves regardless of version.
|
||||||
|
url_x64="$base/rdbms-playground-v$version-x86_64-pc-windows-gnu.exe#/rdbms-playground.exe"
|
||||||
|
url_arm64="$base/rdbms-playground-v$version-aarch64-pc-windows-gnullvm.exe#/rdbms-playground.exe"
|
||||||
|
|
||||||
|
# Note: \$.tag_name emits a literal $ (Scoop's JSONPath); the regex uses [0-9.]
|
||||||
|
# rather than \d so the manifest contains no backslashes to escape.
|
||||||
|
cat <<EOF
|
||||||
|
{
|
||||||
|
"version": "$version",
|
||||||
|
"description": "A cross-platform TUI playground for learning relational databases.",
|
||||||
|
"homepage": "https://relplay.org",
|
||||||
|
"license": "MIT OR Apache-2.0",
|
||||||
|
"architecture": {
|
||||||
|
"64bit": {
|
||||||
|
"url": "$url_x64",
|
||||||
|
"hash": "$hash_x64"
|
||||||
|
},
|
||||||
|
"arm64": {
|
||||||
|
"url": "$url_arm64",
|
||||||
|
"hash": "$hash_arm64"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"bin": "rdbms-playground.exe",
|
||||||
|
"checkver": {
|
||||||
|
"url": "https://git.lazyeval.net/api/v1/repos/oli/rdbms-playground/releases/latest",
|
||||||
|
"jsonpath": "\$.tag_name",
|
||||||
|
"regex": "v([0-9.]+)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EOF
|
||||||
Executable
+92
@@ -0,0 +1,92 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
#
|
||||||
|
# Tests for the package-manifest render scripts (Scoop + Homebrew).
|
||||||
|
#
|
||||||
|
# Validates that each render script emits a well-formed manifest for given
|
||||||
|
# inputs, and that the inputs land in the right places. Dependency-light:
|
||||||
|
# requires node (always in the CI image) for JSON parsing; uses jq and ruby for
|
||||||
|
# extra checks when present (both available on the dev box). No network.
|
||||||
|
#
|
||||||
|
# Run: scripts/test-package-renders.sh
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
here=$(cd "$(dirname "$0")" && pwd)
|
||||||
|
tmp=$(mktemp -d)
|
||||||
|
trap 'rm -rf "$tmp"' EXIT
|
||||||
|
|
||||||
|
fail() { echo "FAIL: $*" >&2; exit 1; }
|
||||||
|
pass() { echo "ok: $*"; }
|
||||||
|
|
||||||
|
# Distinct dummy hashes so we can assert each lands in the right slot.
|
||||||
|
VER=9.9.9
|
||||||
|
H_WIN_X64=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa01
|
||||||
|
H_WIN_ARM=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa02
|
||||||
|
H_MAC_ARM=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa03
|
||||||
|
H_MAC_X64=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa04
|
||||||
|
H_LIN_ARM=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa05
|
||||||
|
H_LIN_X64=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa06
|
||||||
|
|
||||||
|
# ---- Scoop ----------------------------------------------------------------
|
||||||
|
scoop=$tmp/rdbms-playground.json
|
||||||
|
# Pass a leading 'v' to confirm it gets stripped.
|
||||||
|
"$here/render-scoop-manifest.sh" "v$VER" "$H_WIN_X64" "$H_WIN_ARM" > "$scoop"
|
||||||
|
|
||||||
|
node -e 'JSON.parse(require("fs").readFileSync(process.argv[1],"utf8"))' "$scoop" \
|
||||||
|
|| fail "scoop manifest is not valid JSON"
|
||||||
|
pass "scoop manifest parses as JSON"
|
||||||
|
|
||||||
|
# Field-level assertions via node (no jq dependency).
|
||||||
|
check_json() { # <jsexpr> <expected>
|
||||||
|
local got
|
||||||
|
got=$(node -e '
|
||||||
|
const m = JSON.parse(require("fs").readFileSync(process.argv[1],"utf8"));
|
||||||
|
process.stdout.write(String(eval(process.argv[2])));
|
||||||
|
' "$scoop" "$1")
|
||||||
|
[ "$got" = "$2" ] || fail "scoop: $1 = '$got', expected '$2'"
|
||||||
|
}
|
||||||
|
check_json 'm.version' "$VER"
|
||||||
|
check_json 'm.bin' "rdbms-playground.exe"
|
||||||
|
check_json 'm.architecture["64bit"].hash' "$H_WIN_X64"
|
||||||
|
check_json 'm.architecture["arm64"].hash' "$H_WIN_ARM"
|
||||||
|
check_json 'm.checkver.jsonpath' '$.tag_name'
|
||||||
|
grep -q "rdbms-playground-v$VER-x86_64-pc-windows-gnu.exe#/rdbms-playground.exe" "$scoop" \
|
||||||
|
|| fail "scoop: x64 url/fragment missing"
|
||||||
|
grep -q "rdbms-playground-v$VER-aarch64-pc-windows-gnullvm.exe#/rdbms-playground.exe" "$scoop" \
|
||||||
|
|| fail "scoop: arm64 url/fragment missing"
|
||||||
|
pass "scoop manifest fields correct"
|
||||||
|
|
||||||
|
if command -v jq >/dev/null 2>&1; then
|
||||||
|
jq -e . "$scoop" >/dev/null || fail "scoop: jq rejected the manifest"
|
||||||
|
pass "scoop manifest valid per jq"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ---- Homebrew -------------------------------------------------------------
|
||||||
|
formula=$tmp/rdbms-playground.rb
|
||||||
|
"$here/render-homebrew-formula.sh" "$VER" "$H_MAC_ARM" "$H_MAC_X64" "$H_LIN_ARM" "$H_LIN_X64" > "$formula"
|
||||||
|
|
||||||
|
if command -v ruby >/dev/null 2>&1; then
|
||||||
|
ruby -c "$formula" >/dev/null || fail "homebrew formula is not valid Ruby"
|
||||||
|
pass "homebrew formula parses as Ruby"
|
||||||
|
else
|
||||||
|
echo "warn: ruby not present — skipping formula syntax check" >&2
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Each hash must appear exactly once (right asset → right slot), the version
|
||||||
|
# must be present, and #{version} must survive verbatim for brew's test block.
|
||||||
|
for pair in \
|
||||||
|
"aarch64-apple-darwin:$H_MAC_ARM" \
|
||||||
|
"x86_64-apple-darwin:$H_MAC_X64" \
|
||||||
|
"aarch64-unknown-linux-musl:$H_LIN_ARM" \
|
||||||
|
"x86_64-unknown-linux-musl:$H_LIN_X64"; do
|
||||||
|
target=${pair%%:*}; hash=${pair#*:}
|
||||||
|
grep -q "rdbms-playground-v$VER-$target\"" "$formula" || fail "homebrew: url for $target missing"
|
||||||
|
grep -q "sha256 \"$hash\"" "$formula" || fail "homebrew: sha256 for $target missing"
|
||||||
|
done
|
||||||
|
grep -q 'version "'"$VER"'"' "$formula" || fail "homebrew: version line missing"
|
||||||
|
grep -q 'assert_match "rdbms-playground #{version}"' "$formula" \
|
||||||
|
|| fail "homebrew: test block #{version} interpolation was mangled"
|
||||||
|
grep -q 'rdbms-playground-v9.9.9-x86_64-pc-windows' "$formula" \
|
||||||
|
&& fail "homebrew: formula unexpectedly references a Windows asset"
|
||||||
|
pass "homebrew formula fields correct"
|
||||||
|
|
||||||
|
echo "all render tests passed"
|
||||||
Reference in New Issue
Block a user