Skip to content

Allow stopping a running test run via the Run Tests button#29

Open
JohnRDOrazio wants to merge 9 commits into
mainfrom
feature/stop-test-run-button
Open

Allow stopping a running test run via the Run Tests button#29
JohnRDOrazio wants to merge 9 commits into
mainfrom
feature/stop-test-run-button

Conversation

@JohnRDOrazio

@JohnRDOrazio JohnRDOrazio commented Apr 3, 2026

Copy link
Copy Markdown
Member

Summary

  • The "Run Tests" button now toggles: click to start, click again to stop, click again to restart
  • Adds a TestState.Stopped state that halts test progression and ignores incoming WebSocket responses
  • When stopped, the button turns yellow ("Tests Stopped"); when restarting, the full UI resets (card colors, icons, counters, timers, error tooltips)
  • Resolves the TODO at the old else branch that previously just logged a warning

Test plan

  • Start a test run, verify spinner and "Tests Running..." label appear
  • Click the button mid-run, verify it stops (spinner stops, button turns warning yellow, label says "Tests Stopped")
  • Click again to restart, verify all cards reset to blue/question-mark state and counters reset to 0
  • Let a full run complete, verify "Tests Complete" state still works correctly
  • After completion, click to restart and verify full UI reset

See #28 for a future enhancement to support resuming from where stopped.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • You can stop a running test; button switches to “Tests Stopped” with warning styling and spinner stops.
    • Starting a new run from Ready/Finished/Stopped resets UI (counters, timers, status icons, and error tooltips).
  • Bug Fixes

    • Incoming messages from previous runs are ignored so stale updates won’t affect the UI.
    • Prevents sending/processing test messages when the connection isn’t initialized.

JohnRDOrazio and others added 2 commits April 3, 2026 11:45
The test runner button now toggles between start and stop. Clicking
during a run sets a new Stopped state, halts test progression, and
updates the button to a warning style. Clicking again restarts tests
with proper icon and style restoration.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adds resetTestUI() to clear card colors, icons, error tooltips,
success/fail counters, and timer displays before a new test run.
Ensures consistent UI when restarting after a stop or completed run.

See #28 for a possible future enhancement to resume from where stopped.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Apr 3, 2026

Copy link
Copy Markdown

Walkthrough

Added run-token guarded WebSocket flows and a new Stopped runner state; start button can stop an in-progress run and later restart. Introduced sendMessage() and resetTestUI() to manage outbound messages and reset UI/tooltips/counters at run start. Docker build and .dockerignore were added/updated.

Changes

Cohort / File(s) Summary
Runner state, token, messaging, and UI reset
assets/js/index.js, assets/js/resources.js
Added TestState.Stopped, currentRunToken, and sendMessage(data) to attach runToken to outbound WS messages. Start button now stops an in-progress run (clears token, updates spinner/button to stopped state) and can start new runs by generating a new token. WebSocket onmessage now ignores messages when stopped/when token missing or mismatched. Added resetTestUI() to clear card styles/icons, reset timers/counters, dispose/clear Bootstrap tooltip instances (tooltipMap), remove injected .error-tooltip elements, and zero internal success/failure counters. Replaced direct conn.send(JSON.stringify(...)) calls with sendMessage() and added guard if conn is uninitialized.
Docker build and context rules
Dockerfile, .dockerignore
Added multi-stage Dockerfile for PHP 8.4: installs build/runtime deps, compiles/enables extensions (intl, zip, calendar, gettext, apcu, yaml), installs Composer, copies app sources, generates credentials.php with bcrypt-hashed defaults, and runs PHP built-in server on port 3003. Updated .dockerignore to exclude everything by default (*) and explicitly include src, includes, layout, components, assets, i18n, composer.json, composer.lock, index.php, admin.php, resources.php, and .env.example.

Sequence Diagram(s)

sequenceDiagram
    participant UI as UI (Start/Stop Button)
    participant Runner as Client Runner Logic
    participant WS as WebSocket Server

    UI->>Runner: click Start (Ready/JobsFinished/Stopped)
    Runner->>Runner: currentRunToken = crypto.randomUUID()\nresetTestUI()\nset state = Running
    Runner->>WS: sendMessage({ type: "run-start", ..., runToken })
    WS-->>Runner: stream event (with runToken)
    alt state == Running AND runToken matches
        Runner->>UI: update cards / counters / timers
    else state == Stopped OR runToken mismatches OR no token
        Runner->>Runner: ignore incoming event
        Runner->>UI: keep/clear spinner and show stopped label if applicable
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 I hopped in with a brand new token bright,
I stopped the whirl, reset the lights at night,
Tooltips vanished, counters made small,
Fresh token ready — hear the runner call,
A twitch of whiskers, tests take flight! ✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately and concisely describes the primary change: enabling users to stop a running test via button interaction, which is the central feature of the changeset.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/stop-test-run-button

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@assets/js/index.js`:
- Around line 685-687: Move the early-exit guard into the very start of the
WebSocket message handler so incoming messages are ignored before any UI
mutation: in conn.onmessage check currentState against TestState.Stopped and the
current run token/id (add a per-run token generated by runTests and stored on
the module) and return immediately if the run token mismatches or currentState
=== TestState.Stopped; also update runTests()/restart logic to create and set a
fresh run token (or recreate the socket) each run so old/out-of-run responses
cannot be applied to the UI.
- Around line 799-800: In resetTestUI(), before removing DOM elements with
selector '#testSuiteAccordion .error-tooltip', iterate over tooltipMap (the map
storing Bootstrap Tooltip instances), call hide() and dispose() on each Tooltip
instance to prevent leaked listeners, then clear tooltipMap (tooltipMap.clear())
and only after that remove the DOM elements; reference the tooltipMap variable
and the resetTestUI() function and the '.error-tooltip' trigger elements when
making these changes.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: c2327a83-467e-41be-a178-8b9d04cb9cee

📥 Commits

Reviewing files that changed from the base of the PR and between bd345a8 and b9bb91c.

📒 Files selected for processing (1)
  • assets/js/index.js

Comment thread assets/js/index.js Outdated
Comment thread assets/js/index.js Outdated
JohnRDOrazio and others added 2 commits April 19, 2026 01:12
Generate a unique run token via crypto.randomUUID() each time tests
start, and check it at the very top of conn.onmessage so that stale
or out-of-run responses are discarded before any UI mutation occurs.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ments

In resetTestUI(), iterate over tooltipMap calling hide() and dispose()
on each Tooltip instance and clear the map before removing the
.error-tooltip DOM elements to prevent leaked event listeners.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
assets/js/index.js (1)

550-564: ⚠️ Potential issue | 🟠 Major

Keep the button enabled after JobsFinished so restart is reachable.

Line 1312 now allows restarting from JobsFinished, but the completion branch still disables the button at Line 559. After a full run, the user cannot click the button to restart.

🐛 Proposed fix
-                    btnPrimary.disabled = true;
+                    btnPrimary.disabled = false;
                     btnPrimary.classList.remove('btn-primary');
                     btnPrimary.classList.add('btn-secondary');

Also applies to: 1312-1312

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@assets/js/index.js` around lines 550 - 564, In the TestState.JobsFinished
branch, do not disable the test-runner button — locate the
TestState.JobsFinished case in assets/js/index.js and remove or change the code
that sets btnPrimary.disabled = true and that swaps btn-primary to
btn-secondary; instead ensure the button remains enabled (or explicitly set
btnPrimary.disabled = false) so the restart path (see the restart-enabled logic
around TestState handling at the other branch) can be reached; keep the icon
class updates (spinIcon.classList.remove/add) and the call to
setTestRunnerBtnLblTxt('Tests Complete') but do not alter the button's enabled
state or primary styling.
♻️ Duplicate comments (2)
assets/js/index.js (2)

