From a87e667b4850228a26147d04631d60502dd1623b Mon Sep 17 00:00:00 2001 From: Thomas Cederholm Date: Thu, 2 Jul 2026 15:45:23 +0200 Subject: [PATCH 1/3] feat: add fiscal composite actions Move three fiscal CI composite actions here from experience-fiscal-common/.github/shared-workflows: fiscal-api-country-filtering, spanner-pgadapter-liquibase, spanner-pgadapter-execute-sql. Consumed via extenda/shared-workflows/composite-actions/fiscal/@v0. --- .../fiscal-api-country-filtering/README.md | 74 +++++++++++++++ .../fiscal-api-country-filtering/action.yaml | 62 +++++++++++++ .../decorators/country-filter.js | 85 ++++++++++++++++++ .../fiscal-api-country-filtering/redocly.yaml | 8 ++ .../spanner-pgadapter-execute-sql/action.yaml | 84 +++++++++++++++++ .../spanner-pgadapter-liquibase/README.md | 40 +++++++++ .../spanner-pgadapter-liquibase/action.yaml | 89 +++++++++++++++++++ 7 files changed, 442 insertions(+) create mode 100644 composite-actions/fiscal/fiscal-api-country-filtering/README.md create mode 100644 composite-actions/fiscal/fiscal-api-country-filtering/action.yaml create mode 100644 composite-actions/fiscal/fiscal-api-country-filtering/decorators/country-filter.js create mode 100644 composite-actions/fiscal/fiscal-api-country-filtering/redocly.yaml create mode 100644 composite-actions/fiscal/spanner-pgadapter-execute-sql/action.yaml create mode 100644 composite-actions/fiscal/spanner-pgadapter-liquibase/README.md create mode 100644 composite-actions/fiscal/spanner-pgadapter-liquibase/action.yaml diff --git a/composite-actions/fiscal/fiscal-api-country-filtering/README.md b/composite-actions/fiscal/fiscal-api-country-filtering/README.md new file mode 100644 index 00000000..24e17311 --- /dev/null +++ b/composite-actions/fiscal/fiscal-api-country-filtering/README.md @@ -0,0 +1,74 @@ +# fiscal-api-country-filtering + +Replaces the fiscal-engine's shared `openapi.yaml`, **in place**, with a country-scoped bundle +that strips every other country's `x-country`-tagged documentation via a Redocly decorator, then +re-runs `jar:jar` so the already-built jar picks up the change. The engine ships one canonical +spec with a `Fiscal-Attributes` `oneOf` — one branch per country's response `attributes` shape +(`src/main/resources/schemas/v1/openapi.yaml` in `hiiretail-fiscal-engine`). Because the engine +serves whatever classpath resource ends up at `/schemas/v1/openapi.yaml` verbatim, overwriting it +with the filtered content is enough to make e.g. Belgium's live service never document Portugal's +attribute keys — **no engine code change needed**. + +This is a repo-local composite action +(`./.github/shared-workflows/fiscal-api-country-filtering`) in `experience-fiscal-common`, shared +across all country repos that fiscalize via `fiscal-engine`. + +## Usage + +Run this any time **after** a normal `mvn package` — it re-seals the jar for you, so callers don't +need to reorder their own build around it. (A full `mvn package` re-runs `resources:resources` +internally and would clobber a filtered file if this action ran *before* packaging, which is why +the re-jar happens inside the action rather than being left to every caller to get right.) It does +no checkout of its own (an `actions/checkout` step here would run `git clean -ffdx` by default and +wipe `target/`), so it must run later in the same job as the checkout and package steps, not a +separate job. + +```yaml +- run: ./mvnw package -DskipTests +- uses: extenda/experience-fiscal-common/.github/shared-workflows/fiscal-api-country-filtering@master + with: + country: belgium +- run: docker build -t $IMAGE . # picks up the re-jarred, Belgium-only target/*.jar +``` + +## Inputs + +| Input | Required | Default | Description | +|-------|----------|---------|-------------| +| `country` | yes | – | The lower-case api name from this action's `redocly.yaml` `apis:` map, e.g. `belgium`. | +| `schema-dir` | no | `target/classes/schemas/v1` | Directory (relative to the workspace root) containing the built `openapi.yaml`. Overwritten in place. | + +## Adding a country + +One identifier throughout: the lower-case country name used as the `apis:` key (e.g. `belgium`) +is reused verbatim as both the decorator's `country` option and the `x-country` tag value — no +separate short code to keep in sync. + +1. In the engine's `openapi.yaml`, add a `Fiscal-Attributes-` branch to the + `Fiscal-Attributes` `oneOf`, tagged `x-country: ` (lower-case, e.g. `x-country: portugal`). +2. In this action's `redocly.yaml`, add a sibling `apis:` entry named `` and point the + decorator's `country` option at that same value. + +## Notes / validation status + +- **Decorators only run during `bundle`, not `lint`** — linting the pre-bundle source wouldn't + exercise the country-filtering transformation, so this action lints the *bundled* output instead. +- `redocly.yaml` and `decorators/` live in this action's own repo, not the caller's checkout — + `docker run -v $PWD:/spec` only mounts the caller's workspace, so they're staged (`cp`) into + `schema-dir` via `${{ github.action_path }}` before bundling. The staging step clears any + pre-existing `redocly.yaml`/`decorators/` at the destination first, so re-runs on a reused + workspace can't leave a stale or nested `decorators/decorators/` copy behind. +- Bundling reads and writes `openapi.yaml` at the same path — safe because the bundler loads the + whole document graph into memory before it writes any output. +- A single surviving `oneOf` branch (e.g. Belgium today, with only one real country onboarded) is + collapsed by the decorator into the wrapper schema directly, rather than left as a `oneOf` of one. +- `jar:jar` only re-zips already-compiled `target/classes` — no recompile, no test run — so it's + cheap to run as a dedicated step after packaging. +- **Not yet validated against a real country pipeline in CI** — verified locally by manually + running every step's command (staging, in-place bundle, lint, and `jar:jar`) against + `hiiretail-fiscal-engine`'s own freshly-built jar: confirmed the jar shipped both + `Fiscal-Attributes-BE` and `Fiscal-Attributes-EU` before, and only the collapsed Belgium schema + after. Confirm on a first real run in an actual country pipeline before relying on it for prod. +- `hiiretail-fiscal-engine` currently also keeps its own local copy of `redocly.yaml`/ + `decorators/country-filter.js` for local dev/testing — decide whether that should be removed now + that this is the canonical CI-facing copy, or kept in sync by hand. diff --git a/composite-actions/fiscal/fiscal-api-country-filtering/action.yaml b/composite-actions/fiscal/fiscal-api-country-filtering/action.yaml new file mode 100644 index 00000000..5cdd997c --- /dev/null +++ b/composite-actions/fiscal/fiscal-api-country-filtering/action.yaml @@ -0,0 +1,62 @@ +name: 'Fiscal API country filtering' +description: >- + Replaces the fiscal-engine's shared openapi.yaml, IN PLACE, with a country-scoped bundle that + strips every other country's `x-country`-tagged documentation via a Redocly decorator (see the + engine's `Fiscal-Attributes` oneOf, src/main/resources/schemas/v1/openapi.yaml), then re-runs + `jar:jar` so the already-built jar picks up the change. The engine serves whatever classpath + resource ends up at /schemas/v1/openapi.yaml verbatim — no engine code change needed. Run it any + time after a normal `mvn package` (a full `package` re-runs `resources:resources` and would + clobber a filtered file, which is why the re-jar happens here instead of asking every caller to + order steps around that). Does no checkout of its own (an `actions/checkout` step here would run + `git clean -ffdx` by default and wipe `target/`). + +inputs: + country: + required: true + description: The lower-case api name from this action's redocly.yaml `apis:` map, e.g. 'belgium'. + schema-dir: + required: false + description: Directory (relative to the workspace root) containing the built openapi.yaml. + default: 'target/classes/schemas/v1' + +runs: + using: composite + steps: + # redocly/cli is pinned by digest below for immutability. Dependabot's docker ecosystem only + # scans Dockerfile/docker-compose.yml `FROM`/`image:` fields, not `docker run` invocations + # inside a composite action's shell script — this is NOT auto-updated. Check + # https://hub.docker.com/r/redocly/cli/tags periodically and bump both occurrences by hand. + + # redocly.yaml and decorators/ live in THIS action's own repo (experience-fiscal-common), not + # the caller's checkout — `docker run -v $PWD:/spec` only mounts the caller's workspace, so + # they have to be staged alongside the spec before redocly can see them. + - name: Stage the country-filter config next to the built spec + shell: bash + run: | + rm -rf "${{ inputs.schema-dir }}/redocly.yaml" "${{ inputs.schema-dir }}/decorators" + cp "${{ github.action_path }}/redocly.yaml" "${{ inputs.schema-dir }}/redocly.yaml" + cp -r "${{ github.action_path }}/decorators" "${{ inputs.schema-dir }}/decorators" + + # Bundles openapi.yaml and overwrites it in place with the country-filtered result — the + # bundler reads the whole document graph before it writes any output, so reading and writing + # the same path is safe. + - name: Bundle the ${{ inputs.country }} spec in place + shell: bash + run: | + docker run --rm -v "$PWD/${{ inputs.schema-dir }}:/spec" redocly/cli:2.35.1@sha256:404c57d791f2a9d08973bb568da2e070bbc7bb96c94248eba38493537b78abb5 \ + bundle ${{ inputs.country }} --config redocly.yaml -o openapi.yaml + + # Decorators only run during `bundle`, not `lint` — linting the pre-bundle source wouldn't + # exercise the country-filtering transformation at all, so lint the bundled (now in-place) + # result instead. + - name: Lint the bundled ${{ inputs.country }} spec + shell: bash + run: | + docker run --rm -v "$PWD/${{ inputs.schema-dir }}:/spec" redocly/cli:2.35.1@sha256:404c57d791f2a9d08973bb568da2e070bbc7bb96c94248eba38493537b78abb5 lint openapi.yaml + + # target/classes/schemas/v1/openapi.yaml was already zipped into the jar by the caller's own + # `mvn package`. jar:jar just re-zips target/classes — no recompile, no test run — so the jar + # on disk picks up the file we just overwrote. + - name: Re-jar with the filtered spec + shell: bash + run: ./mvnw jar:jar diff --git a/composite-actions/fiscal/fiscal-api-country-filtering/decorators/country-filter.js b/composite-actions/fiscal/fiscal-api-country-filtering/decorators/country-filter.js new file mode 100644 index 00000000..4485aaed --- /dev/null +++ b/composite-actions/fiscal/fiscal-api-country-filtering/decorators/country-filter.js @@ -0,0 +1,85 @@ +// For any component schema with a `oneOf` whose branches are tagged `x-country` on the branch +// component itself, keeps only the branch matching the configured `country` and deletes now- +// unreferenced x-country-tagged components. Modeled on Redocly's built-in `remove-x-internal` +// decorator (walk components.schemas, delete tagged nodes). +function StripOtherCountries({ country }) { + if (!country) { + throw new Error('country-filter/strip-other-countries requires a `country` option'); + } + return { + Root: { + leave(root) { + const schemas = root?.components?.schemas; + if (!schemas) return; + + for (const [schemaName, schema] of Object.entries(schemas)) { + if (!Array.isArray(schema?.oneOf)) continue; + + const before = schema.oneOf; + schema.oneOf = before.filter((branch) => { + const branchName = refName(branch?.$ref); + const branchCountry = branchName && schemas[branchName]?.['x-country']; + return !branchCountry || branchCountry === country; + }); + + for (const branch of before) { + if (!schema.oneOf.includes(branch)) { + console.log( + `country-filter: removed '${refName(branch?.$ref)}' from '${schemaName}.oneOf' (x-country != ${country})` + ); + } + } + + if (schema.oneOf.length === 0) { + throw new Error( + `country-filter: no branch of '${schemaName}' tagged x-country: ${country} — add ` + + `one before configuring redocly.yaml's '${country}' apis entry.` + ); + } + + // A single surviving branch makes the oneOf wrapper pointless — collapse the branch's + // content into the wrapper itself so consumers see one plain schema, not a oneOf of one. + if (schema.oneOf.length === 1) { + const branchName = refName(schema.oneOf[0].$ref); + const branchSchema = schemas[branchName]; + delete schema.oneOf; + Object.assign(schema, branchSchema); + delete schema['x-country']; + delete schema['title']; + console.log(`country-filter: '${branchName}' is copied into '${schemaName}'`); + } + } + + for (const [schemaName, schema] of Object.entries(schemas)) { + if (schema?.['x-country'] && !isReferenced(schemas, schemaName)) { + delete schemas[schemaName]; + console.log(`country-filter: dropped unreferenced component '${schemaName}'`); + } + } + } + } + }; +} + +function refName(ref) { + return typeof ref === 'string' && ref.startsWith('#/components/schemas/') + ? ref.slice('#/components/schemas/'.length) + : undefined; +} + +// ponytail: string-scan ref check, not a real ref-graph walk — fine while component names are +// unique tokens; swap for a proper walker if a name ever collides as a substring of another ref. +function isReferenced(schemas, name) { + return JSON.stringify(schemas).includes(`#/components/schemas/${name}`); +} + +module.exports = function countryFilterPlugin() { + return { + id: 'country-filter', + decorators: { + oas3: { + 'strip-other-countries': StripOtherCountries + } + } + }; +}; diff --git a/composite-actions/fiscal/fiscal-api-country-filtering/redocly.yaml b/composite-actions/fiscal/fiscal-api-country-filtering/redocly.yaml new file mode 100644 index 00000000..4d20de68 --- /dev/null +++ b/composite-actions/fiscal/fiscal-api-country-filtering/redocly.yaml @@ -0,0 +1,8 @@ +extends: [recommended] + +plugins: ['decorators/country-filter.js'] + +apis: + belgium: + root: openapi.yaml + decorators: { country-filter/strip-other-countries: { country: belgium } } diff --git a/composite-actions/fiscal/spanner-pgadapter-execute-sql/action.yaml b/composite-actions/fiscal/spanner-pgadapter-execute-sql/action.yaml new file mode 100644 index 00000000..ec658334 --- /dev/null +++ b/composite-actions/fiscal/spanner-pgadapter-execute-sql/action.yaml @@ -0,0 +1,84 @@ +name: 'Spanner (PG-dialect) execute SQL' +description: >- + Execute caller-supplied SQL against a PostgreSQL-dialect Cloud Spanner database + by fronting it with PGAdapter and using the standard psql client. Domain-agnostic + (the SQL is an input), so it can be lifted into extenda/shared-workflows/generic/ + unchanged. A sibling of spanner-pgadapter-liquibase for ad-hoc DML/DDL. + +inputs: + service-account-key: + description: 'GCP service-account key (JSON) with access to the Spanner database.' + required: true + project: + description: 'GCP project hosting the Spanner instance.' + required: true + instance: + description: 'Spanner instance id.' + required: false + default: 'fiscal-signing' + database: + description: 'Spanner database id (must be PostgreSQL dialect).' + required: true + sql: + description: 'SQL to execute. May contain multiple semicolon-separated statements.' + required: true + pgadapter-image: + description: >- + PGAdapter image. Pinned by digest for immutability (Dependabot's docker ecosystem only + scans Dockerfile/docker-compose.yml, not action.yaml inputs, so this is NOT auto-updated — + check https://console.cloud.google.com/artifacts/docker/cloud-spanner-pg-adapter/us/gcr.io/pgadapter + periodically and bump both the tag and digest together by hand. + required: false + default: 'gcr.io/cloud-spanner-pg-adapter/pgadapter:v0.55.0@sha256:30bb42ded681effccf83f93e5f5107c1ef9e5e48513d0f7e1d30c7bfc69fa5fa' + +runs: + using: composite + steps: + # Authenticate via the trusted action. It writes the credential file, registers it for + # log masking, and exports GOOGLE_APPLICATION_CREDENTIALS — so the key value is never + # interpolated into a shell command or handled by this action directly. + - uses: extenda/actions/setup-gcloud@v0 + with: + service-account-key: ${{ inputs.service-account-key }} + + - name: Start PGAdapter + shell: bash + run: | + if [ -z "${GOOGLE_APPLICATION_CREDENTIALS:-}" ]; then + echo "GOOGLE_APPLICATION_CREDENTIALS not set; setup-gcloud must run first" >&2 + exit 1 + fi + # Mount the credential FILE (a path, not the secret value) and let PGAdapter pick it + # up via Application Default Credentials. + docker run -d --name pgadapter \ + -p 5432:5432 \ + -v "${GOOGLE_APPLICATION_CREDENTIALS}:/var/run/secrets/gcp-credentials.json:ro" \ + -e GOOGLE_APPLICATION_CREDENTIALS=/var/run/secrets/gcp-credentials.json \ + "${{ inputs.pgadapter-image }}" \ + -p "${{ inputs.project }}" -i "${{ inputs.instance }}" -x + # PGAdapter is the last thing to open the port; wait until it accepts TCP. + for i in $(seq 1 30); do + if (exec 3<>/dev/tcp/localhost/5432) 2>/dev/null; then echo "PGAdapter ready"; exit 0; fi + sleep 1 + done + echo "PGAdapter did not become ready" >&2 + docker logs pgadapter || true + exit 1 + + - name: Execute SQL + shell: bash + # SQL is passed via the environment and piped to psql's stdin, so its contents are + # never interpolated into the command line. PGAdapter authenticates to Spanner via the + # mounted key; the client-side username/password are unused. + env: + SQL: ${{ inputs.sql }} + DATABASE: ${{ inputs.database }} + run: | + printf '%s\n' "${SQL}" | psql \ + "postgresql://spanner:spanner@localhost:5432/${DATABASE}?sslmode=disable" \ + -v ON_ERROR_STOP=1 + + - name: Stop PGAdapter + if: always() + shell: bash + run: docker rm -f pgadapter || true diff --git a/composite-actions/fiscal/spanner-pgadapter-liquibase/README.md b/composite-actions/fiscal/spanner-pgadapter-liquibase/README.md new file mode 100644 index 00000000..52ec2e43 --- /dev/null +++ b/composite-actions/fiscal/spanner-pgadapter-liquibase/README.md @@ -0,0 +1,40 @@ +# spanner-pgadapter-liquibase + +Runs **standard Postgres Liquibase** against a **PostgreSQL-dialect Cloud Spanner** +database by fronting Spanner with [PGAdapter](https://github.com/GoogleCloudPlatform/pgadapter). + +It is the PG-dialect counterpart to `extenda/shared-workflows/generic/cloud-sql-liquibase` +(which targets Cloud SQL via the Cloud SQL Auth Proxy) and a deliberate alternative to +`extenda/actions/liquibase-spanner` (which speaks Spanner's **GoogleSQL** dialect). The fiscal +engine standardised on one portable Postgres SQL codepath, so its changesets are applied with +plain Postgres Liquibase here too — see engine decisions #9/#10. + +This is a repo-local composite action (`./.github/shared-workflows/spanner-pgadapter-liquibase`). It is +written to lift into `extenda/shared-workflows/generic/spanner-pgadapter-liquibase@v0` unchanged; +update the `uses:` reference when it moves. + +## Inputs + +| Input | Required | Default | Description | +|-------|----------|---------|-------------| +| `service-account-key` | yes | – | GCP SA key (JSON) with access to the Spanner database. | +| `project` | yes | – | GCP project hosting the Spanner instance. | +| `instance` | no | `fiscal-signing` | Spanner instance id. | +| `database` | yes | – | Spanner database id (**must** be PostgreSQL dialect). | +| `search-path` | yes | – | Directory containing the changelog and its includes. | +| `changelog-file` | no | `changelog-master.yaml` | Changelog file, relative to `search-path`. | +| `pgadapter-image` | no | pinned in `action.yaml` | PGAdapter image (tag+digest). | + +## Notes / validation status + +- **Credentials** are handled by `extenda/actions/setup-gcloud@v0` (same as + `cloud-sql-liquibase`): the action exports `GOOGLE_APPLICATION_CREDENTIALS`, and only that + file *path* is mounted into the PGAdapter container, which then uses Application Default + Credentials. The key value is never interpolated into a shell command or written by hand. +- Sets `spanner.ddl_transaction_mode=AutocommitExplicitTransaction` on the connection so + PGAdapter converts Liquibase's transactional DDL into Spanner DDL batches. +- The fiscal changesets ship on the `fiscal-engine` jar, so callers extract them first + (e.g. `mvn dependency:unpack-dependencies`) and pass the directory as `search-path`. +- **Not yet validated in CI against a real Spanner instance.** The PGAdapter flags and the + `liquibase-github-actions/update` input names should be confirmed on a first run before + relying on it for prod. diff --git a/composite-actions/fiscal/spanner-pgadapter-liquibase/action.yaml b/composite-actions/fiscal/spanner-pgadapter-liquibase/action.yaml new file mode 100644 index 00000000..755311e5 --- /dev/null +++ b/composite-actions/fiscal/spanner-pgadapter-liquibase/action.yaml @@ -0,0 +1,89 @@ +name: 'Spanner (PG-dialect) Liquibase migration' +description: >- + Run standard Postgres Liquibase against a PostgreSQL-dialect Cloud Spanner + database by fronting it with PGAdapter. The PG-dialect counterpart to + generic/cloud-sql-liquibase (which targets Cloud SQL) and a deliberate + alternative to extenda/actions/liquibase-spanner (which is GoogleSQL dialect). + Designed to be lifted into extenda/shared-workflows/generic/ unchanged. + +inputs: + service-account-key: + description: 'GCP service-account key (JSON) with access to the Spanner database.' + required: true + project: + description: 'GCP project hosting the Spanner instance.' + required: true + instance: + description: 'Spanner instance id.' + required: false + default: 'fiscal-signing' + database: + description: 'Spanner database id (must be PostgreSQL dialect).' + required: true + changelog-file: + description: 'Liquibase changelog file, relative to search-path.' + required: false + default: 'changelog-master.yaml' + search-path: + description: 'Directory that contains the changelog (and its includes).' + required: true + pgadapter-image: + description: >- + PGAdapter image. Pinned by digest for immutability (Dependabot's docker ecosystem only + scans Dockerfile/docker-compose.yml, not action.yaml inputs, so this is NOT auto-updated — + check https://console.cloud.google.com/artifacts/docker/cloud-spanner-pg-adapter/us/gcr.io/pgadapter + periodically and bump both the tag and digest together by hand. + required: false + default: 'gcr.io/cloud-spanner-pg-adapter/pgadapter:v0.55.0@sha256:30bb42ded681effccf83f93e5f5107c1ef9e5e48513d0f7e1d30c7bfc69fa5fa' + +runs: + using: composite + steps: + # Authenticate via the trusted action. It writes the credential file, registers it for + # log masking, and exports GOOGLE_APPLICATION_CREDENTIALS — so the key value is never + # interpolated into a shell command or handled by this action directly. + - uses: extenda/actions/setup-gcloud@v0 + with: + service-account-key: ${{ inputs.service-account-key }} + + - name: Start PGAdapter + shell: bash + run: | + if [ -z "${GOOGLE_APPLICATION_CREDENTIALS:-}" ]; then + echo "GOOGLE_APPLICATION_CREDENTIALS not set; setup-gcloud must run first" >&2 + exit 1 + fi + # Mount the credential FILE (a path, not the secret value) and let PGAdapter pick it + # up via Application Default Credentials. + docker run -d --name pgadapter \ + -p 5432:5432 \ + -v "${GOOGLE_APPLICATION_CREDENTIALS}:/var/run/secrets/gcp-credentials.json:ro" \ + -e GOOGLE_APPLICATION_CREDENTIALS=/var/run/secrets/gcp-credentials.json \ + "${{ inputs.pgadapter-image }}" \ + -p "${{ inputs.project }}" -i "${{ inputs.instance }}" -x + # PGAdapter is the last thing to open the port; wait until it accepts TCP. + for i in $(seq 1 30); do + if (exec 3<>/dev/tcp/localhost/5432) 2>/dev/null; then echo "PGAdapter ready"; exit 0; fi + sleep 1 + done + echo "PGAdapter did not become ready" >&2 + docker logs pgadapter || true + exit 1 + + # ddl_transaction_mode=AutocommitExplicitTransaction makes PGAdapter turn the + # transactional DDL Liquibase emits into Spanner DDL batches (Spanner forbids + # DDL inside explicit transactions). PGAdapter authenticates to Spanner via the + # mounted key, so the client-side username/password are unused. + - name: Run Liquibase update + uses: liquibase-github-actions/update@db87f3cf24f450dd01a6784a4cd7feec97a21676 # v4.25.1 + with: + changelogFile: ${{ inputs.changelog-file }} + searchPath: ${{ inputs.search-path }} + url: 'jdbc:postgresql://localhost:5432/${{ inputs.database }}?options=-c%20spanner.ddl_transaction_mode=AutocommitExplicitTransaction' + username: spanner + password: spanner + + - name: Stop PGAdapter + if: always() + shell: bash + run: docker rm -f pgadapter || true From 6f98c53932120e5af96fd0f0a3bc803597731166 Mon Sep 17 00:00:00 2001 From: Thomas Cederholm Date: Thu, 2 Jul 2026 15:49:23 +0200 Subject: [PATCH 2/3] docs: point fiscal action docs to their shared-workflows home The moved READMEs and action.yaml headers still described the old experience-fiscal-common location (and a planned generic/ home). Update the usage refs to extenda/shared-workflows/composite-actions/fiscal/@v0 and drop the obsolete 'to be lifted into generic/' notes. --- .../fiscal/fiscal-api-country-filtering/README.md | 6 +++--- .../fiscal/fiscal-api-country-filtering/action.yaml | 2 +- .../fiscal/spanner-pgadapter-execute-sql/action.yaml | 3 +-- .../fiscal/spanner-pgadapter-liquibase/README.md | 5 ++--- .../fiscal/spanner-pgadapter-liquibase/action.yaml | 1 - 5 files changed, 7 insertions(+), 10 deletions(-) diff --git a/composite-actions/fiscal/fiscal-api-country-filtering/README.md b/composite-actions/fiscal/fiscal-api-country-filtering/README.md index 24e17311..0c1ecca9 100644 --- a/composite-actions/fiscal/fiscal-api-country-filtering/README.md +++ b/composite-actions/fiscal/fiscal-api-country-filtering/README.md @@ -9,8 +9,8 @@ serves whatever classpath resource ends up at `/schemas/v1/openapi.yaml` verbati with the filtered content is enough to make e.g. Belgium's live service never document Portugal's attribute keys — **no engine code change needed**. -This is a repo-local composite action -(`./.github/shared-workflows/fiscal-api-country-filtering`) in `experience-fiscal-common`, shared +This is a shared composite action +(`extenda/shared-workflows/composite-actions/fiscal/fiscal-api-country-filtering`), shared across all country repos that fiscalize via `fiscal-engine`. ## Usage @@ -25,7 +25,7 @@ separate job. ```yaml - run: ./mvnw package -DskipTests -- uses: extenda/experience-fiscal-common/.github/shared-workflows/fiscal-api-country-filtering@master +- uses: extenda/shared-workflows/composite-actions/fiscal/fiscal-api-country-filtering@v0 with: country: belgium - run: docker build -t $IMAGE . # picks up the re-jarred, Belgium-only target/*.jar diff --git a/composite-actions/fiscal/fiscal-api-country-filtering/action.yaml b/composite-actions/fiscal/fiscal-api-country-filtering/action.yaml index 5cdd997c..d574476d 100644 --- a/composite-actions/fiscal/fiscal-api-country-filtering/action.yaml +++ b/composite-actions/fiscal/fiscal-api-country-filtering/action.yaml @@ -27,7 +27,7 @@ runs: # inside a composite action's shell script — this is NOT auto-updated. Check # https://hub.docker.com/r/redocly/cli/tags periodically and bump both occurrences by hand. - # redocly.yaml and decorators/ live in THIS action's own repo (experience-fiscal-common), not + # redocly.yaml and decorators/ live in THIS action's own repo (extenda/shared-workflows), not # the caller's checkout — `docker run -v $PWD:/spec` only mounts the caller's workspace, so # they have to be staged alongside the spec before redocly can see them. - name: Stage the country-filter config next to the built spec diff --git a/composite-actions/fiscal/spanner-pgadapter-execute-sql/action.yaml b/composite-actions/fiscal/spanner-pgadapter-execute-sql/action.yaml index ec658334..25ddde23 100644 --- a/composite-actions/fiscal/spanner-pgadapter-execute-sql/action.yaml +++ b/composite-actions/fiscal/spanner-pgadapter-execute-sql/action.yaml @@ -2,8 +2,7 @@ name: 'Spanner (PG-dialect) execute SQL' description: >- Execute caller-supplied SQL against a PostgreSQL-dialect Cloud Spanner database by fronting it with PGAdapter and using the standard psql client. Domain-agnostic - (the SQL is an input), so it can be lifted into extenda/shared-workflows/generic/ - unchanged. A sibling of spanner-pgadapter-liquibase for ad-hoc DML/DDL. + (the SQL is an input). A sibling of spanner-pgadapter-liquibase for ad-hoc DML/DDL. inputs: service-account-key: diff --git a/composite-actions/fiscal/spanner-pgadapter-liquibase/README.md b/composite-actions/fiscal/spanner-pgadapter-liquibase/README.md index 52ec2e43..fd2ed5fb 100644 --- a/composite-actions/fiscal/spanner-pgadapter-liquibase/README.md +++ b/composite-actions/fiscal/spanner-pgadapter-liquibase/README.md @@ -9,9 +9,8 @@ It is the PG-dialect counterpart to `extenda/shared-workflows/generic/cloud-sql- engine standardised on one portable Postgres SQL codepath, so its changesets are applied with plain Postgres Liquibase here too — see engine decisions #9/#10. -This is a repo-local composite action (`./.github/shared-workflows/spanner-pgadapter-liquibase`). It is -written to lift into `extenda/shared-workflows/generic/spanner-pgadapter-liquibase@v0` unchanged; -update the `uses:` reference when it moves. +This is a shared composite action, consumed as +`extenda/shared-workflows/composite-actions/fiscal/spanner-pgadapter-liquibase@v0`. ## Inputs diff --git a/composite-actions/fiscal/spanner-pgadapter-liquibase/action.yaml b/composite-actions/fiscal/spanner-pgadapter-liquibase/action.yaml index 755311e5..9b496ea7 100644 --- a/composite-actions/fiscal/spanner-pgadapter-liquibase/action.yaml +++ b/composite-actions/fiscal/spanner-pgadapter-liquibase/action.yaml @@ -4,7 +4,6 @@ description: >- database by fronting it with PGAdapter. The PG-dialect counterpart to generic/cloud-sql-liquibase (which targets Cloud SQL) and a deliberate alternative to extenda/actions/liquibase-spanner (which is GoogleSQL dialect). - Designed to be lifted into extenda/shared-workflows/generic/ unchanged. inputs: service-account-key: From f8744c41c0bcf47f4013c2db3b395c12f741bad4 Mon Sep 17 00:00:00 2001 From: Thomas Cederholm Date: Thu, 2 Jul 2026 16:16:56 +0200 Subject: [PATCH 3/3] feat: Rename fiscal openapi action to openapi-actions The action outgrew "country filtering": it now materializes the engine's openapi.yaml from the fiscal-engine dependency and optionally stamps info.version (a set-version Redocly decorator reading API_VERSION), on top of the existing x-country oneOf filtering and re-jar. This drops all per-adapter pom config. Plugin id country-filter -> openapi. --- .../fiscal-api-country-filtering/README.md | 74 ---------------- .../fiscal-api-country-filtering/redocly.yaml | 8 -- .../fiscal/openapi-actions/README.md | 87 +++++++++++++++++++ .../action.yaml | 40 +++++++-- .../decorators/openapi.js} | 35 ++++++-- .../fiscal/openapi-actions/redocly.yaml | 12 +++ 6 files changed, 157 insertions(+), 99 deletions(-) delete mode 100644 composite-actions/fiscal/fiscal-api-country-filtering/README.md delete mode 100644 composite-actions/fiscal/fiscal-api-country-filtering/redocly.yaml create mode 100644 composite-actions/fiscal/openapi-actions/README.md rename composite-actions/fiscal/{fiscal-api-country-filtering => openapi-actions}/action.yaml (57%) rename composite-actions/fiscal/{fiscal-api-country-filtering/decorators/country-filter.js => openapi-actions/decorators/openapi.js} (67%) create mode 100644 composite-actions/fiscal/openapi-actions/redocly.yaml diff --git a/composite-actions/fiscal/fiscal-api-country-filtering/README.md b/composite-actions/fiscal/fiscal-api-country-filtering/README.md deleted file mode 100644 index 0c1ecca9..00000000 --- a/composite-actions/fiscal/fiscal-api-country-filtering/README.md +++ /dev/null @@ -1,74 +0,0 @@ -# fiscal-api-country-filtering - -Replaces the fiscal-engine's shared `openapi.yaml`, **in place**, with a country-scoped bundle -that strips every other country's `x-country`-tagged documentation via a Redocly decorator, then -re-runs `jar:jar` so the already-built jar picks up the change. The engine ships one canonical -spec with a `Fiscal-Attributes` `oneOf` — one branch per country's response `attributes` shape -(`src/main/resources/schemas/v1/openapi.yaml` in `hiiretail-fiscal-engine`). Because the engine -serves whatever classpath resource ends up at `/schemas/v1/openapi.yaml` verbatim, overwriting it -with the filtered content is enough to make e.g. Belgium's live service never document Portugal's -attribute keys — **no engine code change needed**. - -This is a shared composite action -(`extenda/shared-workflows/composite-actions/fiscal/fiscal-api-country-filtering`), shared -across all country repos that fiscalize via `fiscal-engine`. - -## Usage - -Run this any time **after** a normal `mvn package` — it re-seals the jar for you, so callers don't -need to reorder their own build around it. (A full `mvn package` re-runs `resources:resources` -internally and would clobber a filtered file if this action ran *before* packaging, which is why -the re-jar happens inside the action rather than being left to every caller to get right.) It does -no checkout of its own (an `actions/checkout` step here would run `git clean -ffdx` by default and -wipe `target/`), so it must run later in the same job as the checkout and package steps, not a -separate job. - -```yaml -- run: ./mvnw package -DskipTests -- uses: extenda/shared-workflows/composite-actions/fiscal/fiscal-api-country-filtering@v0 - with: - country: belgium -- run: docker build -t $IMAGE . # picks up the re-jarred, Belgium-only target/*.jar -``` - -## Inputs - -| Input | Required | Default | Description | -|-------|----------|---------|-------------| -| `country` | yes | – | The lower-case api name from this action's `redocly.yaml` `apis:` map, e.g. `belgium`. | -| `schema-dir` | no | `target/classes/schemas/v1` | Directory (relative to the workspace root) containing the built `openapi.yaml`. Overwritten in place. | - -## Adding a country - -One identifier throughout: the lower-case country name used as the `apis:` key (e.g. `belgium`) -is reused verbatim as both the decorator's `country` option and the `x-country` tag value — no -separate short code to keep in sync. - -1. In the engine's `openapi.yaml`, add a `Fiscal-Attributes-` branch to the - `Fiscal-Attributes` `oneOf`, tagged `x-country: ` (lower-case, e.g. `x-country: portugal`). -2. In this action's `redocly.yaml`, add a sibling `apis:` entry named `` and point the - decorator's `country` option at that same value. - -## Notes / validation status - -- **Decorators only run during `bundle`, not `lint`** — linting the pre-bundle source wouldn't - exercise the country-filtering transformation, so this action lints the *bundled* output instead. -- `redocly.yaml` and `decorators/` live in this action's own repo, not the caller's checkout — - `docker run -v $PWD:/spec` only mounts the caller's workspace, so they're staged (`cp`) into - `schema-dir` via `${{ github.action_path }}` before bundling. The staging step clears any - pre-existing `redocly.yaml`/`decorators/` at the destination first, so re-runs on a reused - workspace can't leave a stale or nested `decorators/decorators/` copy behind. -- Bundling reads and writes `openapi.yaml` at the same path — safe because the bundler loads the - whole document graph into memory before it writes any output. -- A single surviving `oneOf` branch (e.g. Belgium today, with only one real country onboarded) is - collapsed by the decorator into the wrapper schema directly, rather than left as a `oneOf` of one. -- `jar:jar` only re-zips already-compiled `target/classes` — no recompile, no test run — so it's - cheap to run as a dedicated step after packaging. -- **Not yet validated against a real country pipeline in CI** — verified locally by manually - running every step's command (staging, in-place bundle, lint, and `jar:jar`) against - `hiiretail-fiscal-engine`'s own freshly-built jar: confirmed the jar shipped both - `Fiscal-Attributes-BE` and `Fiscal-Attributes-EU` before, and only the collapsed Belgium schema - after. Confirm on a first real run in an actual country pipeline before relying on it for prod. -- `hiiretail-fiscal-engine` currently also keeps its own local copy of `redocly.yaml`/ - `decorators/country-filter.js` for local dev/testing — decide whether that should be removed now - that this is the canonical CI-facing copy, or kept in sync by hand. diff --git a/composite-actions/fiscal/fiscal-api-country-filtering/redocly.yaml b/composite-actions/fiscal/fiscal-api-country-filtering/redocly.yaml deleted file mode 100644 index 4d20de68..00000000 --- a/composite-actions/fiscal/fiscal-api-country-filtering/redocly.yaml +++ /dev/null @@ -1,8 +0,0 @@ -extends: [recommended] - -plugins: ['decorators/country-filter.js'] - -apis: - belgium: - root: openapi.yaml - decorators: { country-filter/strip-other-countries: { country: belgium } } diff --git a/composite-actions/fiscal/openapi-actions/README.md b/composite-actions/fiscal/openapi-actions/README.md new file mode 100644 index 00000000..77bb554f --- /dev/null +++ b/composite-actions/fiscal/openapi-actions/README.md @@ -0,0 +1,87 @@ +# openapi-actions + +Builds the deployable, country-scoped OpenAPI spec for a fiscal adapter, then re-seals the jar so +the running service serves it. In one pass it: + +1. **Materializes** the engine's shared `openapi.yaml` out of the `fiscal-engine` dependency into + the build output — the adapter ships no spec of its own. +2. **Stamps `info.version`** from the optional `version` input (the engine ships it as a literal + `${project.version}` token so each adapter sets its own build version — see below). +3. **Country-filters** it in place: a Redocly decorator strips every other country's + `x-country`-tagged `oneOf` branch, leaving only this country's `attributes` shape. +4. **Re-runs `jar:jar`** so the already-built jar picks up the overwritten resource. + +The engine serves whatever classpath resource ends up at `/schemas/v1/openapi.yaml` verbatim, and +the adapter jar precedes `fiscal-engine.jar` on the classpath — so overwriting that path is enough +to make e.g. Belgium's live service serve a Belgium-only, correctly-versioned spec. **No engine +code change and no per-adapter `pom.xml` config needed.** + +The engine ships one canonical spec whose response `attributes` are `Fiscalize-Attributes` and +`Cancel-Attributes` `oneOf`s — one `x-country`-tagged branch per country +(`src/main/resources/schemas/v1/openapi.yaml` in `hiiretail-fiscal-engine`). + +This is a shared composite action +(`extenda/shared-workflows/composite-actions/fiscal/openapi-actions`), used by all country repos +that fiscalize via `fiscal-engine`. + +## Usage + +Run this any time **after** a normal `mvn package` — it re-seals the jar for you, so callers don't +need to reorder their own build around it. It does no checkout of its own (an `actions/checkout` +step here would run `git clean -ffdx` by default and wipe `target/`), so it must run later in the +same job as the checkout and package steps, not a separate job. + +```yaml +- run: ./mvnw package -DskipTests +- uses: extenda/shared-workflows/composite-actions/fiscal/openapi-actions@v0 + with: + country: belgium + version: ${{ steps.semver.outputs.version }} # optional; omit for local/acceptance runs +- run: docker build -t $IMAGE . # picks up the re-jarred, versioned, Belgium-only target/*.jar +``` + +## Inputs + +| Input | Required | Default | Description | +|-------|----------|---------|-------------| +| `country` | yes | – | The lower-case api name from this action's `redocly.yaml` `apis:` map, e.g. `belgium`. | +| `version` | no | `''` | When set, replaces the engine spec's literal `${project.version}` token so the served `info.version` is the adapter's build version. When empty, the token is left as-is (fine for local/acceptance runs, where the version is cosmetic). | +| `schema-dir` | no | `target/classes/schemas/v1` | Directory (relative to the workspace root) the spec is materialized into and filtered in place. | + +## Adding a country + +One identifier throughout: the lower-case country name used as the `apis:` key (e.g. `belgium`) is +reused verbatim as both the decorator's `country` option and the `x-country` tag value — no +separate short code to keep in sync. + +1. In the engine's `openapi.yaml`, add a `Fiscalize-Attributes-` branch to the + `Fiscalize-Attributes` `oneOf` and a `Cancel-Attributes-` branch to the + `Cancel-Attributes` `oneOf`, each tagged `x-country: ` (lower-case, e.g. + `x-country: portugal`). +2. In this action's `redocly.yaml`, add a sibling `apis:` entry named `` with + `openapi/strip-other-countries: { country: }`. + +## Notes / validation status + +- **Version is set by a decorator, not text substitution.** The action forwards the `version` + input into the bundle container as `API_VERSION`; the `openapi/set-version` decorator reads it + and overrides `info.version`, logging `openapi: set info.version = `. Empty ⇒ no-op. +- **Decorators only run during `bundle`, not `lint`** — linting the pre-bundle source wouldn't + exercise the transformation, so this action lints the *bundled* output instead. +- `redocly.yaml` and `decorators/` live in this action's own repo, not the caller's checkout — + `docker run -v $PWD:/spec` only mounts the caller's workspace, so they're staged (`cp`) into + `schema-dir` via `${{ github.action_path }}` before bundling. The staging step clears any + pre-existing `redocly.yaml`/`decorators/` at the destination first, so re-runs on a reused + workspace can't leave a stale or nested `decorators/decorators/` copy behind. +- Bundling reads and writes `openapi.yaml` at the same path — safe because the bundler loads the + whole document graph into memory before it writes any output. +- A single surviving `oneOf` branch (e.g. Belgium today, with only one real country onboarded) is + collapsed by the decorator into the wrapper schema directly, rather than left as a `oneOf` of one. +- `jar:jar` only re-zips already-compiled `target/classes` — no recompile, no test run — so it's + cheap to run as a dedicated step after packaging. +- **Not yet validated against a real country pipeline in CI** — verified locally by running every + step's command (materialize, stage, in-place bundle with `API_VERSION`, lint, `jar:jar`) against + `hiiretail-fiscal-engine`'s freshly-built spec: confirmed the bundle ships both + `Fiscalize-Attributes-BE`/`-EU` and `Cancel-Attributes-BE`/`-EU` before, and only the collapsed + Belgium schemas plus the stamped `info.version` after. Confirm on a first real run in an actual + country pipeline before relying on it for prod. diff --git a/composite-actions/fiscal/fiscal-api-country-filtering/action.yaml b/composite-actions/fiscal/openapi-actions/action.yaml similarity index 57% rename from composite-actions/fiscal/fiscal-api-country-filtering/action.yaml rename to composite-actions/fiscal/openapi-actions/action.yaml index d574476d..47981084 100644 --- a/composite-actions/fiscal/fiscal-api-country-filtering/action.yaml +++ b/composite-actions/fiscal/openapi-actions/action.yaml @@ -1,19 +1,24 @@ name: 'Fiscal API country filtering' description: >- - Replaces the fiscal-engine's shared openapi.yaml, IN PLACE, with a country-scoped bundle that + Materializes the fiscal-engine's shared openapi.yaml from the fiscal-engine dependency, + optionally stamps `info.version`, then replaces it IN PLACE with a country-scoped bundle that strips every other country's `x-country`-tagged documentation via a Redocly decorator (see the - engine's `Fiscal-Attributes` oneOf, src/main/resources/schemas/v1/openapi.yaml), then re-runs - `jar:jar` so the already-built jar picks up the change. The engine serves whatever classpath - resource ends up at /schemas/v1/openapi.yaml verbatim — no engine code change needed. Run it any - time after a normal `mvn package` (a full `package` re-runs `resources:resources` and would - clobber a filtered file, which is why the re-jar happens here instead of asking every caller to - order steps around that). Does no checkout of its own (an `actions/checkout` step here would run - `git clean -ffdx` by default and wipe `target/`). + engine's `Fiscalize-Attributes`/`Cancel-Attributes` oneOf, src/main/resources/schemas/v1/openapi.yaml), + and re-runs `jar:jar` so the already-built jar picks up the change. The engine serves whatever + classpath resource ends up at /schemas/v1/openapi.yaml verbatim — no engine code and no per-country + pom change needed. Run it after a normal `mvn package`. Does no checkout of its own (an + `actions/checkout` step here would run `git clean -ffdx` by default and wipe `target/`). inputs: country: required: true description: The lower-case api name from this action's redocly.yaml `apis:` map, e.g. 'belgium'. + version: + required: false + description: >- + When set, replaces the engine spec's literal `${project.version}` token so the served + `info.version` is the adapter's build version. When empty, the token is left as-is. + default: '' schema-dir: required: false description: Directory (relative to the workspace root) containing the built openapi.yaml. @@ -22,6 +27,19 @@ inputs: runs: using: composite steps: + # The adapter ships no spec of its own — the engine dependency owns openapi.yaml. Unpack it into + # the build output so the steps below (and the re-jar) operate on a real file. Same pattern the + # callers already use for the engine's Liquibase changesets. + - name: Materialize the engine's OpenAPI spec + shell: bash + run: | + ./mvnw -q dependency:unpack-dependencies \ + -DincludeArtifactIds=fiscal-engine \ + -Dincludes=schemas/v1/openapi.yaml \ + -DoutputDirectory=target/openapi-engine-spec + mkdir -p "${{ inputs.schema-dir }}" + cp target/openapi-engine-spec/schemas/v1/openapi.yaml "${{ inputs.schema-dir }}/openapi.yaml" + # redocly/cli is pinned by digest below for immutability. Dependabot's docker ecosystem only # scans Dockerfile/docker-compose.yml `FROM`/`image:` fields, not `docker run` invocations # inside a composite action's shell script — this is NOT auto-updated. Check @@ -40,10 +58,14 @@ runs: # Bundles openapi.yaml and overwrites it in place with the country-filtered result — the # bundler reads the whole document graph before it writes any output, so reading and writing # the same path is safe. + # API_VERSION is passed through to the container's env (docker `-e NAME` forwards the host + # value); the `set-version` decorator reads it and overrides info.version. Empty => no-op. - name: Bundle the ${{ inputs.country }} spec in place shell: bash + env: + API_VERSION: ${{ inputs.version }} run: | - docker run --rm -v "$PWD/${{ inputs.schema-dir }}:/spec" redocly/cli:2.35.1@sha256:404c57d791f2a9d08973bb568da2e070bbc7bb96c94248eba38493537b78abb5 \ + docker run --rm -e API_VERSION -v "$PWD/${{ inputs.schema-dir }}:/spec" redocly/cli:2.35.1@sha256:404c57d791f2a9d08973bb568da2e070bbc7bb96c94248eba38493537b78abb5 \ bundle ${{ inputs.country }} --config redocly.yaml -o openapi.yaml # Decorators only run during `bundle`, not `lint` — linting the pre-bundle source wouldn't diff --git a/composite-actions/fiscal/fiscal-api-country-filtering/decorators/country-filter.js b/composite-actions/fiscal/openapi-actions/decorators/openapi.js similarity index 67% rename from composite-actions/fiscal/fiscal-api-country-filtering/decorators/country-filter.js rename to composite-actions/fiscal/openapi-actions/decorators/openapi.js index 4485aaed..e4069d7d 100644 --- a/composite-actions/fiscal/fiscal-api-country-filtering/decorators/country-filter.js +++ b/composite-actions/fiscal/openapi-actions/decorators/openapi.js @@ -4,7 +4,7 @@ // decorator (walk components.schemas, delete tagged nodes). function StripOtherCountries({ country }) { if (!country) { - throw new Error('country-filter/strip-other-countries requires a `country` option'); + throw new Error('openapi/strip-other-countries requires a `country` option'); } return { Root: { @@ -25,14 +25,14 @@ function StripOtherCountries({ country }) { for (const branch of before) { if (!schema.oneOf.includes(branch)) { console.log( - `country-filter: removed '${refName(branch?.$ref)}' from '${schemaName}.oneOf' (x-country != ${country})` + `openapi: removed '${refName(branch?.$ref)}' from '${schemaName}.oneOf' (x-country != ${country})` ); } } if (schema.oneOf.length === 0) { throw new Error( - `country-filter: no branch of '${schemaName}' tagged x-country: ${country} — add ` + + `openapi: no branch of '${schemaName}' tagged x-country: ${country} — add ` + `one before configuring redocly.yaml's '${country}' apis entry.` ); } @@ -46,14 +46,14 @@ function StripOtherCountries({ country }) { Object.assign(schema, branchSchema); delete schema['x-country']; delete schema['title']; - console.log(`country-filter: '${branchName}' is copied into '${schemaName}'`); + console.log(`openapi: '${branchName}' is copied into '${schemaName}'`); } } for (const [schemaName, schema] of Object.entries(schemas)) { if (schema?.['x-country'] && !isReferenced(schemas, schemaName)) { delete schemas[schemaName]; - console.log(`country-filter: dropped unreferenced component '${schemaName}'`); + console.log(`openapi: dropped unreferenced component '${schemaName}'`); } } } @@ -73,12 +73,31 @@ function isReferenced(schemas, name) { return JSON.stringify(schemas).includes(`#/components/schemas/${name}`); } -module.exports = function countryFilterPlugin() { +// Overrides info.version from the API_VERSION env var when set, so each adapter stamps its +// own build version onto the shared engine spec as part of the same bundle pass — no pom filtering +// or text substitution. No-op when the env var is empty (the engine's literal ${project.version} +// token is left in place, which is fine for local/acceptance runs where the version is cosmetic). +function SetVersion() { return { - id: 'country-filter', + Info: { + leave(info) { + const version = process.env.API_VERSION; + if (version) { + info.version = version; + console.log(`openapi: set info.version = ${version}`); + } + } + } + }; +} + +module.exports = function openapiPlugin() { + return { + id: 'openapi', decorators: { oas3: { - 'strip-other-countries': StripOtherCountries + 'strip-other-countries': StripOtherCountries, + 'set-version': SetVersion } } }; diff --git a/composite-actions/fiscal/openapi-actions/redocly.yaml b/composite-actions/fiscal/openapi-actions/redocly.yaml new file mode 100644 index 00000000..1132f6ee --- /dev/null +++ b/composite-actions/fiscal/openapi-actions/redocly.yaml @@ -0,0 +1,12 @@ +extends: [recommended] + +plugins: ['decorators/openapi.js'] + +# Applies to every api below: stamps info.version from the API_VERSION env var when set. +decorators: + openapi/set-version: on + +apis: + belgium: + root: openapi.yaml + decorators: { openapi/strip-other-countries: { country: belgium } }