Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 20 additions & 15 deletions .claude/skills/view-chain-layout/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
name: view-chain-layout
description: The layout rules for a z2ui5_cl_ui5_view_builder chain - one call per line, four spaces per tree level, the end( ) column, which factory( ) shape goes with which chain shape, blank lines, and the chain-format gate that checks them. Identical in abap2UI5, abap2UI5/samples and abap2UI5/samples-controls. Use when writing, reviewing or reformatting any view built with the builder, when a chain has drifted, and when chain-format, chain-indentation or chain-element-per-line fails.
description: The layout rules for a z2ui5_cl_ui5_view_builder chain - one call per line, four spaces per tree level, the end( ) column, which factory( ) shape goes with which chain shape, blank lines, and the linter rule that checks them. Identical in abap2UI5, abap2UI5/samples, abap2UI5/samples-controls and abap2UI5/samples-stack. Use when writing, reviewing or reformatting any view built with the builder, when a chain has drifted, and when chain-house-layout fails.
---

# The layout of a view-builder chain
Expand Down Expand Up @@ -122,23 +122,28 @@ not emitted at all until a config asks for it, because it encodes one house
style — this one — and because its fixes span a whole chain, which would defer
any other rule's fix inside the same chain to a second `--fix` pass.

How to run it depends on the repository, and they are not yet the same:
All four repositories run the rule itself. `scripts/chain-format.mjs`, which
used to be the same algorithm written a second time, is gone:

| | how |
|---|---|
| `abap2UI5` | `npm run check:abap2ui5` / `npm run fmt:chains` — the linter, enabled in `abap2ui5lint.jsonc` |
| `abap2UI5/samples` | `npm run check:chains` / `npm run fmt:chains` — still `scripts/chain-format.mjs` |
| `abap2UI5/samples-controls` | `npm run check:chains` / `npm run fmt:chains` — the same script; also the first step of `npm run gates` |

`scripts/chain-format.mjs` is the same algorithm as the rule, kept
byte-identical in the two sample repositories. **It is meant to go away**: both
repos pin the linter by SHA at a version that predates the rule, and bumping
that pin also moves them onto the linter's new packaging, where the UI5 render
runtime is a separate optional peer (`@abap2ui5/render-runtime`) rather than an
optional dependency. Until that package resolves for them, bumping would take
their render gate — 172 and 416 documents — down with it. So the script stays
until the pin can move, and then it is deleted in the same change that adds
`"chain-house-layout": "warning"` to their configs.
| `abap2UI5` | `npm run check:abap2ui5` / `npm run fmt:chains` — enabled in `abap2ui5lint.jsonc` |
| `abap2UI5/samples` | `npm run check:abap2ui5` / `npm run fmt:chains` — enabled in `abap2ui5lint.jsonc` |
| `abap2UI5/samples-stack` | `npm run check:abap2ui5` / `npm run fmt:chains` — enabled in `abap2ui5lint.jsonc` |
| `abap2UI5/samples-controls` | `npm run check:chains` / `npm run fmt:chains` — `abap2ui5lint-chains.jsonc`; also the first step of `npm run gates` |

samples-controls needs a config of its own because its corpus gate
(`view-gates.mjs`) only sees files that have a meta sidecar, while the layout
is a property of the source and has to cover the whole tree — including the
fourteen hand-written `src/03` classes and the generated overview app. That
config switches the property-gate rules off, because view-gates judges those
against the sidecars and a second opinion here would only be a worse one.

Two things that config must NOT do, both measured rather than assumed:
`properties: false` looks like the obvious way to isolate the layout and takes
the chain rules down with it — the check then passes everything, silently. And
`chain-house-layout` is opt-in, so a `rules` entry is what turns it on; without
one the run is green no matter how mangled the chain.

Nothing else covers this, which is why the rule exists:

Expand Down
96 changes: 96 additions & 0 deletions .github/workflows/bump-framework-pin.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
name: bump-framework-pin

