From 1ddba77c283f57000baf972170bef1ba66945927 Mon Sep 17 00:00:00 2001 From: Mikkeli Date: Sat, 29 Aug 2026 10:24:52 +0900 Subject: [PATCH] feat: add a branding consistency checker MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adapted from musiikki/codename-206, where the same script guards a single plugin. It matters more here: renaming is the first thing anyone does with a template, and identity is duplicated across Cargo.toml, bundler.toml, src/lib.rs and the docs with nothing keeping them in agreement. A half-finished rename builds a bundle under one name while the docs and deploy script look for another. Changes from the codename-206 original: - Checks for files this repo does not ship (deploy.ps1) report as `skip` rather than FAIL, and start enforcing once the file appears. - STALE ships pre-seeded with this template's own placeholders, and each entry is skipped while it is still part of a canonical value. That makes renaming self-checking: entries arm themselves as you change the value they belong to, with no bookkeeping. Substring rather than equality, because placeholders nest — "your-domain" lives inside the canonical CLAP_ID. - Covers identity this template carries that codename-206's did not: VENDOR, EMAIL, the Cargo homepage feeding Plugin::URL, that the `env!` indirection is still in place, and the README's bundle command. - Asserts CLAP_ID is reverse-DNS safe. A stray capital or space there is not a compile error, it is a silent identity change between machines. Two placeholder spellings had to move to avoid false positives, since a checker that cries wolf trains you to ignore it: bundler.toml's comment contained the literal "Plugin Name", and the CLAP_ID TODO spelled the placeholder a third way that matched neither the constant nor the STALE list. The TODO now illustrates the form instead of repeating a placeholder. .gitattributes pins *.sh to LF so core.autocrlf=true on Windows cannot check the script out with a CRLF shebang that dash rejects. Verified three ways: clean template passes, a partial rename names every drifted file, and a complete rename passes and still compiles. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01WHt4DykxXaQnyaJfyS8KCn --- .gitattributes | 4 ++ README.md | 27 +++++++++ bundler.toml | 2 +- check-branding.sh | 147 ++++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 2 +- 5 files changed, 180 insertions(+), 2 deletions(-) create mode 100644 .gitattributes create mode 100755 check-branding.sh diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..62da761 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,4 @@ +# Shell scripts must keep LF endings. core.autocrlf=true on Windows would +# otherwise check them out CRLF, which a strict POSIX sh (dash, CI containers) +# rejects at the shebang. +*.sh text eol=lf diff --git a/README.md b/README.md index 2f2ef4d..03308d7 100644 --- a/README.md +++ b/README.md @@ -13,8 +13,33 @@ cd my-plugin # Edit: src/lib.rs, Cargo.toml, bundler.toml — replace placeholder values # See musiikki/codename-206 for a real-world example of how to fill these in + +# Then verify the rename landed everywhere +sh check-branding.sh ``` +## Renaming + +Branding is duplicated across `Cargo.toml`, `bundler.toml`, `src/lib.rs` and the docs, +and nothing makes those agree — so a half-finished rename builds a bundle under one name +while the docs and deploy script look for another. + +`check-branding.sh` declares the identity once at the top and asserts every copy matches: + +```bash +sh check-branding.sh # exit 0 = consistent, 1 = drift found +``` + +To rename: edit the canonical block at the top of the script, run it, and fix everything +it reports. The placeholder spellings are pre-seeded into its `STALE` list, and each one +arms itself as soon as you change the matching canonical value — so leftovers surface +without you having to track them. It also asserts `VST3_CLASS_ID` is exactly 16 bytes and +that `CLAP_ID` is reverse-DNS safe, both of which are otherwise a compile error or a +silent identity change between machines. + +Checks for files the template doesn't ship (e.g. a `deploy.ps1`) report as `skip` and +start enforcing once you add them. + ## Build ```bash @@ -74,6 +99,8 @@ bundler.toml # Bundle name for `cargo xtask bundle` src/lib.rs # Plugin skeleton — params, DSP, and egui editor, with one example gain param xtask/ # Bundler xtask (cargo xtask bundle) .cargo/config.toml # xtask alias +check-branding.sh # Asserts the plugin's identity strings agree everywhere (see Renaming) +.gitattributes # Pins *.sh to LF so Windows checkouts keep a valid shebang ``` ## License diff --git a/bundler.toml b/bundler.toml index 256196a..7dfe766 100644 --- a/bundler.toml +++ b/bundler.toml @@ -1,7 +1,7 @@ # Metadata for NIH-plug's `cargo xtask bundle ` bundler. # # [package_name] -# name = "Human Readable Plugin Name" # defaults to +# name = "Human Readable Bundle Name" # defaults to [nih_plug_template] name = "Plugin Name" diff --git a/check-branding.sh b/check-branding.sh new file mode 100755 index 0000000..45690f2 --- /dev/null +++ b/check-branding.sh @@ -0,0 +1,147 @@ +#!/bin/sh +# check-branding.sh - verify the plugin's branding/identity strings agree everywhere. +# +# Plugin branding is duplicated across the build manifest, the bundler manifest, +# the source, the docs and (once you add one) the deploy script. Nothing makes +# them agree, so a rename drifts silently: the bundle builds under one name and +# the deploy script looks for another. This asserts they match, and that the +# identity constants are legal. +# +# In a template this matters more than usual: renaming is the first thing you do +# with this repo, and it touches every file below. Run this immediately after, +# and the STALE machinery will list whatever you missed. +# +# Usage: sh check-branding.sh (exit 0 = consistent, 1 = drift found) +# +# --------------------------------------------------------------------------- +# CANONICAL IDENTITY - the single source of truth. Edit these to rename, then +# run this script; every failure it prints is a place still holding the old name. +# --------------------------------------------------------------------------- + +PKG="nih_plug_template" # Cargo package name == bundler.toml table key +DISPLAY="Plugin Name" # bundle name -> ".vst3" / ".clap" +PLUGIN_NAME="Plugin Name" # Plugin::NAME - what the DAW displays +VENDOR="Your Name" # Plugin::VENDOR +EMAIL="your@email.com" # Plugin::EMAIL +HOMEPAGE="https://github.com/YOUR-USERNAME/YOUR-PLUGIN-NAME" # Cargo homepage -> Plugin::URL +CLAP_ID="com.your-domain.plugin-name" # CLAP_ID - stable plugin identity +VST3_CLASS_ID="Unique16CharId!!" # VST3_CLASS_ID - stable identity, MUST be 16 bytes + +# Spellings that must no longer appear anywhere in tracked files. Seeded with +# this template's own placeholders, so that renaming is self-checking: each entry +# is skipped while it is still one of the canonical values above, and becomes a +# live check the moment you change that value. Add your own old names here after +# a later rename. +STALE="nih_plug_template +Plugin Name +Your Name +your@email.com +com.your-domain.plugin-name +Unique16CharId!! +YOUR-USERNAME +YOUR-PLUGIN-NAME +your-domain" + +set -u +cd "$(dirname "$0")" || exit 1 + +pass=0; fail=0; skip=0 +ok() { pass=$((pass+1)); printf ' ok %s\n' "$1"; } +bad() { fail=$((fail+1)); printf ' FAIL %s\n' "$1"; [ $# -gt 1 ] && printf ' %s\n' "$2"; return 0; } +note() { skip=$((skip+1)); printf ' skip %s\n' "$1"; } + +# has FILE PATTERN -> fixed-string search +has() { grep -qF "$2" "$1" 2>/dev/null; } + +check() { # check LABEL FILE LITERAL + if [ ! -f "$2" ]; then bad "$1" "missing file: $2"; return; fi + if has "$2" "$3"; then ok "$1"; else bad "$1" "$2 does not contain: $3"; fi +} + +# Same as check, but a missing file is "not applicable yet" rather than drift. +# The template ships without a deploy script; this starts checking once you add one. +opt_check() { # opt_check LABEL FILE LITERAL + if [ ! -f "$2" ]; then note "$1 ($2 not present)"; return; fi + if has "$2" "$3"; then ok "$1"; else bad "$1" "$2 does not contain: $3"; fi +} + +printf 'branding check (canonical: %s / "%s")\n\n' "$PKG" "$DISPLAY" + +printf 'build manifests\n' +check "Cargo.toml package name" Cargo.toml "name = \"$PKG\"" +check "Cargo.toml homepage" Cargo.toml "homepage = \"$HOMEPAGE\"" +check "bundler.toml table key" bundler.toml "[$PKG]" +check "bundler.toml display name" bundler.toml "name = \"$DISPLAY\"" + +printf '\nsource identity (src/lib.rs)\n' +check "Plugin::NAME" src/lib.rs "const NAME: &'static str = \"$PLUGIN_NAME\"" +check "Plugin::VENDOR" src/lib.rs "const VENDOR: &'static str = \"$VENDOR\"" +check "Plugin::EMAIL" src/lib.rs "const EMAIL: &'static str = \"$EMAIL\"" +check "CLAP_ID" src/lib.rs "const CLAP_ID: &'static str = \"$CLAP_ID\"" +check "VST3_CLASS_ID" src/lib.rs "const VST3_CLASS_ID: [u8; 16] = *b\"$VST3_CLASS_ID\"" + +# Plugin::URL is env!("CARGO_PKG_HOMEPAGE"), so the manifest is the only place +# it is spelled. Assert the indirection is still in place rather than hardcoded. +check "Plugin::URL from manifest" src/lib.rs "const URL: &'static str = env!(\"CARGO_PKG_HOMEPAGE\")" + +# VST3 class IDs are a fixed 16-byte array. A shorter or longer literal is a +# compile error, but it is far clearer to say so here than to read rustc on it. +n=$(printf '%s' "$VST3_CLASS_ID" | wc -c | tr -d ' ') +if [ "$n" -eq 16 ]; then ok "VST3_CLASS_ID is 16 bytes" +else bad "VST3_CLASS_ID is 16 bytes" "got $n bytes: \"$VST3_CLASS_ID\""; fi + +# CLAP IDs are reverse-DNS by convention and are compared verbatim by hosts, so +# a stray space or capital is a silent identity change between machines. +case "$CLAP_ID" in + *\ *|*[A-Z]*) bad "CLAP_ID is reverse-DNS safe" "no spaces or capitals allowed: \"$CLAP_ID\"" ;; + *.*) ok "CLAP_ID is reverse-DNS safe" ;; + *) bad "CLAP_ID is reverse-DNS safe" "expected dotted reverse-DNS, got: \"$CLAP_ID\"" ;; +esac + +printf '\ndocs\n' +opt_check "README bundle command" README.md "cargo xtask bundle $PKG" + +printf '\ndeploy script\n' +opt_check "deploy.ps1 default package" deploy.ps1 "\$Package = \"$PKG\"" +opt_check "deploy.ps1 VST3 bundle" deploy.ps1 "\$vst3Name = \"$DISPLAY.vst3\"" +opt_check "deploy.ps1 CLAP bundle" deploy.ps1 "\$clapName = \"$DISPLAY.clap\"" + +printf '\nstale spellings\n' +# Every canonical spelling concatenated, for the substring test below. +CANONICAL_BLOB="$PKG +$DISPLAY +$PLUGIN_NAME +$VENDOR +$EMAIL +$HOMEPAGE +$CLAP_ID +$VST3_CLASS_ID" +# Fed by here-doc rather than a pipe on purpose: a piped `while` runs in a +# subshell, and the pass/fail counters it increments would be discarded. +while IFS= read -r s; do + [ -n "$s" ] || continue + + # Still part of a canonical value? Then it is current branding, not a leftover. + # This is what lets the list ship pre-seeded: entries arm themselves as you + # rename. Substring, not equality, because placeholders nest - "your-domain" + # lives inside the canonical CLAP_ID, and "YOUR-USERNAME" inside the homepage. + # Over-skipping is the safe direction: a false FAIL on a name you still use + # would train you to ignore this section. + case "$CANONICAL_BLOB" in + *"$s"*) note "\"$s\" (still canonical)"; continue ;; + esac + + # This script names every placeholder by definition, so it can never be a hit. + hits=$(git ls-files 2>/dev/null | while read -r f; do + [ "$f" = "check-branding.sh" ] || { [ -f "$f" ] && grep -lF "$s" "$f" 2>/dev/null; } + done) + if [ -z "$hits" ]; then ok "no occurrences of \"$s\"" + else bad "no occurrences of \"$s\"" "still present in: $(echo "$hits" | tr '\n' ' ')"; fi +done < = Some("Your plugin description"); const CLAP_MANUAL_URL: Option<&'static str> = Some(Self::URL);