803-811: ⚠️ Potential issue | 🟡 Minor

Dispose tooltip instances by trigger, not only by tooltipMap.

tooltipMap is lossy: the click-away/Escape paths hide tooltips and then clear the map without disposing. If one of those hidden .error-tooltip triggers is later removed at Line 811, its Bootstrap Tooltip instance can be left behind. Dispose each trigger’s bootstrap.Tooltip.getInstance(el) before removal.

🧹 Proposed fix
-    // Dispose Bootstrap Tooltip instances to prevent leaked event listeners
-    tooltipMap.forEach(tooltip => {
-        tooltip.hide();
-        tooltip.dispose();
-    });
-    tooltipMap.clear();
+    // Dispose Bootstrap Tooltip instances to prevent leaked event listeners
+    document.querySelectorAll('#testSuiteAccordion .error-tooltip').forEach(el => {
+        const tooltip = tooltipMap.get(el) || bootstrap.Tooltip.getInstance(el);
+        if (tooltip) {
+            tooltip.hide();
+            tooltip.dispose();
+        }
+    });
+    tooltipMap.clear();
 
     // Remove error tooltip DOM elements added during the previous run
     document.querySelectorAll('#testSuiteAccordion .error-tooltip').forEach(el => el.remove());

You can verify the lossy map paths with:

