From 416ae130e61626c0e5d53581c901ad823cdd8219 Mon Sep 17 00:00:00 2001 From: Maxence Maireaux Date: Tue, 15 Sep 2026 23:20:41 +0200 Subject: [PATCH] feat: Prepare stack v4.0.0 --- .../scripts/generate-composition-overlay.sh | 62 ++++++++++++++----- .speakeasy/workflow.lock | 8 ++- .speakeasy/workflow.yaml | 2 + Justfile | 27 +++++--- releases/base.yaml | 2 + releases/overlays/generated.overlay.json | 19 +++++- releases/overlays/shared.overlay.yaml | 5 -- 7 files changed, 93 insertions(+), 32 deletions(-) diff --git a/.github/scripts/generate-composition-overlay.sh b/.github/scripts/generate-composition-overlay.sh index ff66f2c53f..b1f5811501 100644 --- a/.github/scripts/generate-composition-overlay.sh +++ b/.github/scripts/generate-composition-overlay.sh @@ -7,24 +7,55 @@ output_file="${2:-releases/overlays/generated.overlay.json}" mkdir -p "$(dirname "$output_file")" +base_input_count=$(yq -r ' + [.sources[].inputs[] | select(.modelNamespace == null)] + | length +' "$workflow_file") + +if [[ "$base_input_count" != "1" ]]; then + echo "expected exactly one base OpenAPI input, found $base_input_count" >&2 + exit 1 +fi + +base_location=$(yq -r ' + .sources[].inputs[] + | select(.modelNamespace == null) + | .location +' "$workflow_file") + { + # Component metadata is merged with the base metadata by Speakeasy. Restore + # the canonical info object from the sole non-namespaced base input. + yq -o=json -I=0 '{"target": "$.info", "update": .info}' "$base_location" + while IFS=$'\t' read -r location namespace; do # Speakeasy applies modelNamespace to schema names and structural refs, # but explicit discriminator mappings need the same transformation. - yq -o=json "$location" | jq -c --arg namespace "$namespace" ' + # Extract only discriminator metadata before converting to JSON. Some + # component specs contain valid uint64 bounds that yq cannot marshal + # through its signed JSON integer representation. + yq -o=json -I=0 ' + .components.schemas as $schemas + | $schemas + | .. + | select(kind == "map" and .discriminator.mapping != null) + | { + "path": path, + "mapping": .discriminator.mapping, + "schema_names": ($schemas | keys) + } + | select(.mapping != null) + ' "$location" | jq -c --arg namespace "$namespace" ' def json_path_segment: if type == "number" then "[" + tostring + "]" else "[" + (@json) + "]" end; - .components.schemas as $schemas - | $schemas - | to_entries[] - | .key as $schema_name - | .value as $schema - | ($schema | path(.. | objects | select(.discriminator.mapping? != null))) as $path - | ($schema | getpath($path) | .discriminator.mapping) as $mapping + .path[2] as $schema_name + | .path[3:] as $path + | .mapping as $mapping + | .schema_names as $schema_names | { target: ( "$.components.schemas[" @@ -40,7 +71,10 @@ mkdir -p "$(dirname "$output_file")" . as $ref | if ( ($ref | startswith("#/components/schemas/")) - and ($schemas[$ref | sub("^#/components/schemas/"; "")] != null) + and ( + ($schema_names | index($ref | sub("^#/components/schemas/"; ""))) + != null + ) ) then ( "#/components/schemas/" @@ -48,7 +82,7 @@ mkdir -p "$(dirname "$output_file")" + "_" + ($ref | sub("^#/components/schemas/"; "")) ) - elif $schemas[$ref] != null + elif ($schema_names | index($ref)) != null then "#/components/schemas/" + $namespace + "_" + $ref else $ref end @@ -62,9 +96,9 @@ mkdir -p "$(dirname "$output_file")" # disabled. Convert every singleton resource enum, including future # branches, into the constant expected by generated union helpers. if [[ "$namespace" == "ledger" ]]; then - yq -o=json "$location" | jq -c --arg namespace "$namespace" ' - .components.schemas.V2QueryParams.oneOf - | to_entries[] + yq -o=json -I=0 '.components.schemas.V2QueryParams.oneOf // []' "$location" \ + | jq -c --arg namespace "$namespace" ' + to_entries[] | select( (.value.properties.resource.enum? | type) == "array" and (.value.properties.resource.enum | length) == 1 @@ -105,7 +139,7 @@ mkdir -p "$(dirname "$output_file")" { overlay: "1.0.0", info: { - title: "Generated namespace fixes for the Formance Stack OpenAPI spec", + title: "Generated composition fixes for the Formance Stack OpenAPI spec", version: "0.0.1" }, actions: . diff --git a/.speakeasy/workflow.lock b/.speakeasy/workflow.lock index 023844d61a..95fc989711 100644 --- a/.speakeasy/workflow.lock +++ b/.speakeasy/workflow.lock @@ -1,9 +1,9 @@ -speakeasyVersion: 1.796.4 +speakeasyVersion: 1.797.0 sources: stacks-source: sourceNamespace: stacks-source - sourceRevisionDigest: sha256:e21e54e7818b64f5bd5b04dc7d0f16434b7d6cd22ec399eab0b8b4353ca72c21 - sourceBlobDigest: sha256:bab4ef7cb2db033bdbfea628f9fa8831eea64b2990bdc91e35552809d3497e9b + sourceRevisionDigest: sha256:9df14dce9ad22e9a6989dda36e511ab3ce3973fcad5f1bbfce8118cc49f2e3e6 + sourceBlobDigest: sha256:da34e684e433e7d236caa00e25f3a97d66ad660153d0201f78ffaecfde5b2b00 tags: - latest - SDK_VERSION @@ -19,6 +19,8 @@ workflow: - location: ./components/gateway.openapi.yaml modelNamespace: gateway - location: ./components/ledger.openapi.yaml + modelNamespace: ledgerV3 + - location: ./components/ledger-v2.openapi.yaml modelNamespace: ledger - location: ./components/payments.openapi.yaml modelNamespace: payments diff --git a/.speakeasy/workflow.yaml b/.speakeasy/workflow.yaml index dcb8cecc64..3e6fe609ea 100644 --- a/.speakeasy/workflow.yaml +++ b/.speakeasy/workflow.yaml @@ -8,6 +8,8 @@ sources: - location: ./components/gateway.openapi.yaml modelNamespace: gateway - location: ./components/ledger.openapi.yaml + modelNamespace: ledgerV3 + - location: ./components/ledger-v2.openapi.yaml modelNamespace: ledger - location: ./components/payments.openapi.yaml modelNamespace: payments diff --git a/Justfile b/Justfile index b945ea2e2a..9542fc61d4 100644 --- a/Justfile +++ b/Justfile @@ -1,18 +1,20 @@ # Component versions -LEDGER_VERSION := "v2.4.12" +LEDGER_VERSION := "v3.0.0-beta.3" +LEDGER_V2_VERSION := "v2.4.12" PAYMENTS_VERSION := "v3.4.4" -WALLETS_VERSION := "v2.2.0" -WEBHOOKS_VERSION := "v2.5.0" -AUTH_VERSION := "v2.5.0" +WALLETS_VERSION := "v2.2.1" +WEBHOOKS_VERSION := "v2.5.4" +AUTH_VERSION := "v2.5.1" SEARCH_VERSION := "v2.1.0" -ORCHESTRATION_VERSION := "v2.6.0" -RECONCILIATION_VERSION := "v2.4.0" -GATEWAY_VERSION := "v2.3.1" +ORCHESTRATION_VERSION := "v2.6.4" +RECONCILIATION_VERSION := "v2.5.0" +GATEWAY_VERSION := "v2.3.2" # Download all component OpenAPI specs from GitHub releases download-specs: mkdir -p components - wget -q https://github.com/formancehq/ledger/releases/download/{{ LEDGER_VERSION }}/openapi.yaml -O components/ledger.openapi.yaml + wget -q https://github.com/formancehq/ledger/releases/download/{{ LEDGER_VERSION }}/openapi.yml -O components/ledger.openapi.yaml + wget -q https://github.com/formancehq/ledger/releases/download/{{ LEDGER_V2_VERSION }}/openapi.yaml -O components/ledger-v2.openapi.yaml wget -q https://github.com/formancehq/payments/releases/download/{{ PAYMENTS_VERSION }}/openapi.yaml -O components/payments.openapi.yaml wget -q https://raw.githubusercontent.com/formancehq/gateway/{{ GATEWAY_VERSION }}/openapi.yaml -O components/gateway.openapi.yaml wget -q https://github.com/formancehq/auth/releases/download/{{ AUTH_VERSION }}/openapi.yaml -O components/auth.openapi.yaml @@ -25,7 +27,8 @@ download-specs: # Prepend API path prefix to each component spec prepend-paths: download-specs yq -i '.paths |= (to_entries | map(select(.key == "/*").key = "/api/auth" + .key) | from_entries)' components/auth.openapi.yaml - yq -i '.paths |= (to_entries | map(select(.key == "/*").key = "/api/ledger" + .key) | from_entries)' components/ledger.openapi.yaml + yq -i '.paths |= (to_entries | map(select(.key == "/*").key = "/api/ledger" + .key) | from_entries) | (.paths[] | .. | select(has("tags")).tags) = ["ledger.v3"]' components/ledger.openapi.yaml + yq -i '.paths |= (to_entries | map(select(.key == "/*").key = "/api/ledger" + .key) | from_entries) | del(.paths."/api/ledger/_info")' components/ledger-v2.openapi.yaml yq -i '.paths |= (to_entries | map(select(.key == "/*").key = "/api/payments" + .key) | from_entries)' components/payments.openapi.yaml yq -i '.paths |= (to_entries | map(select(.key == "/*").key = "/api/search" + .key) | from_entries)' components/search.openapi.yaml yq -i '.paths |= (to_entries | map(select(.key == "/*").key = "/api/webhooks" + .key) | from_entries)' components/webhooks.openapi.yaml @@ -51,6 +54,8 @@ build-openapi version="v0.0.0": generate-composition-overlay # Validate invariants introduced by composing namespaced OpenAPI documents. validate-openapi: + # Global API metadata must remain exactly sourced from base.yaml. + jq -e --argjson base_info "$(yq -o=json -I=0 '.info' releases/base.yaml)" '(.info | .version = $base_info.version) == $base_info' releases/build/generate.json >/dev/null # Every local discriminator mapping must resolve to an existing schema. jq -e '.components.schemas as $schemas | [ $schemas | .. | objects | select(.discriminator.mapping? != null) | .discriminator.mapping[] | select(type == "string" and startswith("#/components/schemas/")) | sub("^#/components/schemas/"; "") | select($schemas[.] == null) ] | length == 0' releases/build/generate.json >/dev/null # Ledger query-template helpers rely on these resource constants. @@ -59,6 +64,10 @@ validate-openapi: test "$(jq -c '.paths["x-speakeasy-errors"].statusCodes' releases/build/generate.json)" = '["default"]' # Every operation tag must be declared globally. jq -e '[.tags[].name] as $tags | [.paths[] | to_entries[] | select(.key | IN("get", "post", "put", "patch", "delete", "options", "head", "trace")) | .value.tags[]? | select(. as $tag | $tags | index($tag) == null)] | length == 0' releases/build/generate.json >/dev/null + # Namespace conflict suffixes are merge artifacts, never callable API paths. + jq -e '[.paths | keys[] | select(contains("#"))] | length == 0' releases/build/generate.json >/dev/null + # Both supported Ledger API generations must remain in the composed spec. + jq -e '.paths["/api/ledger/v2"] != null and .paths["/api/ledger/v3/"] != null' releases/build/generate.json >/dev/null # Composition cleanups must remain applied when component specs change. jq -e '.components.schemas.auth_Scope == null and .components.schemas.auth_ScopeOptions == null' releases/build/generate.json >/dev/null jq -e '[.. | objects | select(.enum? != null) | .enum | select(length != (unique | length))] | length == 0' releases/build/generate.json >/dev/null diff --git a/releases/base.yaml b/releases/base.yaml index 6109ad0065..87c5ac8559 100644 --- a/releases/base.yaml +++ b/releases/base.yaml @@ -42,6 +42,7 @@ servers: tags: - name: ledger.v1 - name: ledger.v2 + - name: ledger.v3 - name: payments.v1 - name: payments.v3 - name: auth.v1 @@ -70,6 +71,7 @@ x-tagGroups: tags: - ledger.v1 - ledger.v2 + - ledger.v3 - name: Payments tags: - payments.v1 diff --git a/releases/overlays/generated.overlay.json b/releases/overlays/generated.overlay.json index 6e81bd2d8d..66c7c6de29 100644 --- a/releases/overlays/generated.overlay.json +++ b/releases/overlays/generated.overlay.json @@ -1,10 +1,27 @@ { "overlay": "1.0.0", "info": { - "title": "Generated namespace fixes for the Formance Stack OpenAPI spec", + "title": "Generated composition fixes for the Formance Stack OpenAPI spec", "version": "0.0.1" }, "actions": [ + { + "target": "$.info", + "update": { + "title": "Formance Stack API", + "description": "Open, modular foundation for unique payments flows\n\n# Introduction\nThis API is documented in **OpenAPI format**.\n\n# Authentication\nFormance Stack offers one forms of authentication:\n - OAuth2\nOAuth2 - an open protocol to allow secure authorization in a simple\nand standard method from web, mobile and desktop applications.\n\n", + "contact": { + "name": "Formance", + "url": "https://www.formance.com", + "email": "support@formance.com" + }, + "x-logo": { + "url": "https://avatars.githubusercontent.com/u/84325077?s=200&v=4", + "altText": "Formance" + }, + "version": "SDK_VERSION" + } + }, { "target": "$.components.schemas[\"ledger_V2BulkElement\"].discriminator.mapping", "update": { diff --git a/releases/overlays/shared.overlay.yaml b/releases/overlays/shared.overlay.yaml index 3cee03591b..21e4763741 100644 --- a/releases/overlays/shared.overlay.yaml +++ b/releases/overlays/shared.overlay.yaml @@ -3,11 +3,6 @@ info: title: Shared overlay for Formance Stack OpenAPI spec version: 0.0.1 actions: - # Restore info.version (overwritten by last-wins merge from component specs) - - target: $.info - update: - version: SDK_VERSION - # Remove the NoAuthorization securityScheme definition (injected by component merge) - target: $.components.securitySchemes.NoAuthorization remove: true