diff --git a/scripts/deploy.sh b/scripts/deploy.sh index efb51e3..f539aa3 100755 --- a/scripts/deploy.sh +++ b/scripts/deploy.sh @@ -188,6 +188,23 @@ if changed '^(resources/(js|css|data)/|vite\.config\.|package(-lock)?\.json|tsco FRONTEND_CHANGED=true fi +# The pattern above classifies a diff. This asks the far simpler question the +# diff is only a proxy for: is what we built older than what we built it from? +# +# The two disagree whenever a release is skipped, and a skipped rebuild does not +# retry itself — the next deploy diffs against the commit that skipped it, sees +# nothing front-end in that range, and leaves the stale bundle in place forever. +# One misclassified path therefore strands the site until somebody runs FORCE=1. +# It happened: data corrections deployed green and never reached the page, and +# the deploy that fixed the classifier could not undo its own backlog. +# +# Comparing artifacts to sources costs one `find` and cannot be fooled by a +# pattern nobody updated. +if [ "$FRONTEND_CHANGED" = false ] && bundles_are_stale; then + echo " the built bundles are older than the sources — rebuilding regardless of the diff" + FRONTEND_CHANGED=true +fi + # Kept apart from PHP_CHANGED below: a Blade edit needs the caches rebuilt and # the bytecode dropped, but there is nothing new to download for it. if changed '^composer\.(json|lock)$'; then diff --git a/tests/Feature/DeployScriptTest.php b/tests/Feature/DeployScriptTest.php index 7eab054..4fa2940 100644 --- a/tests/Feature/DeployScriptTest.php +++ b/tests/Feature/DeployScriptTest.php @@ -290,3 +290,37 @@ function deployMatches(string $flag, string $path): bool expect(strpos($script, 'no server-rendered

')) ->toBeLessThan(strpos($script, 'serving a different build')); }); + +it('rebuilds when the artifacts are older than the sources, whatever the diff says', function (): void { + /* + * The changed-paths pattern classifies a diff. This asks the question the + * diff is only a proxy for: is what we built older than what we built it + * from? + * + * They disagree whenever a release is skipped, and a skipped rebuild does + * not retry itself — the next deploy diffs against the commit that skipped + * it, sees nothing front-end in that range, and leaves the stale bundle in + * place indefinitely. One misclassified path strands the site until someone + * runs FORCE=1 by hand. + * + * That is not hypothetical. `resources/data/*.json` was classified as + * content, so corrected prices deployed green and never reached the page — + * and the deploy that fixed the classifier could not undo its own backlog, + * because by then the data change was behind it. + */ + $script = file_get_contents(base_path('scripts/deploy.sh')); + + // The staleness check must be consulted for the front-end decision, not + // only inside the unchanged-commit branch. + expect(substr_count($script, 'bundles_are_stale'))->toBeGreaterThanOrEqual( + 3, + 'bundles_are_stale should be defined and consulted in both the skip branch and the front-end decision', + ); + + expect($script)->toMatch('/FRONTEND_CHANGED"?\s*=\s*false.*bundles_are_stale/s'); + + // And it has to be defined before both uses, or the shell sees an empty command. + $definedAt = strpos($script, 'bundles_are_stale() {'); + expect($definedAt)->not->toBeFalse(); + expect($definedAt)->toBeLessThan(strrpos($script, 'bundles_are_stale;')); +});