#!/bin/bash
# Description: Inspect tooltip lifecycle paths that hide/clear tooltipMap versus dispose instances.
# Expectation: any path that clears tooltipMap without dispose means resetTestUI() should also dispose by DOM trigger.
rg -n -C3 'tooltipMap\.clear\(\)|Tooltip\.getInstance|const resetTestUI|error-tooltip' assets/js/index.js
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@assets/js/index.js` around lines 803 - 811, The DOM cleanup that removes
'#testSuiteAccordion .error-tooltip' elements can leak Bootstrap Tooltip
instances because tooltipMap is sometimes cleared after hiding tooltips without
calling dispose; update the removal loop so for each matched element you first
call bootstrap.Tooltip.getInstance(el) and if an instance exists call
instance.dispose(), then remove the element; ensure this change complements the
existing tooltipMap disposal (tooltipMap.forEach(...); tooltipMap.clear()) so no
tooltip instance remains when triggers are removed.

621-625: ⚠️ Potential issue | 🟠 Major

Make the run token an actual response-correlation guard.

Line 1327 generates a token, but Line 622 only checks that some token exists. Because the token is never sent with requests or compared with incoming responses, late messages from a stopped run can still mutate the UI after the user restarts and currentState becomes active again.

Either have the backend echo a per-run token and reject mismatches here, or create a fresh isolated WebSocket/session per run. Also clear currentRunToken when stopping/finishing.

🔧 Minimal direction
 conn.onmessage = ( e ) => {
     if ( currentState === TestState.Stopped || currentRunToken === null ) {
         return;
     }
     const responseData = JSON.parse( e.data );
+    if ( responseData.runToken !== currentRunToken ) {
+        return;
+    }
-conn.send( JSON.stringify( { action: 'executeValidation', ...currentSourceDataChecks[ index++ ] } ) );
+conn.send( JSON.stringify( { runToken: currentRunToken, action: 'executeValidation', ...currentSourceDataChecks[ index++ ] } ) );
 // Stop the running test run
 console.log( 'Stopping test run...' );
 currentState = TestState.Stopped;
+currentRunToken = null;

Also applies to: 1327-1328, 1345-1348

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@assets/js/index.js` around lines 621 - 625, The message handler
(conn.onmessage) only checks that currentRunToken exists but never verifies it
against the incoming response, so stale messages can affect a new run; update
the protocol so each run generates and sends a per-run token (the token created
around lines 1327-1328) and have conn.onmessage compare currentRunToken to
responseData.runToken (or equivalent) before mutating UI, and also ensure
currentRunToken is explicitly cleared when stopping/finishing a run (the
stop/finish handlers) or when restarting to fully isolate runs; alternatively,
create a fresh WebSocket/session per run and close it on stop so old messages
cannot arrive.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@assets/js/index.js`:
- Around line 550-564: In the TestState.JobsFinished branch, do not disable the
test-runner button — locate the TestState.JobsFinished case in
assets/js/index.js and remove or change the code that sets btnPrimary.disabled =
true and that swaps btn-primary to btn-secondary; instead ensure the button
remains enabled (or explicitly set btnPrimary.disabled = false) so the restart
path (see the restart-enabled logic around TestState handling at the other
branch) can be reached; keep the icon class updates
(spinIcon.classList.remove/add) and the call to setTestRunnerBtnLblTxt('Tests
Complete') but do not alter the button's enabled state or primary styling.

---

Duplicate comments:
In `@assets/js/index.js`:
- Around line 803-811: The DOM cleanup that removes '#testSuiteAccordion
.error-tooltip' elements can leak Bootstrap Tooltip instances because tooltipMap
is sometimes cleared after hiding tooltips without calling dispose; update the
removal loop so for each matched element you first call
bootstrap.Tooltip.getInstance(el) and if an instance exists call
instance.dispose(), then remove the element; ensure this change complements the
existing tooltipMap disposal (tooltipMap.forEach(...); tooltipMap.clear()) so no
tooltip instance remains when triggers are removed.
- Around line 621-625: The message handler (conn.onmessage) only checks that
currentRunToken exists but never verifies it against the incoming response, so
stale messages can affect a new run; update the protocol so each run generates
and sends a per-run token (the token created around lines 1327-1328) and have
conn.onmessage compare currentRunToken to responseData.runToken (or equivalent)
before mutating UI, and also ensure currentRunToken is explicitly cleared when
stopping/finishing a run (the stop/finish handlers) or when restarting to fully
isolate runs; alternatively, create a fresh WebSocket/session per run and close
it on stop so old messages cannot arrive.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: af53dcd8-92db-48b5-8151-f6c9135c5a50

📥 Commits

Reviewing files that changed from the base of the PR and between b9bb91c and 55f9900.

📒 Files selected for processing (1)
  • assets/js/index.js

JohnRDOrazio and others added 3 commits April 19, 2026 01:18
Remove the code in the JobsFinished branch that disabled the button
and swapped btn-primary to btn-secondary, which prevented the restart
click handler from being reached. Also clear currentRunToken on
finish to fully isolate completed runs from stale WebSocket messages.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Dispose any Bootstrap Tooltip instance on each .error-tooltip element
via bootstrap.Tooltip.getInstance() before removing it from the DOM,
covering tooltips that may not be tracked in tooltipMap. Also clear
currentRunToken when stopping a test run to fully isolate runs.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add a sendMessage() helper that attaches currentRunToken to every
outgoing WebSocket message. On incoming responses, verify the echoed
runToken matches the current run and discard mismatched responses.
This completes the client side of the run token protocol added in
Liturgical-Calendar/LiturgicalCalendarAPI#533.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@JohnRDOrazio

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Apr 19, 2026

Copy link
Copy Markdown
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

JohnRDOrazio and others added 2 commits April 19, 2026 04:20
Port the stop/restart test run functionality from the Calendars page
to the Resources page:
- Add TestState.Stopped state
- Add per-run token (currentRunToken) with sendMessage() helper
- Add early-exit guard in onmessage to discard stale responses
- Add resetTestUI() with proper Bootstrap Tooltip disposal
- Update click handler to toggle between running and stopping
- Keep button enabled after tests complete for easy restart

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add a multi-stage Dockerfile (PHP 8.4 built-in server on port 3003)
that installs dependencies, copies application code, and generates
default credentials (admin/admin) for HTTP Basic Auth.

Used by the docker-compose.yml in the LiturgicalCalendarFrontend
repository to bring up the full localhost development stack.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

♻️ Duplicate comments (1)
assets/js/index.js (1)

627-633: ⚠️ Potential issue | 🟠 Major

Reject missing run tokens too.

Line 632 only rejects mismatched tokens when the response includes one. Untagged late responses from a stopped run can still pass during a restart and mutate the new UI state. Require exact token equality once the backend echoes runToken for every response.

🔒 Proposed client-side guard once responses are tokenized
-        if ( responseData.runToken && responseData.runToken !== currentRunToken ) {
+        if ( responseData.runToken !== currentRunToken ) {
             return;
         }

Verify the WebSocket backend echoes runToken for every response path:

#!/bin/bash
# Description: Inspect WebSocket request/response handling for runToken propagation.
# Expected: every JSON response emitted for tagged validation/unit-test requests includes the same runToken.

rg -n -C3 '\brunToken\b|executeValidation|validateCalendar|executeUnitTest' --glob '!node_modules/**' --glob '!dist/**' --glob '!build/**'
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@assets/js/index.js` around lines 627 - 633, The guard currently only ignores
responses when a runToken exists and mismatches; update the check in
assets/js/index.js around TestState/Stated handling so that any response missing
responseData.runToken is also rejected when a currentRunToken exists: after
parsing responseData, if currentRunToken !== null then require
responseData.runToken === currentRunToken (otherwise return). Modify the
condition that references responseData.runToken and currentRunToken (and keep
the early return when currentState === TestState.Stopped) so untagged late
responses cannot mutate new UI state.
🧹 Nitpick comments (1)
Dockerfile (1)

13-13: Install Composer from the official image (or verify installer signature).

Piping the Composer installer over TLS is common, but it skips the checksum verification step recommended by Composer and exposes the build to any compromise of getcomposer.org. Prefer copying the binary from the pinned official image, or verify the installer hash against https://composer.github.io/installer.sig before executing.

