-
Notifications
You must be signed in to change notification settings - Fork 12
Feature/node lts versions #48
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Draft
pablojmarti
wants to merge
11
commits into
trunk
Choose a base branch
from
feature/node-lts-versions
base: trunk
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
Changes from all commits
Commits
Show all changes
11 commits
Select commit
Hold shift + click to select a range
e50dbdc
Updates to only use the latest 3 versions of node in our build
pablojmarti d6f2031
Adds info about update to readme
pablojmarti 94857ab
Adds helper script to test installation
pablojmarti 6b89d99
Adds Claude.md to repo
pablojmarti 4cc153b
moves 8.1 to bookworm as well to help with build issues
pablojmarti 98b1a56
Adds fail fast to test pipeline
pablojmarti 4bbad1d
Updates for swapping claude to agents and updated readme
pablojmarti 45de87e
Removes old versions of PHP from build
pablojmarti c431b16
Installs opentelemtry on all builds now
pablojmarti 86dac95
updates for new supported version of php
pablojmarti b4eba1e
Adds latest rule
pablojmarti File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file was deleted.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,56 @@ | ||
| # AGENTS.md | ||
|
|
||
| This file provides guidance to AI coding agents working with code in this repository. | ||
|
|
||
| **Keep this file current.** Any change to the Dockerfile, build/CI scripts, workflow matrix, env-var contract, or tests must be reflected here in the same change. | ||
|
|
||
| ## What this repo is | ||
|
|
||
| Source for the `10up/wordpress-ci` Docker images: one Debian-based PHP image per PHP version, bundling the tooling WordPress CI/CD pipelines need (composer, nvm/node, wp-cli, ClamAV, kubectl, docker-cli, terminus, gh, glab, awscli/ansible via pip, etc.). There is no application code. The deliverables are the image and the helper scripts it ships in `/custom-scripts`. | ||
|
|
||
| ## Commands | ||
|
|
||
| ```bash | ||
| # Build locally (PHP_IMG is required, there is no default) | ||
| docker build --build-arg PHP_IMG=php:8.3-bookworm --build-arg COMPOSER_VERSION=2 -t wordpress-ci:local . | ||
|
|
||
| # Try a script inside the image against the current dir | ||
| docker run --rm -v "$PWD":/work -w /work wordpress-ci:local virus-scan | ||
|
|
||
| # Tests (host-side, no Docker; fake binaries/functions on PATH or NVM_DIR) | ||
| bash tests/test-virus-scan.sh | ||
| bash tests/test-install-node.sh | ||
|
|
||
| # Lint shell (all current findings are info-level) | ||
| shellcheck scripts/* build/*.sh entrypoint.sh tests/*.sh | ||
| ``` | ||
|
|
||
| `tests/test-virus-scan.sh` hardcodes `mktemp -d /private/tmp/...`, so it only runs on macOS as written. `tests/test-install-node.sh` uses `${TMPDIR:-/tmp}` and is portable. CI does not run the tests or shellcheck. The only CI job is the image build. | ||
|
|
||
| ## Architecture | ||
|
|
||
| **Image build (`Dockerfile`)**, in this order: apt packages (incl. `clamav-daemon`), then `freshclam` early so the virus DB is baked in (running it later in the file hit bugs), locale, PHP ini (`memory_limit=-1`) and extensions (including `opentelemetry` from pecl), nvm in `/tmp/.nvm` + the latest `NODE_LTS_COUNT` (default 3) Node LTS lines via `build/install-node.sh`, pip `requirements.txt` (uses `PIP_BREAK_SYSTEM_PACKAGES=1` for bookworm), composer, wp-cli, then docker-cli/kubectl/terminus/gh/glab, which are always the latest release and unpinned. Finally `scripts/*` go to `/custom-scripts` (on `PATH`), plus `BASH_ENV=/root/.bashrc` so non-interactive CI shells load nvm and the `wp` alias. | ||
|
|
||
| - `build/install-composer.sh` installs `composer1-bin`, `composer2-bin`, and a default `composer` chosen by the `COMPOSER_VERSION` build arg (default 2, and every image in CI uses 2). | ||
| - `build/install-wpcli.sh` installs `/usr/local/bin/wp-cli.phar`. `wp` exists only as a `.bashrc` alias (`php wp-cli.phar --allow-root`), not as a binary. Bash doesn't expand aliases in non-interactive shells, so `bash -c 'wp ...'` fails with "command not found" even though `BASH_ENV` loads `.bashrc`. | ||
| - `build/install-node.sh [count]` installs nvm's relative aliases oldest first: `lts/-2`, `lts/-1`, then `lts/*`. It resolves them at build time, so the weekly no-cache rebuild rolls the set forward automatically when a new line goes LTS (versions get dropped the same way). It rejects a count that isn't a positive integer. Keeping 3 is a deliberate choice, even when the oldest line is past EOL (e.g. Node 20 until 26 goes LTS). The Dockerfile then runs `nvm alias default 'lts/*'`. Pipelines switch with `nvm use <major>` or run `nvm install <v>` for anything else. | ||
|
|
||
| **Runtime (`entrypoint.sh`, POSIX sh)**: sources nvm, then applies optional env vars: `PRIVATE_KEY`/`PUBLIC_KEY` go to `/root/.ssh/id_rsa{,.pub}`, `TERMINUS_TOKEN` runs a terminus login, `GIT_USER_NAME`/`GIT_USER_EMAIL` set git global config, `COMPOSER_CONFIG` runs `composer config -g`, and `BUILD_CACHE_DIR` points the composer and npm caches at `$BUILD_CACHE_DIR/.composer-cache` and `$BUILD_CACHE_DIR/node_modules_cache` (for GitLab: `${CI_PROJECT_DIR}`). It then prints the versions of php, composer, node, npm and wp-cli, and runs `exec "$@"`. The script has no `set -e`, so a missing tool in that printout only logs "not found". When removing a tool from the image, also remove its version line here. | ||
|
|
||
| **CI scripts (`scripts/`, extensionless, copied to `/custom-scripts`)**: | ||
| - `all-scripts` *sources* (does not execute) every top-level `/custom-scripts/*.sh` (user-added), then `php-syntax`, then `virus-scan`, all under `set -euo pipefail`. Because they are sourced, an `exit` in any of them ends the whole run, and `virus-scan` always exits. It must stay last. | ||
| - `php-syntax` runs `php -l` in parallel (`xargs -P10`) on every `*.php` outside `vendor/`. Output goes to stdout only when `IS_DEBUG_ENABLED=true`. | ||
| - `virus-scan` starts a private, temporary `clamd` (generated config in a `mktemp` dir, `LogClean yes`, excludes `.composer-cache` and `node_modules_cache`), waits up to 30s for `--ping`, runs `clamdscan --multiscan "$PWD"`, then kills the daemon and removes the temp dir via an EXIT trap. The scanned-file count comes from the **daemon log** (`: OK` / ` FOUND` lines), because clamdscan's report on a directory doesn't give a per-file count. `: OK` lines are filtered out of the printed report. **Exit contract:** 1 only when an infection is found. Every scanner or daemon failure exits 0 (fail-open, so a broken scanner never blocks a deploy). `CLAMAV_DB_DIR` and `TMPDIR` can be overridden; the tests rely on both. | ||
| - `slack-message` posts a Slack attachment via webhook. `-u` and `-m` are required (see the README for all flags). | ||
|
|
||
| **Tests** follow one pattern: fake the external tool, run the real script, then assert exit codes and the calls or output that were recorded. | ||
| - `test-virus-scan.sh` stubs `clamd`/`clamdscan` as bash scripts driven by `FAKE_SCAN_EXIT` (0 clean, 1 infected, 2 scan error, 3 no summary) and `FAKE_START_MODE`. It checks the generated `clamd.conf`, the output, and that the daemon was stopped. Changes to virus-scan output or config need matching test updates. | ||
| - `test-install-node.sh` points `NVM_DIR` at a fake `nvm.sh` that defines recording `nvm`/`npm` functions (`FAKE_FAIL_VERSION` makes one install fail). It checks install order, the count handling, and that the script stops at the first failed install. | ||
|
|
||
| ## Release / CI (`.github/workflows/build.yaml`) | ||
|
|
||
| - Runs on every push to any branch, plus a weekly rebuild (Sun 04:00 UTC) to pick up security updates. Builds are always `no-cache: true`, wrapped in a 3-attempt retry. | ||
| - The matrix is `base_container`, which currently builds PHP 8.2, 8.3 and 8.4, all on `-bookworm` images. Each entry needs a matching "Set PHP X settings" step that sets `BUILD_TAGS` and `COMPOSER_VERSION` (always 2). The newest version also carries the `latest` tag, which is currently 8.4, so adding a newer PHP means moving `latest` to it. | ||
| - PHP 7.4, 8.0 and 8.1 are deprecated and removed from the matrix. Their Docker Hub tags still exist but are no longer rebuilt. Don't reintroduce bullseye-based images: Debian bullseye support ended on Aug 31, 2026, and its security package files return 404, so any bullseye build fails at the first `apt-get install`. | ||
| - The matrix has `fail-fast: false` set **temporarily** so each PHP version reports its own result while this branch is being tested. Remove it before merging. | ||
| - Images push to Docker Hub only from `trunk`. Branch builds only validate. Adding a PHP version means adding a matrix entry plus a settings step (the README has the template). |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,26 +1,37 @@ | ||
| #!/bin/bash | ||
|
|
||
| # Install only the current LTS version and npm packages | ||
| # This avoids extra GB of build container image size and maintains security | ||
| # Install the latest N LTS lines of node (default 3), oldest first | ||
| # Uses nvm's relative LTS aliases (lts/-2, lts/-1, lts/*), so the weekly rebuild | ||
| # rolls the set forward automatically when a new line enters LTS | ||
| # nvm can be used in individual pipelines to install other versions | ||
|
|
||
| # LTS calendar: https://nodejs.org/en/about/releases/ | ||
|
|
||
| # TODO: NODE_VERSION variable could be centralized in the Dockerfile for quicker management | ||
|
|
||
| # catch Errors | ||
| set -euo pipefail | ||
|
|
||
| #Get node version from Docker build argument | ||
| NODE_VERSION="$1" | ||
| # Get number of LTS lines from Docker build argument | ||
| LTS_COUNT="${1:-3}" | ||
|
|
||
| if [[ ! "${LTS_COUNT}" =~ ^[1-9][0-9]*$ ]]; then | ||
| >&2 echo "ERROR: LTS count must be a positive integer, got '${LTS_COUNT}'" | ||
| exit 1 | ||
| fi | ||
|
|
||
| # set up nvm in this script | ||
| . "$NVM_DIR/nvm.sh" | ||
|
|
||
| echo "Building node environment for version ${NODE_VERSION}" | ||
| for (( offset = LTS_COUNT - 1; offset >= 0; offset-- )); do | ||
| if [[ "${offset}" -eq 0 ]]; then | ||
| NODE_VERSION="lts/*" | ||
| else | ||
| NODE_VERSION="lts/-${offset}" | ||
| fi | ||
|
|
||
| nvm install "${NODE_VERSION}" | ||
| echo "Building node environment for version ${NODE_VERSION}" | ||
| nvm install "${NODE_VERSION}" | ||
| done | ||
|
|
||
| npm cache clean --force | ||
|
|
||
| echo "node ${NODE_VERSION} build completed..." | ||
| echo "node build completed for the latest ${LTS_COUNT} LTS versions..." |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,113 @@ | ||
| #!/usr/bin/env bash | ||
|
|
||
| set -uo pipefail | ||
|
|
||
| TEST_DIR="$(mktemp -d "${TMPDIR:-/tmp}/install-node-tests.XXXXXX")" | ||
| readonly TEST_DIR | ||
| REPOSITORY_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" | ||
| readonly REPOSITORY_DIR | ||
| readonly SCRIPT_PATH="${REPOSITORY_DIR}/build/install-node.sh" | ||
|
|
||
| failures=0 | ||
|
|
||
| cleanup() { | ||
| if [[ -d "${TEST_DIR}" ]]; then | ||
| rm -r -- "${TEST_DIR}" | ||
| fi | ||
| } | ||
| trap cleanup EXIT | ||
|
|
||
| fail() { | ||
| printf 'not ok - %s\n' "$1" >&2 | ||
| failures=$((failures + 1)) | ||
| } | ||
|
|
||
| assert_status() { | ||
| local expected="$1" | ||
| local actual="$2" | ||
| local description="$3" | ||
|
|
||
| if [[ "${actual}" -ne "${expected}" ]]; then | ||
| fail "${description}: expected exit ${expected}, got ${actual}" | ||
| fi | ||
| } | ||
|
|
||
| assert_file_equals() { | ||
| local file="$1" | ||
| local expected="$2" | ||
| local description="$3" | ||
|
|
||
| if [[ ! -f "${file}" ]] || [[ "$(cat "${file}")" != "${expected}" ]]; then | ||
| fail "${description}" | ||
| fi | ||
| } | ||
|
|
||
| # Fake nvm.sh: defines nvm and npm functions that record their calls | ||
| create_fake_nvm() { | ||
| local nvm_dir="$1" | ||
|
|
||
| mkdir -p "${nvm_dir}" | ||
|
|
||
| cat > "${nvm_dir}/nvm.sh" <<'EOF' | ||
| nvm() { | ||
| printf '%s\n' "$*" >> "${TEST_STATE}/nvm.calls" | ||
| if [[ "$1" == "install" && "$2" == "${FAKE_FAIL_VERSION:-}" ]]; then | ||
| return 3 | ||
| fi | ||
| } | ||
|
|
||
| npm() { | ||
| printf '%s\n' "$*" >> "${TEST_STATE}/npm.calls" | ||
| } | ||
| EOF | ||
| } | ||
|
|
||
| run_install_case() { | ||
| local name="$1" | ||
| local expected_exit="$2" | ||
| shift 2 | ||
| local state_dir="${TEST_DIR}/${name}" | ||
| local actual_exit | ||
|
|
||
| mkdir -p "${state_dir}" | ||
| create_fake_nvm "${state_dir}/nvm" | ||
|
|
||
| ( | ||
| NVM_DIR="${state_dir}/nvm" \ | ||
| TEST_STATE="${state_dir}" \ | ||
| bash "${SCRIPT_PATH}" "$@" | ||
| ) > "${state_dir}/output" 2>&1 | ||
| actual_exit=$? | ||
|
|
||
| assert_status "${expected_exit}" "${actual_exit}" "${name}" | ||
| } | ||
|
|
||
| run_install_case default 0 | ||
| assert_file_equals "${TEST_DIR}/default/nvm.calls" "$(printf '%s\n' 'install lts/-2' 'install lts/-1' 'install lts/*')" \ | ||
| 'default installs the three latest LTS lines, oldest first' | ||
| assert_file_equals "${TEST_DIR}/default/npm.calls" 'cache clean --force' 'npm cache is cleaned once' | ||
|
|
||
| run_install_case single 0 1 | ||
| assert_file_equals "${TEST_DIR}/single/nvm.calls" 'install lts/*' 'a count of 1 installs only the latest LTS' | ||
|
|
||
| run_install_case five 0 5 | ||
| assert_file_equals "${TEST_DIR}/five/nvm.calls" \ | ||
| "$(printf '%s\n' 'install lts/-4' 'install lts/-3' 'install lts/-2' 'install lts/-1' 'install lts/*')" \ | ||
| 'the LTS count is configurable' | ||
|
|
||
| run_install_case zero 1 0 | ||
| [[ -f "${TEST_DIR}/zero/nvm.calls" ]] && fail 'a count of 0 installs nothing' | ||
|
|
||
| run_install_case not_a_number 1 abc | ||
| [[ -f "${TEST_DIR}/not_a_number/nvm.calls" ]] && fail 'a non-numeric count installs nothing' | ||
|
|
||
| FAKE_FAIL_VERSION='lts/-1' run_install_case install_failure 3 | ||
| assert_file_equals "${TEST_DIR}/install_failure/nvm.calls" "$(printf '%s\n' 'install lts/-2' 'install lts/-1')" \ | ||
| 'a failed install stops the build' | ||
|
|
||
| if [[ "${failures}" -gt 0 ]]; then | ||
| printf '%s test assertion(s) failed\n' "${failures}" >&2 | ||
| exit 1 | ||
| fi | ||
|
|
||
| printf 'ok - install-node behavior\n' |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As Claude now supports AGENTS.md, we should switch to it. We should also make this AI-agnostic, so no mention of Claude Code, but rather AI agents in general.
We should likely address this
Known doc driftsection at the bottom, no?