diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 101f1b2..9c7bd7f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -59,10 +59,33 @@ jobs: - name: Node SDK conformance (stripe-node, octokit, twilio-node) run: just conformance-node - - name: Conformance matrix freshness (CONFORMANCE.md) + - name: Generated files fresh (CONFORMANCE.md, README counts) run: | just conformance-matrix - if ! git diff --exit-code CONFORMANCE.md conformance/matrix.json; then - echo "::error::CONFORMANCE.md or conformance/matrix.json is stale — run 'just conformance-matrix' and commit" + just readme-counts + if ! git diff --exit-code CONFORMANCE.md conformance/matrix.json README.md; then + echo "::error::CONFORMANCE.md, conformance/matrix.json or README.md is stale — run 'just conformance-matrix readme-counts' and commit" + exit 1 + fi + + # The repo description quotes the adapter count; it went stale silently + # once (said 91 through 95). GITHUB_TOKEN cannot write repo metadata, so + # this job only fails loudly on drift after landing on main — the fix is + # `just repo-description` (needs an admin gh token locally). + repo-description: + if: github.event_name == 'push' && github.ref == 'refs/heads/main' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Description count matches adapters/ + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + want="$(find adapters -mindepth 2 -maxdepth 2 -name adapter.yaml | wc -l)" + desc="$(gh api repos/StuntAPI/stunt --jq .description)" + have="$(printf '%s' "$desc" | sed -E 's/^[^0-9]*([0-9]+).*/\1/')" + echo "repo has $want adapters; description says '$have'" + if [ "$have" != "$want" ]; then + echo "::error::repo description is stale — run 'just repo-description' (needs an admin token)" exit 1 fi diff --git a/conformance/cmd/genmatrix/main.go b/conformance/cmd/genmatrix/main.go index 07782c9..8b2820b 100644 --- a/conformance/cmd/genmatrix/main.go +++ b/conformance/cmd/genmatrix/main.go @@ -943,16 +943,22 @@ type matrixJSON struct { } type adapterJSON struct { - ID string `json:"id"` - APIName string `json:"api_name"` - APIVersion string `json:"api_version"` - Routes int `json:"routes"` - WSRoutes int `json:"ws_routes,omitempty"` - GraphQL bool `json:"graphql,omitempty"` - GRPCService string `json:"grpc_service,omitempty"` - Verification string `json:"verification"` - SDKs []sdkJSON `json:"sdks"` - Behaviors []string `json:"behaviors"` + ID string `json:"id"` + // Manifest identity for the website catalog sync (stunt-www + // scripts/sync-catalog.mjs): the catalog must derive these from this + // file, never re-parse adapter.yaml with a second parser. + DisplayName string `json:"display_name"` + AdapterVersion string `json:"adapter_version"` + RealHosts []string `json:"real_hosts"` + APIName string `json:"api_name"` + APIVersion string `json:"api_version"` + Routes int `json:"routes"` + WSRoutes int `json:"ws_routes,omitempty"` + GraphQL bool `json:"graphql,omitempty"` + GRPCService string `json:"grpc_service,omitempty"` + Verification string `json:"verification"` + SDKs []sdkJSON `json:"sdks"` + Behaviors []string `json:"behaviors"` // VMBehaviors are verified by the engine-level Go suite (markers in // adapters/_style_test.go) — real handler execution, no SDK. VMBehaviors []string `json:"vm_behaviors,omitempty"` @@ -1033,14 +1039,20 @@ func renderJSON(adapters []*adapter.Adapter, checks []check, sdkVer map[string]s m.Generated.Tiers.Boot++ } row := adapterJSON{ - ID: a.ID, - Routes: len(a.Endpoints), - WSRoutes: len(a.Websockets), - Verification: tier, - Behaviors: []string{}, - VMBehaviors: vmBehav[a.ID], - Missing: gaps[a.ID].Missing, - Deviations: gaps[a.ID].Deviations, + ID: a.ID, + DisplayName: a.Name, + AdapterVersion: a.Version, + RealHosts: a.RealHosts, + Routes: len(a.Endpoints), + WSRoutes: len(a.Websockets), + Verification: tier, + Behaviors: []string{}, + VMBehaviors: vmBehav[a.ID], + Missing: gaps[a.ID].Missing, + Deviations: gaps[a.ID].Deviations, + } + if row.RealHosts == nil { + row.RealHosts = []string{} } if row.Missing == nil { row.Missing = []string{} diff --git a/conformance/matrix.json b/conformance/matrix.json index 87e1d4f..8924f5f 100644 --- a/conformance/matrix.json +++ b/conformance/matrix.json @@ -12,6 +12,9 @@ "adapters": [ { "id": "adyen-style", + "display_name": "Adyen Checkout + Notification API simulator (unofficial)", + "adapter_version": "0.1.0", + "real_hosts": [], "api_name": "Adyen Checkout + Notification API", "api_version": "v68", "routes": 14, @@ -403,6 +406,9 @@ }, { "id": "anaplan-style", + "display_name": "Anaplan API simulator (unofficial)", + "adapter_version": "0.1.0", + "real_hosts": [], "api_name": "Anaplan API", "api_version": "2.0", "routes": 18, @@ -721,6 +727,9 @@ }, { "id": "apple-apns-style", + "display_name": "Apple Push Notification service (APNs) simulator (unofficial)", + "adapter_version": "0.1.0", + "real_hosts": [], "api_name": "Apple Push Notification service (APNs)", "api_version": "v2", "routes": 2, @@ -782,6 +791,9 @@ }, { "id": "apple-appstoreconnect-style", + "display_name": "App Store Connect API simulator (unofficial)", + "adapter_version": "0.1.0", + "real_hosts": [], "api_name": "App Store Connect API", "api_version": "v3", "routes": 15, @@ -6079,6 +6091,9 @@ }, { "id": "apple-music-style", + "display_name": "Apple Music API simulator (unofficial)", + "adapter_version": "0.2.0", + "real_hosts": [], "api_name": "Apple Music API", "api_version": "1.0", "routes": 18, @@ -6390,6 +6405,9 @@ }, { "id": "apple-searchads-style", + "display_name": "Apple Search Ads API simulator (unofficial)", + "adapter_version": "0.1.0", + "real_hosts": [], "api_name": "Apple Search Ads API", "api_version": "v4", "routes": 12, @@ -6628,6 +6646,9 @@ }, { "id": "apps-script-style", + "display_name": "Google Apps Script API simulator (unofficial)", + "adapter_version": "0.1.0", + "real_hosts": [], "api_name": "Google Apps Script API", "api_version": "v1", "routes": 11, @@ -6867,6 +6888,9 @@ }, { "id": "auth0-style", + "display_name": "Auth0-style Authentication API simulator (unofficial)", + "adapter_version": "0.1.0", + "real_hosts": [], "api_name": "Auth0 Authentication \u0026 Management API", "api_version": "v2", "routes": 17, @@ -8983,6 +9007,9 @@ }, { "id": "avalara-style", + "display_name": "Avalara AvaTax REST API simulator (unofficial)", + "adapter_version": "0.1.0", + "real_hosts": [], "api_name": "Avalara AvaTax REST API", "api_version": "2", "routes": 8, @@ -9131,6 +9158,9 @@ }, { "id": "aws-cognito-style", + "display_name": "Amazon Cognito Identity Provider API simulator (unofficial)", + "adapter_version": "0.2.0", + "real_hosts": [], "api_name": "Amazon Cognito Identity Provider API", "api_version": "2016-04-18", "routes": 7, @@ -9251,6 +9281,9 @@ }, { "id": "aws-iam-sts-style", + "display_name": "AWS IAM + STS API simulator (unofficial)", + "adapter_version": "0.1.0", + "real_hosts": [], "api_name": "AWS STS + IAM API", "api_version": "2011-06-15", "routes": 2, @@ -9318,6 +9351,9 @@ }, { "id": "aws-s3-style", + "display_name": "Amazon S3-style API simulator (unofficial)", + "adapter_version": "0.1.0", + "real_hosts": [], "api_name": "Amazon S3 API", "api_version": "2006-03-01", "routes": 8, @@ -9480,6 +9516,9 @@ }, { "id": "azure-devops-style", + "display_name": "Azure DevOps REST API simulator (unofficial)", + "adapter_version": "0.2.0", + "real_hosts": [], "api_name": "Azure DevOps REST API", "api_version": "7.1", "routes": 17, @@ -10919,6 +10958,9 @@ }, { "id": "azure-servicebus-style", + "display_name": "Azure Service Bus + Storage simulator (unofficial)", + "adapter_version": "0.2.0", + "real_hosts": [], "api_name": "Azure Service Bus + Storage", "api_version": "2024-01-01", "routes": 18, @@ -11198,6 +11240,9 @@ }, { "id": "azure-storage-style", + "display_name": "Azure Blob Storage API simulator (unofficial)", + "adapter_version": "0.1.0", + "real_hosts": [], "api_name": "Azure Storage Blob REST API", "api_version": "2024-08-04", "routes": 9, @@ -11383,6 +11428,9 @@ }, { "id": "blog-style", + "display_name": "Blog GraphQL example adapter", + "adapter_version": "0.1.0", + "real_hosts": [], "api_name": "GraphQL demo", "api_version": "1.0", "routes": 0, @@ -11396,6 +11444,9 @@ }, { "id": "bluesky-style", + "display_name": "Bluesky-style AT Protocol API simulator (unofficial)", + "adapter_version": "0.1.0", + "real_hosts": [], "api_name": "Bluesky AT Protocol", "api_version": "com.atproto (2024-Q4)", "routes": 7, @@ -12318,6 +12369,9 @@ }, { "id": "braintree-style", + "display_name": "Braintree GraphQL + REST API simulator (unofficial)", + "adapter_version": "0.3.0", + "real_hosts": [], "api_name": "Braintree GraphQL + REST API", "api_version": "2024-09-01", "routes": 15, @@ -12574,6 +12628,9 @@ }, { "id": "braze-style", + "display_name": "Braze REST API simulator (unofficial)", + "adapter_version": "0.2.0", + "real_hosts": [], "api_name": "Braze REST API", "api_version": "2.0", "routes": 12, @@ -12813,6 +12870,9 @@ }, { "id": "chainlink-style", + "display_name": "Chainlink Data Feeds + Functions + Automation + CCIP simulator (unofficial)", + "adapter_version": "0.2.0", + "real_hosts": [], "api_name": "Chainlink Data Feeds + Functions + Automation", "api_version": "1.0", "routes": 21, @@ -13147,6 +13207,9 @@ }, { "id": "cloudflare-style", + "display_name": "Cloudflare API + Workers + R2 + D1 simulator (unofficial)", + "adapter_version": "0.1.0", + "real_hosts": [], "api_name": "Cloudflare API", "api_version": "4", "routes": 35, @@ -26895,6 +26958,9 @@ }, { "id": "cloudkit-style", + "display_name": "CloudKit Web Services API simulator (unofficial)", + "adapter_version": "0.1.0", + "real_hosts": [], "api_name": "CloudKit Web Services API", "api_version": "1", "routes": 5, @@ -27005,6 +27071,9 @@ }, { "id": "discord-style", + "display_name": "Discord-style API simulator (unofficial)", + "adapter_version": "0.1.0", + "real_hosts": [], "api_name": "Discord API", "api_version": "v10", "routes": 26, @@ -28340,6 +28409,9 @@ }, { "id": "drive-style", + "display_name": "Google-Drive-style API simulator (unofficial)", + "adapter_version": "0.2.0", + "real_hosts": [], "api_name": "Google Drive API", "api_version": "v3", "routes": 13, @@ -28796,6 +28868,9 @@ }, { "id": "dropbox-style", + "display_name": "Dropbox-style API simulator (unofficial)", + "adapter_version": "0.1.0", + "real_hosts": [], "api_name": "Dropbox API", "api_version": "2", "routes": 8, @@ -29228,6 +29303,9 @@ }, { "id": "dune-style", + "display_name": "Dune Analytics SQL API simulator (unofficial)", + "adapter_version": "0.1.0", + "real_hosts": [], "api_name": "Dune Analytics API", "api_version": "v1", "routes": 6, @@ -29353,6 +29431,9 @@ }, { "id": "dynamodb-style", + "display_name": "Amazon DynamoDB-style API simulator (unofficial)", + "adapter_version": "0.1.0", + "real_hosts": [], "api_name": "Amazon DynamoDB API", "api_version": "2012-08-10", "routes": 1, @@ -29437,6 +29518,9 @@ }, { "id": "echo-style", + "display_name": "Echo gRPC example adapter", + "adapter_version": "0.1.0", + "real_hosts": [], "api_name": "gRPC echo demo", "api_version": "1.0", "routes": 0, @@ -29456,6 +29540,9 @@ }, { "id": "emailoctopus-style", + "display_name": "EmailOctopus-style API simulator (unofficial)", + "adapter_version": "0.1.0", + "real_hosts": [], "api_name": "EmailOctopus API", "api_version": "2.0.0", "routes": 21, @@ -29794,6 +29881,9 @@ }, { "id": "entra-id-style", + "display_name": "Microsoft Entra ID (Azure AD) API simulator (unofficial)", + "adapter_version": "0.1.0", + "real_hosts": [], "api_name": "Microsoft Graph / Entra ID", "api_version": "v1.0", "routes": 9, @@ -100071,6 +100161,9 @@ }, { "id": "erc4337-style", + "display_name": "ERC-4337 bundler RPC simulator (unofficial)", + "adapter_version": "0.1.0", + "real_hosts": [], "api_name": "ERC-4337 Bundler RPC", "api_version": "0.7", "routes": 2, @@ -100133,6 +100226,9 @@ }, { "id": "escrow-style", + "display_name": "Escrow.com-style transaction API simulator (unofficial)", + "adapter_version": "0.1.0", + "real_hosts": [], "api_name": "Escrow.com API", "api_version": "2017-09-01", "routes": 9, @@ -100317,6 +100413,9 @@ }, { "id": "eth-jsonrpc-style", + "display_name": "Ethereum JSON-RPC provider simulator (unofficial)", + "adapter_version": "0.1.0", + "real_hosts": [], "api_name": "Ethereum JSON-RPC", "api_version": "1.0", "routes": 1, @@ -100367,6 +100466,9 @@ }, { "id": "etherscan-style", + "display_name": "Etherscan-style explorer API simulator (unofficial)", + "adapter_version": "0.1.0", + "real_hosts": [], "api_name": "Etherscan API", "api_version": "1.0", "routes": 1, @@ -100410,6 +100512,9 @@ }, { "id": "fattureincloud-style", + "display_name": "Fatture in Cloud-style API v2 simulator (unofficial)", + "adapter_version": "0.1.0", + "real_hosts": [], "api_name": "Fatture in Cloud API v2", "api_version": "2.0.29", "routes": 42, @@ -101379,6 +101484,9 @@ }, { "id": "firebase-style", + "display_name": "Firebase Auth + Firestore + Cloud Messaging API simulator (unofficial)", + "adapter_version": "0.1.0", + "real_hosts": [], "api_name": "Firebase Auth + Firestore + Cloud Messaging API", "api_version": "v1", "routes": 22, @@ -101782,6 +101890,9 @@ }, { "id": "ga4-style", + "display_name": "Google Analytics GA4 Data API + Admin API simulator (unofficial)", + "adapter_version": "0.1.0", + "real_hosts": [], "api_name": "Google Analytics Data API + Admin API", "api_version": "v1beta", "routes": 7, @@ -102163,6 +102274,9 @@ }, { "id": "gcalendar-style", + "display_name": "Google Calendar API simulator (unofficial)", + "adapter_version": "0.1.0", + "real_hosts": [], "api_name": "Google Calendar API", "api_version": "v3", "routes": 10, @@ -102475,6 +102589,9 @@ }, { "id": "gdocs-style", + "display_name": "Google Docs API simulator (unofficial)", + "adapter_version": "0.2.0", + "real_hosts": [], "api_name": "Google Docs API", "api_version": "v1", "routes": 5, @@ -102591,6 +102708,9 @@ }, { "id": "github-style", + "display_name": "GitHub App-style API simulator (unofficial)", + "adapter_version": "0.2.0", + "real_hosts": [], "api_name": "GitHub REST + GraphQL API", "api_version": "2022-11-28", "routes": 26, @@ -106945,6 +107065,9 @@ }, { "id": "gmail-style", + "display_name": "Gmail API simulator (unofficial)", + "adapter_version": "0.1.0", + "real_hosts": [], "api_name": "Gmail API", "api_version": "v1", "routes": 16, @@ -107459,6 +107582,9 @@ }, { "id": "google-admin-style", + "display_name": "Google Admin SDK Directory API simulator (unofficial)", + "adapter_version": "0.1.0", + "real_hosts": [], "api_name": "Google Admin SDK Directory API", "api_version": "directory_v1", "routes": 13, @@ -108173,6 +108299,9 @@ }, { "id": "google-iam-style", + "display_name": "Google Cloud IAM + Service Accounts API simulator (unofficial)", + "adapter_version": "0.1.0", + "real_hosts": [], "api_name": "Google Cloud IAM API + Service Accounts", "api_version": "v1", "routes": 10, @@ -108843,6 +108972,9 @@ }, { "id": "google-style", + "display_name": "Google OAuth2 API simulator (unofficial)", + "adapter_version": "0.1.0", + "real_hosts": [], "api_name": "Google OAuth2 API", "api_version": "v2", "routes": 4, @@ -108930,6 +109062,9 @@ }, { "id": "gsearchconsole-style", + "display_name": "Google Search Console API simulator (unofficial)", + "adapter_version": "0.2.0", + "real_hosts": [], "api_name": "Google Search Console API", "api_version": "v1", "routes": 11, @@ -109139,6 +109274,9 @@ }, { "id": "gsheets-style", + "display_name": "Google Sheets API simulator (unofficial)", + "adapter_version": "0.1.0", + "real_hosts": [], "api_name": "Google Sheets API", "api_version": "v4", "routes": 10, @@ -109355,6 +109493,9 @@ }, { "id": "gtasks-style", + "display_name": "Google Tasks API simulator (unofficial)", + "adapter_version": "0.1.0", + "real_hosts": [], "api_name": "Google Tasks API", "api_version": "v1", "routes": 11, @@ -109583,6 +109724,9 @@ }, { "id": "helius-style", + "display_name": "Helius Solana RPC + Enhanced API simulator (unofficial)", + "adapter_version": "0.1.0", + "real_hosts": [], "api_name": "Helius Solana RPC + Enhanced API", "api_version": "v0", "routes": 11, @@ -109782,6 +109926,9 @@ }, { "id": "hn-style", + "display_name": "Hacker News-style Firebase API simulator (unofficial)", + "adapter_version": "0.1.0", + "real_hosts": [], "api_name": "Hacker News Firebase API", "api_version": "v0", "routes": 11, @@ -109943,6 +110090,9 @@ }, { "id": "hubspot-style", + "display_name": "HubSpot CRM API simulator (unofficial)", + "adapter_version": "0.2.0", + "real_hosts": [], "api_name": "HubSpot CRM API", "api_version": "v3", "routes": 33, @@ -112574,6 +112724,9 @@ }, { "id": "instagram-style", + "display_name": "Instagram-style API simulator (unofficial)", + "adapter_version": "0.1.0", + "real_hosts": [], "api_name": "Instagram Graph API", "api_version": "v21.0", "routes": 10, @@ -112799,6 +112952,9 @@ }, { "id": "jira-style", + "display_name": "Jira Cloud REST API simulator (unofficial)", + "adapter_version": "0.1.0", + "real_hosts": [], "api_name": "Jira Cloud REST API", "api_version": "3", "routes": 32, @@ -115764,6 +115920,9 @@ }, { "id": "jumio-style", + "display_name": "Jumio Netverify API simulator (unofficial)", + "adapter_version": "0.2.0", + "real_hosts": [], "api_name": "Jumio API", "api_version": "v1", "routes": 5, @@ -115885,6 +116044,9 @@ }, { "id": "linkedin-style", + "display_name": "LinkedIn-style API simulator (unofficial)", + "adapter_version": "0.1.0", + "real_hosts": [], "api_name": "LinkedIn API", "api_version": "v2", "routes": 8, @@ -116070,6 +116232,9 @@ }, { "id": "llm-style", + "display_name": "LLM API simulator (OpenAI + Anthropic, unofficial)", + "adapter_version": "0.1.0", + "real_hosts": [], "api_name": "OpenAI API + Anthropic API", "api_version": "OpenAI v1 / Anthropic v1", "routes": 3, @@ -117107,6 +117272,9 @@ }, { "id": "marketo-style", + "display_name": "Marketo Engage REST API simulator (unofficial)", + "adapter_version": "0.1.0", + "real_hosts": [], "api_name": "Marketo Engage REST API", "api_version": "1.0", "routes": 21, @@ -117469,6 +117637,9 @@ }, { "id": "microsoft-graph-style", + "display_name": "Microsoft Graph API simulator (unofficial)", + "adapter_version": "0.1.0", + "real_hosts": [], "api_name": "Microsoft Graph API", "api_version": "v1.0", "routes": 55, @@ -188323,6 +188494,9 @@ }, { "id": "netsuite-style", + "display_name": "NetSuite SuiteTalk REST API simulator (unofficial)", + "adapter_version": "0.2.0", + "real_hosts": [], "api_name": "NetSuite SuiteTalk REST API", "api_version": "1.0", "routes": 9, @@ -188505,6 +188679,9 @@ }, { "id": "oneinch-style", + "display_name": "1inch Aggregation Protocol API simulator (unofficial)", + "adapter_version": "0.1.0", + "real_hosts": [], "api_name": "1inch Aggregation Protocol API", "api_version": "v6.0", "routes": 5, @@ -188599,6 +188776,9 @@ }, { "id": "onfido-style", + "display_name": "Onfido API simulator (unofficial)", + "adapter_version": "0.1.0", + "real_hosts": [], "api_name": "Onfido API", "api_version": "v3.6", "routes": 7, @@ -189040,6 +189220,9 @@ }, { "id": "opensea-style", + "display_name": "OpenSea API v2 + Seaport simulator (unofficial)", + "adapter_version": "0.1.0", + "real_hosts": [], "api_name": "OpenSea API", "api_version": "2.0.0", "routes": 7, @@ -189732,6 +189915,9 @@ }, { "id": "paypal-style", + "display_name": "PayPal Orders v2-style API simulator (unofficial)", + "adapter_version": "0.2.0", + "real_hosts": [], "api_name": "PayPal Orders API", "api_version": "v2", "routes": 17, @@ -190067,6 +190253,9 @@ }, { "id": "persona-style", + "display_name": "Persona Inquiry API simulator (unofficial)", + "adapter_version": "0.1.0", + "real_hosts": [], "api_name": "Persona Inquiry API", "api_version": "2023-01-05", "routes": 5, @@ -191033,6 +191222,9 @@ }, { "id": "photos-style", + "display_name": "Google-Photos-style API simulator (unofficial)", + "adapter_version": "0.1.0", + "real_hosts": [], "api_name": "Google Photos Library API", "api_version": "v1", "routes": 12, @@ -191281,6 +191473,9 @@ }, { "id": "pinata-style", + "display_name": "Pinata IPFS pinning API simulator (unofficial)", + "adapter_version": "0.1.0", + "real_hosts": [], "api_name": "Pinata API", "api_version": "1.0", "routes": 6, @@ -191388,6 +191583,9 @@ }, { "id": "plaid-style", + "display_name": "Plaid-style API simulator (unofficial)", + "adapter_version": "0.2.0", + "real_hosts": [], "api_name": "Plaid API", "api_version": "2020-09-14", "routes": 13, @@ -192747,6 +192945,9 @@ }, { "id": "powerplatform-style", + "display_name": "Microsoft Power Platform API simulator (unofficial)", + "adapter_version": "0.1.0", + "real_hosts": [], "api_name": "Microsoft Power Platform API", "api_version": "2", "routes": 9, @@ -192928,6 +193129,9 @@ }, { "id": "printful-style", + "display_name": "Printful-style API simulator (unofficial)", + "adapter_version": "0.1.0", + "real_hosts": [], "api_name": "Printful API", "api_version": "v2", "routes": 13, @@ -193369,6 +193573,9 @@ }, { "id": "printify-style", + "display_name": "Printify-style API simulator (unofficial)", + "adapter_version": "0.1.0", + "real_hosts": [], "api_name": "Printify API", "api_version": "v1", "routes": 16, @@ -193774,6 +193981,9 @@ }, { "id": "producthunt-style", + "display_name": "Product Hunt-style GraphQL API simulator (unofficial)", + "adapter_version": "0.2.0", + "real_hosts": [], "api_name": "Product Hunt GraphQL API", "api_version": "2", "routes": 0, @@ -193794,6 +194004,9 @@ }, { "id": "psd2-style", + "display_name": "Open Banking / PSD2 (Berlin Group NextGenPSD2) simulator (unofficial)", + "adapter_version": "0.1.0", + "real_hosts": [], "api_name": "Open Banking / PSD2 (Berlin Group NextGenPSD2)", "api_version": "1.3.6", "routes": 19, @@ -194114,6 +194327,9 @@ }, { "id": "qbo-style", + "display_name": "QuickBooks Online-style API simulator (unofficial)", + "adapter_version": "0.1.0", + "real_hosts": [], "api_name": "QuickBooks Online API", "api_version": "v3", "routes": 11, @@ -194330,6 +194546,9 @@ }, { "id": "reddit-style", + "display_name": "Reddit-style API simulator (unofficial)", + "adapter_version": "0.1.0", + "real_hosts": [], "api_name": "Reddit API", "api_version": "1.0", "routes": 2, @@ -194395,6 +194614,9 @@ }, { "id": "resend-style", + "display_name": "Resend-style API simulator (unofficial)", + "adapter_version": "0.1.0", + "real_hosts": [], "api_name": "Resend API", "api_version": "1.0.0", "routes": 6, @@ -194599,6 +194821,9 @@ }, { "id": "revenuecat-style", + "display_name": "RevenueCat-style API simulator (unofficial)", + "adapter_version": "0.2.0", + "real_hosts": [], "api_name": "RevenueCat API", "api_version": "v1", "routes": 7, @@ -194797,6 +195022,9 @@ }, { "id": "salesforce-style", + "display_name": "Salesforce REST API simulator (unofficial)", + "adapter_version": "0.1.0", + "real_hosts": [], "api_name": "Salesforce REST API", "api_version": "v60.0", "routes": 29, @@ -195243,6 +195471,9 @@ }, { "id": "sendgrid-style", + "display_name": "Twilio SendGrid-style API simulator (unofficial)", + "adapter_version": "0.1.0", + "real_hosts": [], "api_name": "Twilio SendGrid v3 API", "api_version": "v3", "routes": 5, @@ -195455,6 +195686,9 @@ }, { "id": "servicenow-style", + "display_name": "ServiceNow Table API simulator (unofficial)", + "adapter_version": "0.1.0", + "real_hosts": [], "api_name": "ServiceNow Table API", "api_version": "2", "routes": 44, @@ -196135,6 +196369,9 @@ }, { "id": "shopify-style", + "display_name": "Shopify Admin-style API simulator (unofficial)", + "adapter_version": "0.2.0", + "real_hosts": [], "api_name": "Shopify Admin REST + GraphQL API", "api_version": "2024-10", "routes": 21, @@ -196500,6 +196737,9 @@ }, { "id": "signin-with-apple-style", + "display_name": "Sign in with Apple simulator (unofficial)", + "adapter_version": "0.1.0", + "real_hosts": [], "api_name": "Sign in with Apple", "api_version": "v2", "routes": 3, @@ -196565,6 +196805,9 @@ }, { "id": "slack-style", + "display_name": "Slack-style API simulator (unofficial)", + "adapter_version": "0.1.0", + "real_hosts": [], "api_name": "Slack Web API", "api_version": "1.0", "routes": 7, @@ -197778,6 +198021,9 @@ }, { "id": "smartbill-style", + "display_name": "SmartBill-style API simulator (unofficial)", + "adapter_version": "0.2.0", + "real_hosts": [], "api_name": "SmartBill Cloud API", "api_version": "1.0", "routes": 18, @@ -198054,6 +198300,9 @@ }, { "id": "sqs-style", + "display_name": "Amazon SQS-style API simulator (unofficial)", + "adapter_version": "0.1.0", + "real_hosts": [], "api_name": "Amazon SQS API", "api_version": "2012-11-05", "routes": 2, @@ -198143,6 +198392,9 @@ }, { "id": "square-style", + "display_name": "Square API simulator (unofficial)", + "adapter_version": "0.1.0", + "real_hosts": [], "api_name": "Square API", "api_version": "2024-08-21", "routes": 18, @@ -199698,6 +199950,9 @@ }, { "id": "stripe-style", + "display_name": "Stripe-style API simulator (unofficial)", + "adapter_version": "0.3.0", + "real_hosts": [], "api_name": "Stripe API", "api_version": "2025-01-27.acacia", "routes": 158, @@ -203982,6 +204237,9 @@ }, { "id": "tenderly-style", + "display_name": "Tenderly Simulation API simulator (unofficial)", + "adapter_version": "0.1.0", + "real_hosts": [], "api_name": "Tenderly Simulation API", "api_version": "v1", "routes": 5, @@ -204089,6 +204347,9 @@ }, { "id": "thegraph-style", + "display_name": "The Graph subgraph GraphQL simulator (unofficial)", + "adapter_version": "0.2.0", + "real_hosts": [], "api_name": "The Graph (GraphQL over subgraphs)", "api_version": "1.0", "routes": 1, @@ -204134,6 +204395,9 @@ }, { "id": "threads-style", + "display_name": "Threads-style API simulator (unofficial)", + "adapter_version": "0.1.0", + "real_hosts": [], "api_name": "Threads API (Meta)", "api_version": "v1.0", "routes": 8, @@ -204320,6 +204584,9 @@ }, { "id": "twilio-style", + "display_name": "Twilio-style API simulator (unofficial)", + "adapter_version": "0.1.0", + "real_hosts": [], "api_name": "Twilio API", "api_version": "2010-04-01", "routes": 6, @@ -205239,6 +205506,9 @@ }, { "id": "twitter-style", + "display_name": "X.com / Twitter-style API simulator (unofficial)", + "adapter_version": "0.1.0", + "real_hosts": [], "api_name": "Twitter/X API", "api_version": "v2", "routes": 9, @@ -206089,6 +206359,9 @@ }, { "id": "walletconnect-style", + "display_name": "WalletConnect v2 relay simulator (unofficial)", + "adapter_version": "0.1.0", + "real_hosts": [], "api_name": "WalletConnect (Relay Protocol v2)", "api_version": "2.0", "routes": 7, @@ -206237,6 +206510,9 @@ }, { "id": "whatsapp-style", + "display_name": "WhatsApp Business Cloud API-style simulator (unofficial)", + "adapter_version": "0.1.0", + "real_hosts": [], "api_name": "WhatsApp Business Cloud API (Meta)", "api_version": "v21.0", "routes": 8, @@ -206425,6 +206701,9 @@ }, { "id": "workday-style", + "display_name": "Workday REST API simulator (unofficial)", + "adapter_version": "0.1.0", + "real_hosts": [], "api_name": "Workday REST API", "api_version": "v40.0", "routes": 8, @@ -206561,6 +206840,9 @@ }, { "id": "x-articles-style", + "display_name": "X Articles API simulator (unofficial)", + "adapter_version": "0.1.0", + "real_hosts": [], "api_name": "X (Twitter) Articles API", "api_version": "v2", "routes": 8, @@ -207408,6 +207690,9 @@ }, { "id": "xero-style", + "display_name": "Xero Accounting API simulator (unofficial)", + "adapter_version": "0.1.0", + "real_hosts": [], "api_name": "Xero Accounting API", "api_version": "2.0", "routes": 15, @@ -208526,6 +208811,9 @@ }, { "id": "youtube-style", + "display_name": "YouTube-style API simulator (unofficial)", + "adapter_version": "0.1.0", + "real_hosts": [], "api_name": "YouTube Data API", "api_version": "v3", "routes": 14, @@ -209077,6 +209365,9 @@ }, { "id": "zendesk-style", + "display_name": "Zendesk REST API simulator (unofficial)", + "adapter_version": "0.1.0", + "real_hosts": [], "api_name": "Zendesk REST API", "api_version": "2", "routes": 37, @@ -209685,6 +209976,9 @@ }, { "id": "zuora-style", + "display_name": "Zuora Billing REST API simulator (unofficial)", + "adapter_version": "0.1.0", + "real_hosts": [], "api_name": "Zuora REST API", "api_version": "v1", "routes": 20, diff --git a/justfile b/justfile index 6d4413c..836922c 100644 --- a/justfile +++ b/justfile @@ -38,9 +38,10 @@ ci-full: ci conformance conformance-node #!/bin/sh set -e just conformance-matrix - if ! git diff --quiet CONFORMANCE.md conformance/matrix.json; then - echo "✗ CONFORMANCE.md or conformance/matrix.json is stale — run 'just conformance-matrix' and commit" >&2 - git diff --stat CONFORMANCE.md conformance/matrix.json >&2 + just readme-counts + if ! git diff --quiet CONFORMANCE.md conformance/matrix.json README.md; then + echo "✗ CONFORMANCE.md, conformance/matrix.json or README.md is stale — run 'just conformance-matrix readme-counts' and commit" >&2 + git diff --stat CONFORMANCE.md conformance/matrix.json README.md >&2 exit 1 fi echo "✓ conformance matrix fresh" @@ -127,6 +128,33 @@ conformance-node: conformance-matrix: cd conformance && go run ./cmd/genmatrix -json matrix.json +# Re-template the adapter count in README.md from the adapters/ dir. The +# ci-full freshness gate regenerates and diffs it, so the number can never +# silently go stale again. +readme-counts: + #!/bin/sh + set -e + # Same rule genmatrix uses (dirs holding an adapter.yaml), so the two + # derivations can never disagree on a non-*-style directory name. + n="$(find adapters -mindepth 2 -maxdepth 2 -name adapter.yaml | wc -l | tr -d ' ')" + [ "$n" -gt 0 ] || { echo "✗ no adapters found — run from the repo root" >&2; exit 1; } + perl -pi -e 's/(\*\*Reference adapters in this repo\*\*[^0-9]*)[0-9]+( of them)/${1}'"$n"'${2}/' README.md + grep -qE "Reference adapters in this repo\*\*[^0-9]*$n of them" README.md || { + echo "✗ README adapter-count anchor not found — the line was reworded; update justfile readme-counts" >&2 + exit 1 + } + +# Reset the GitHub repo description with the current adapter count. The +# description is repo metadata GITHUB_TOKEN cannot write, so CI only checks +# it for drift on main pushes — this is the one-command fix. +repo-description: + #!/bin/sh + set -e + n="$(find adapters -mindepth 2 -maxdepth 2 -name adapter.yaml | wc -l | tr -d ' ')" + [ "$n" -gt 0 ] || { echo "✗ no adapters found — run from the repo root" >&2; exit 1; } + gh api -X PATCH repos/StuntAPI/stunt -f description="Local mock API server: $n offline, stateful stand-ins for public APIs (Stripe, GitHub, Twilio, AWS, Auth0…) — one static Go binary. Test without accounts, keys, or network." + echo "✓ repo description set ($n adapters)" + # Vendor provider route tables from their official published specs into # conformance/surfaces/ — the ONLY network-touching step of the matrix. # Manual: run when onboarding an adapter or refreshing a spec (CI never