Commit Graph
5 Commits
Author SHA1 Message Date
kristianwindandClaude Opus 5 f14c51dcf2 Rename the repository to woo-discount-manager, keep the installed slug
Kristian named the five plugins as a family, and three repos disagreed with what
he calls them. His decision: rename the repo, never the installed slug.

MEASURED BEFORE DOING IT, because four published releases carry the old URL in
their Update URI header. A throwaway repo was created, renamed and deleted:

    old API path                    301 -> new
    old web path                    301 -> new
    old /releases/download/... path 301 -> new

and WordPress follows up to 5 redirects by default (class-wp-http.php:191), on
GET. Confirmed afterwards against the real thing: the old feed URL resolves to
v1.4.5 and the old download path still serves 48185 bytes.

So a shop on 1.4.2 keeps updating through the redirect, and 1.4.6 moves it onto
the new address directly.

PLUGIN_SLUG and PLUGIN_MAIN are deliberately untouched and now disagree with the
repository name. That is permanent: the installed directory has been
woocommerce-email-discount-manager since the first release and is on a live
shop, and changing it would not move that installation — it would put a SECOND
copy beside it and leave the old one active. ci/plugin.conf says so in the file,
because otherwise somebody will helpfully align them.

Also in, both for the family rather than for this plugin alone:

- docs/REUSE.md gains the hostname-keyed hook as a first-class section. It is
  the only bug we have found that is CAUSED by there being more than one plugin,
  so it is invisible to anyone reading a single repo — along with the same shape
  in a shared-*.php file copied into five plugins, which fatals with "Cannot
  redeclare" on the second activation without function_exists() guards.
- ci/static.sh fails on anything shaped like a credential in a tracked file.
  Suggested by the AI-manager port: a pushed key cannot be unpushed, only
  rotated. Deliberately narrow — sk-ant-, sk-, ghp_, gho_, github_pat_, AKIA,
  PRIVATE KEY blocks — because a broad entropy heuristic fires on minified
  assets and a check that cries wolf gets commented out.

Verified failing before being believed: a file containing sk-ant-api03-... makes
the step report the filename and the suite exit 1.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-16 18:18:50 +02:00
kristianwindandClaude Opus 5 a1f6e54f9d The smoke test could not fail, and other people's eyes
Four fixes, two of them serious, three of them found by the session porting
this into subscriptions.

1. THE SMOKE TEST COULD NOT FAIL. `wp eval-file` runs the file through eval()
   INSIDE A METHOD, so the top level is function scope. `$wc_edm_failures = 0`
   at the top and `global $wc_edm_failures` in the helper were two different
   variables: the helpers incremented the real global, the verdict read the
   local, and the local was always zero.

   Measured rather than reasoned. A deliberately false assertion added to the
   unmodified file:

       FAIL  DELIBERATE FAILURE — this run must go red
       All assertions passed on WordPress 7.1 with WooCommerce 11.1.0.
       EXIT=0

   and with the fix, the same forced failure:

       FAIL  DELIBERATE FAILURE — this run must go red
       1 assertion(s) failed on WordPress 7.1.
       EXIT=1

   The FAIL lines always printed, so failures found by reading the output were
   real finds. What was broken is the CI gate: a regression on either forge
   would have gone green. Four lines at the top now prove the counter reaches
   the verdict before anything relies on it — this is the one file whose whole
   job is to be able to go red, so it checks that it can.

2. `git ls-files` QUOTES NON-ASCII PATHS. "TESTMILJØ.md" comes back as the
   literal string "TESTMILJ\303\230.md", which matches no pattern in
   .distignore, so the file is kept — an internal document walking into the
   release zip with nothing said. Fixed in ci/dist-files.sh and in
   ci/static.sh's trailing-?> check, which had the same bug and would have
   silently skipped the file it exists to check. Verified by adding such a
   filename and watching it be excluded. `done < <(...)` rather than a pipe, or
   the loop runs in a subshell and the failure counter stops incrementing.

3. THE FEED CACHE WAS SIX HOURS, and Kristian hit it: a release published and
   his shop shown nothing. The reasoning ("WordPress checks twice a day") was
   wrong. WordPress gates its own check by screen in wp_update_plugins() — one
   minute on Dashboard → Updates, one hour on Plugins, 2–12 hours in the
   background — so WordPress is already the rate limiter and a longer cache
   only hides releases underneath it. Fifteen minutes now, pinned by an
   assertion.

4. The wp_update_plugins() assertion made a real HTTP request, because the
   force-check tests had just deleted the transient. It passed only because
   this feed repo is public, which made it measure the environment rather than
   the code. Re-primed first.

