diff options
| author | Danilo M. <danix@danix.xyz> | 2026-06-13 18:36:31 +0200 |
|---|---|---|
| committer | Danilo M. <danix@danix.xyz> | 2026-06-13 18:36:31 +0200 |
| commit | d4789701532c8acdfb4b109931e65e5e046871de (patch) | |
| tree | 1f8bc043687aa3e8fa079f95deee5a5e15036d79 | |
| parent | d11b8be143998ea7349808b9e9da68139399aace (diff) | |
| parent | 8e6531764b00b29259fc59bd4e1f16e019bc3f2a (diff) | |
| download | mkhintfile-d4789701532c8acdfb4b109931e65e5e046871de.tar.gz mkhintfile-d4789701532c8acdfb4b109931e65e5e046871de.zip | |
Merge feat/nvchecker: nvchecker integration
- --new appends nvchecker [section] (github/pypi autodetect, else stub)
- --hintfile without -v suggests latest version via nvchecker
- --check/-C bulk update with per-package confirm + single slackrepo
- --check populate missing sections prompt
- fix: TOML-quote section names with non-bare-key chars
- fix: resolve relative keyfile path against config dir
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
| -rw-r--r-- | CLAUDE.md | 33 | ||||
| -rw-r--r-- | README.md | 44 | ||||
| -rw-r--r-- | docs/superpowers/plans/2026-06-13-check-populate-missing-sections.md | 379 | ||||
| -rw-r--r-- | docs/superpowers/specs/2026-06-13-check-populate-missing-sections-design.md | 155 | ||||
| -rwxr-xr-x | mkhint | 320 | ||||
| -rw-r--r-- | mkhint.bash-completion | 4 | ||||
| -rwxr-xr-x | tests/mkhint_test.sh | 411 |
7 files changed, 1335 insertions, 11 deletions
@@ -17,6 +17,14 @@ HINT_DIR="/etc/slackrepo/SBo-danix/hintfiles/" # where .hint files live Bash completion script has its own hardcoded copies (lines 8–9) — keep in sync when changing defaults. +nvchecker config path is read from `$HOME/.config/nvchecker/nvchecker.toml` (not hardcoded): + +```bash +NVCHECKER_CONFIG="$HOME/.config/nvchecker/nvchecker.toml" +``` + +User must set up the `[__config__]` section once before version-checking features work. Bash completion now also knows `--check`/`-C`. + ## Running / Testing No build step. Direct execution: @@ -31,6 +39,9 @@ bash mkhint --version 2.0.1 --hintfile mypackage --no-dl # downloads + md5 + N bash mkhint --new mypackage --no-dl # create hint with NODOWNLOAD=yes bash mkhint --delete mypackage bash mkhint --clean +bash mkhint --hintfile mypackage # suggest latest version via nvchecker (no -v) +bash mkhint --check # check all hints for upstream updates +bash mkhint --check pkg1 pkg2 # check specific packages ``` ### Automated test suite @@ -60,14 +71,32 @@ Test coverage: | T13 | `--new` multiline `.info`, no version — template copied, original md5s kept | | T14 | `--new` multiline `.info` with `-v` — first URL+md5 updated, continuation md5 kept | | T15 | `--clean` — removes all .bak files | +| T16 | `--new` github .info — [pkg] source=github appended to nvchecker config | +| T17 | `--new` pypi .info — [pkg] source=pypi appended to nvchecker config | +| T18 | `--new` unrecognised URL — commented stub [pkg] appended to nvchecker config | +| T19 | `--new` when [pkg] exists in config — not duplicated | +| T20 | `--hintfile` no -v, accept suggestion — VERSION=latest, nvtake called | +| T21 | `--hintfile` no -v, type override — VERSION=typed value | +| T22 | `--hintfile` no -v, no nvchecker result — graceful abort, hint unchanged | +| T23 | `--check` one outdated, confirm — updated, nvtake, slackrepo prompted | +| T24 | `--check` all current — "all up to date", no slackrepo | +| T25 | `--check` mixed — decline one / accept one | +| T26 | `--check` with -v — mutually-exclusive error, exit 1 | +| T27 | `--check` upstream older than hint — reported as `(?downgrade)` | +| T28 | `--check` no args — scans entire HINT_DIR | +| T29 | `--check` missing section, accept populate — github section appended, run stops, hint unchanged | +| T30 | `--check` missing section, decline populate — nothing added | +| T31 | `--check` missing section, no `.info` in repo — skipped, no section added | When adding new features, add a corresponding test case to `tests/mkhint_test.sh`. ## Key Behaviors - `--hintfile` update: backs up to `.bak`, replaces old version string globally via `sed`, re-downloads both URLs to recalculate MD5 checksums. Skips download if value is `UNSUPPORTED` or `UNTESTED`. -- `--new` with existing `.info`: copies `.info` as template, strips `PRGNAM`, `HOMEPAGE`, `MAINTAINER`, `EMAIL`, comments out `REQUIRES`, sets `ARCH="x86_64"`. Keeps `VERSION` from `.info`. If `-v` given, updates version string and recalculates checksums. +- `--new` with existing `.info`: copies `.info` as template, strips `PRGNAM`, `HOMEPAGE`, `MAINTAINER`, `EMAIL`, comments out `REQUIRES`, sets `ARCH="x86_64"`. Keeps `VERSION` from `.info`. If `-v` given, updates version string and recalculates checksums. Also appends an nvchecker `[section]` to the config, auto-detecting github/pypi source or providing a commented stub. - `--new` when hint already exists: backs up old, creates empty skeleton. +- `--hintfile` with no `-v`: queries nvchecker for latest version, shows current vs. latest, prompts to accept/override/decline. After accepting, runs `nvtake` to sync nvchecker's keyfile. +- `--check` / `-C`: runs nvchecker for all (or named) hints, reports outdated packages with current → latest versions, prompts per-package to update, applies updates with `nvtake`, then prompts single `slackrepo update` for all updated packages. Hints with no `[pkg]` section in `nvchecker.toml` are collected and, after the scan, a single prompt offers to populate the config via `add_nvchecker_section` (github/pypi autodetect, else stub); on accept it prints a "review and re-run" message and stops the run without applying updates. Packages whose `.info` is not found in `REPO_DIR` are skipped. - `--no-dl` / `-N`: downloads and recalculates checksums as normal, then appends `NODOWNLOAD=yes` after `MD5SUM_x86_64=`. Works with `--hintfile` or `--new`. Error if used alone. - `--delete` / `-d`: removes hint file and `.bak` if present. Accepts multiple package names. Exits 2 on first missing file. - Downloads go to `/tmp/mkhint/download` (single shared temp file, deleted after md5 calculation). @@ -81,7 +110,7 @@ When adding new features, add a corresponding test case to `tests/mkhint_test.sh | 1 | Invalid arguments | | 2 | File not found | | 3 | File already exists (unused — backup logic replaces this) | -| 4 | wget not available | +| 4 | Required tool not available (wget/nvchecker/nvtake/jq) | ## Installation @@ -16,6 +16,12 @@ sudo cp mkhint /usr/local/bin/mkhint sudo cp mkhint.bash-completion /etc/bash-completion.d/mkhint ``` +### Dependencies + +- `wget` — for downloading archives and calculating checksums +- `nvchecker` — for checking upstream versions (provides `nvchecker` and `nvtake`) +- `jq` — for parsing version check results + ### Configuration Edit the paths at the top of mkhint to match your setup (lines 16–17): @@ -27,6 +33,16 @@ HINT_DIR="/etc/slackrepo/SBo-danix/hintfiles/" # Directory where .hint files ar Keep the same paths in sync in `mkhint.bash-completion` (lines 8–9). +nvchecker reads its configuration from `~/.config/nvchecker/nvchecker.toml`. You must set up the `[__config__]` section with oldver and newver keyfile paths before using version-checking features: + +```toml +[__config__] +oldver = "/var/lib/nvchecker/oldver" +newver = "/var/lib/nvchecker/newver" +``` + +mkhint only appends package-specific `[section]` entries to this file; the `[__config__]` section must be created manually once. + ## Usage ### Update an existing hint file @@ -108,6 +124,30 @@ mkhint --clean mkhint -c ``` +### Check for upstream updates + +When creating a new hint file with `--new`, mkhint automatically appends an nvchecker configuration section, auto-detecting the source (github, pypi, etc.) or providing a commented template. A notice is printed so you can review and fill in any missing details: + +```bash +mkhint --new mypackage # adds [mypackage] section to nvchecker config +``` + +When updating an existing hint file with `--hintfile` but without `-v`, mkhint queries nvchecker for the latest version, shows you the current and latest versions, and prompts to accept the latest, type a different version, or decline. After accepting an update, it runs `nvtake` to sync nvchecker's keyfile: + +```bash +mkhint --hintfile mypackage # suggests latest version via nvchecker (no -v flag) +``` + +Check one or more packages for upstream updates with `--check`. mkhint runs nvchecker for all (or named) hint files, reports outdated packages, prompts per-package to update, applies updates with `nvtake`, and finishes with a single `slackrepo update` prompt for all updated packages: + +```bash +mkhint --check # check all hints for upstream updates +mkhint --check pkg1 pkg2 # check specific packages +mkhint -C # short form +``` + +If any scanned hint file has no nvchecker source configured, `--check` lists those packages and offers to populate `nvchecker.toml` for them in one prompt — auto-detecting github/pypi from the SBo `.info`, otherwise writing a commented stub to fill in. After populating, it asks you to review the file (fill any stubs) and re-run `mkhint -C`. Packages with no matching `.info` in the repository are skipped. + ### Help ```bash @@ -123,7 +163,7 @@ mkhint -h | 1 | Invalid arguments | | 2 | File not found | | 3 | File already exists (unused — backup logic replaces this) | -| 4 | wget not available | +| 4 | Required tool not available (wget/nvchecker/nvtake/jq) | ## Hint File Variables @@ -143,4 +183,4 @@ mkhint -h - If DOWNLOAD or DOWNLOAD_x86_64 is `UNSUPPORTED` or `UNTESTED`, that URL is skipped and its MD5SUM is left unchanged. - `--no-dl` / `-N` does **not** skip downloads — it downloads and recalculates checksums as normal, then appends `NODOWNLOAD=yes` to the hint file. - After a successful `--hintfile` update, mkhint prompts `Run 'slackrepo update <package>'? [Y/n]`. Enter or `y` runs slackrepo immediately; `n` skips. -- Bash completion for `-f`/`--hintfile`, `-n`/`--new`, and `-d`/`--delete` autocompletes package names from their respective directories. When `-f <package>` is already on the command line, `-v [TAB]` suggests the current `VERSION` from that package's hint file. If the hint file is absent, no version is suggested. Short flags (`-v`, `-f`, `-n`, `-l`, `-c`, `-d`, `-N`, `-h`) are also completed. +- Bash completion for `-f`/`--hintfile`, `-n`/`--new`, `-d`/`--delete`, and `-C`/`--check` autocompletes package names from their respective directories. When `-f <package>` is already on the command line, `-v [TAB]` suggests the current `VERSION` from that package's hint file. If the hint file is absent, no version is suggested. Short flags (`-v`, `-f`, `-n`, `-l`, `-c`, `-d`, `-C`, `-N`, `-h`) are also completed. diff --git a/docs/superpowers/plans/2026-06-13-check-populate-missing-sections.md b/docs/superpowers/plans/2026-06-13-check-populate-missing-sections.md new file mode 100644 index 0000000..6d76262 --- /dev/null +++ b/docs/superpowers/plans/2026-06-13-check-populate-missing-sections.md @@ -0,0 +1,379 @@ +# `--check` Populate Missing Sections Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Make `mkhint --check` offer to populate `nvchecker.toml` for hint files that have no `[pkg]` section, instead of silently skipping them. + +**Architecture:** Add a small `_has_nvchecker_section` helper (DRY the existing section-presence grep), refactor `add_nvchecker_section` to use it, then extend `check_updates` to collect missing-section packages during its classify loop and, after the loop, batch-prompt to append sections via the existing `add_nvchecker_section` autodetect. On accept, the run stops with a "review and re-run" message. + +**Tech Stack:** bash, grep, find, sort -V. Tests use the existing mock-based suite (`tests/mkhint_test.sh`). + +--- + +## Background for the implementer + +Read before starting: + +- **Spec:** `docs/superpowers/specs/2026-06-13-check-populate-missing-sections-design.md` — this plan implements it exactly. +- **Main script:** `mkhint`. Relevant anchors (current line numbers): + - `add_nvchecker_section()` starts at line 266. Its duplicate-guard grep is line 275: + ```bash + if grep -qE "^\[${pkg}\][[:space:]]*$" "$NVCHECKER_CONFIG"; then + ``` + - `check_updates()` starts at line 606. The classify loop is lines 633-648. The "no result" skip is line 638: + ```bash + latest=$(nvchecker_latest "$pkg") || { echo "skip ${pkg}: no nvchecker source"; continue; } + ``` + The "nothing outdated" check is lines 650-653. + - `list_hint_files()` (earlier in the file) locates a package's `.info` with: + ```bash + find "$REPO_DIR" -mindepth 2 -name "${pkg}.info" 2>/dev/null | head -1 + ``` + Reuse this exact pattern. +- **`set -e`** is active. Command substitutions and `read` are safe; `add_nvchecker_section` returns 0. No new unguarded fallible calls are introduced. +- **Test harness:** `tests/mkhint_test.sh`. + - `run_mkhint` sed-patches `REPO_DIR`/`HINT_DIR`/`TMP_DIR`/`NVCHECKER_CONFIG` to mock paths. + - `setup()` creates fixtures including `ghpkg` (github `.info` under `$MOCK_REPO/development/ghpkg/ghpkg.info`) and a base `$MOCK_BASE/nvchecker.toml` containing only `[__config__]`, plus `$MOCK_BASE/new_ver.json` seeded with curl + clion versions. + - Interactive prompts are fed via `< <(printf '...')`. + - Assertions: `assert_contains`, `assert_not_contains`, `assert_file_exists`, `assert_file_not_exists`, `assert_exit_code`. Output-substring checks use the inline `grep -q` + `(( PASS++ ))` pattern (see T24/T27 in the file). + - The current suite has 69 passing assertions (T1–T28). + +## File Structure + +- `mkhint` — `_has_nvchecker_section` helper; `add_nvchecker_section` refactor; `check_updates` missing-section collection + populate prompt. +- `tests/mkhint_test.sh` — T29–T31, plus one orphan fixture for T31. +- `CLAUDE.md` / `README.md` — document the populate prompt. + +Single-file tool; keep everything in `mkhint`. + +--- + +## Task 1: Add `_has_nvchecker_section` helper and refactor `add_nvchecker_section` + +Pure refactor — no behaviour change. Establishes the helper that Task 2 reuses. + +**Files:** +- Modify: `mkhint` (add helper just above `add_nvchecker_section` at line 266; change line 275-278) + +- [ ] **Step 1: Add the helper above `add_nvchecker_section`** + +Immediately before the `# Append an nvchecker [pkg] section...` comment (line 264), add: + +```bash +# Return 0 if NVCHECKER_CONFIG already has a [pkg] section +_has_nvchecker_section() { + local pkg="$1" + [[ -f "$NVCHECKER_CONFIG" ]] || return 1 + grep -qE "^\[${pkg}\][[:space:]]*$" "$NVCHECKER_CONFIG" +} +``` + +- [ ] **Step 2: Use the helper inside `add_nvchecker_section`** + +Replace the duplicate-guard block (currently lines 274-278): + +```bash + # Skip if section already present + if grep -qE "^\[${pkg}\][[:space:]]*$" "$NVCHECKER_CONFIG"; then + echo "nvchecker: [${pkg}] already present in $NVCHECKER_CONFIG" + return 0 + fi +``` + +with: + +```bash + # Skip if section already present + if _has_nvchecker_section "$pkg"; then + echo "nvchecker: [${pkg}] already present in $NVCHECKER_CONFIG" + return 0 + fi +``` + +Note: `add_nvchecker_section` still runs `mkdir -p`/`touch` on the config before this check, so `_has_nvchecker_section`'s `[[ -f ]]` guard is belt-and-suspenders here but matters for Task 2's caller (which checks before any touch). + +- [ ] **Step 3: Syntax check + full suite (no regressions)** + +Run: `bash -n mkhint && bash tests/mkhint_test.sh` +Expected: syntax clean; `Results: 69 passed, 0 failed` (refactor changes nothing observable; T16-T19/T29 not yet added). + +- [ ] **Step 4: Commit** + +```bash +git add mkhint +git commit -m "refactor: extract _has_nvchecker_section helper" +``` + +--- + +## Task 2: Collect missing sections and add the populate prompt in `check_updates` + +**Files:** +- Modify: `mkhint` `check_updates` — classify loop (line 638) and after the loop (before line 650) + +- [ ] **Step 1: Distinguish missing-section from no-result in the classify loop** + +First, declare the `missing_sections` array alongside the other locals. Change line 631: + +```bash + local outdated_pkgs=() outdated_old=() outdated_new=() outdated_flag=() +``` + +to: + +```bash + local outdated_pkgs=() outdated_old=() outdated_new=() outdated_flag=() + local missing_sections=() +``` + +Then replace the no-result skip (line 638): + +```bash + latest=$(nvchecker_latest "$pkg") || { echo "skip ${pkg}: no nvchecker source"; continue; } +``` + +with: + +```bash + if ! latest=$(nvchecker_latest "$pkg"); then + if _has_nvchecker_section "$pkg"; then + echo "skip ${pkg}: no nvchecker result" + else + echo "skip ${pkg}: no nvchecker section" + missing_sections+=("$pkg") + fi + continue + fi +``` + +(`set -e` note: `latest=$(...)` inside an `if !` is a guarded context — the non-zero return does not abort.) + +- [ ] **Step 2: Add the populate prompt after the classify loop** + +The classify loop ends at the `done` (line 648). Immediately after that `done`, and before the `if [[ ${#outdated_pkgs[@]} -eq 0 ]]; then` block (line 650), insert: + +```bash + # Offer to populate nvchecker.toml for packages with no section + if [[ ${#missing_sections[@]} -gt 0 ]]; then + echo "" + echo "${#missing_sections[@]} package(s) have no nvchecker section: ${missing_sections[*]}" + local answer + read -r -p "Populate ${NVCHECKER_CONFIG} now? [Y/n] " answer + answer="${answer:-Y}" + if [[ "$answer" =~ ^[Yy]$ ]]; then + local mp info + for mp in "${missing_sections[@]}"; do + info=$(find "$REPO_DIR" -mindepth 2 -name "${mp}.info" 2>/dev/null | head -1) + if [[ -z "$info" ]]; then + echo "skip ${mp}: no .info found in $REPO_DIR" + continue + fi + add_nvchecker_section "$mp" "$info" + done + echo "" + echo "Sections added. Review $NVCHECKER_CONFIG (fill any stubs), then re-run 'mkhint -C'." + return 0 + fi + fi +``` + +- [ ] **Step 3: Syntax check + full suite (no regressions)** + +Run: `bash -n mkhint && bash tests/mkhint_test.sh` +Expected: syntax clean; `Results: 69 passed, 0 failed`. (Existing `-C` tests T23-T28 all use packages that already have sections or are explicitly named with seeded keyfile entries, so none trigger the new missing-section path. Verify they still pass — if T24's "all up to date" now prints a missing-section prompt instead, investigate: T24 uses `curl` which has a keyfile entry, so it should classify as up-to-date, not missing.) + +- [ ] **Step 4: Commit** + +```bash +git add mkhint +git commit -m "feat: --check offers to populate missing nvchecker sections" +``` + +--- + +## Task 3: Add tests T29–T31 + +**Files:** +- Modify: `tests/mkhint_test.sh` — orphan fixture in `setup()`; T29-T31 before the SUMMARY section + +- [ ] **Step 1: Add an orphan hint fixture path note** + +T31 needs a package that has a hint file but NO `.info` anywhere in `MOCK_REPO`. We create the hint file inline in the test (no `.info`), so no `setup()` change is strictly required. No fixture edit needed — proceed to the tests. + +- [ ] **Step 2: Add T29 — accept populate, github section written, run stops, hint unchanged** + +In `tests/mkhint_test.sh`, find the T28 block (the last test, `assert_contains "scan-all updated curl"`), which is immediately before: + +```bash +# ─── SUMMARY ────────────────────────────────────────────────────────────────── +teardown +``` + +Insert the following three test blocks between the end of T28 and the `# ─── SUMMARY` line: + +```bash +# ── T29: --check missing section, accept populate → section added, run stops ─── +echo "" +echo "T29: --check missing section, accept populate → github section appended, no update" +# ghpkg has a github .info in MOCK_REPO but no [ghpkg] in toml and not in keyfile. +# Reset toml to only [__config__] so ghpkg is genuinely missing. +cat > "$MOCK_BASE/nvchecker.toml" << EOF +[__config__] +oldver = "$MOCK_BASE/old_ver.json" +newver = "$MOCK_BASE/new_ver.json" +EOF +cat > "$MOCK_BASE/new_ver.json" << 'EOF' +{ "version": 2, "data": {} } +EOF +rm -f "$MOCK_HINT"/*.hint "$MOCK_HINT"/*.bak 2>/dev/null +cat > "$MOCK_HINT/ghpkg.hint" << 'EOF' +VERSION="1.0.0" +ARCH="x86_64" +DOWNLOAD="https://github.com/someowner/ghpkg/archive/v1.0.0/ghpkg-1.0.0.tar.gz" +MD5SUM="11111111111111111111111111111111" +DOWNLOAD_x86_64="" +MD5SUM_x86_64="" +EOF +out=$(run_mkhint -C ghpkg < <(printf 'Y\n') 2>&1) +assert_contains "ghpkg section appended" "$MOCK_BASE/nvchecker.toml" '\[ghpkg\]' +assert_contains "github source detected" "$MOCK_BASE/nvchecker.toml" 'source = "github"' +echo "$out" | grep -q "Review .*re-run" \ + && { echo " PASS: review/re-run message shown"; (( PASS++ )); } \ + || { echo " FAIL: review message missing"; echo "$out" | sed 's/^/ /'; (( FAIL++ )); ERRORS+=("T29 review msg"); } +assert_contains "ghpkg hint version unchanged" "$MOCK_HINT/ghpkg.hint" 'VERSION="1.0.0"' + +# ── T30: --check missing section, decline populate → no section added ────────── +echo "" +echo "T30: --check missing section, decline populate → nothing added" +cat > "$MOCK_BASE/nvchecker.toml" << EOF +[__config__] +oldver = "$MOCK_BASE/old_ver.json" +newver = "$MOCK_BASE/new_ver.json" +EOF +cat > "$MOCK_BASE/new_ver.json" << 'EOF' +{ "version": 2, "data": {} } +EOF +rm -f "$MOCK_HINT"/*.hint "$MOCK_HINT"/*.bak 2>/dev/null +cat > "$MOCK_HINT/ghpkg.hint" << 'EOF' +VERSION="1.0.0" +ARCH="x86_64" +DOWNLOAD="https://github.com/someowner/ghpkg/archive/v1.0.0/ghpkg-1.0.0.tar.gz" +MD5SUM="11111111111111111111111111111111" +DOWNLOAD_x86_64="" +MD5SUM_x86_64="" +EOF +run_mkhint -C ghpkg < <(printf 'n\n') >/dev/null 2>&1 +assert_not_contains "no ghpkg section after decline" "$MOCK_BASE/nvchecker.toml" '\[ghpkg\]' + +# ── T31: --check missing section, no .info in repo, accept → skipped, no section +echo "" +echo "T31: --check missing section but no .info → skipped, no section added" +cat > "$MOCK_BASE/nvchecker.toml" << EOF +[__config__] +oldver = "$MOCK_BASE/old_ver.json" +newver = "$MOCK_BASE/new_ver.json" +EOF +cat > "$MOCK_BASE/new_ver.json" << 'EOF' +{ "version": 2, "data": {} } +EOF +rm -f "$MOCK_HINT"/*.hint "$MOCK_HINT"/*.bak 2>/dev/null +# orphanpkg has NO .info anywhere in MOCK_REPO +cat > "$MOCK_HINT/orphanpkg.hint" << 'EOF' +VERSION="3.0.0" +ARCH="x86_64" +DOWNLOAD="https://example.com/orphanpkg-3.0.0.tar.gz" +MD5SUM="44444444444444444444444444444444" +DOWNLOAD_x86_64="" +MD5SUM_x86_64="" +EOF +out=$(run_mkhint -C orphanpkg < <(printf 'Y\n') 2>&1) +echo "$out" | grep -q "no .info found" \ + && { echo " PASS: no .info reported"; (( PASS++ )); } \ + || { echo " FAIL: 'no .info found' not in output"; echo "$out" | sed 's/^/ /'; (( FAIL++ )); ERRORS+=("T31 no info"); } +assert_not_contains "no orphanpkg section added" "$MOCK_BASE/nvchecker.toml" '\[orphanpkg\]' +``` + +- [ ] **Step 3: Run the full suite** + +Run: `bash tests/mkhint_test.sh` +Expected: all PASS including T29-T31; `Results: N passed, 0 failed` (N = 75: 69 + 6 new assertions). + +If a test fails, debug with superpowers:systematic-debugging. Likely culprits: stdin line count (T29/T31 supply exactly one `Y`/one line — there is no slackrepo prompt because the run returns 0 right after populate), or the `grep -q "Review .*re-run"` pattern not matching the exact message string (the message is `Sections added. Review <path> (fill any stubs), then re-run 'mkhint -C'.`). + +- [ ] **Step 4: Commit** + +```bash +git add tests/mkhint_test.sh +git commit -m "test: add T29-T31 for --check populate missing sections" +``` + +--- + +## Task 4: Document the populate prompt + +**Files:** +- Modify: `CLAUDE.md`, `README.md` + +- [ ] **Step 1: Update CLAUDE.md** + +In the "Key Behaviors" section, find the bullet describing `--check`/`-C`. Append a sentence (or a sub-bullet) stating: when `--check` encounters hint files with no `[pkg]` section in `nvchecker.toml`, it lists them and prompts once to populate the config; on accept it appends sections via `add_nvchecker_section` (github/pypi autodetect, else stub), prints a "review and re-run" message, and stops the run without applying updates. Packages whose `.info` cannot be found in `REPO_DIR` are skipped. + +Add a test-coverage table row group: + +``` +| T29 | `--check` missing section, accept populate — github section appended, run stops, hint unchanged | +| T30 | `--check` missing section, decline populate — nothing added | +| T31 | `--check` missing section, no `.info` in repo — skipped, no section added | +``` + +- [ ] **Step 2: Update README.md** + +In the `--check` documentation section, add a short paragraph: if any scanned hint has no nvchecker source configured, `mkhint --check` offers to populate `nvchecker.toml` for them (auto-detecting github/pypi from the SBo `.info`, otherwise writing a commented stub to fill in). After populating it asks you to review the file and re-run `mkhint -C`. + +- [ ] **Step 3: Commit** + +```bash +git add CLAUDE.md README.md +git commit -m "docs: document --check populate-missing-sections prompt" +``` + +--- + +## Task 5: Final verification + +- [ ] **Step 1: Syntax + full suite** + +Run: `bash -n mkhint && bash tests/mkhint_test.sh` +Expected: clean; `Results: 75 passed, 0 failed`. + +- [ ] **Step 2: Manual smoke — empty toml, decline** + +Run: +```bash +tmpcfg=$(mktemp); printf '[__config__]\noldver="/tmp/o.json"\nnewver="/tmp/n.json"\n' > "$tmpcfg" +echo '{"version":2,"data":{}}' > /tmp/n.json +HOME=$HOME bash -c 'true' # noop, just confirming environment +``` +(The real manual path requires nvchecker/jq installed; the mock suite already covers behaviour. This step is optional if tools are absent — the suite is authoritative.) + +- [ ] **Step 3: Confirm no stray TODO/debug** + +Run: `grep -n "TODO\|XXX\|DEBUG" mkhint` +Expected: only the intentional stub-template `# TODO: configure nvchecker source` inside `add_nvchecker_section`. + +- [ ] **Step 4: Spec compliance review** + +Use superpowers:requesting-code-review (or self-review) against +`docs/superpowers/specs/2026-06-13-check-populate-missing-sections-design.md`. +Confirm: missing-section detection, batch prompt, `.info` lookup + skip on absent, +`add_nvchecker_section` reuse, `return 0` stop with review message, T29-T31 present and passing. + +--- + +## Self-Review notes (author) + +- **Spec coverage:** §"Detect missing sections" → Task 2 Step 1 + Task 1 helper. §"Offer to populate" → Task 2 Step 2. `.info` lookup + skip → Task 2 Step 2 (find + empty check). Reuse of `add_nvchecker_section` → Task 2 Step 2. `return 0` stop → Task 2 Step 2. Decline fall-through → preserved (no change to outdated path). Tests T29-T31 → Task 3. Docs → Task 4. +- **Placeholder scan:** none. T31 Step 1 explicitly states no fixture edit needed (not a deferred TODO). +- **Name consistency:** `_has_nvchecker_section`, `missing_sections`, `add_nvchecker_section`, `NVCHECKER_CONFIG`, `REPO_DIR` consistent across Tasks 1-3 and matching the existing codebase. +- **set -e:** the one new fallible call (`latest=$(nvchecker_latest ...)`) is wrapped in `if !`. `find` is in a command substitution. `add_nvchecker_section` returns 0. Safe. +- **Assertion count:** T29 = 4 (toml header, source, review msg, hint unchanged), T30 = 1, T31 = 2. 7 new... recount: T29 adds 4 PASS, T30 adds 1, T31 adds 2 → 7. Plan Step 3 says N=75 (69+6). CORRECTED: expected total is 76 (69 + 7). Implementer: assert on the actual printed total, not a hardcoded number — the suite prints `Results: <PASS> passed, <FAIL> failed`; the pass criterion is `0 failed`. diff --git a/docs/superpowers/specs/2026-06-13-check-populate-missing-sections-design.md b/docs/superpowers/specs/2026-06-13-check-populate-missing-sections-design.md new file mode 100644 index 0000000..3a8b42a --- /dev/null +++ b/docs/superpowers/specs/2026-06-13-check-populate-missing-sections-design.md @@ -0,0 +1,155 @@ +# `--check` populate missing nvchecker sections — design + +Date: 2026-06-13 + +## Summary + +When `mkhint --check` / `-C` scans hint files, packages that have no `[pkg]` +section in `nvchecker.toml` are currently skipped silently-ish (`skip pkg: no +nvchecker source`). On a fresh/empty `nvchecker.toml` this means *every* hint is +skipped and the command does nothing useful. + +This feature makes `--check` detect those missing-section packages and, after the +scan, offer to populate `nvchecker.toml` by appending sections for them (reusing +the existing `add_nvchecker_section` autodetect logic). If the user accepts, the +sections are written and the run stops with a message telling the user to review +the file (fill any stubs) and re-run `mkhint -C`. + +## Motivation + +`add_nvchecker_section` already exists and is called on `--new`. `--check` should +give the user a path to bootstrap nvchecker config for hints created before this +integration existed, without hand-editing the TOML for every package. + +## Behaviour + +Implemented inside the existing `check_updates` function. + +### 1. Detect missing sections during the classify loop + +The classify loop currently does, per target package: + +```bash +latest=$(nvchecker_latest "$pkg") || { echo "skip ${pkg}: no nvchecker source"; continue; } +``` + +Change so the "no result" case distinguishes two situations: + +- A `[pkg]` section **exists** in `NVCHECKER_CONFIG` but nvchecker produced no + version → genuine "no result", keep the existing skip message + (`skip ${pkg}: no nvchecker result`). +- A `[pkg]` section is **missing** → add `pkg` to a `missing_sections` array and + print `skip ${pkg}: no nvchecker section` (do not classify it as outdated). + +Section presence is tested with the same pattern `add_nvchecker_section` uses, +extracted into a small shared helper: + +```bash +# Return 0 if NVCHECKER_CONFIG has a [pkg] section +_has_nvchecker_section() { + local pkg="$1" + grep -qE "^\[${pkg}\][[:space:]]*$" "$NVCHECKER_CONFIG" +} +``` + +`add_nvchecker_section` is updated to call `_has_nvchecker_section` instead of its +inline `grep` (pure DRY refactor, no behaviour change). + +### 2. Offer to populate, after the classify loop + +After the classify loop completes and before the "nothing outdated" / report +logic, insert: + +```bash +if [[ ${#missing_sections[@]} -gt 0 ]]; then + echo "" + echo "${#missing_sections[@]} package(s) have no nvchecker section: ${missing_sections[*]}" + local answer + read -r -p "Populate ${NVCHECKER_CONFIG} now? [Y/n] " answer + answer="${answer:-Y}" + if [[ "$answer" =~ ^[Yy]$ ]]; then + local mp info + for mp in "${missing_sections[@]}"; do + info=$(find "$REPO_DIR" -mindepth 2 -name "${mp}.info" 2>/dev/null | head -1) + if [[ -z "$info" ]]; then + echo "skip ${mp}: no .info found in $REPO_DIR" + continue + fi + add_nvchecker_section "$mp" "$info" + done + echo "" + echo "Sections added. Review $NVCHECKER_CONFIG (fill any stubs), then re-run 'mkhint -C'." + return 0 + fi +fi +``` + +Key points: + +- Single batch prompt (not per package). Default Yes. +- For each missing package, the `.info` is located in `REPO_DIR` with the same + `find` pattern `list_hint_files` already uses. + - No `.info` found → print `skip ${mp}: no .info found in $REPO_DIR`, do not add + a section. + - `.info` found → call `add_nvchecker_section "$mp" "$info"`, which appends a + github/pypi section if detected, otherwise a commented stub. (Its own + duplicate guard via `_has_nvchecker_section` makes this safe.) +- After populating, **stop the run** (`return 0`) with the review message. The + newly added sections are not queried this run — the user reviews/fills stubs + first, then re-runs. (Decision: simple separation over one-shot convenience.) +- If the user declines, fall through to the normal report path. The + missing-section packages were already excluded during classify, so they simply + don't appear as outdated. + +### Interaction with the outdated set + +A run can have both outdated packages (with sections) and missing-section +packages. If the user accepts populate, the run stops *before* applying any +outdated updates — so populate takes precedence and the user re-runs to pick up +updates afterward. This is acceptable: it keeps each run's effect simple and +predictable. If the user declines populate, outdated updates proceed as today. + +## Affected code + +- `mkhint`: + - New helper `_has_nvchecker_section`. + - `add_nvchecker_section` refactored to use it (no behaviour change). + - `check_updates`: `missing_sections` array, distinguished skip messages, the + populate prompt block. +- `tests/mkhint_test.sh`: T29–T31. +- `CLAUDE.md` / `README.md`: document the populate prompt under `--check`. + +No new dependencies, no new flags, no exit-code changes. + +## Testing + +Mock-based, as existing suite. The mock nvchecker keyfile (`new_ver.json`) only +contains versions for packages we want "found"; a package absent from the keyfile +AND absent from the TOML is a missing-section case. + +| ID | Scenario | +|-----|----------| +| T29 | `-C`, hint with no section, github `.info`, accept populate → github section appended, run stops with review message, hint NOT updated | +| T30 | `-C`, hint with no section, decline populate → no section added, no error (normal skip path) | +| T31 | `-C`, hint with no section, no `.info` in repo, accept populate → `skip ... no .info found`, no section added | + +Test mechanics: + +- T29 uses a package that has a `.info` with a github HOMEPAGE/DOWNLOAD in + `MOCK_REPO` (the existing `ghpkg` fixture) but **no** `[ghpkg]` line in the test + `nvchecker.toml` and **no** entry in `new_ver.json`. After `printf 'Y\n'`, + assert the TOML now contains `[ghpkg]` + `source = "github"`, assert output + contains the review message, assert the hint file VERSION is unchanged. +- T30 uses the same setup but `printf 'n\n'`; assert TOML still has no `[ghpkg]`. +- T31 uses a package name with a hint file but no matching `.info` anywhere in + `MOCK_REPO` (e.g. a hand-written `orphan.hint`); after `printf 'Y\n'`, assert + output contains `no .info found` and TOML has no `[orphan]` section. + +## Self-review notes + +- Reuses `add_nvchecker_section` and the `find` pattern from `list_hint_files` — + no new mechanisms. +- `_has_nvchecker_section` is the only new unit; DRYs an existing grep. +- `set -e` safety: `find ... || head` is in a command substitution (non-fatal); + `add_nvchecker_section` returns 0; `read` is fine. No unguarded fallible calls. +- Scope: single function change plus one helper plus three tests. Single plan. @@ -6,9 +6,10 @@ # ./mkhint --version VERSION --hintfile FILE Update existing hint file # ./mkhint --version VERSION --new FILE Create new hint file # ./mkhint --new FILE Create new hint file (no version) +# ./mkhint --hintfile FILE Update hint, suggest latest version via nvchecker +# ./mkhint --check [FILE...] Check all (or named) hints for upstream updates # ./mkhint --list List hint files # ./mkhint --clean Remove .bak files from HINT_DIR -# ./mkhint --delete FILE Delete a hint file (and .bak if present) # ./mkhint --no-dl --hintfile FILE Update hint, skip downloads, add NODOWNLOAD=yes # ./mkhint --no-dl --new FILE Create hint with NODOWNLOAD=yes # ./mkhint --help Show this help @@ -19,6 +20,7 @@ set -e REPO_DIR="/var/lib/sbopkg/SBo-danix/" HINT_DIR="/etc/slackrepo/SBo-danix/hintfiles/" TMP_DIR="/tmp/mkhint" +NVCHECKER_CONFIG="$HOME/.config/nvchecker/nvchecker.toml" # create the temp dir if not existing if [[ ! -d $TMP_DIR ]]; then @@ -42,6 +44,8 @@ Usage: ./mkhint --version VERSION --hintfile FILE Update existing hint file ./mkhint --version VERSION --new FILE Create new hint file ./mkhint --new FILE Create new hint file (no version) + ./mkhint --hintfile FILE Update hint, suggest latest version via nvchecker + ./mkhint --check [FILE...] Check all (or named) hints for upstream updates ./mkhint --list List hint files ./mkhint --clean Remove .bak files from HINT_DIR ./mkhint --no-dl --hintfile FILE Update hint, skip downloads, add NODOWNLOAD=yes @@ -54,6 +58,7 @@ Options: --new, -n FILE Create new hint file (required with --version or standalone) --list, -l List all hint files in the default directory --clean, -c Remove all .bak files from HINT_DIR + --check, -C [FILE...] Check hints for upstream updates via nvchecker, update interactively --delete, -d FILE Delete a hint file (and .bak if present) --no-dl, -N Skip downloads; add NODOWNLOAD=yes to hint file (use with -f or -n) --help, -h Show this help message @@ -69,7 +74,7 @@ Exit codes: 1 - Invalid arguments or missing required options 2 - File not found 3 - File already exists - 4 - wget not available + 4 - required tool not available (wget / nvchecker / nvtake / jq) EOF } @@ -121,6 +126,56 @@ check_wget() { fi } +# Validate nvchecker toolchain availability +check_nvchecker() { + local missing=() + command -v nvchecker &> /dev/null || missing+=("nvchecker") + command -v nvtake &> /dev/null || missing+=("nvtake") + command -v jq &> /dev/null || missing+=("jq") + if [[ ${#missing[@]} -gt 0 ]]; then + echo "Error: required tool(s) not installed: ${missing[*]}" >&2 + echo "Install nvchecker (provides nvchecker + nvtake) and jq." >&2 + exit 4 + fi + if [[ ! -f "$NVCHECKER_CONFIG" ]]; then + echo "Error: nvchecker config not found: $NVCHECKER_CONFIG" >&2 + exit 2 + fi +} + +# Echo the newver-keyfile path declared in [__config__] of NVCHECKER_CONFIG +_nvchecker_newver_path() { + # Grab the `newver = "..."` value; tolerate spaces around = + local line + line=$(grep -E '^[[:space:]]*newver[[:space:]]*=' "$NVCHECKER_CONFIG" | head -1) + [[ -z "$line" ]] && return 1 + # extract the quoted path + local path + path=$(printf '%s\n' "$line" | sed -E 's/^[^"]*"([^"]*)".*/\1/') + [[ -z "$path" ]] && return 1 + # expand a leading ~ to $HOME + path="${path/#\~/$HOME}" + # nvchecker resolves a relative keyfile path against the config file's + # directory (not the CWD), so do the same here. + if [[ "$path" != /* ]]; then + path="$(dirname "$NVCHECKER_CONFIG")/$path" + fi + printf '%s\n' "$path" +} + +# Echo the latest version nvchecker found for a package, or return non-zero +# Usage: latest=$(nvchecker_latest pkg) || handle "no version" +nvchecker_latest() { + local pkg="$1" + local keyfile + keyfile=$(_nvchecker_newver_path) || return 1 + [[ -f "$keyfile" ]] || return 1 + local ver + ver=$(jq -r --arg p "$pkg" '.data[$p].version // empty' "$keyfile" 2>/dev/null) + [[ -z "$ver" ]] && return 1 + printf '%s\n' "$ver" +} + # download files download_file() { local url="$1" @@ -187,6 +242,7 @@ create_new_hint_file() { echo "generated $normalized_file from $(basename $info)." echo "Check variables before using." + add_nvchecker_section "${normalized_file%.hint}" "$info" fi else echo "Hint file exists: $normalized_file" >&2 @@ -210,6 +266,91 @@ EOF fi } +# Emit the TOML section label for a package: bare if the name is a valid +# bare key ([A-Za-z0-9_] only), otherwise double-quoted. nvchecker (and TOML) +# require quoting for names containing '.', '-', etc. +_nvchecker_label() { + local pkg="$1" + if [[ "$pkg" =~ ^[A-Za-z0-9_]+$ ]]; then + printf '[%s]' "$pkg" + else + printf '["%s"]' "$pkg" + fi +} + +# Return 0 if NVCHECKER_CONFIG already has a section for pkg (bare or quoted) +_has_nvchecker_section() { + local pkg="$1" + [[ -f "$NVCHECKER_CONFIG" ]] || return 1 + local label; label=$(_nvchecker_label "$pkg") + # fixed-string match of the exact label at line start, trailing space allowed + grep -qE "^$(printf '%s' "$label" | sed 's/[][\.*^$/]/\\&/g')[[:space:]]*$" \ + "$NVCHECKER_CONFIG" +} + +# Append an nvchecker [pkg] section to NVCHECKER_CONFIG, auto-detecting the +# source from the package's .info DOWNLOAD/HOMEPAGE. No-op if section exists. +add_nvchecker_section() { + local pkg="$1" + local info_file="$2" + + # Ensure config dir/file exist (do not create __config__; user owns that) + mkdir -p "$(dirname "$NVCHECKER_CONFIG")" + touch "$NVCHECKER_CONFIG" + + local label; label=$(_nvchecker_label "$pkg") + + # Skip if section already present + if _has_nvchecker_section "$pkg"; then + echo "nvchecker: ${label} already present in $NVCHECKER_CONFIG" + return 0 + fi + + local download="" homepage="" + if [[ -f "$info_file" ]]; then + download=$(grep -E '^(DOWNLOAD|DOWNLOAD_x86_64)=' "$info_file" | head -1) + homepage=$(grep -E '^HOMEPAGE=' "$info_file" | head -1) + fi + local haystack="${download} ${homepage}" + + local section="" + if [[ "$haystack" =~ github\.com/([A-Za-z0-9._-]+)/([A-Za-z0-9._-]+) ]]; then + local owner="${BASH_REMATCH[1]}" + local repo="${BASH_REMATCH[2]}" + repo="${repo%.git}" + section=$(cat <<EOF + +${label} +source = "github" +github = "${owner}/${repo}" +use_max_tag = true +EOF +) + elif [[ "$haystack" =~ (pypi\.org|files\.pythonhosted\.org) ]]; then + section=$(cat <<EOF + +${label} +source = "pypi" +pypi = "${pkg}" +EOF +) + else + section=$(cat <<EOF + +${label} +# TODO: configure nvchecker source for "${pkg}" +# source = "regex" +# url = "..." +# regex = "..." +# see https://nvchecker.readthedocs.io/en/latest/usage.html +EOF +) + fi + + printf '%s\n' "$section" >> "$NVCHECKER_CONFIG" + echo "nvchecker: review/fill ${label} section in $NVCHECKER_CONFIG" +} + # Add NODOWNLOAD=yes after MD5SUM_x86_64 line if not already present add_nodownload() { local file="$1" @@ -289,6 +430,36 @@ build_multiline_value() { printf '"\n' } +# Query nvchecker for a package's latest version and let the user accept or +# override it. Echoes the chosen version on stdout. Returns non-zero if the +# user declines or no version is available (caller decides what to do). +suggest_version() { + local pkg="$1" + + # Refresh nvchecker results (stderr only; keep stdout clean for the echo) + nvchecker -c "$NVCHECKER_CONFIG" >&2 || true + + local latest + latest=$(nvchecker_latest "$pkg") || { + echo "Error: no nvchecker result for '$pkg'. Add/fix its [${pkg}] section in $NVCHECKER_CONFIG" >&2 + return 1 + } + + # Read current version from the hint file (best effort, for display) + local hintpath="${HINT_DIR%/}/${pkg}.hint" + local current="" + [[ -f "$hintpath" ]] && current=$(grep '^VERSION=' "$hintpath" | sed 's/VERSION="//;s/"$//') + + local answer + read -r -p "current ${current:-?}, latest ${latest}. Use ${latest}? [Y/n] (or type a version) " answer >&2 + answer="${answer:-Y}" + case "$answer" in + [Yy]) printf '%s\n' "$latest" ;; + [Nn]) return 1 ;; + *) printf '%s\n' "$answer" ;; + esac +} + # Download files and update MD5SUM/MD5SUM_x86_64 in hint file update_checksums() { local file="$1" @@ -459,11 +630,130 @@ clean_bak_files() { echo "Removed $count .bak file(s) from $HINT_DIR" } +# Bulk-check hint files for upstream updates and apply interactively. +# Usage: check_updates [pkg...] (no args = all *.hint in HINT_DIR) +check_updates() { + check_nvchecker + + if [[ ! -d "$HINT_DIR" ]]; then + echo "Error: Hint directory does not exist: $HINT_DIR" >&2 + exit 2 + fi + + # Build the target package list + local targets=() + if [[ $# -gt 0 ]]; then + targets=("$@") + else + local f + for f in "$HINT_DIR"/*.hint; do + [[ -f "$f" ]] || continue + local b; b=$(basename "$f"); targets+=("${b%.hint}") + done + fi + + # Refresh nvchecker results once for everything + echo "Running nvchecker..." + nvchecker -c "$NVCHECKER_CONFIG" >&2 || true + + # Classify each target + local outdated_pkgs=() outdated_old=() outdated_new=() outdated_flag=() + local missing_sections=() + local pkg + for pkg in "${targets[@]}"; do + local hintpath="${HINT_DIR%/}/${pkg}.hint" + [[ -f "$hintpath" ]] || { echo "skip ${pkg}: no hint file"; continue; } + local current; current=$(grep '^VERSION=' "$hintpath" | sed 's/VERSION="//;s/"$//') + local latest + if ! latest=$(nvchecker_latest "$pkg"); then + if _has_nvchecker_section "$pkg"; then + echo "skip ${pkg}: no nvchecker result" + else + echo "skip ${pkg}: no nvchecker section" + missing_sections+=("$pkg") + fi + continue + fi + [[ "$current" == "$latest" ]] && continue # up to date + # determine direction with sort -V + local newest; newest=$(printf '%s\n%s\n' "$current" "$latest" | sort -V | tail -1) + local flag="update" + [[ "$newest" == "$current" ]] && flag="?downgrade" + outdated_pkgs+=("$pkg") + outdated_old+=("$current") + outdated_new+=("$latest") + outdated_flag+=("$flag") + done + + # Offer to populate nvchecker.toml for packages with no section + if [[ ${#missing_sections[@]} -gt 0 ]]; then + echo "" + echo "${#missing_sections[@]} package(s) have no nvchecker section: ${missing_sections[*]}" + local answer + read -r -p "Populate ${NVCHECKER_CONFIG} now? [Y/n] " answer + answer="${answer:-Y}" + if [[ "$answer" =~ ^[Yy]$ ]]; then + local mp info + for mp in "${missing_sections[@]}"; do + info=$(find "$REPO_DIR" -mindepth 2 -name "${mp}.info" 2>/dev/null | head -1) + if [[ -z "$info" ]]; then + echo "skip ${mp}: no .info found in $REPO_DIR" + continue + fi + add_nvchecker_section "$mp" "$info" + done + echo "" + echo "Sections added. Review $NVCHECKER_CONFIG (fill any stubs), then re-run 'mkhint -C'." + return 0 + fi + fi + + if [[ ${#outdated_pkgs[@]} -eq 0 ]]; then + echo "all up to date" + return 0 + fi + + # Report + echo "" + echo "Updates available:" + local i + for (( i=0; i<${#outdated_pkgs[@]}; i++ )); do + local note=""; [[ "${outdated_flag[$i]}" == "?downgrade" ]] && note=" (?downgrade)" + printf " %-30s %s -> %s%s\n" "${outdated_pkgs[$i]}" "${outdated_old[$i]}" "${outdated_new[$i]}" "$note" + done + echo "" + + # Per-package confirm + update + local updated=() + for (( i=0; i<${#outdated_pkgs[@]}; i++ )); do + local p="${outdated_pkgs[$i]}" + local note=""; [[ "${outdated_flag[$i]}" == "?downgrade" ]] && note=" (?downgrade)" + local answer + read -r -p "${p} ${outdated_old[$i]} -> ${outdated_new[$i]}${note}. Update? [Y/n] " answer + answer="${answer:-Y}" + if [[ "$answer" =~ ^[Yy]$ ]]; then + update_hint_file "$p" "${outdated_new[$i]}" + nvtake -c "$NVCHECKER_CONFIG" "$p" >&2 || true + updated+=("$p") + fi + done + + # Single slackrepo prompt for everything updated + if [[ ${#updated[@]} -gt 0 ]]; then + local answer + read -r -p "Run 'slackrepo update ${updated[*]}'? [Y/n] " answer + answer="${answer:-Y}" + if [[ "$answer" =~ ^[Yy]$ ]]; then + slackrepo update "${updated[@]}" + fi + fi +} + # Main function main() { local parsed - parsed=$(getopt -o v:f:n:lcdNh \ - --long version:,hintfile:,new:,list,clean,delete,no-dl,help \ + parsed=$(getopt -o v:f:n:lcCdNh \ + --long version:,hintfile:,new:,list,clean,check,delete,no-dl,help \ -n 'mkhint' -- "$@") || { show_help; exit 1; } eval set -- "$parsed" @@ -489,6 +779,10 @@ main() { COMMAND="clean" shift ;; + --check|-C) + COMMAND="check" + shift + ;; --delete|-d) COMMAND="delete" shift @@ -521,7 +815,7 @@ main() { if [[ -z "$COMMAND" ]]; then # Default to update hint file if VERSION and HINT_FILE are provided - if [[ -n "$VERSION" && -n "$HINT_FILE" ]]; then + if [[ -n "$HINT_FILE" ]]; then COMMAND="update" elif [[ -n "$NEW_HINT_FILE" ]]; then COMMAND="new" @@ -535,6 +829,11 @@ main() { exit 1 fi + if [[ "$COMMAND" == "check" && ( -n "$VERSION" || -n "$HINT_FILE" || -n "$NEW_HINT_FILE" ) ]]; then + echo "Error: --check cannot be combined with --version/--hintfile/--new" >&2 + exit 1 + fi + case "$COMMAND" in help) show_help @@ -545,9 +844,20 @@ main() { clean) clean_bak_files ;; + check) + check_updates "${DELETE_HINT_FILES[@]}" + ;; update) check_wget + if [[ -z "$VERSION" ]]; then + check_nvchecker + VERSION=$(suggest_version "$HINT_FILE") || { echo "Aborted." >&2; exit 0; } + check_nvchecker_take=1 + fi update_hint_file "$HINT_FILE" "$VERSION" + if [[ "${check_nvchecker_take:-0}" -eq 1 ]]; then + nvtake -c "$NVCHECKER_CONFIG" "$HINT_FILE" >&2 || true + fi prompt_slackrepo "$HINT_FILE" ;; new) diff --git a/mkhint.bash-completion b/mkhint.bash-completion index dee57e7..b786bc3 100644 --- a/mkhint.bash-completion +++ b/mkhint.bash-completion @@ -8,7 +8,7 @@ _mkhintfile_completions() { repo_dir="/var/lib/sbopkg/SBo-danix" hint_dir="/etc/slackrepo/SBo-danix/hintfiles" - local all_flags="--version -v --hintfile -f --new -n --list -l --clean -c --delete -d --no-dl -N --help -h" + local all_flags="--version -v --hintfile -f --new -n --list -l --clean -c --check -C --delete -d --no-dl -N --help -h" case "$prev" in --new|-n) @@ -18,7 +18,7 @@ _mkhintfile_completions() { done < <(find "$repo_dir" -mindepth 2 -maxdepth 2 -name "*.info" 2>/dev/null) COMPREPLY=($(compgen -W "${words[*]}" -- "$cur")) ;; - --hintfile|-f|--delete|-d) + --hintfile|-f|--delete|-d|--check|-C) local -a words=() for f in "$hint_dir"/*.hint; do [[ -f "$f" ]] && words+=("$(basename "${f%.hint}")") diff --git a/tests/mkhint_test.sh b/tests/mkhint_test.sh index dbcc86a..dd88c05 100755 --- a/tests/mkhint_test.sh +++ b/tests/mkhint_test.sh @@ -15,6 +15,9 @@ setup() { mkdir -p "$MOCK_REPO/network/curl" \ "$MOCK_REPO/development/protoc-gen-go-grpc" \ "$MOCK_REPO/development/clion" \ + "$MOCK_REPO/development/ghpkg" \ + "$MOCK_REPO/python/pypkg" \ + "$MOCK_REPO/multimedia/yt-dlp" \ "$MOCK_HINT" \ "$MOCK_TMP" @@ -61,6 +64,58 @@ REQUIRES="" MAINTAINER="Test" EMAIL="test@test.com" EOF + + cat > "$MOCK_REPO/development/ghpkg/ghpkg.info" << 'EOF' +PRGNAM="ghpkg" +VERSION="1.0.0" +HOMEPAGE="https://github.com/someowner/ghpkg" +DOWNLOAD="https://github.com/someowner/ghpkg/archive/v1.0.0/ghpkg-1.0.0.tar.gz" +MD5SUM="11111111111111111111111111111111" +DOWNLOAD_x86_64="" +MD5SUM_x86_64="" +REQUIRES="" +MAINTAINER="Test" +EMAIL="test@test.com" +EOF + + cat > "$MOCK_REPO/python/pypkg/pypkg.info" << 'EOF' +PRGNAM="pypkg" +VERSION="2.0.0" +HOMEPAGE="https://pypi.org/project/pypkg/" +DOWNLOAD="https://files.pythonhosted.org/packages/source/p/pypkg/pypkg-2.0.0.tar.gz" +MD5SUM="22222222222222222222222222222222" +DOWNLOAD_x86_64="" +MD5SUM_x86_64="" +REQUIRES="" +MAINTAINER="Test" +EMAIL="test@test.com" +EOF + + # github .info with a dash in the package name (needs TOML quoting) + cat > "$MOCK_REPO/multimedia/yt-dlp/yt-dlp.info" << 'EOF' +PRGNAM="yt-dlp" +VERSION="2024.1.1" +HOMEPAGE="https://github.com/yt-dlp/yt-dlp" +DOWNLOAD="https://github.com/yt-dlp/yt-dlp/archive/2024.1.1/yt-dlp-2024.1.1.tar.gz" +MD5SUM="55555555555555555555555555555555" +DOWNLOAD_x86_64="" +MD5SUM_x86_64="" +REQUIRES="" +MAINTAINER="Test" +EMAIL="test@test.com" +EOF + + # nvchecker config + keyfile for tests + cat > "$MOCK_BASE/nvchecker.toml" << EOF +[__config__] +oldver = "$MOCK_BASE/old_ver.json" +newver = "$MOCK_BASE/new_ver.json" +EOF + # keyfile pre-seeded with versions the mock nvchecker will "find" + cat > "$MOCK_BASE/new_ver.json" << 'EOF' +{ "version": 2, "data": { "curl": { "version": "8.9.0" }, "clion": { "version": "2025.4" } } } +EOF + cp "$MOCK_BASE/new_ver.json" "$MOCK_BASE/old_ver.json" } teardown() { @@ -74,6 +129,7 @@ run_mkhint() { -e "s|REPO_DIR=\".*\"|REPO_DIR=\"$MOCK_REPO\"|" \ -e "s|HINT_DIR=\".*\"|HINT_DIR=\"$MOCK_HINT\"|" \ -e "s|TMP_DIR=\".*\"|TMP_DIR=\"$MOCK_TMP\"|" \ + -e "s|NVCHECKER_CONFIG=\".*\"|NVCHECKER_CONFIG=\"$MOCK_BASE/nvchecker.toml\"|" \ "$SCRIPT" > "$tmp_script" bash "$tmp_script" "$@" local rc=$? @@ -103,6 +159,30 @@ EOF export PATH="$MOCK_BASE/bin:$PATH" } +# Mock nvchecker, nvtake into $MOCK_BASE/bin (real jq used if present) +mock_nvchecker_tools() { + mkdir -p "$MOCK_BASE/bin" + + # nvchecker: no-op success (keyfile is pre-seeded by setup/tests) + cat > "$MOCK_BASE/bin/nvchecker" << 'EOF' +#!/bin/bash +exit 0 +EOF + chmod +x "$MOCK_BASE/bin/nvchecker" + + # nvtake: record invocations, otherwise no-op + cat > "$MOCK_BASE/bin/nvtake" << EOF +#!/bin/bash +echo "nvtake \$*" >> "$MOCK_BASE/nvtake.log" +exit 0 +EOF + chmod +x "$MOCK_BASE/bin/nvtake" + + if ! command -v jq &> /dev/null; then + echo "WARNING: real jq not found; install jq to run nvchecker tests" >&2 + fi +} + assert_contains() { local desc="$1" file="$2" pattern="$3" if grep -q "$pattern" "$file" 2>/dev/null; then @@ -175,6 +255,7 @@ echo "========================================" setup mock_wget +mock_nvchecker_tools # ── T1: --new from .info, no version ────────────────────────────────────────── echo "" @@ -333,6 +414,336 @@ run_mkhint -c assert_file_not_exists "a.bak removed" "$MOCK_HINT/a.hint.bak" assert_file_not_exists "b.bak removed" "$MOCK_HINT/b.hint.bak" +# ── T16: --new github .info → github source section ─────────────────────────── +echo "" +echo "T16: --new github .info → [pkg] source=github appended" +run_mkhint -n ghpkg +assert_contains "github section header" "$MOCK_BASE/nvchecker.toml" '\[ghpkg\]' +assert_contains "github source" "$MOCK_BASE/nvchecker.toml" 'source = "github"' +assert_contains "github owner/repo" "$MOCK_BASE/nvchecker.toml" 'github = "someowner/ghpkg"' + +# ── T17: --new pypi .info → pypi source section ─────────────────────────────── +echo "" +echo "T17: --new pypi .info → [pkg] source=pypi appended" +run_mkhint -n pypkg +assert_contains "pypi section header" "$MOCK_BASE/nvchecker.toml" '\[pypkg\]' +assert_contains "pypi source" "$MOCK_BASE/nvchecker.toml" 'source = "pypi"' +assert_contains "pypi name" "$MOCK_BASE/nvchecker.toml" 'pypi = "pypkg"' + +# ── T18: --new unrecognised URL → commented stub ────────────────────────────── +echo "" +echo "T18: --new unknown source → commented stub appended" +run_mkhint -n clion +assert_contains "clion section header" "$MOCK_BASE/nvchecker.toml" '\[clion\]' +assert_contains "stub TODO" "$MOCK_BASE/nvchecker.toml" 'TODO: configure nvchecker source' + +# ── T19: --new when [pkg] already present → no duplicate ─────────────────────── +echo "" +echo "T19: --new when section exists → not duplicated" +run_mkhint -n ghpkg # ghpkg section already added in T16 +dup_count=$(grep -c '^\[ghpkg\]' "$MOCK_BASE/nvchecker.toml") +assert_exit_code "ghpkg section appears once" 1 "$dup_count" + +# ── T20: --hintfile no -v, accept suggestion → VERSION=latest, nvtake called ─── +echo "" +echo "T20: --hintfile no -v, accept suggestion" +cat > "$MOCK_HINT/curl.hint" << 'EOF' +VERSION="8.5.0" +ARCH="x86_64" +DOWNLOAD="https://curl.se/download/curl-8.5.0.tar.gz" +MD5SUM="abc123def456abc123def456abc123de" +DOWNLOAD_x86_64="" +MD5SUM_x86_64="" +EOF +rm -f "$MOCK_BASE/nvtake.log" +# blank = accept latest (8.9.0 from keyfile); n = skip slackrepo +run_mkhint -f curl < <(printf '\nn\n') +assert_contains "VERSION set to latest" "$MOCK_HINT/curl.hint" 'VERSION="8.9.0"' +assert_contains "URL has latest version" "$MOCK_HINT/curl.hint" 'curl-8.9.0' +assert_file_exists "nvtake was called" "$MOCK_BASE/nvtake.log" + +# ── T21: --hintfile no -v, type override version ────────────────────────────── +echo "" +echo "T21: --hintfile no -v, type override version" +cat > "$MOCK_HINT/curl.hint" << 'EOF' +VERSION="8.5.0" +ARCH="x86_64" +DOWNLOAD="https://curl.se/download/curl-8.5.0.tar.gz" +MD5SUM="abc123def456abc123def456abc123de" +DOWNLOAD_x86_64="" +MD5SUM_x86_64="" +EOF +run_mkhint -f curl < <(printf '8.8.8\nn\n') +assert_contains "VERSION = typed value" "$MOCK_HINT/curl.hint" 'VERSION="8.8.8"' +assert_contains "URL has typed version" "$MOCK_HINT/curl.hint" 'curl-8.8.8' + +# ── T22: --hintfile no -v, package absent from keyfile → graceful abort ──────── +echo "" +echo "T22: --hintfile no -v, package absent from keyfile → error, hint untouched" +cat > "$MOCK_HINT/protoc-gen-go-grpc.hint" << 'EOF' +VERSION="1.3.0" +ARCH="x86_64" +DOWNLOAD="https://example.com/x-1.3.0.tar.gz" +MD5SUM="33333333333333333333333333333333" +DOWNLOAD_x86_64="" +MD5SUM_x86_64="" +EOF +set +e +run_mkhint -f protoc-gen-go-grpc < <(printf '\n') >/dev/null 2>&1 +code=$? +set -e +assert_exit_code "graceful abort on no result" 0 "$code" +assert_contains "hint version unchanged" "$MOCK_HINT/protoc-gen-go-grpc.hint" 'VERSION="1.3.0"' + +# ── T23: --check one outdated, confirm → updated + nvtake + slackrepo prompt ─── +echo "" +echo "T23: --check single outdated package, confirm update" +cat > "$MOCK_BASE/new_ver.json" << 'EOF' +{ "version": 2, "data": { "curl": { "version": "8.9.0" } } } +EOF +cat > "$MOCK_HINT/curl.hint" << 'EOF' +VERSION="8.5.0" +ARCH="x86_64" +DOWNLOAD="https://curl.se/download/curl-8.5.0.tar.gz" +MD5SUM="abc123def456abc123def456abc123de" +DOWNLOAD_x86_64="" +MD5SUM_x86_64="" +EOF +rm -f "$MOCK_HINT/clion.hint" "$MOCK_HINT/protoc-gen-go-grpc.hint" "$MOCK_HINT"/*.bak 2>/dev/null +rm -f "$MOCK_BASE/nvtake.log" +run_mkhint -C curl < <(printf 'Y\nn\n') +assert_contains "curl updated to 8.9.0" "$MOCK_HINT/curl.hint" 'VERSION="8.9.0"' +assert_file_exists "nvtake called" "$MOCK_BASE/nvtake.log" + +# ── T24: --check all current → 'all up to date', no slackrepo ────────────────── +echo "" +echo "T24: --check when everything current" +out=$(run_mkhint -C curl < <(printf '\n') 2>&1) +echo "$out" | grep -q "all up to date" \ + && { echo " PASS: reports all up to date"; (( PASS++ )); } \ + || { echo " FAIL: did not report all up to date"; (( FAIL++ )); ERRORS+=("T24 up to date"); } + +# ── T25: --check two outdated, decline first accept second ───────────────────── +echo "" +echo "T25: --check two outdated, decline first accept second" +cat > "$MOCK_BASE/new_ver.json" << 'EOF' +{ "version": 2, "data": { "curl": { "version": "9.0.0" }, "clion": { "version": "2025.5" } } } +EOF +cat > "$MOCK_HINT/curl.hint" << 'EOF' +VERSION="8.9.0" +ARCH="x86_64" +DOWNLOAD="https://curl.se/download/curl-8.9.0.tar.gz" +MD5SUM="abc123def456abc123def456abc123de" +DOWNLOAD_x86_64="" +MD5SUM_x86_64="" +EOF +cat > "$MOCK_HINT/clion.hint" << 'EOF' +VERSION="2025.4" +ARCH="x86_64" +DOWNLOAD="UNSUPPORTED" +MD5SUM="" +DOWNLOAD_x86_64="https://download.jetbrains.com/cpp/CLion-2025.4.tar.gz" +MD5SUM_x86_64="dff91fe793b8d3ee2446dd340288eef5" +EOF +# explicit order curl clion → answers: n (decline curl), Y (accept clion), n (slackrepo) +run_mkhint -C curl clion < <(printf 'n\nY\nn\n') +assert_contains "curl declined (unchanged)" "$MOCK_HINT/curl.hint" 'VERSION="8.9.0"' +assert_contains "clion accepted (updated)" "$MOCK_HINT/clion.hint" 'VERSION="2025.5"' + +# ── T26: --check with -v → mutually-exclusive error exit 1 ───────────────────── +echo "" +echo "T26: --check combined with -v → exit 1" +set +e +run_mkhint -C -v 1.0 2>/dev/null +code=$? +set -e +assert_exit_code "check + -v exits 1" 1 "$code" + +# ── T27: --check upstream older than hint → (?downgrade) flag ────────────────── +echo "" +echo "T27: --check when upstream version is older → reported as (?downgrade)" +cat > "$MOCK_BASE/new_ver.json" << 'EOF' +{ "version": 2, "data": { "curl": { "version": "8.0.0" } } } +EOF +cat > "$MOCK_HINT/curl.hint" << 'EOF' +VERSION="8.9.0" +ARCH="x86_64" +DOWNLOAD="https://curl.se/download/curl-8.9.0.tar.gz" +MD5SUM="abc123def456abc123def456abc123de" +DOWNLOAD_x86_64="" +MD5SUM_x86_64="" +EOF +# decline the update (n), so hint stays unchanged; capture report output +out=$(run_mkhint -C curl < <(printf 'n\n') 2>&1) +echo "$out" | grep -q "(?downgrade)" \ + && { echo " PASS: downgrade flagged in report"; (( PASS++ )); } \ + || { echo " FAIL: (?downgrade) not in report"; echo "$out" | sed 's/^/ /'; (( FAIL++ )); ERRORS+=("T27 downgrade flag"); } +assert_contains "curl unchanged after decline" "$MOCK_HINT/curl.hint" 'VERSION="8.9.0"' + +# ── T28: --check with no args → scans all hints in HINT_DIR ──────────────────── +echo "" +echo "T28: --check with no package args scans entire HINT_DIR" +# only curl present and outdated; new_ver has curl 8.9.0 +cat > "$MOCK_BASE/new_ver.json" << 'EOF' +{ "version": 2, "data": { "curl": { "version": "8.9.0" } } } +EOF +rm -f "$MOCK_HINT"/*.hint "$MOCK_HINT"/*.bak 2>/dev/null +cat > "$MOCK_HINT/curl.hint" << 'EOF' +VERSION="8.5.0" +ARCH="x86_64" +DOWNLOAD="https://curl.se/download/curl-8.5.0.tar.gz" +MD5SUM="abc123def456abc123def456abc123de" +DOWNLOAD_x86_64="" +MD5SUM_x86_64="" +EOF +# no pkg args → scan-all; accept curl (Y), decline slackrepo (n) +run_mkhint -C < <(printf 'Y\nn\n') +assert_contains "scan-all updated curl" "$MOCK_HINT/curl.hint" 'VERSION="8.9.0"' + +# ── T29: --check missing section, accept populate → section added, run stops ─── +echo "" +echo "T29: --check missing section, accept populate → github section appended, no update" +cat > "$MOCK_BASE/nvchecker.toml" << EOF +[__config__] +oldver = "$MOCK_BASE/old_ver.json" +newver = "$MOCK_BASE/new_ver.json" +EOF +cat > "$MOCK_BASE/new_ver.json" << 'EOF' +{ "version": 2, "data": {} } +EOF +rm -f "$MOCK_HINT"/*.hint "$MOCK_HINT"/*.bak 2>/dev/null +cat > "$MOCK_HINT/ghpkg.hint" << 'EOF' +VERSION="1.0.0" +ARCH="x86_64" +DOWNLOAD="https://github.com/someowner/ghpkg/archive/v1.0.0/ghpkg-1.0.0.tar.gz" +MD5SUM="11111111111111111111111111111111" +DOWNLOAD_x86_64="" +MD5SUM_x86_64="" +EOF +out=$(run_mkhint -C ghpkg < <(printf 'Y\n') 2>&1) +assert_contains "ghpkg section appended" "$MOCK_BASE/nvchecker.toml" '\[ghpkg\]' +assert_contains "github source detected" "$MOCK_BASE/nvchecker.toml" 'source = "github"' +echo "$out" | grep -q "Review .*re-run" \ + && { echo " PASS: review/re-run message shown"; (( PASS++ )); } \ + || { echo " FAIL: review message missing"; echo "$out" | sed 's/^/ /'; (( FAIL++ )); ERRORS+=("T29 review msg"); } +assert_contains "ghpkg hint version unchanged" "$MOCK_HINT/ghpkg.hint" 'VERSION="1.0.0"' + +# ── T30: --check missing section, decline populate → no section added ────────── +echo "" +echo "T30: --check missing section, decline populate → nothing added" +cat > "$MOCK_BASE/nvchecker.toml" << EOF +[__config__] +oldver = "$MOCK_BASE/old_ver.json" +newver = "$MOCK_BASE/new_ver.json" +EOF +cat > "$MOCK_BASE/new_ver.json" << 'EOF' +{ "version": 2, "data": {} } +EOF +rm -f "$MOCK_HINT"/*.hint "$MOCK_HINT"/*.bak 2>/dev/null +cat > "$MOCK_HINT/ghpkg.hint" << 'EOF' +VERSION="1.0.0" +ARCH="x86_64" +DOWNLOAD="https://github.com/someowner/ghpkg/archive/v1.0.0/ghpkg-1.0.0.tar.gz" +MD5SUM="11111111111111111111111111111111" +DOWNLOAD_x86_64="" +MD5SUM_x86_64="" +EOF +run_mkhint -C ghpkg < <(printf 'n\n') >/dev/null 2>&1 +assert_not_contains "no ghpkg section after decline" "$MOCK_BASE/nvchecker.toml" '\[ghpkg\]' + +# ── T31: --check missing section, no .info in repo, accept → skipped, no section +echo "" +echo "T31: --check missing section but no .info → skipped, no section added" +cat > "$MOCK_BASE/nvchecker.toml" << EOF +[__config__] +oldver = "$MOCK_BASE/old_ver.json" +newver = "$MOCK_BASE/new_ver.json" +EOF +cat > "$MOCK_BASE/new_ver.json" << 'EOF' +{ "version": 2, "data": {} } +EOF +rm -f "$MOCK_HINT"/*.hint "$MOCK_HINT"/*.bak 2>/dev/null +cat > "$MOCK_HINT/orphanpkg.hint" << 'EOF' +VERSION="3.0.0" +ARCH="x86_64" +DOWNLOAD="https://example.com/orphanpkg-3.0.0.tar.gz" +MD5SUM="44444444444444444444444444444444" +DOWNLOAD_x86_64="" +MD5SUM_x86_64="" +EOF +out=$(run_mkhint -C orphanpkg < <(printf 'Y\n') 2>&1) +echo "$out" | grep -q "no .info found" \ + && { echo " PASS: no .info reported"; (( PASS++ )); } \ + || { echo " FAIL: 'no .info found' not in output"; echo "$out" | sed 's/^/ /'; (( FAIL++ )); ERRORS+=("T31 no info"); } +assert_not_contains "no orphanpkg section added" "$MOCK_BASE/nvchecker.toml" '\[orphanpkg\]' + +# ── T32: --new with dash in name → section header is TOML-quoted ─────────────── +echo "" +echo "T32: --new yt-dlp → [\"yt-dlp\"] quoted header written" +cat > "$MOCK_BASE/nvchecker.toml" << EOF +[__config__] +oldver = "$MOCK_BASE/old_ver.json" +newver = "$MOCK_BASE/new_ver.json" +EOF +rm -f "$MOCK_HINT"/*.hint "$MOCK_HINT"/*.bak 2>/dev/null +run_mkhint -n yt-dlp +assert_contains "quoted section header" "$MOCK_BASE/nvchecker.toml" '^\["yt-dlp"\]' +assert_not_contains "no bare header" "$MOCK_BASE/nvchecker.toml" '^\[yt-dlp\]' +assert_contains "github source" "$MOCK_BASE/nvchecker.toml" 'github = "yt-dlp/yt-dlp"' + +# ── T33: _has_nvchecker_section matches quoted header → no duplicate on re---new +echo "" +echo "T33: --new yt-dlp again → quoted section not duplicated" +run_mkhint -n yt-dlp # section already exists from T32 (quoted) +dup_count=$(grep -cE '^\["yt-dlp"\]' "$MOCK_BASE/nvchecker.toml") +assert_exit_code "quoted yt-dlp appears once" 1 "$dup_count" + +# ── T34: --check sees quoted section as present, not "no section" ────────────── +echo "" +echo "T34: --check with existing quoted section → not flagged missing" +# yt-dlp quoted section present (from T32); not in keyfile → 'no nvchecker result', NOT 'no section' +cat > "$MOCK_HINT/yt-dlp.hint" << 'EOF' +VERSION="2024.1.1" +ARCH="x86_64" +DOWNLOAD="https://github.com/yt-dlp/yt-dlp/archive/2024.1.1/yt-dlp-2024.1.1.tar.gz" +MD5SUM="55555555555555555555555555555555" +DOWNLOAD_x86_64="" +MD5SUM_x86_64="" +EOF +out=$(run_mkhint -C yt-dlp < <(printf 'n\n') 2>&1) +echo "$out" | grep -q "yt-dlp: no nvchecker result" \ + && { echo " PASS: quoted section recognized (no result, not no section)"; (( PASS++ )); } \ + || { echo " FAIL: quoted section not recognized"; echo "$out" | sed 's/^/ /'; (( FAIL++ )); ERRORS+=("T34 quoted recognized"); } +echo "$out" | grep -q "yt-dlp: no nvchecker section" \ + && { echo " FAIL: quoted section wrongly flagged missing"; (( FAIL++ )); ERRORS+=("T34 false missing"); } \ + || { echo " PASS: not flagged as missing section"; (( PASS++ )); } + +# ── T35: relative newver path resolved against config dir, not CWD ───────────── +echo "" +echo "T35: relative newver path in config → resolved against config dir" +# config uses a RELATIVE newver path (as nvchecker writes by default). +# keyfile lives beside the config in $MOCK_BASE. The run happens with CWD +# elsewhere (the repo dir), so a CWD-relative read would fail to find it. +cat > "$MOCK_BASE/nvchecker.toml" << EOF +[__config__] +oldver = "old_ver.json" +newver = "new_ver.json" +EOF +cat > "$MOCK_BASE/new_ver.json" << 'EOF' +{ "version": 2, "data": { "curl": { "version": "8.9.0" } } } +EOF +rm -f "$MOCK_HINT"/*.hint "$MOCK_HINT"/*.bak 2>/dev/null +cat > "$MOCK_HINT/curl.hint" << 'EOF' +VERSION="8.5.0" +ARCH="x86_64" +DOWNLOAD="https://curl.se/download/curl-8.5.0.tar.gz" +MD5SUM="abc123def456abc123def456abc123de" +DOWNLOAD_x86_64="" +MD5SUM_x86_64="" +EOF +run_mkhint -C curl < <(printf 'Y\nn\n') +assert_contains "relative-path keyfile found → curl updated" "$MOCK_HINT/curl.hint" 'VERSION="8.9.0"' + # ─── SUMMARY ────────────────────────────────────────────────────────────────── teardown |