# The abaplint configs here pin abap2UI5 to a RELEASE tag, which is what makes
# this repository lint against the framework its readers actually installed.
# A pin nobody moves is a different defect from a pin nobody set, though: the
# samples would keep proving themselves against an ever-older release while the
# framework moves on, and the first person to notice would be a reader whose
# new install behaves differently from the corpus.
#
# So: weekly, resolve the newest release tag, move every config that carries
# one (check-framework-pin --set knows which those are - the 702 config keeps
# its branch), and PROVE the new pin by running the abaplint gates before the
# pull request exists. A framework release that breaks a sample fails this
# workflow instead of landing on main.
#
# A release is published twice - `X.Y.Z` and, minutes later, `X.Y.Z-702` for
# the downported distribution. Both are the same version of the framework and
# the tag list is sorted by version, so the -702 suffix is stripped and the
# highest remaining x.y.z wins.

on:
schedule:
- cron: '17 5 * * 2' # Tuesdays 05:17 UTC — clear of the Monday linter bumps
workflow_dispatch:

permissions:
contents: read

jobs:
bump-framework-pin:
runs-on: ubuntu-latest
timeout-minutes: 30
permissions:
contents: write
pull-requests: write
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: '22'

- name: Resolve the newest abap2UI5 release and move the pin
id: pin
run: |
set -euo pipefail
# --exit-code: `git ls-remote` prints nothing and still exits 0 when no
# ref matches, so a repository that stopped tagging releases would
# resolve to an EMPTY version and this step would happily write it.
TAG=$(git ls-remote --exit-code --tags --refs https://github.com/abap2UI5/abap2UI5 \
| sed 's#.*refs/tags/##' \
| sed 's/-702$//' \
| grep -E '^[0-9]+\.[0-9]+\.[0-9]+$' \
| sort -V | tail -1)
test -n "$TAG"
echo "tag=$TAG" >> "$GITHUB_OUTPUT"
BEFORE=$(git diff --quiet && echo clean || echo dirty)
node scripts/check-framework-pin.mjs --set "$TAG"
if git diff --quiet; then
echo "changed=false" >> "$GITHUB_OUTPUT"
echo "already on $TAG ($BEFORE before)"
else
echo "changed=true" >> "$GITHUB_OUTPUT"
fi

- if: steps.pin.outputs.changed == 'true'
run: node scripts/check-framework-pin.mjs

# The proof. A release that dropped or changed API a sample uses fails
# HERE, where the diff is one line and the cause is obvious, instead of on
# somebody's unrelated pull request.
- if: steps.pin.outputs.changed == 'true'
run: npm ci
- if: steps.pin.outputs.changed == 'true'
name: Lint the corpus against the new release
run: |
set -euo pipefail
npx abaplint abaplint.jsonc

- if: steps.pin.outputs.changed == 'true'
uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1
with:
branch: bump-framework-pin
title: 'chore: lint against abap2UI5 ${{ steps.pin.outputs.tag }}'
commit-message: 'chore: pin the abaplint configs to abap2UI5 ${{ steps.pin.outputs.tag }}'
body: |
Weekly move of the framework pin in the abaplint configs to the newest
published release, `${{ steps.pin.outputs.tag }}`.

The abaplint gates ran against the new release in the workflow that
opened this pull request, so a red check here means the release
changed something a sample depends on — worth reading before merging,
because a reader on that release hits the same thing.
add-paths: |
abaplint.jsonc
.github/abaplint
delete-branch: true
45 changes: 45 additions & 0 deletions .github/workflows/check-framework-pin.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
name: check-framework-pin

# The abaplint configs here resolve abap2UI5 as a git dependency, and abaplint
# clones the DEFAULT branch unless a `"branch"` key says otherwise. So these
# samples used to be linted against the framework's development tip while every
# reader of them has a RELEASE installed - a gap that hides two defects, both
# silent:
#
# a sample uses API that exists only on main, this repository stays green,
# and the reader who copies it gets a syntax error. That is not theoretical:
# z2ui5_cl_ui5_view_builder was on main from 2026-08-12 and in no release
# until 1.143.0, three weeks later.
#
# the framework changes something on main and the abaplint workflows here go
# red overnight, with no commit in this repository to explain it.
#
# The configs now name a release tag, and this gate is what keeps them naming
# ONE - a bump has to move every config, and a forgotten file cannot quietly
# lint against a different framework than its neighbours.
#
# Plain node, no dependencies, so it stays a few seconds and can run before any
# install - which is also why `npm run check` puts it first.

on:
pull_request:
push:
branches: [main]

permissions:
contents: read