ci_in_container() replaces the inline docker plumbing in ci_php(), taken from
the subscriptions port: the script goes in as a file rather than interpolated
into `sh -c`, and --entrypoint sh matters for images whose entrypoint swallows
its arguments.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-16 13:18:22 +02:00
kristianwindandClaude Opus 5 056be0e917 Credit the plugin to its actual author, and 1.4.3
The header went out as "Author: WEDSMP" with a Plugin URI of example.com. That
is what wp-admin shows to whoever installs it, which is why it is worth a
release of its own rather than waiting for the next change.

Author is now Kristian Wind, with an Author URI pointing at where the source
lives, and the readme credits kristianwind rather than wedsmp.

ci/static.sh now fails on a placeholder in the header. It catches the generic
markers — example.com, TODO, yourname, an unfilled <...> — and it would have
caught the Plugin URI. It would NOT have caught "WEDSMP": a made-up vendor name
is indistinguishable from a real one to any check, so an empty field is warned
about and a wrong-but-plausible one cannot be.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-16 12:59:44 +02:00
kristianwindandClaude Opus 5 4a5e3d8879 Run the text-domain check in a container when the runner has no PHP
The first Gitea run failed on `php: command not found`. GitHub's hosted
runners ship PHP; the Gitea runner image does not, and the whole static job
went red on one line after everything else in it had passed.

ci_php() now uses the local php when there is one — a laptop, GitHub's
runners — and a php:8.4-cli container otherwise. The repository goes in over a
tarball and `docker cp` rather than a bind mount, because a Gitea Actions job
runs inside a container talking to the HOST's docker daemon, so a host path
there lands on an empty directory nobody created.

Also: the include scan now says when it found nothing to check instead of
reporting success. Running the suite with a deliberately crippled PATH showed
it printing "0 plugin-relative include(s) checked" and then "green" — a check
passing by checking nothing, which is the same failure the PHP lint step
already guards against.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-16 10:40:19 +02:00
kristianwindandClaude Opus 5 eca2851d35 Test against the newest WordPress and WooCommerce, and ship updates
Three things this repository could not do: prove the plugin still works on a
WordPress or WooCommerce released after it, get a fix onto a shop without
someone uploading a zip, and do either of those for the next plugin without
starting over.

ci/ is the test harness. ci/site-test.sh builds a throwaway WordPress +
WooCommerce in containers at any versions you name — including `nightly` and
`beta` — installs exactly the files the release zip will contain, and runs
tests/smoke.php inside it with wp eval-file. Real cart, real coupons, real
WC_Shipping_Rate objects: a mocked cart keeps passing after WooCommerce has
changed underneath it, which is the one failure worth catching here. WP_DEBUG
is on and any notice logged from this plugin's directory fails the build,
because that is the shape an upgrade actually breaks in — nothing fails, a
notice appears, and two releases later it is a fatal.

compat.yml runs that weekly against today's releases, the next WordPress and
the next WooCommerce, the oldest PHP the header promises and the newest PHP.
When it is green it opens a PR bumping "Tested up to" and "WC tested up to" —
only when green, because WooCommerce shows that second value to the shop owner
as an untested-plugin banner, and that banner is what they read before deciding
whether to press update.

includes/class-updater.php is the distribution half. It hooks
update_plugins_{hostname} for the host in the new "Update URI" header and reads
the Gitea release API, so WordPress handles the notice, the download, the
"Enable auto-updates" link and wp plugin update with no further code. Update
URI earns its place twice: since 5.8 a plugin that declares one is never asked
about on wordpress.org, so a .org plugin sharing this slug cannot be pushed
onto a shop running this one. The check sends nothing about the site and
overrides WordPress's default user agent, which carries the site's own URL.

release.yml tags, tests, builds, publishes — then reads the feed back with no
credentials. A private repository and a release with no zip attached both leave
a page that looks perfect to anyone logged in and an update no shop is ever
offered.

Measured before committing, all green: WP 7.1 + WC 11.1 on PHP 8.3 and 7.4,
WP 7.2-alpha on 8.3, and the beta cell falling back to stable on 8.4. No
deprecations from this plugin on any of them.

Three guards are there because the bug they catch is invisible without them:
the annotated tag is re-fetched before its message is read (checkout turns it
into a lightweight tag pointing at the merge commit), the two workflow roots
are kept in sync (.gitea/workflows hides .github/workflows entirely), and every
file the code loads must be in the release zip — which caught class-updater.php
being referenced and uncommitted while every local test passed.

docs/REUSE.md is the port to the next plugin: copy ci/, rewrite five lines.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-16 10:34:38 +02:00