From 13007fe6d75dc095a205c6cc0d86c66ff0aff644 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 4 Nov 2026 15:33:42 +0000 Subject: [PATCH] Regenerate the API and controls references daily, as a pull request check:api-reference and check:controls fail as soon as z2ui5_if_client, the user exit or a custom control changes on abap2UI5 main - and deploy.yml runs them, so the site stopped publishing from 2026-11-03 until the reference was regenerated by hand. regenerate-references.yaml runs generate:api and generate:controls every day before the deploy, checks the result, and opens (or updates) one pull request, starting check.yml on it - the move bump-linter.yaml makes for the linter. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_014YZFmFmt8CUMDLwxTqptCi --- .github/workflows/regenerate-references.yaml | 90 ++++++++++++++++++++ AGENTS.md | 2 +- 2 files changed, 91 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/regenerate-references.yaml diff --git a/.github/workflows/regenerate-references.yaml b/.github/workflows/regenerate-references.yaml new file mode 100644 index 00000000..2c25d14a --- /dev/null +++ b/.github/workflows/regenerate-references.yaml @@ -0,0 +1,90 @@ +# The client API reference (resources/api.md, public/api/client-api.json, the +# user-exit block) and the custom-control reference (resources/controls.md) +# are generated from abap2UI5 main. check:api-reference and check:controls +# fail the moment an interface or a control changes over there - and with +# them every deploy, because deploy.yml runs the same gates: from 2026-11-03 +# to 2026-11-04 the site was not published at all, because z2ui5_if_client +# had gained _bind_tab and _bind_tab_row and nobody had run +# `npm run generate:api` here. +# +# So, every day before the deploy (05:00): regenerate both, and when that +# changes anything, open (or update) one pull request with the result - the +# same move bump-linter.yaml makes for the linter. +name: regenerate-references + +on: + schedule: + - cron: '23 4 * * *' # daily 04:23 UTC - before deploy.yml at 05:00 + workflow_dispatch: + +permissions: + contents: write + pull-requests: write + # To start check.yml on the PR's branch (below). + actions: write + +concurrency: + group: regenerate-references + cancel-in-progress: false + +jobs: + regenerate: + runs-on: ubuntu-latest + timeout-minutes: 20 + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0 + with: + node-version: '22' + cache: npm + - run: npm ci + + - name: Regenerate the references from abap2UI5 main + id: gen + env: + GITHUB_TOKEN: ${{ github.token }} + run: | + set -euo pipefail + npm run generate:api + npm run generate:controls + if git diff --quiet; then + echo "changed=false" >> "$GITHUB_OUTPUT" + else + echo "changed=true" >> "$GITHUB_OUTPUT" + git diff --stat + fi + + # The gates that read what was just written: both freshness checks (they + # must hold now), the prose the generated blocks add, and the build. + - if: steps.gen.outputs.changed == 'true' + run: npm run check:api-reference + - if: steps.gen.outputs.changed == 'true' + env: + GITHUB_TOKEN: ${{ github.token }} + run: npm run check:controls + - if: steps.gen.outputs.changed == 'true' + run: npm run check:vocabulary + - if: steps.gen.outputs.changed == 'true' + run: npm run docs:build + + - if: steps.gen.outputs.changed == 'true' + id: pr + uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1 + with: + branch: regenerate-references + title: 'chore: regenerate the API and controls references' + commit-message: 'chore: regenerate the API and controls references from abap2UI5 main' + body: | + `z2ui5_if_client`, the user exit or a custom control changed on + abap2UI5 `main`; `npm run generate:api` and + `npm run generate:controls` rewrote the generated references. + Until this is merged, check:api-reference / check:controls fail + and deploy.yml does not publish the site. + + # A push or a PR made with the workflow's own GITHUB_TOKEN starts no + # workflow runs - GitHub's rule against loops - so check.yml never ran on + # this PR. workflow_dispatch is exempt. + - if: steps.pr.outputs.pull-request-operation == 'created' || steps.pr.outputs.pull-request-operation == 'updated' + run: gh workflow run check.yml --ref regenerate-references + env: + GH_TOKEN: ${{ github.token }} diff --git a/AGENTS.md b/AGENTS.md index 52347b9c..42e9418d 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -57,7 +57,7 @@ in it are decidable, and all fifteen are decided before a merge: | `docs:build` | a page that does not build is a page nobody can read | | `check:examples` | the ABAP in the fenced blocks, against the real framework: does it compile, and does the view it builds name controls and properties that exist on the UI5 floor this documentation targets. **It ran no abaplint rules at all until 2026-09-04** — the generated config had no `rules` block, so abaplint walked 139 files, ran nothing, and printed `0 issue(s) found` for years. Ten examples with an unbalanced parenthesis were sitting behind that. The three compile rules it runs now (`parser_error`, `check_syntax`, `unknown_types`) are the sample repositories' own; the reasoning for stopping there, measured against the full 188-rule set, is in the file. A fragment cannot be compiled, so every fence is also read for the two things decidable without a system: a call to a method of the frozen builder, and a `|…|` string template whose literal text runs over a line break (only a `{ … }` may) - nine binding strings were written that way and none compiled | | `check:api-names` | every `client->` name on the site — method, parameter, `cs_*` constant — against `z2ui5_if_client` on `main`, plus every `blob/main/` link into the framework's tree, plus **no name from the frozen package** (`src/99`: `z2ui5_cl_util*`, `z2ui5_cl_pop_*`, `z2ui5_cl_xml_view*`, `z2ui5_if_exit`, `z2ui5_if_types`, …) anywhere but on the deprecations page and in the changelog. `check:examples` compiles the fenced blocks that are whole CLASSES; this is the rest of the page: the sentence, the two-line snippet, the constant block a page reproduces, the source link. Four pages taught API that 1.143.0 had deleted and nothing was red | -| `check:api-reference` | the committed client API reference — the generated block in `resources/api.md` and `docs/public/api/client-api.json` — regenerated from `z2ui5_if_client` on `main` and compared byte for byte. Goes stale whenever the interface changes over there and the committed reference still describes the shape before it. `npm run generate:api` rewrites both. The same run renders the user exit — `z2ui5_if_ui5_exit` plus the defaults `z2ui5_cl_ui5_user_exit` seeds, read out of the class, not retyped (`scripts/lib/user-exit.mjs`, pinned by `test/user-exit.test.mjs`) — into the block between the same `api:` markers on `advanced/extensibility/user_exits.md`, and `--check` holds that block too. Three artifacts, not two | +| `check:api-reference` | the committed client API reference — the generated block in `resources/api.md` and `docs/public/api/client-api.json` — regenerated from `z2ui5_if_client` on `main` and compared byte for byte. Goes stale whenever the interface changes over there and the committed reference still describes the shape before it. `npm run generate:api` rewrites both; `regenerate-references.yaml` runs it (and `generate:controls`) every day before the deploy and opens a pull request when anything moved. The same run renders the user exit — `z2ui5_if_ui5_exit` plus the defaults `z2ui5_cl_ui5_user_exit` seeds, read out of the class, not retyped (`scripts/lib/user-exit.mjs`, pinned by `test/user-exit.test.mjs`) — into the block between the same `api:` markers on `advanced/extensibility/user_exits.md`, and `--check` holds that block too. Three artifacts, not two | | `check:controls` | the committed custom-control reference — the generated block in `resources/controls.md` — regenerated from `app/webapp/cc/*.js` on `main` and compared byte for byte. The metadata (properties with type and default, events with parameters, aggregations) is read by RUNNING each module in an empty vm with stand-in dependencies (`scripts/lib/controls.mjs` says why a regex is the wrong tool: a default is sometimes an expression, and one control builds its definition first and hands it to `extend( )` by name). Each section links the pages of this site that use the control, found by walking them — so a cookbook page that starts using one changes the block, and the gate says to run `npm run generate:controls`. Lists the directory through the GitHub contents API (with `GITHUB_TOKEN` in CI) or `git ls-tree` with `A2UI5_HOME`; unreachable means SKIPPED, never a false OK. Pinned by `test/controls.test.mjs` | | `check:audience` | every prose line of the manual against the phrases of the site for decision makers — `license fee`, `per-user fee`, `subscription`, `ROI`, `total cost`, `vendor lock`, `SLA`, `decision maker`, `IT management`, `business case` (whole words, any case, plural allowed; `scripts/lib/audience.mjs` is the list) — on every page but the four whose subject they are: `resources/license.md`, `resources/support.md`, `resources/sponsor.md` and `get_started/about.md` (the hand-off sentence). **The manual is for developers and links https://abap2ui5.github.io/why/**; both AGENTS.md files said so and nothing held it, and the business case had grown back on a dozen pages (two whole essays, the comparison tables of two more, the openings of Performance and Security). A page that argues the case is a second, unmaintained copy of that site. The prose is `scripts/lib/prose.mjs`'s, so a fence, a link target or a comment is never a hit; plain "per user" is not on the list because a work process per user is a technical fact. Floor: exit 1 when no page was walked. Pinned by `test/audience.test.mjs` | | `check:conventions` | the fenced ABAP against the house style the reader meets next: the view-chain layout, and the three section blocks of an app class. `check:examples` asks whether an example compiles and names real API — both questions about the framework; neither can see that a snippet is written in a different style from every sample. Measured against [samples-controls](https://github.com/abap2UI5/samples-controls) (637 classes, gated, at zero): five chains here showed the reader a different tree than the one that renders, and 57 of 86 app classes carried neither `PROTECTED SECTION.` nor `PRIVATE SECTION.`. What this gate deliberately does NOT take over is the blank-line and `t_arg` continuation rules — those are pattern-lint *warnings* over there and that corpus carries 382 of them. And, third, the lifecycle: in every runnable app class (the Run button's own decision, so this and `check:playground` never disagree) a `client->view_display( )` in `main( )` must sit under an `IF`/`ELSEIF`/`WHEN` on `client->check_on_init( )` or `check_on_navigated( )` — a nested IF inherits the branch; `ELSE`, an event `WHEN` and a negated check (`= abap_false`, `NOT`) do not. Only `main( )` is read: the canonical shape calls a `view_display( )` method of the class from the branch, and following that call would be a compiler. Twenty cookbook classes sent their view on every roundtrip, the one thing Life Cycle, Common Failures and the tutorial tell the reader not to do, and nothing was red. A page that means the exception says so directly above the fence, `` (Step 2 of the tutorial, the first screen before events exist), and a marker whose example no longer needs it fails as loudly as a missing one. `scripts/lib/lifecycle.mjs`, pinned by `test/lifecycle.test.mjs` |