concurrency:
group: check-framework-pin-${{ github.ref }}
cancel-in-progress: true

jobs:
check-framework-pin:
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: '22'
- run: node scripts/check-framework-pin.mjs
115 changes: 115 additions & 0 deletions .github/workflows/sync-shared.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
name: sync-shared

# The files this repository carries a COPY of, pulled from the repository that
# owns them.
#
# abap2UI5 declares itself the source of everything that exists byte-identical
# in several repositories here - the view-chain-layout skill, the shared gate
# scripts - and its `check:shared` notices when a copy stops matching. Noticing
# was as far as it went: the fix lived in another repository, so a change to a
# shared script meant somebody remembering to carry it into three others by
# hand, and the gate went red in the meantime in repositories that had done
# nothing wrong. The copies had already drifted that way once - chain-format
# differed between two of them while a skill in a third still called them
# byte-identical.
#
# So the copies are pulled rather than pushed. No cross-repository credentials
# exist anywhere in this: the repository updates its own files with its own
# token, which is the same shape as every other bump workflow in this
# organisation. The manifest comes from the source repository, so which files
# are shared is still decided in exactly one place.
#
# Section-shared content is deliberately NOT synced: the app rule block inside
# this repository's own abaplint.jsonc, the AGENTS.md sections. Those live
# inside files this repository owns the rest of, and copying a source over them
# would destroy what it owns. `check:shared` still reports them.

on:
schedule:
- cron: '47 5 * * 2' # Tuesdays 05:47 UTC, after the framework pin bump
workflow_dispatch:

permissions:
contents: read

jobs:
sync:
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
contents: write
pull-requests: write
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

- name: Check out the source repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: abap2UI5/abap2UI5
ref: main
path: .abap2UI5
sparse-checkout: |
.claude
.github
docs

- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: '22'

- name: Copy every shared file this repository carries
id: sync
run: |
set -euo pipefail
GATE=.abap2UI5/.github/scripts/shared-file-gate.mjs
test -f "$GATE" || {
echo "::error::$GATE is missing — the source repository moved the gate."
echo "::error::Fix the path here; a silent skip would let the copies drift again."
exit 1
}
# The consumer name is this repository's own, taken from
# GITHUB_REPOSITORY rather than from the event payload: this workflow
# only ever runs on `schedule` and `workflow_dispatch`, and a scheduled
# run carries no repository object to read a name off.
NAME="${GITHUB_REPOSITORY#*/}"
# Tab-separated: source path in abap2UI5, path in this repository.
node "$GATE" --manifest "$NAME" > manifest.txt
test -s manifest.txt || {
echo "::error::the manifest is empty for $NAME —"
echo "::error::either this repository is no longer a consumer, or its name changed."
exit 1
}
while IFS=$'\t' read -r src dst; do
[ -n "$src" ] || continue
test -f ".abap2UI5/$src" || { echo "::error::.abap2UI5/$src does not exist"; exit 1; }
mkdir -p "$(dirname "$dst")"
cp ".abap2UI5/$src" "$dst"
echo " $src -> $dst"
done < manifest.txt
rm -f manifest.txt
if git diff --quiet; then
echo "changed=false" >> "$GITHUB_OUTPUT"
echo "every copy already matches its source"
else
echo "changed=true" >> "$GITHUB_OUTPUT"
git diff --stat
fi

- if: steps.sync.outputs.changed == 'true'
uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1
with:
branch: sync-shared
title: 'chore: pull the shared files from abap2UI5'
commit-message: 'chore: sync the shared files from abap2UI5/abap2UI5'
body: |
A file this repository carries a copy of moved in
[abap2UI5/abap2UI5](https://github.com/abap2UI5/abap2UI5), which owns
it. This pull request carries the change across.

The copies are byte-equal by design and `check:shared` in the source
repository is what notices when they stop being — so this is the fix
for a gate that would otherwise be red there while nothing was wrong
here. Read the diff as you would any other change to these files: if
it looks wrong, the argument belongs in the source repository, not in
a local edit that will be pulled over next week.
delete-branch: true
1 change: 1 addition & 0 deletions abaplint.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
},
{
"url": "https://github.com/abap2UI5/abap2UI5",
"branch": "1.143.0",
"folder": "/abap2UI5",
"files": "/src/**/*.*"
}
Expand Down
Loading