🔐 Option A — copy from the official Composer image
-    curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer && \
-    rm -rf /var/lib/apt/lists/*
+    rm -rf /var/lib/apt/lists/*
+
+# Pull Composer from the official pinned image
+COPY --from=composer:2 /usr/bin/composer /usr/local/bin/composer
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Dockerfile` at line 13, The Dockerfile currently installs Composer by piping
the installer (curl ... | php) which skips signature verification; replace that
step by either copying the official Composer binary from the pinned Composer
image (use a multi-stage copy from composer:<version> and copy /usr/bin/composer
to /usr/local/bin/composer) or, if you must run the installer, fetch
https://composer.github.io/installer.sig and verify the installer's SHA384
signature against the downloaded installer before executing php; update the RUN
step that contains "curl -sS https://getcomposer.org/installer | php --
--install-dir=/usr/local/bin --filename=composer" accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@assets/js/index.js`:
- Around line 468-476: The sendMessage calls inside the TestState handling are
still spreading entries from currentSourceDataChecks which may carry legacy
category values; modify both sendMessage invocations (the one before the switch
break and the one inside the TestState.ExecutingValidations branch) to
explicitly set category: 'sourceDataCheck' on the object you send (i.e.,
sendMessage({ action: 'executeValidation', category: 'sourceDataCheck',
...currentSourceDataChecks[index++] })) so that all source-data validation
WebSocket messages use the required category; update the calls that reference
sendMessage, currentSourceDataChecks, messageCounter, index and
TestState.ExecutingValidations accordingly.
- Around line 801-810: The resetTestUI function is too broad and also resets
summary/status badges; narrow the two querySelectorAll calls to target only test
result cards (e.g. change selectors from '#testSuiteAccordion .bg-success,
`#testSuiteAccordion` .bg-danger' and '#testSuiteAccordion .fa-circle-check,
`#testSuiteAccordion` .fa-circle-xmark' to selectors that scope to result cards
such as '#testSuiteAccordion .test-result-card .bg-success, `#testSuiteAccordion`
.test-result-card .bg-danger' and '#testSuiteAccordion .test-result-card
.fa-circle-check, `#testSuiteAccordion` .test-result-card .fa-circle-xmark').
Update both selector strings in resetTestUI so only result cards are reset,
leaving summary/status badges untouched.

In `@assets/js/resources.js`:
- Around line 616-624: The resetTestUI function is too broad: its selectors
remove success/failure classes and icons from all elements under
`#testSuiteAccordion` (including summary/status badges); narrow the selectors to
only target result card elements (e.g., select '#testSuiteAccordion
.card.result' or the specific result card class used) so only result cards have
their 'bg-success'/'bg-danger' and 'fa-circle-check'/'fa-circle-xmark' replaced
with 'bg-info'/'fa-circle-question'; update the querySelectorAll calls inside
resetTestUI to use the more specific result-card selector (refer to resetTestUI,
the two querySelectorAll usages) so summary/status badges remain unchanged.

In `@Dockerfile`:
- Line 35: The Dockerfile currently copies .env.example to .env.local which
bakes example placeholder config into the image; remove or stop using the COPY
./.env.example ./.env.local step and instead leave .env.local out of the image,
copy only .env.example if you want the template (e.g., keep .env.example but do
not rename it), and document that operators should supply runtime configuration
via docker run --env-file, environment variables, or mounted volume/secret;
update the Dockerfile to stop creating .env.local and update any README/compose
docs accordingly so the app reads real runtime environment values rather than
baked placeholders.
- Around line 37-38: The Dockerfile currently bakes a credentials.php with a
hardcoded admin/admin pair (AUTH_USERS) which exposes the image; replace this by
removing the build-time RUN that writes credentials.php and instead generate
credentials.php at container start (e.g., in your entrypoint) using environment
variables or secrets (AUTH_USER and AUTH_PASSWORD or AUTH_PASSWORD_HASH),
computing a bcrypt hash at runtime and failing fast if the vars are missing;
ensure includes/auth.php reads the generated credentials.php, and optionally
gate the old build-time behavior behind an explicit build ARG (disabled by
default) with clear documentation.
- Around line 41-61: The Dockerfile main stage currently runs as root and uses
PHP's built-in server; create a non-root user (e.g., app user/group) and chown
the application directory (/var/www/html) before switching to that user with a
USER directive to fix the Trivy DS-0002 issue, replace the CMD ["php","-S",...]
used in the main stage with a production-ready entrypoint (e.g., start php-fpm
and front with nginx or use php:8.4-apache/FrankenPHP) or, if this image is
dev-only, document that clearly in the image README and change the image
tag/name to indicate dev; additionally add a HEALTHCHECK instruction probing the
app (HTTP on :3003 or socket) to allow orchestrators to detect unhealthy
containers.
- Around line 46-55: The Dockerfile's runtime stage installs development
packages libicu-dev and libzip-dev which are unnecessary; replace them with the
corresponding runtime shared libraries (bookworm names libicu72 and libzip4) in
the RUN apt-get install line and remove the "-dev" packages while keeping
libyaml-0-2 and locales-all; optionally, if you don't need all locales, replace
locales-all with generating only required locales via locale-gen to reduce image
size.

---

Duplicate comments:
In `@assets/js/index.js`:
- Around line 627-633: The guard currently only ignores responses when a
runToken exists and mismatches; update the check in assets/js/index.js around
TestState/Stated handling so that any response missing responseData.runToken is
also rejected when a currentRunToken exists: after parsing responseData, if
currentRunToken !== null then require responseData.runToken === currentRunToken
(otherwise return). Modify the condition that references responseData.runToken
and currentRunToken (and keep the early return when currentState ===
TestState.Stopped) so untagged late responses cannot mutate new UI state.

---

Nitpick comments:
In `@Dockerfile`:
- Line 13: The Dockerfile currently installs Composer by piping the installer
(curl ... | php) which skips signature verification; replace that step by either
copying the official Composer binary from the pinned Composer image (use a
multi-stage copy from composer:<version> and copy /usr/bin/composer to
/usr/local/bin/composer) or, if you must run the installer, fetch
https://composer.github.io/installer.sig and verify the installer's SHA384
signature against the downloaded installer before executing php; update the RUN
step that contains "curl -sS https://getcomposer.org/installer | php --
--install-dir=/usr/local/bin --filename=composer" accordingly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: cb886ab2-c83b-4b42-a100-8112075c13f4

📥 Commits

Reviewing files that changed from the base of the PR and between 55f9900 and 5294c22.

📒 Files selected for processing (4)
  • .dockerignore
  • Dockerfile
  • assets/js/index.js
  • assets/js/resources.js
✅ Files skipped from review due to trivial changes (1)
  • .dockerignore

Comment thread assets/js/index.js
Comment on lines +468 to +476
sendMessage( { action: 'executeValidation', ...currentSourceDataChecks[ index++ ] } );
break;
}
case TestState.ExecutingValidations:
if ( ++messageCounter === 3 ) {
console.log( 'one cycle complete, passing to next test..' )
messageCounter = 0;
if ( index < currentSourceDataChecks.length ) {
conn.send( JSON.stringify( { action: 'executeValidation', ...currentSourceDataChecks[ index++ ] } ) );
sendMessage( { action: 'executeValidation', ...currentSourceDataChecks[ index++ ] } );

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Force source-data validation messages to use sourceDataCheck.

These sends still spread currentSourceDataChecks, which includes legacy category: "universalcalendar" entries for the base checks. Override the category at the send boundary so all source-data validation WebSocket messages use the expected category.

🛠️ Proposed fix
-            sendMessage( { action: 'executeValidation', ...currentSourceDataChecks[ index++ ] } );
+            sendMessage( { action: 'executeValidation', ...currentSourceDataChecks[ index++ ], category: 'sourceDataCheck' } );
...
-                    sendMessage( { action: 'executeValidation', ...currentSourceDataChecks[ index++ ] } );
+                    sendMessage( { action: 'executeValidation', ...currentSourceDataChecks[ index++ ], category: 'sourceDataCheck' } );

As per coding guidelines, assets/js/*.js: “For source data validation WebSocket messages, use category: 'sourceDataCheck' (not category-specific names like 'widerregioncalendar' or 'nationalcalendar')”.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
sendMessage( { action: 'executeValidation', ...currentSourceDataChecks[ index++ ] } );
break;
}
case TestState.ExecutingValidations:
if ( ++messageCounter === 3 ) {
console.log( 'one cycle complete, passing to next test..' )
messageCounter = 0;
if ( index < currentSourceDataChecks.length ) {
conn.send( JSON.stringify( { action: 'executeValidation', ...currentSourceDataChecks[ index++ ] } ) );
sendMessage( { action: 'executeValidation', ...currentSourceDataChecks[ index++ ] } );
sendMessage( { action: 'executeValidation', ...currentSourceDataChecks[ index++ ], category: 'sourceDataCheck' } );
break;
}
case TestState.ExecutingValidations:
if ( ++messageCounter === 3 ) {
console.log( 'one cycle complete, passing to next test..' )
messageCounter = 0;
if ( index < currentSourceDataChecks.length ) {
sendMessage( { action: 'executeValidation', ...currentSourceDataChecks[ index++ ], category: 'sourceDataCheck' } );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@assets/js/index.js` around lines 468 - 476, The sendMessage calls inside the
TestState handling are still spreading entries from currentSourceDataChecks
which may carry legacy category values; modify both sendMessage invocations (the
one before the switch break and the one inside the
TestState.ExecutingValidations branch) to explicitly set category:
'sourceDataCheck' on the object you send (i.e., sendMessage({ action:
'executeValidation', category: 'sourceDataCheck',
...currentSourceDataChecks[index++] })) so that all source-data validation
WebSocket messages use the required category; update the calls that reference
sendMessage, currentSourceDataChecks, messageCounter, index and
TestState.ExecutingValidations accordingly.

Comment thread assets/js/index.js
Comment on lines +801 to +810
const resetTestUI = () => {
// Reset all test cards (source data, calendar data, and unit tests)
document.querySelectorAll('#testSuiteAccordion .bg-success, #testSuiteAccordion .bg-danger').forEach(el => {
el.classList.remove('bg-success', 'bg-danger');
el.classList.add('bg-info');
});
document.querySelectorAll('#testSuiteAccordion .fa-circle-check, #testSuiteAccordion .fa-circle-xmark').forEach(el => {
el.classList.remove('fa-circle-check', 'fa-circle-xmark');
el.classList.add('fa-circle-question');
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Avoid resetting status badges as test cards.

The broad #testSuiteAccordion .bg-success/.bg-danger and icon selectors also match summary/status UI, such as the specific unit test “Successful”/“Failed” badges. Restrict the reset to result cards.

🎯 Proposed selector narrowing
-    document.querySelectorAll('#testSuiteAccordion .bg-success, `#testSuiteAccordion` .bg-danger').forEach(el => {
+    document.querySelectorAll('#testSuiteAccordion .card.bg-success, `#testSuiteAccordion` .card.bg-danger').forEach(el => {
         el.classList.remove('bg-success', 'bg-danger');
         el.classList.add('bg-info');
     });
-    document.querySelectorAll('#testSuiteAccordion .fa-circle-check, `#testSuiteAccordion` .fa-circle-xmark').forEach(el => {
+    document.querySelectorAll('#testSuiteAccordion .card .fa-circle-check, `#testSuiteAccordion` .card .fa-circle-xmark').forEach(el => {
         el.classList.remove('fa-circle-check', 'fa-circle-xmark');
         el.classList.add('fa-circle-question');
     });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@assets/js/index.js` around lines 801 - 810, The resetTestUI function is too
broad and also resets summary/status badges; narrow the two querySelectorAll
calls to target only test result cards (e.g. change selectors from
'#testSuiteAccordion .bg-success, `#testSuiteAccordion` .bg-danger' and
'#testSuiteAccordion .fa-circle-check, `#testSuiteAccordion` .fa-circle-xmark' to
selectors that scope to result cards such as '#testSuiteAccordion
.test-result-card .bg-success, `#testSuiteAccordion` .test-result-card .bg-danger'
and '#testSuiteAccordion .test-result-card .fa-circle-check, `#testSuiteAccordion`
.test-result-card .fa-circle-xmark'). Update both selector strings in
resetTestUI so only result cards are reset, leaving summary/status badges
untouched.

Comment thread assets/js/resources.js
Comment on lines +616 to +624
const resetTestUI = () => {
document.querySelectorAll('#testSuiteAccordion .bg-success, #testSuiteAccordion .bg-danger').forEach(el => {
el.classList.remove('bg-success', 'bg-danger');
el.classList.add('bg-info');
});
document.querySelectorAll('#testSuiteAccordion .fa-circle-check, #testSuiteAccordion .fa-circle-xmark').forEach(el => {
el.classList.remove('fa-circle-check', 'fa-circle-xmark');
el.classList.add('fa-circle-question');
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Limit reset styling to result cards only.

These selectors reset every .bg-success/.bg-danger and check/xmark icon under #testSuiteAccordion, which can also hit summary/status badges. Target .card result elements so the “Successful”/“Failed” UI chrome stays intact.

🎯 Proposed selector narrowing
-    document.querySelectorAll('#testSuiteAccordion .bg-success, `#testSuiteAccordion` .bg-danger').forEach(el => {
+    document.querySelectorAll('#testSuiteAccordion .card.bg-success, `#testSuiteAccordion` .card.bg-danger').forEach(el => {
         el.classList.remove('bg-success', 'bg-danger');
         el.classList.add('bg-info');
     });
-    document.querySelectorAll('#testSuiteAccordion .fa-circle-check, `#testSuiteAccordion` .fa-circle-xmark').forEach(el => {
+    document.querySelectorAll('#testSuiteAccordion .card .fa-circle-check, `#testSuiteAccordion` .card .fa-circle-xmark').forEach(el => {
         el.classList.remove('fa-circle-check', 'fa-circle-xmark');
         el.classList.add('fa-circle-question');
     });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@assets/js/resources.js` around lines 616 - 624, The resetTestUI function is
too broad: its selectors remove success/failure classes and icons from all
elements under `#testSuiteAccordion` (including summary/status badges); narrow the
selectors to only target result card elements (e.g., select '#testSuiteAccordion
.card.result' or the specific result card class used) so only result cards have
their 'bg-success'/'bg-danger' and 'fa-circle-check'/'fa-circle-xmark' replaced
with 'bg-info'/'fa-circle-question'; update the querySelectorAll calls inside
resetTestUI to use the more specific result-card selector (refer to resetTestUI,
the two querySelectorAll usages) so summary/status badges remain unchanged.

Comment thread Dockerfile
COPY ./assets ./assets
COPY ./i18n ./i18n
COPY ./*.php ./
COPY ./.env.example ./.env.local

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Shipping .env.example as .env.local can leak example/placeholder config into the runtime image.

.env.example is by convention a template with non-secret, non-production values. Baking it in as .env.local may cause the app to pick up placeholder values in production, and makes it harder for operators to override via mounted config/env vars. Consider leaving .env.local out of the image and providing it via a volume/secret at runtime, or via docker run --env-file.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Dockerfile` at line 35, The Dockerfile currently copies .env.example to
.env.local which bakes example placeholder config into the image; remove or stop
using the COPY ./.env.example ./.env.local step and instead leave .env.local out
of the image, copy only .env.example if you want the template (e.g., keep
.env.example but do not rename it), and document that operators should supply
runtime configuration via docker run --env-file, environment variables, or
mounted volume/secret; update the Dockerfile to stop creating .env.local and
update any README/compose docs accordingly so the app reads real runtime
environment values rather than baked placeholders.

Comment thread Dockerfile
Comment on lines +37 to +38
# Generate default credentials file for Docker (user: admin, password: admin)
RUN php -r "file_put_contents('credentials.php', \"<?php\ndefine('AUTH_USERS', ['admin' => '\" . password_hash('admin', PASSWORD_BCRYPT) . \"']);\n\");"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Hardcoded default admin/admin credentials baked into the image.

Any image built from this Dockerfile ships with a publicly-known username and password. Since credentials.php is baked in at build time and AUTH_USERS is what includes/auth.php authenticates against, anyone who pulls/runs the image has admin access unless an operator knows to overwrite the file post-build. Also, bcrypt-hashing a constant string at build time provides no security benefit — the plaintext is in the Dockerfile.

Prefer injecting credentials at container start from env vars / secrets (e.g., an entrypoint that generates credentials.php from AUTH_USER/AUTH_PASSWORD_HASH), and fail fast if none are provided. At minimum, document this loudly and gate it behind a build arg that is not the default.

🔒 Example: generate credentials at runtime from env
-# Generate default credentials file for Docker (user: admin, password: admin)
-RUN php -r "file_put_contents('credentials.php', \"<?php\ndefine('AUTH_USERS', ['admin' => '\" . password_hash('admin', PASSWORD_BCRYPT) . \"']);\n\");"
+# credentials.php is generated at container start from AUTH_USER / AUTH_PASSWORD
+# (see docker-entrypoint.sh). The image ships without default credentials.

And an entrypoint (in the main stage) along the lines of:

: "${AUTH_USER:?AUTH_USER must be set}"
: "${AUTH_PASSWORD:?AUTH_PASSWORD must be set}"
HASH=$(php -r "echo password_hash(getenv('AUTH_PASSWORD'), PASSWORD_BCRYPT);")
php -r "file_put_contents('/var/www/html/credentials.php', \"<?php\ndefine('AUTH_USERS', ['\" . getenv('AUTH_USER') . \"' => '$HASH']);\n\");"
exec "$@"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Dockerfile` around lines 37 - 38, The Dockerfile currently bakes a
credentials.php with a hardcoded admin/admin pair (AUTH_USERS) which exposes the
image; replace this by removing the build-time RUN that writes credentials.php
and instead generate credentials.php at container start (e.g., in your
entrypoint) using environment variables or secrets (AUTH_USER and AUTH_PASSWORD
or AUTH_PASSWORD_HASH), computing a bcrypt hash at runtime and failing fast if
the vars are missing; ensure includes/auth.php reads the generated
credentials.php, and optionally gate the old build-time behavior behind an
explicit build ARG (disabled by default) with clear documentation.

Comment thread Dockerfile
Comment on lines +41 to +61
FROM php:8.4-cli AS main

# Set the working directory
WORKDIR /var/www/html

# Install runtime dependencies
RUN apt-get update -y && \
apt-get install -y --no-install-suggests --no-install-recommends \
libyaml-0-2 libicu-dev libzip-dev locales-all && \
rm -rf /var/lib/apt/lists/*

# Copy the compiled PHP extensions from the build stage
COPY --from=build /usr/local/lib/php/extensions /usr/local/lib/php/extensions
COPY --from=build /usr/local/etc/php/conf.d /usr/local/etc/php/conf.d
COPY --from=build /var/www/html /var/www/html

# Expose port 3003 to the host
EXPOSE 3003

# Command to run PHP's built-in server
CMD ["php", "-S", "0.0.0.0:3003", "-t", "/var/www/html"]

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Container runs as root and serves via PHP’s built-in web server.

Two production-hardening gaps in the main stage:

  1. No USER directive — the container runs as root (Trivy DS-0002). Add a non-root user and chown the app directory.
  2. php -S is documented by PHP as not intended for production use; it is single-threaded and lacks the robustness of a real server. If this image is intended for dev only, make that explicit in the image/README; otherwise front it with something like php-fpm + nginx, or use php:8.4-apache/FrankenPHP.

A HEALTHCHECK would also be worth adding for orchestrators.

🔧 Minimal non-root user example
 # Copy the built application contents
 COPY --from=build /var/www/html /var/www/html

+# Run as a non-root user
+RUN groupadd --system --gid 1001 app && \
+    useradd  --system --uid 1001 --gid app --home-dir /var/www/html --shell /usr/sbin/nologin app && \
+    chown -R app:app /var/www/html
+USER app
+
 EXPOSE 3003
+
+HEALTHCHECK --interval=30s --timeout=5s --retries=3 \
+    CMD php -r "exit(`@file_get_contents`('http://127.0.0.1:3003/') !== false ? 0 : 1);"
 CMD ["php", "-S", "0.0.0.0:3003", "-t", "/var/www/html"]
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
FROM php:8.4-cli AS main
# Set the working directory
WORKDIR /var/www/html
# Install runtime dependencies
RUN apt-get update -y && \
apt-get install -y --no-install-suggests --no-install-recommends \
libyaml-0-2 libicu-dev libzip-dev locales-all && \
rm -rf /var/lib/apt/lists/*
# Copy the compiled PHP extensions from the build stage
COPY --from=build /usr/local/lib/php/extensions /usr/local/lib/php/extensions
COPY --from=build /usr/local/etc/php/conf.d /usr/local/etc/php/conf.d
COPY --from=build /var/www/html /var/www/html
# Expose port 3003 to the host
EXPOSE 3003
# Command to run PHP's built-in server
CMD ["php", "-S", "0.0.0.0:3003", "-t", "/var/www/html"]
FROM php:8.4-cli AS main
# Set the working directory
WORKDIR /var/www/html
# Install runtime dependencies
RUN apt-get update -y && \
apt-get install -y --no-install-suggests --no-install-recommends \
libyaml-0-2 libicu-dev libzip-dev locales-all && \
rm -rf /var/lib/apt/lists/*
# Copy the compiled PHP extensions from the build stage
COPY --from=build /usr/local/lib/php/extensions /usr/local/lib/php/extensions
COPY --from=build /usr/local/etc/php/conf.d /usr/local/etc/php/conf.d
COPY --from=build /var/www/html /var/www/html
# Run as a non-root user
RUN groupadd --system --gid 1001 app && \
useradd --system --uid 1001 --gid app --home-dir /var/www/html --shell /usr/sbin/nologin app && \
chown -R app:app /var/www/html
USER app
# Expose port 3003 to the host
EXPOSE 3003
# Health check
HEALTHCHECK --interval=30s --timeout=5s --retries=3 \
CMD php -r "exit(`@file_get_contents`('http://127.0.0.1:3003/') !== false ? 0 : 1);"
# Command to run PHP's built-in server
CMD ["php", "-S", "0.0.0.0:3003", "-t", "/var/www/html"]
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Dockerfile` around lines 41 - 61, The Dockerfile main stage currently runs as
root and uses PHP's built-in server; create a non-root user (e.g., app
user/group) and chown the application directory (/var/www/html) before switching
to that user with a USER directive to fix the Trivy DS-0002 issue, replace the
CMD ["php","-S",...] used in the main stage with a production-ready entrypoint
(e.g., start php-fpm and front with nginx or use php:8.4-apache/FrankenPHP) or,
if this image is dev-only, document that clearly in the image README and change
the image tag/name to indicate dev; additionally add a HEALTHCHECK instruction
probing the app (HTTP on :3003 or socket) to allow orchestrators to detect
unhealthy containers.

Comment thread Dockerfile
Comment on lines +46 to +55
# Install runtime dependencies
RUN apt-get update -y && \
apt-get install -y --no-install-suggests --no-install-recommends \
libyaml-0-2 libicu-dev libzip-dev locales-all && \
rm -rf /var/lib/apt/lists/*

# Copy the compiled PHP extensions from the build stage
COPY --from=build /usr/local/lib/php/extensions /usr/local/lib/php/extensions
COPY --from=build /usr/local/etc/php/conf.d /usr/local/etc/php/conf.d
COPY --from=build /var/www/html /var/www/html

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Confirm the exact runtime package names available on the php:8.4-cli (Debian bookworm) base
# for libicu and libzip, so the runtime stage can drop the -dev packages.
docker run --rm php:8.4-cli bash -lc '
  apt-get update -y >/dev/null 2>&1 &&
  apt-cache search --names-only "^libicu[0-9]+$" &&
  apt-cache search --names-only "^libzip[0-9]+$" &&
  dpkg -l | grep -E "libicu|libzip|gettext" || true
'

Repository: Liturgical-Calendar/UnitTestInterface

Length of output: 127


🏁 Script executed:

git ls-files | grep -i dockerfile

Repository: Liturgical-Calendar/UnitTestInterface

Length of output: 91


🏁 Script executed:

#!/bin/bash
# Find the Dockerfile mentioned in the review (lines 46-55)
# and read the build stage to understand what extensions/dependencies are installed
fd -i dockerfile -x wc -l {} \; -exec cat {} \;

Repository: Liturgical-Calendar/UnitTestInterface

Length of output: 268


🌐 Web query:

Debian bookworm php:8.4-cli libicu libzip runtime packages

💡 Result:

Debian 12 (bookworm) does not include PHP 8.4 in its official repositories; it ships PHP 8.2 by default (php8.2-cli). PHP 8.4 packages like php8.4-cli, php8.4-zip are available in Ondřej Surý's third-party repository at packages.sury.org/php for bookworm, as well as in Docker images (php:8.4-cli-bookworm) and newer Debian suites like trixie/testing. To install on Debian bookworm: 1. Add the Sury repository: apt install -y apt-transport-https lsb-release ca-certificates curl gnupg2 curl -fsSL https://packages.sury.org/php/apt.gpg | gpg --dearmor -o /etc/apt/trusted.gpg.d/sury-php.gpg echo "deb https://packages.sury.org/php/ $(lsb_release -sc) main" | tee /etc/apt/sources.list.d/sury-php.list apt update 2. Install PHP 8.4 CLI runtime: apt install -y php8.4-cli php8.4-common Runtime dependencies for php8.4-cli (analogous to php8.2-cli and Debian php8.4 in sid/trixie): - libc6, libargon2-1, libedit2, libpcre2-8-0, libsodium23, libssl3, libxml2, media-types, php8.4-common (= version), php8.4-opcache, php8.4-readline, tzdata, ucf, zlib1g For libzip (zip extension): Install php8.4-zip, which depends on libzip4t64 (runtime library for zip archives). For libicu (intl extension): Install php8.4-intl, which depends on libicu72 (International Components for Unicode runtime library, version 72.1-3+deb12u1 in bookworm). These are pulled automatically or explicitly via apt. Verify with apt show php8.4-cli php8.4-zip php8.4-intl after adding the repo. Note: PHP 8.4 is not recommended for production until stable.

Citations:


🏁 Script executed:

cat Dockerfile

Repository: Liturgical-Calendar/UnitTestInterface

Length of output: 2276


🌐 Web query:

PHP gettext extension runtime requirements Debian bookworm

💡 Result:

The PHP gettext extension is a core extension compiled into PHP by default, with no separate runtime package required in Debian Bookworm (Debian 12). No php8.2-gettext or php-gettext package exists; searches for it return errors. The core php8.2-common package (version 8.2.30-1~deb12u1) provides the base runtime including standard extensions like gettext. Runtime requirements are satisfied by installing php8.2-common (which depends on libc6, libffi8, libssl3, php-common, ucf). Gettext functions (gettext, dgettext, etc.) are available once PHP 8.2 is installed. Note: php-php-gettext is a separate PEAR package emulating gettext in pure PHP (depends on php-mbstring/php8.2-mbstring, php-pear), used only if avoiding the native extension or system gettext libs, but not the native extension itself.

Citations:


🌐 Web query:

libzip4t64 libzip4 Debian bookworm difference

💡 Result:

In Debian bookworm (version 12, released June 2023), the libzip package provides libzip4 (version 1.7.3-1+b1) as the runtime library for reading, creating, and modifying zip archives. This is the standard package available across all architectures like amd64, arm64, armhf, i386, etc. libzip4t64 is not available in bookworm. It appears only in later suites: sid/unstable (for ia64 debports port) at version 1.7.3-1.1+b1, and during the transition in experimental. The libzip source in bookworm builds only libzip4 and libzip-dev, not libzip4t64. The key difference is that libzip4t64 is the renamed runtime library package (libzip4 -> libzip4t64) for the Debian 64-bit time_t transition (to address the Year 2038 problem on 32-bit architectures like armhf). This transition involved NMUs (non-maintainer uploads) starting March 2024 (e.g., libzip 1.7.3-1.1 NMU, bug #1062743), after bookworm's freeze and release. Libraries exposing time_t in their ABI were renamed with "t64" suffix to enable co-installability of old and new ABI versions during the transition. Since bookworm predates this transition (glibc in bookworm uses 32-bit time_t on 32-bit arches), it uses the pre-transition libzip4 without ABI changes for 64-bit time_t.

Citations:


Replace -dev packages with runtime libraries in runtime stage.

libicu-dev and libzip-dev are development headers not needed at runtime. The intl and zip extensions (compiled in the build stage) only require the runtime shared libraries: libicu72 and libzip4 on bookworm. Removing the -dev packages significantly reduces the final image size. The libyaml-0-2 and locales-all packages are correct.

Note: gettext is built into PHP core and requires no separate runtime package.

🧰 Suggested change
-RUN apt-get update -y && \
-    apt-get install -y --no-install-suggests --no-install-recommends \
-    libyaml-0-2 libicu-dev libzip-dev locales-all && \
-    rm -rf /var/lib/apt/lists/*
+RUN apt-get update -y && \
+    apt-get install -y --no-install-suggests --no-install-recommends \
+        libyaml-0-2 libicu72 libzip4 locales-all && \
+    rm -rf /var/lib/apt/lists/*

If locales-all (~200MB) is not required, consider generating only the locales the app needs via locale-gen.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# Install runtime dependencies
RUN apt-get update -y && \
apt-get install -y --no-install-suggests --no-install-recommends \
libyaml-0-2 libicu-dev libzip-dev locales-all && \
rm -rf /var/lib/apt/lists/*
# Copy the compiled PHP extensions from the build stage
COPY --from=build /usr/local/lib/php/extensions /usr/local/lib/php/extensions
COPY --from=build /usr/local/etc/php/conf.d /usr/local/etc/php/conf.d
COPY --from=build /var/www/html /var/www/html
# Install runtime dependencies
RUN apt-get update -y && \
apt-get install -y --no-install-suggests --no-install-recommends \
libyaml-0-2 libicu72 libzip4 locales-all && \
rm -rf /var/lib/apt/lists/*
# Copy the compiled PHP extensions from the build stage
COPY --from=build /usr/local/lib/php/extensions /usr/local/lib/php/extensions
COPY --from=build /usr/local/etc/php/conf.d /usr/local/etc/php/conf.d
COPY --from=build /var/www/html /var/www/html
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Dockerfile` around lines 46 - 55, The Dockerfile's runtime stage installs
development packages libicu-dev and libzip-dev which are unnecessary; replace
them with the corresponding runtime shared libraries (bookworm names libicu72
and libzip4) in the RUN apt-get install line and remove the "-dev" packages
while keeping libyaml-0-2 and locales-all; optionally, if you don't need all
locales, replace locales-all with generating only required locales via
locale-gen to reduce image size.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant