@devopsplaybook.io/common-utils 1.7.0-beta.16.a8b6bbc → 1.8.0-beta.17.1c8c436
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -29,11 +29,14 @@ on:
|
|
|
29
29
|
required: false
|
|
30
30
|
|
|
31
31
|
# Immutable build strategy: the image validated on the pull request is promoted
|
|
32
|
-
# as-is, so merging runs no build, lint or test.
|
|
33
|
-
#
|
|
32
|
+
# as-is, so merging runs no build, lint or test.
|
|
33
|
+
#
|
|
34
|
+
# `contents: read` only. Callers run with the default read-only token, whose
|
|
35
|
+
# ceiling is `contents: read, pull-requests: none`, so requesting anything more
|
|
36
|
+
# here makes the caller fail to start with "Invalid workflow file". The PR is
|
|
37
|
+
# therefore resolved without needing pull-request scopes.
|
|
34
38
|
permissions:
|
|
35
39
|
contents: read
|
|
36
|
-
pull-requests: read
|
|
37
40
|
|
|
38
41
|
jobs:
|
|
39
42
|
promote-image:
|
|
@@ -64,21 +67,33 @@ jobs:
|
|
|
64
67
|
SERVICE_VERSION=$(jq -r '.version' package.json)
|
|
65
68
|
IMAGE="${DOCKER_HUB_USERNAME}/${SERVICE_NAME}"
|
|
66
69
|
|
|
67
|
-
|
|
68
|
-
|
|
70
|
+
PR_NUMBER=""
|
|
71
|
+
PR_SOURCE="none"
|
|
72
|
+
|
|
73
|
+
# Preferred: ask the API which PR introduced this commit. This needs
|
|
74
|
+
# `pull-requests: read`, which callers on the default token are not
|
|
75
|
+
# granted, so a failure here is expected rather than exceptional.
|
|
69
76
|
PR_NUMBER=$(curl -sfL \
|
|
70
77
|
-H "Authorization: Bearer ${GH_TOKEN}" \
|
|
71
78
|
-H "Accept: application/vnd.github+json" \
|
|
72
79
|
"${GITHUB_API_URL}/repos/${GITHUB_REPOSITORY}/commits/${GITHUB_SHA}/pulls" \
|
|
73
80
|
| jq -r '.[0].number // empty' || true)
|
|
81
|
+
if [ -n "${PR_NUMBER}" ]; then
|
|
82
|
+
PR_SOURCE="api"
|
|
83
|
+
fi
|
|
74
84
|
|
|
75
|
-
# Fallback:
|
|
76
|
-
# "<title> (#123)"
|
|
85
|
+
# Fallback: parse the commit title, where GitHub writes the PR number
|
|
86
|
+
# for squash ("<title> (#123)") and merge ("Merge pull request #123")
|
|
87
|
+
# commits. Only the title is read, so a "fixes #99" reference in the
|
|
88
|
+
# commit body cannot select the wrong pull request.
|
|
77
89
|
if [ -z "${PR_NUMBER}" ]; then
|
|
78
|
-
|
|
79
|
-
PR_NUMBER=$(printf '%s' "${
|
|
90
|
+
TITLE=$(git log -1 --pretty=%s)
|
|
91
|
+
PR_NUMBER=$(printf '%s' "${TITLE}" | grep -oE '\(#[0-9]+\)' | tail -1 | tr -d '(#)' || true)
|
|
80
92
|
if [ -z "${PR_NUMBER}" ]; then
|
|
81
|
-
PR_NUMBER=$(printf '%s' "${
|
|
93
|
+
PR_NUMBER=$(printf '%s' "${TITLE}" | grep -oE 'Merge pull request #[0-9]+' | grep -oE '[0-9]+' | head -1 || true)
|
|
94
|
+
fi
|
|
95
|
+
if [ -n "${PR_NUMBER}" ]; then
|
|
96
|
+
PR_SOURCE="commit-title"
|
|
82
97
|
fi
|
|
83
98
|
fi
|
|
84
99
|
|
|
@@ -105,11 +120,14 @@ jobs:
|
|
|
105
120
|
echo "service_name=${SERVICE_NAME}"
|
|
106
121
|
echo "service_version=${SERVICE_VERSION}"
|
|
107
122
|
echo "pr_number=${PR_NUMBER}"
|
|
123
|
+
echo "pr_source=${PR_SOURCE}"
|
|
108
124
|
echo "source_image=${SOURCE_IMAGE}"
|
|
109
125
|
echo "strategy=${STRATEGY}"
|
|
110
126
|
} >> "${GITHUB_OUTPUT}"
|
|
111
127
|
|
|
112
|
-
echo "Service:
|
|
128
|
+
echo "Service: ${SERVICE_NAME} ${SERVICE_VERSION}"
|
|
129
|
+
echo "PR: ${PR_NUMBER:-none} (resolved via ${PR_SOURCE})"
|
|
130
|
+
echo "Source: ${SOURCE_IMAGE:-none}"
|
|
113
131
|
echo "Strategy: ${STRATEGY}"
|
|
114
132
|
|
|
115
133
|
- name: Promote the PR image to the release tags
|
|
@@ -21,7 +21,37 @@ on:
|
|
|
21
21
|
required: false
|
|
22
22
|
|
|
23
23
|
jobs:
|
|
24
|
+
version-check:
|
|
25
|
+
runs-on: ubuntu-latest
|
|
26
|
+
steps:
|
|
27
|
+
- uses: actions/checkout@v7
|
|
28
|
+
|
|
29
|
+
- name: Fetch main branch
|
|
30
|
+
run: git fetch origin main --depth=1
|
|
31
|
+
|
|
32
|
+
- name: Verify version is higher than main
|
|
33
|
+
run: |
|
|
34
|
+
PR_VERSION=$(jq -r '.version' package.json)
|
|
35
|
+
MAIN_VERSION=$(git show origin/main:package.json | jq -r '.version')
|
|
36
|
+
|
|
37
|
+
echo "PR version: ${PR_VERSION}"
|
|
38
|
+
echo "Main version: ${MAIN_VERSION}"
|
|
39
|
+
|
|
40
|
+
if [ "$PR_VERSION" = "$MAIN_VERSION" ]; then
|
|
41
|
+
echo "::error::PR version (${PR_VERSION}) must be higher than main branch version (${MAIN_VERSION}). Please bump the version in package.json."
|
|
42
|
+
exit 1
|
|
43
|
+
fi
|
|
44
|
+
|
|
45
|
+
HIGHER=$(printf '%s\n%s' "$PR_VERSION" "$MAIN_VERSION" | sort -V | tail -n1)
|
|
46
|
+
if [ "$HIGHER" != "$PR_VERSION" ]; then
|
|
47
|
+
echo "::error::PR version (${PR_VERSION}) is lower than main branch version (${MAIN_VERSION}). Please bump the version in package.json."
|
|
48
|
+
exit 1
|
|
49
|
+
fi
|
|
50
|
+
|
|
51
|
+
echo "Version check passed: ${PR_VERSION} > ${MAIN_VERSION}"
|
|
52
|
+
|
|
24
53
|
test-and-build:
|
|
54
|
+
needs: version-check
|
|
25
55
|
runs-on: ubuntu-latest
|
|
26
56
|
|
|
27
57
|
steps:
|
|
@@ -29,7 +29,37 @@ on:
|
|
|
29
29
|
required: false
|
|
30
30
|
|
|
31
31
|
jobs:
|
|
32
|
+
version-check:
|
|
33
|
+
runs-on: ubuntu-latest
|
|
34
|
+
steps:
|
|
35
|
+
- uses: actions/checkout@v7
|
|
36
|
+
|
|
37
|
+
- name: Fetch main branch
|
|
38
|
+
run: git fetch origin main --depth=1
|
|
39
|
+
|
|
40
|
+
- name: Verify version is higher than main
|
|
41
|
+
run: |
|
|
42
|
+
PR_VERSION=$(jq -r '.version' package.json)
|
|
43
|
+
MAIN_VERSION=$(git show origin/main:package.json | jq -r '.version')
|
|
44
|
+
|
|
45
|
+
echo "PR version: ${PR_VERSION}"
|
|
46
|
+
echo "Main version: ${MAIN_VERSION}"
|
|
47
|
+
|
|
48
|
+
if [ "$PR_VERSION" = "$MAIN_VERSION" ]; then
|
|
49
|
+
echo "::error::PR version (${PR_VERSION}) must be higher than main branch version (${MAIN_VERSION}). Please bump the version in package.json."
|
|
50
|
+
exit 1
|
|
51
|
+
fi
|
|
52
|
+
|
|
53
|
+
HIGHER=$(printf '%s\n%s' "$PR_VERSION" "$MAIN_VERSION" | sort -V | tail -n1)
|
|
54
|
+
if [ "$HIGHER" != "$PR_VERSION" ]; then
|
|
55
|
+
echo "::error::PR version (${PR_VERSION}) is lower than main branch version (${MAIN_VERSION}). Please bump the version in package.json."
|
|
56
|
+
exit 1
|
|
57
|
+
fi
|
|
58
|
+
|
|
59
|
+
echo "Version check passed: ${PR_VERSION} > ${MAIN_VERSION}"
|
|
60
|
+
|
|
32
61
|
node-build:
|
|
62
|
+
needs: version-check
|
|
33
63
|
if: inputs.node_app_directories != ''
|
|
34
64
|
strategy:
|
|
35
65
|
matrix:
|
|
@@ -106,6 +136,7 @@ jobs:
|
|
|
106
136
|
fi
|
|
107
137
|
|
|
108
138
|
docker-build:
|
|
139
|
+
needs: version-check
|
|
109
140
|
runs-on: ubuntu-latest
|
|
110
141
|
steps:
|
|
111
142
|
- uses: actions/checkout@v7
|
package/AGENTS.md
CHANGED
|
@@ -54,6 +54,7 @@ src/
|
|
|
54
54
|
- **SQLite-first SQL**: Write SQL with `?` placeholders. The `DbUtils` facade and `DbUtilsNoTelemetry` module auto-convert to `$1, $2, ...` for Postgres via `convertToPostgresPlaceholders()`.
|
|
55
55
|
- **Migration convention**: SQL files named `init-NNNN.sql`. `init-0000.sql` must create the `metadata` table. Subsequent files are applied in lexicographic order; applied versions are tracked in `metadata` for idempotency.
|
|
56
56
|
- **Immutable Docker builds**: images are built once on the pull request (`reusable-pr-verify.yml` pushes `beta-pr-<PR number>` and `beta`) and promoted unchanged on merge (`reusable-merge-build.yml` writes `<version>`, `<major>`, `<minor>` and `latest`). The merge workflow runs no build, lint or test: it resolves the merged PR, carbon-copies the manifest with `docker buildx imagetools create --prefer-index=false`, then reads back each published digest and fails on a mismatch. It falls back to a full build only when no `beta-pr-<PR number>` image can be promoted. Image name and version come from the root `package.json`, and PR branches must be up to date with the default branch before merging.
|
|
57
|
+
- **Reusable workflow permissions ceiling**: `reusable-merge-build.yml` may request only `contents: read`. A called workflow cannot exceed its caller's permissions, and callers use the default read-only token whose ceiling is `contents: read, pull-requests: none`. Adding `pull-requests: read` does not fail here — it fails in every consuming repo at startup with `Invalid workflow file ... requesting 'pull-requests: read', but is only allowed 'pull-requests: none'`. `GET /repos/{owner}/{repo}/commits/{sha}/pulls` works with `contents: read`, so no extra scope is needed.
|
|
57
58
|
|
|
58
59
|
## Build and Verification
|
|
59
60
|
|
package/README.md
CHANGED
|
@@ -539,16 +539,31 @@ Docker images are built **once**, on the pull request, and **promoted** on merge
|
|
|
539
539
|
|
|
540
540
|
On merge the workflow:
|
|
541
541
|
|
|
542
|
-
1. Resolves the pull request behind the commit pushed to the default branch through `GET /repos/{owner}/{repo}/commits/{sha}/pulls`, falling back to the PR number in the commit
|
|
542
|
+
1. Resolves the pull request behind the commit pushed to the default branch through `GET /repos/{owner}/{repo}/commits/{sha}/pulls`, falling back to the PR number in the commit **title** (`<title> (#123)` for squash and rebase, `Merge pull request #123` for merge commits). Only the title is parsed, so a `fixes #99` reference in the commit body cannot select the wrong pull request.
|
|
543
543
|
2. Carbon-copies the manifest with `docker buildx imagetools create --prefer-index=false`, which preserves the exact digest and the full multi-platform image index.
|
|
544
544
|
3. Reads back the digest of every tag it published and fails if one differs from the source.
|
|
545
545
|
|
|
546
|
-
|
|
546
|
+
The resolve step logs how it reached its answer, which is the first thing to check when a merge falls back to a build:
|
|
547
|
+
|
|
548
|
+
```text
|
|
549
|
+
Service: planner-llm-agent 0.2.0
|
|
550
|
+
PR: 2 (resolved via api)
|
|
551
|
+
Source: <namespace>/planner-llm-agent:beta-pr-2
|
|
552
|
+
Strategy: retag
|
|
553
|
+
```
|
|
554
|
+
|
|
555
|
+
`Strategy` is `retag` when the PR image was found and `build` when it was not.
|
|
556
|
+
|
|
557
|
+
No `npm ci`, build, lint or test runs on merge, and no Docker build happens unless promotion is impossible. The build fallback keeps releases working when a commit reaches the default branch with no matching `beta-pr-<PR number>` image, such as a direct push, a rebase merge (which leaves no PR number in the commit title), or a PR whose image was deleted from the registry.
|
|
558
|
+
|
|
559
|
+
> **Callers need no `permissions:` block.** `reusable-merge-build` requests only `contents: read`. A reusable workflow cannot ask for more than its caller is allowed, and the default read-only token ceiling is `contents: read, pull-requests: none` — requesting `pull-requests: read` makes the caller fail to start with `Invalid workflow file … The workflow is requesting 'pull-requests: read', but is only allowed 'pull-requests: none'`. The API lookup above works with `contents: read` alone.
|
|
547
560
|
|
|
548
561
|
The image name and version come from the root `package.json`, so bump the minor version in the pull request that carries the change.
|
|
549
562
|
|
|
550
563
|
> **Keep PR branches up to date with the default branch before merging.** The promoted image is the one built from the PR head, so if the default branch moved in the meantime the released artifact will not contain those commits.
|
|
551
564
|
|
|
565
|
+
Validated end to end on `planner-llm-agent` 0.2.0: the pull request pushed `beta-pr-2` and `beta`, the merge promoted that image in **40 seconds** with no build, and `beta-pr-2`, `beta`, `0.2.0`, `0.2`, `0` and `latest` all resolve to the single digest `sha256:710fed19…` with identical per-architecture digests.
|
|
566
|
+
|
|
552
567
|
### Adopting in Your Project
|
|
553
568
|
|
|
554
569
|
Create a caller workflow in `.github/workflows/main-build.yml`:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@devopsplaybook.io/common-utils",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.8.0-beta.17.1c8c436",
|
|
4
4
|
"description": "Shared utility modules for devopsplaybook.io projects (DB, Config, OTel context, auth/users, notifications, LLM, system helpers)",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"Open Telemetry",
|