Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ Omit `name` for a whole-domain repository.
Both use the SDK test kit and the same update recipe.
The kit runs manifest examples with scratch fixtures and checks each answer.
It uses dry runs where available and prints skip reasons in plain `just check` output.
`just update VERSION` runs the target version's updater.
Older repositories use the bootstrap in [Update an extension](docs/north-star/contract/extension-repository.md#update-an-extension).
Further work lives in [docs hub](docs/backlog/XT-104-docs-hub.md), [Canadian French](docs/backlog/COR-102-canadian-french.md),
[Markdown view](docs/backlog/XT-103-markdown-view.md), and [video and image jobs](docs/backlog/XT-602-video-image-jobs.md).

Expand Down
2 changes: 1 addition & 1 deletion docs/backlog/COR-103-verification-loop.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ Each check fails first on one named past mistake.
| COR-103-T5 | Observe declared codes | agent | COR-103-T2, COR-103-T3 | done |
| COR-103-T6 | Signal and refuses commands | agent | COR-103-T3 | done |
| COR-103-T7 | One install and uninstall path | agent | - | done |
| COR-103-T8 | Update through the target updater | agent | COR-103-T7 | todo |
| COR-103-T8 | Update through the target updater | agent | COR-103-T7 | done |
| COR-103-T9 | Sign off an isolated checkout | agent | COR-103-T7 | done |
| COR-103-T10 | Release with a receipt | agent | COR-103-T7, COR-103-T9 | todo |
| COR-103-T11 | macOS job in the template | agent | - | done |
Expand Down
2 changes: 1 addition & 1 deletion docs/north-star/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ Each part has one owner: one package and one spec section.
| 3 | Extension host | Discovery, manifests, dispatch, answer checks, and grants | `internal/cli/` | [Extensions](contract/extensions.md) |
| 3 | Core commands | Help, completion, describe, doctor, auth, run, and tap, and core's catalog | `internal/cli/` | [core-spec.md](core-spec.md) |
| 2 | Extension entry | One entry point that serves an extension as `ncly-<slug>` or bundled, the export of its sidecars, and its embedded workers | `sdk/extension/` | [SDK](contract/sdk.md) |
| 2 | Extension repository | Staged install and uninstall | `sdk/extensionrepo/` | [Extension repository](contract/extension-repository.md) |
| 2 | Extension repository | Repository checks, updates, install and uninstall | `sdk/extensionrepo/` | [Extension repository](contract/extension-repository.md) |
| 2 | Wrap helper | One program that a handler runs, such as a worker: its check, input, environment, captured output, and failures | `sdk/wrap/` | [Wrapped programs](contract/wrap.md) |
| 2 | Test kit | The tests of an extension repository: its checks, its scenarios, and an install into scratch folders | `sdk/extensiontest/` | [Test kit](contract/test-kit.md) |
| 2 | Command line | Declarations turned into commands, global flags, help pages, forms for missing values, and answers printed | `sdk/command/` | [Command descriptions](contract/command-descriptions.md), [Output](contract/output.md) |
Expand Down
33 changes: 32 additions & 1 deletion docs/north-star/contract/extension-repository.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ The repository tests itself through the [test kit](test-kit.md). Its `justfile`
- `just install` builds the executable and exports its sidecars from that build. It replaces the previous install of the slug in the extensions folder of [Discovery](discovery.md), the executable last
- `just uninstall` removes the install of the slug from that folder, the executable first. With nothing installed, it says so and exits 0
- `just acceptance [fixture]` runs `acceptance.sh` as an agent would, with `--json` and `--no-input`. It installs the extension and the `ncly` of the pinned Nicely version into scratch XDG folders. It works on a copy of the fixture and checks that the fixture keeps its checksum. Without a fixture, it makes an input
- `just update <version>` moves the repository to another Nicely version: the template first, then the SDK, on a clean Git tree. Copier leaves `go.mod`, which holds the dependencies of the author. Copier can leave a conflict as markers, a `.rej` file, or an unmerged index entry, and still exit 0. On a conflict, the recipe stops before `go get` and prints the commands that finish the update. Otherwise, `go get` moves the Nicely pin, and the recipe copies each `replace` of Nicely's `go.mod` and drops each one that Nicely dropped. It never resets, stashes, or commits
- `just update <version>` runs the target version's updater on a clean Git tree, with the template before the SDK. [Update an extension](#update-an-extension) defines bootstrap and conflict recovery
- `just contract` prints the folder of this contract in the Go module of the pinned Nicely version, so an agent reads the exact rules offline

`just install` grants no key. A human grants one after reviewing the extension, as [Grants](grants.md) defines.
Expand Down Expand Up @@ -69,3 +69,34 @@ Once per suite, a separate scratch install holds only the extension under test.
The kit installs and uninstalls that extension there.
Then `ncly describe <root> --json` must exit 2 with `NOT_FOUND`.
Extensions that a scenario adds cannot affect this lifecycle check.

## Update an extension

Each template recipe has one command line.
The SDK program owns `check`, `contract`, and `update`, as well as install and uninstall.
The update recipe downloads the target Nicely module, then runs its `sdk/extensionrepo/cmd/extensionrepo` program with `go -C`.
This uses the target's updater and dependency replacements, even when the repository pins an older SDK.
`go run package@version` cannot run a module that has `replace` directives.

An older recipe cannot change during its own run.
For a repository rendered before this recipe, the bootstrap runs the target program directly.
Replace VERSION with the target tag, such as `v0.24.0`, from the extension repository:

```sh
go -C "$(go mod download -json github.com/pascalandy/nicely@VERSION | sed -n 's/^[[:space:]]*"Dir": "\(.*\)",$/\1/p')" run ./sdk/extensionrepo/cmd/extensionrepo update VERSION "$PWD"
```

The updater accepts only `vX.Y.Z` tags and an absolute repository path.
It first requires a committed, clean Git tree.
Copier keeps the author's `go.mod` and applies the target template.
The updater checks for conflict markers, `.rej` files, and unmerged index entries before changing dependencies.
On a conflict, it prints each file and the bootstrap command with `--continue` appended.

Resolve each conflict and mark its file with `git add`.
Remove each `.rej` file, then run the printed command.
Continuation requires the target tag in `.copier-answers.yml` and no remaining conflicts.
It skips Copier, moves the Nicely pin, reconciles the previous and target module replacements, and runs `go mod tidy`.
Both update paths finish with `just check`.
The updater preserves unrelated dependencies and replacements.
It never resets, stashes, or commits.
If Copier fails, its output gives restoration commands and the bootstrap command to retry.
2 changes: 1 addition & 1 deletion docs/north-star/contract/manifest-examples.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ It adds `--dry-run` when the example's command supports that mode, including exa
Without that mode, it runs only commands whose `effects` explicitly declare `paid = false`.
A missing effects declaration permits no assumption.
Each skipped example prints its reason, including commands outside the extension's manifest.
The template runs tests through `go run ./internal/export test`, which keeps package list output without `-v`.
The template's `just check` runs tests through `extensionrepo.Test`, which keeps package list output without `-v`.
The SDK wrapper prints collected notices to stderr after the tests, including when tests fail.
It preserves their exit code and keeps stdout valid for `go test -json`.
Core keeps Go's result cache and uses the rendered template sample to check notices.
Expand Down
16 changes: 16 additions & 0 deletions extensions/skill/nicely-extensions/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,22 @@ The domain names a family of work, such as `video`, and the optional name identi
Neither names the wrapped tool.
For a whole-domain extension, omit `--data name=NAME` and both `-NAME` suffixes.

## Update an extension repository

Run `just update VERSION` on a clean, committed Git tree.
Replace VERSION with the target Nicely tag, such as `v0.24.0`.

For an older repository whose update recipe holds a script, bootstrap from its root:

```sh
go -C "$(go mod download -json github.com/pascalandy/nicely@VERSION | sed -n 's/^[[:space:]]*"Dir": "\(.*\)",$/\1/p')" run ./sdk/extensionrepo/cmd/extensionrepo update VERSION "$PWD"
```

On a conflict, resolve each file and mark it with `git add`.
Remove each `.rej` file, then run the printed bootstrap command with `--continue`.
Success ends with `just check`.
[Update an extension](https://github.com/pascalandy/nicely/blob/main/docs/north-star/contract/extension-repository.md#update-an-extension) holds the recovery rules.

## Remove an extension

`just install` puts an extension in the extensions folder: `nicely/extensions` in the data folder, such as `~/.local/share/nicely/extensions`.
Expand Down
31 changes: 24 additions & 7 deletions scripts/template-test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
# folders. A clone at a release tag installs with the commands of its
# README.md. just update then carries a customized repository to the next
# template revision, and keeps the edits and the dependencies of its author. On
# a conflict, it stops before any go command. The test runs on macOS and Linux,
# a conflict, it stops before dependency changes. The test runs on macOS and Linux,
# with the bash 3.2 of macOS, and uses no network once uv holds the pinned
# Copier.
set -Eeuo pipefail
Expand All @@ -21,6 +21,7 @@ fct_revision() {
git -C "${src}" add -A
git -C "${src}" commit -qm "template ${1}"
git -C "${src}" tag "${1}"
fct_proxy_revision "${src}" "${1}" "${proxy}"
}

# fct_commit commits every change of the rendered repository, as its author.
Expand Down Expand Up @@ -74,10 +75,9 @@ fct_repository() {
src="${scratch}/source" repo="${scratch}/ncly-${slug}" data="${scratch}/data"
extensions="${data}/nicely/extensions"
mkdir -p "${scratch}"
# The template source holds the working tree of this checkout, changes included, tagged as a version.
# The module proxy and Copier source both hold this checkout, with its changes.
mkdir "${src}"
cp "${root}/copier.yml" "${src}/"
cp -R "${root}/template" "${src}/template"
fct_copy_source "${src}"
git -C "${src}" init -q
fct_revision v0.0.1
"${copier[@]}" copy --quiet --vcs-ref v0.0.1 --defaults --data "domain=${domain}" --data "module=example.com/ncly-${slug}" "$@" "${src}" "${repo}"
Expand Down Expand Up @@ -138,8 +138,19 @@ fct_repository() {
}

fct_main() {
local root pin scratch copier template_src
local root pin scratch copier template_src proxy cache_proxy
root="$(cd "${BASH_SOURCE[0]%/*}/.." && pwd -P)"
# Copied recipes only wire commands to the SDK or an owned script.
awk '
/^[^[:space:]#].*:[[:space:]]*$/ { recipe = $1; sub(/:$/, "", recipe); lines = 0; reported = 0 }
/^[[:space:]]+#!/ || /^[[:space:]]+[^[:space:]#]/ {
if ((++lines > 1 || $1 ~ /^#!/) && !reported++) {
print "template-test.sh: recipe " recipe " must have one command line" > "/dev/stderr"
failed = 1
}
}
END { exit failed }
' "${root}/template/justfile"
# shellcheck source=scripts/template-update-test.sh
source "${root}/scripts/template-update-test.sh"
# shellcheck source=scripts/template-input-test.sh
Expand All @@ -154,17 +165,23 @@ fct_main() {
pin="$(sed -n 's/^copier := "\(.*\)"$/\1/p' "${root}/template/justfile")"
[[ "${pin}" == "uv tool run copier@"* ]] || fct_fail "template/justfile pins no Copier"
grep -qF -- "${pin}" "${root}/extensions/skill/nicely-extensions/SKILL.md" || fct_fail "the nicely-extensions skill does not give ${pin}, which template/justfile pins"
awk '$1 == "replace" && !($3 == "=>" && NF == 5) { exit 1 }' "${root}/go.mod" ||
fct_fail "write each replace of Nicely's go.mod as one line, replace X => Y V: just update of template/justfile copies only that form"
scratch="$(mktemp -d "${TMPDIR:-/tmp}/ncly-template-test.XXXXXXXX")"
readonly NCLY_TEMPLATE_TEST_ROOT="${scratch}"
trap 'rm -rf "${NCLY_TEMPLATE_TEST_ROOT}"' EXIT
# Git, in Copier too, takes its identity from this file, without the signing of the user.
printf '[user]\n\tname = template-test\n\temail = template-test@example.com\n[commit]\n\tgpgsign = false\n[tag]\n\tgpgsign = false\n[init]\n\tdefaultBranch = main\n' >"${scratch}/gitconfig"
export GIT_CONFIG_GLOBAL="${scratch}/gitconfig" GIT_CONFIG_NOSYSTEM=1
proxy="${scratch}/proxy/github.com/pascalandy/nicely/@v"
mkdir -p "${proxy}"
cache_proxy="file://$(go env GOMODCACHE)/cache/download"
export GOPROXY="file://${scratch}/proxy,${cache_proxy},${GOPROXY:-https://proxy.golang.org,direct}"
export GOMODCACHE="${scratch}/modcache" GONOSUMDB=github.com/pascalandy/nicely
# Go normally makes cached module folders read-only, which prevents scratch cleanup.
export GOFLAGS="${GOFLAGS:-} -modcacherw"
read -r -a copier <<<"${pin}"
fct_repository "${scratch}/named" video convert video-convert 'video convert' --data name=convert
fct_repository "${scratch}/whole" probe '' probe 'probe inspect'
fct_test_bootstrap "${scratch}/bootstrap" "${copier[@]}"
template_src="${scratch}/whole/source"
fct_test_import_domains "${template_src}" "${scratch}" "${root}" "${copier[@]}"
fct_test_release_url "${template_src}" "${scratch}" "${copier[@]}"
Expand Down
Loading
Loading